; 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 ; ; BOTH VOICES ARE ASKED FOR NOTE 60. The octave between them is in their patches, which is ; M3: a patch belongs to its channel, so four of them can be up at once. Under a device where ; it did not, the second patch loaded would own both voices and they would sound in unison. ; ; 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: ; A patch each, in the format SoundPatch writes: a count and that many pairs. They differ ; in ONE parameter - the octave - and the low one is loaded first, so if a patch belonged to ; the device rather than to its channel the second would overwrite the first and both voices ; would sound the same. SETD.0 PatchLow RSTA CALL loadPatch SETD.0 PatchHigh INIA 0d1 CALL loadPatch 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 ; ---- A patch, onto the channel named by A ---- ; ; The channel is selected first: every parameter write lands on whichever channel was named ; last, so a patch loaded without one would be rewriting somebody else's voice. loadPatch: OUTA 0x41 LDB.0 INCD.0 loadPatchPair: LDA.0 OUTA 0x42 INCD.0 LDA.0 OUTA 0x43 INCD.0 DECB BNB loadPatchPair 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 0d60 0d4 0d60 0d4 0d00 0xFF ; Seven pairs each, and only the last of them differs: the octave, centred on 128. So the two ; voices below are asked for THE SAME NOTE NUMBER and answer an octave apart, which nothing ; but a patch of their own could do. PatchLow: 0d8 0x00 0d2 ; Saw 0x01 0xFF ; at full gain 0x05 0d1 ; and switched on. 0x20 0d0 ; No attack 0x21 0d0 ; no decay 0x22 0xFF ; and held at full, so a note lasts exactly its ticks 0x23 0d5 ; and goes quickly when the gate drops, which the windows below ; depend on: a note still fading into the next voice's stretch is ; a second pitch in it, and a crossing counter given two answers ; with neither. 0x04 0d128 ; Octave, centred. PatchHigh: 0d8 0x00 0d2 0x01 0xFF 0x05 0d1 0x20 0d0 0x21 0d0 0x22 0xFF 0x23 0d5 0x04 0d129 ; And this one an octave up. #Vectors Boot start Device 0x50 tick