; Four voices on one tick, staggered so that each can be heard on its own. ; ; M2 of the music player. What is being checked is that the voices share the tick and NOTHING ; ELSE: each keeps its own place in its own track and its own count of how long the note it is ; holding lasts, so one can rest while another plays. ; ; Twelve ticks of 125,000 cycles, which is a sixteenth note at 120 beats a minute: ; ; ticks 0-3 voice 0 plays middle C voice 1 rests ; ticks 4-7 voice 0 rests voice 1 plays the C above it ; ticks 8-11 voice 0 plays middle C voice 1 plays the C above it ; ; So the first two windows have one voice in them and can be measured for pitch, and the third ; has both and can be measured for level. Two voices sounding at once cannot be asked their ; pitch - a crossing counter given two notes answers with neither. ; ; THIS IS DELIBERATELY NOT Apps/Play.asm. It shares the design and not the code, and it is ; smaller: no track ever ends here, so there is no live flag and no count of what is still ; playing. What it has is the part worth pinning - a cursor and a countdown per voice, moved ; with LDD and STD. ; ; Written by Anachronaut #Program start: RSTA CALL setUpChannel INIA 0d1 CALL setUpChannel INIA 0x60 OUTA 0x46 ; 125,000 cycles a tick. The period before the control byte, because writing control with ; the run bit set is what loads it. INIA 0x01 OUTA 0x52 INIA 0xE8 OUTA 0x53 INIA 0x48 OUTA 0x54 INIA 0x07 OUTA 0x51 SIF ; Twelve ticks. B survives the calls below, because a CALL saves it. INIB 0d12 tickLoop: SETD.1 Voice0 RSTA CALL stepVoice SETD.1 Voice1 INIA 0d1 CALL stepVoice DECB BRB lastTick WAIT BRI tickLoop lastTick: ; The twelfth step started a note and nothing has waited for it yet. Without this the last ; tick is never rendered, and a window measuring it runs off the end of the samples. WAIT done: CIF RSTA OUTA 0x51 HALT ; A is the channel and DP1 the voice's four bytes: cursor high, cursor low, count, spare. ; Both survive the CALL that got here. stepVoice: SETD.3 ThisChannel STA.3 DPUP.1 0d2 LDA.1 DECA STA.1 ; One tick less of whatever is sounding. DPDN.1 0d2 BNA stepDone ; The count ran out. Select the channel first: every sound port writes to whichever channel ; was named last, so a voice that forgot would be playing somebody else's part. LDA.3 OUTA 0x41 RSTA OUTA 0x45 ; Let go of the note that just ended. LDD.0.1 ; DP0 is this voice's place in its track. LDA.0 INCD.0 LDB.0 ; How many ticks it lasts. INCD.0 STD.0.1 DPUP.1 0d2 STB.1 ; A is still the note. Zero is a rest: the ticks pass with nothing started. BRA stepDone OUTA 0x44 stepDone: RET ; Held at full and released quickly, so a note lasts exactly its ticks and the boundary ; between two of them is something a counter can find. setUpChannel: OUTA 0x41 RSTA OUTA 0x42 INIA 0d2 OUTA 0x43 ; Saw INIA 0x01 OUTA 0x42 INIA 0xFF OUTA 0x43 INIA 0x05 OUTA 0x42 INIA 0x01 OUTA 0x43 ; Oscillator 0, on and loud. INIA 0x20 OUTA 0x42 RSTA OUTA 0x43 ; No attack INIA 0x21 OUTA 0x42 RSTA OUTA 0x43 ; no decay INIA 0x22 OUTA 0x42 INIA 0xFF OUTA 0x43 ; and held at full. INIA 0x23 OUTA 0x42 INIA 0d5 OUTA 0x43 RET tick: RETI #Data ; Cursor high, cursor low, count, spare. A label written here comes out as its address, most ; significant first, which is the shape LDD reads - so a voice starts pointed at its track ; with no code at all. The count starts at one so the first pass runs it out and fetches. Voice0: Track0 0d1 0d0 Voice1: Track1 0d1 0d0 ThisChannel: 0x00 ; Middle C, then out of the way, then back - against the octave above doing the opposite. Track0: 0d60 0d4 0d00 0d4 0d60 0d4 0d00 0xFF Track1: 0d00 0d4 0d72 0d4 0d72 0d4 0d00 0xFF #Vectors Boot start Device 0x50 tick