; Play.asm ; Four voices on one clock, which is what music is and one channel cannot be. ; Written by Anachronaut ; ; ---- One tick, four cursors ---- ; ; The sound device has four channels and no idea when. The timer has a period and no idea ; what. This is the thing between them: on every tick it walks four voices, and each voice ; counts down the note it is holding and reads the next one when the count runs out. ; ; THE VOICES SHARE NOTHING BUT THE TICK. Each keeps its own place in its own track and its ; own count of how much longer the current note lasts, so a voice playing whole notes and a ; voice playing eighths cost the same and never have to know about each other. That is why ; the tick is the smallest subdivision in the piece rather than a note length: it is the ; only unit all four can agree on. ; ; ---- What a sequence is ---- ; ; Pairs of bytes: what to play, then how many ticks it lasts. ; ; 0x01 to 0x7F a MIDI note. 60 is middle C and every 12 is an octave. ; 0x00 a rest - the ticks pass with nothing sounding ; 0x80 to 0xFE a command, which takes no time at all. See below. ; 0xFF the sequence is over ; ; MIDI NOTES ONLY REACH 127, so the top of the byte was free and none of that had to be ; invented. Examples/tune.asm spent zero on its end marker and so had no way to write a rest ; at all, which one voice can just about live with and four cannot: voices do not all play at ; once, and the silences are what make them separate parts rather than a chord. ; ; A note's duration is its whole life. The gate goes down when the count runs out and the next ; event begins on the same tick, so a gap between two notes is WRITTEN, as a rest, rather than ; invented by the player out of some fraction it decided on. ; ; ---- Commands, of which there is one ---- ; ; A command consumes no tick: the reader acts on it and reads the next event straight away, so ; commands sit BETWEEN notes in time rather than needing a place of their own. ; ; 0x80
play the rest of this voice on that patch. ; ; A patch change reshapes whatever is still ringing on the voice, and nothing can be done ; about that here: a channel has one set of parameters and a note in its release is using ; them. Deferring the load to the next note would reshape the same tail. So it is a fact about ; the hardware rather than a choice about the format, and the cure is a rest long enough for ; the release, which is the composer's to write. ; ; ONE COMMAND, and the other 125 values left alone. A tempo change, a volume ramp and a note ; slide are all easy to add and impossible to remove, and no piece of music has asked for one. ; ; ---- Sequences, and the order they go in ---- ; ; A sequence is one voice's phrase. Each voice has an ORDER LIST of its own - a table of ; sequence addresses, ending in a zero - and when a sequence runs out the voice takes the next ; address from it. The voice stops when the list does. ; ; THAT IS WHERE REPETITION COMES FROM, and it costs no notation: the bass below plays the same ; sequence in the first bar and the last, and it is written once. A thirty-two bar piece that ; reuses four phrases is four phrases and a list. ; ; PER VOICE RATHER THAN ONE SHARED LIST, because that is the shape this machine likes. A ; voice's order cursor is a pointer it advances by itself, which is the same LDD and STD move ; everything else here makes; a shared table of four-column rows would have every voice ; indexing into one place, which is a worse fit for a machine with four data pointers. The ; four columns are how it READS, and how a tracker would show it; they are not how it is ; stored. Nothing keeps the voices together except that their sequences add up to the same ; length, which is a thing a compiler can check and a hand-written tune has to get right. ; ; A voice silent through a bar has a sequence that rests for it. There is no need for a way to ; say "nothing here" when a rest already says it. ; ; ---- Where the state lives ---- ; ; Six bytes a voice: two of order cursor, two of sequence cursor, one of count, one of live. ; Both cursors are two bytes and at the front because that is what LDD and STD move - a ; pointer through a pointer, which is the whole reason this is a loop over four voices instead ; of the same code written out four times. ; ; A voice starts pointed at Empty, which is a sequence of nothing but its end marker. The ; first tick runs its count out, finds the end, and goes to the order list for the real first ; sequence - so the beginning needs no special case at all. #Include services.asm #Program #Base 0x5000 start: ; ---- An instrument for each voice ---- ; ; Four patches, designed in soundThing and converted by SoundPatch, which is the only thing ; here that understands what a patch means. The player reads a count and that many parameter ; and value pairs and knows nothing else about them. ; ; WHICH PATCH EACH VOICE STARTS ON IS DECLARED, in VoiceStart below, rather than being four ; calls in a row here. That is the shape a tune read from a file will have - a starting ; instrument is state and belongs in a header - and saying it once means the two forms do ; not drift. ; ; A PATCH BELONGS TO ITS CHANNEL, which is why four of them can be up at once. It did not ; used to: the LFOs belonged to the whole device, so whichever patch was loaded last owned ; them for every voice. Kalimba has LFO 0 switched off and the other three have it on, so ; under the old device this piece would have played with the arpeggio's setting on all four ; parts - which is exactly the fault that made Lunar Porter's low fuel warning stop ; trilling after the first landing of a run. CALL loadStartPatches INIA 0x60 ; Room over the top for four voices at once. OUTA 0x46 ; ---- The beat ---- ; ; 125,000 cycles is a sixteenth note at 120 beats a minute, which is the smallest thing ; this piece divides a beat into. The period goes in 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 ; Run, repeat, interrupt. SIF ; ---- The loop ---- ; ; Step every voice, see whether any of them is still going, and then sleep. Stepping before ; waiting is what makes the first note sound on the first tick rather than the second. tickLoop: SETD.1 Voice0 RSTA CALL stepVoice SETD.1 Voice1 INIA 0d1 CALL stepVoice SETD.1 Voice2 INIA 0d2 CALL stepVoice SETD.1 Voice3 INIA 0d3 CALL stepVoice SETD.1 Playing LDA.1 BRA finished WAIT ; Nothing at all until the timer says a tick has gone by. BRI tickLoop finished: ; ---- Let the last note finish, and MEASURE how long that takes ---- ; ; The gates all went down on the tick the tracks ended, but a gate down is a note released ; rather than a note stopped: the oboe and the strings have a release to run. Rendered and ; measured, the tail dies out eight tenths of a second after the last event, and eight ticks ; of ring is one second - which sounds like enough and is two tenths short of it. ; ; A program that exits with sound still in the air leaves nothing able to end it: the ; program is gone and cannot drop a gate. What that turns into depends on the front end - ; behind a window the tail simply finishes, and on a terminal EMULATED TIME STOPS while the ; machine blocks on a key, so the tail freezes and comes out a snippet per keystroke. ; ; Twelve ticks is a second and a half, which covers the measured tail with room over. It is ; a number about THESE FOUR INSTRUMENTS, and a patch with a longer release would want more - ; the general answer is for the system to quieten the device when a program stops, the way ; it puts the screen back, and that is not built. INIB 0d12 lastRing: WAIT DECB BNB lastRing CIF RSTA OUTA 0x51 SWI osExit ; The tick has nothing to do: the loop above is the player and WAIT only needs something to ; have happened. A handler still has to exist, because an interrupt with nothing installed to ; catch it is a fault. Taking it is what brings the line down - a program that POLLED the ; timer would have to read 0x50 instead. tick: RETI #Data #Base 0x3000 ; ---- The order lists ---- ; ; A table of sequence addresses each, ending in a zero. Read the four of them across and they ; are the bars; read one down and it is a part. ; ; bar 1 2 3 4 ; melody Mel1 Mel2 Mel3 Mel4 ; harmony Har1 Har2 Har3 Har4 ; bass BassC BassF BassG BassC ; arp ArpC ArpF ArpG Silent ; ; THE BASS PLAYS BassC TWICE and it is written once. That is the whole return on having an ; order list, and it is worth noticing how little it cost: a table of labels the assembler ; fills in, and ten instructions in stepVoice. Order0: 0d0 0d1 0d2 0d3 0xFF Order1: 0d4 0d5 0d6 0d7 0xFF Order2: 0d8 0d9 0d10 0d8 0xFF ; BassC twice, written once. Order3: 0d11 0d12 0d13 0d14 0xFF ; ---- The tables, which are the only places an address lives ---- ; ; Writing these out by hand is exactly the tedium a compiler exists to remove: every sequence ; has to be counted into its place above, and moving one means renumbering. That it is ; unpleasant is the point of noticing it here rather than after a tool has baked the shape in. PatchTable: OboePatch StringsPatch SquarePatch KalimbaPatch SequenceTable: Mel1 Mel2 Mel3 Mel4 ; 0 to 3 Har1 Har2 Har3 Har4 ; 4 to 7 BassC BassF BassG ; 8 to 10 ArpC ArpF ArpG Silent ; 11 to 14 ; The patch each voice starts on. Not assumed, and not four calls in a row: a starting ; instrument is state, and state belongs somewhere it can be read. VoiceStart: 0d0 0d1 0d2 0d3 ; ---- Four bars of C, F, G, C ---- ; ; A bar is sixteen ticks, and every sequence in a column adds up to sixteen. Nothing enforces ; that here: get one wrong and the voices come apart, quietly, some bars later. It is the ; first thing a compiler should check. ; The melody, in quarters and halves. Mel1: 0d64 0d4 ; E 0d67 0d4 ; G 0d72 0d8 ; C, held 0xFF Mel2: 0d65 0d4 ; F 0d69 0d4 ; A 0d72 0d8 ; C 0xFF Mel3: 0d62 0d4 ; D 0d67 0d4 ; G 0d71 0d8 ; B 0xFF Mel4: 0x80 0d1 ; Patch one is Strings: the last bar is a swell, not a reed. 0d72 0d16 ; and home 0xFF ; A second part underneath it, in halves. Har1: 0d60 0d8 ; C 0d64 0d8 ; E 0xFF Har2: 0d57 0d8 ; A 0d60 0d8 ; C 0xFF Har3: 0d59 0d8 ; B 0d62 0d8 ; D 0xFF Har4: 0d64 0d16 ; E 0xFF ; The bass, one note to a bar - and the first bar's is the last bar's. BassC: 0d48 0d16 ; C 0xFF BassF: 0d41 0d16 ; F 0xFF BassG: 0d43 0d16 ; G 0xFF ; And an arpeggio in eighths, which is the part that proves the others are not waiting for it. ArpC: 0d72 0d2 0d76 0d2 0d79 0d2 0d76 0d2 0d72 0d2 0d76 0d2 0d79 0d2 0d76 0d2 0xFF ArpF: 0d77 0d2 0d81 0d2 0d84 0d2 0d81 0d2 0d77 0d2 0d81 0d2 0d84 0d2 0d81 0d2 0xFF ArpG: 0d79 0d2 0d83 0d2 0d86 0d2 0d83 0d2 0d79 0d2 0d83 0d2 0d86 0d2 0d83 0d2 0xFF Silent: 0d00 0d16 ; Out for the last bar, so it is the other three. 0xFF ; The scheduler, the patch loader and the state a voice keeps. The tune above is this ; program's; everything that plays it is shared with whatever else wants to. #Include player.asm ; The instruments, at the bottom because each brings its own #Data and a base has to come ; before anything is in the segment it bases. #Include oboe.asm #Include strings.asm #Include square.asm #Include kalimba.asm #Vectors Boot start Device 0x50 tick