; tune.asm ; Playing a melody, which needs a sound device and a clock and has neither by halves. ; Written by Anachronaut ; ; ---- Two devices, because one is not enough ---- ; ; The sound device knows how to make a note and knows nothing about when. It has no timer and ; does not interrupt, so a program that only had the sound device could play a tune at ; whatever speed the machine happened to run at, which is not a tune. ; ; The screen finishes a frame sixty times a second and will say so. That is the only regular ; beat on this machine, and it is counted in the machine's own cycles, so this plays at the ; same speed whether the emulator is running at a megahertz or as fast as it can go. Every ; duration below is in frames: 30 is half a second. ; ; A programmable timer is the device that ought to be doing this, and it does not exist yet. ; Borrowing the screen's frame costs nothing and works, which is the whole reason to notice ; that a beat is a beat wherever it comes from. ; ; ---- What a patch costs and what a note costs ---- ; ; Setting the sound up is twenty-odd writes, done once before a single note is played. After ; that the inner loop is two: the note, and letting go of it. That split is what the selector ; and value registers are for - see Making A Noise in the Programming Manual. #Program start: ; ---- The instrument ---- ; ; Channel 0, selected once. Every parameter write below lands on it. RSTA OUTA 0x41 ; A saw wave, which has all the harmonics and so is the one to hear a filter on. Writing a ; port leaves A alone, so the nothing that selected the channel also selects parameter 0, ; which is oscillator 0's waveform. SplitLint will point out any attempt to put it there ; twice. OUTA 0x42 INIA 0d2 ; Saw OUTA 0x43 INIA 0x01 ; Oscillator 0, gain OUTA 0x42 INIA 0xFF ; All of it. A channel arrives at full gain already, so this is OUTA 0x43 ; saying so rather than changing it. ; A second oscillator a little out of tune with the first, which is the oldest trick there ; is for making one voice sound like more than one. ; ; SWITCHING IT ON IS A SEPARATE WRITE from setting its gain, and it is the one that matters: ; the two oscillators are averaged rather than added, so `active` is structural. Setting a ; gain on an oscillator that is off does nothing at all, silently, which is how the first ; draft of this program came to have a detune in it that could not be heard. INIA 0x15 ; Oscillator 1, on OUTA 0x42 INIA 0x01 OUTA 0x43 INIA 0x11 ; Oscillator 1, gain OUTA 0x42 INIA 0xC0 OUTA 0x43 INIA 0x13 ; Oscillator 1, detune OUTA 0x42 INIA 0d129 ; Centred on 128, and a step is about nine cents, so this is ; nine cents sharp - a shimmer rather than a wrong note. OUTA 0x43 ; Plucked: no attack to speak of, most of a second of decay, and nothing held. ; ; A sustain of nothing does NOT end the note. It goes quiet and keeps sounding, because a ; voice holding at nothing is what a held key is. Dropping the gate is the only thing that ; ends a note, which is why the loop below does it whether the sound has faded or not. INIA 0x20 ; Amplitude envelope, attack OUTA 0x42 INIA 0d10 OUTA 0x43 INIA 0x21 ; Decay OUTA 0x42 INIA 0d120 OUTA 0x43 INIA 0x22 ; Sustain: nothing OUTA 0x42 RSTA OUTA 0x43 INIA 0x23 ; Release OUTA 0x42 INIA 0d40 OUTA 0x43 ; A low pass with the modulation envelope opening it, so each note starts bright and closes ; down. This is what the second envelope is for, and it can only be spent this way because ; the level is shaped by the first one and not by whichever happens to be wired to the ; output. INIA 0x40 ; Filter, on OUTA 0x42 INIA 0x01 OUTA 0x43 INIA 0x42 ; Cutoff, low to start with OUTA 0x42 INIA 0d90 OUTA 0x43 INIA 0x43 ; A little resonance, to hear it move OUTA 0x42 INIA 0d150 OUTA 0x43 INIA 0x44 ; What opens it: the modulation envelope OUTA 0x42 INIA 0d2 OUTA 0x43 INIA 0x45 ; And how far, upwards from centre OUTA 0x42 INIA 0d220 OUTA 0x43 INIA 0x31 ; That envelope's decay, which is the sweep's length OUTA 0x42 INIA 0d70 OUTA 0x43 INIA 0x32 ; and it closes all the way OUTA 0x42 RSTA OUTA 0x43 INIA 0xC0 ; The device's volume, with room left over the top OUTA 0x46 ; ---- The beat ---- ; ; Ask the screen to interrupt at each frame, and let interrupts in. The screen does not do ; this unless it is asked. INIA 0x01 OUTA 0x35 SIF ; ---- The tune ---- ; ; Data Pointer 0 walks the table, and nothing in this loop is a CALL, so it stays where it ; was left without being saved anywhere. SETD.0 Tune nextNote: LDA.0 ; The note. Zero is the end of the tune. BRA finished OUTA 0x44 ; Writing the note is what starts it. INCD.0 LDB.0 ; How many frames it lasts. INCD.0 holdNote: WAIT ; Nothing at all until the screen says a frame has gone by. DECB BNB holdNote ; Let go. The note is already fading on its own decay, but dropping the gate is what a ; keyboard does and what the release time is waiting for. RSTA OUTA 0x45 BRI nextNote finished: ; Let the last note ring out rather than cutting it off, then put the screen back the way it ; was found - and stop asking to be interrupted before taking away what catches it. INIB 0d45 lastRing: WAIT DECB BNB lastRing CIF RSTA OUTA 0x35 HALT ; Sixty times a second, and it has nothing to do. WAIT only needs something to have happened, ; and this is the something. A handler still has to exist: an interrupt with nothing installed ; to catch it is a fault. frame: RETI #Data ; ---- Notes and how long they last ---- ; ; Pairs: a MIDI note, then a count of frames. 60 is middle C and every 12 is an octave. A zero ; note ends it, which is why there are no rests in here - a rest would want a duration with no ; note, and this table has no way to say that. Adding one is a byte of flag or a note number ; nothing plays, and this program did not need it. Tune: 0d60 0d15 ; C 0d64 0d15 ; E 0d67 0d15 ; G 0d72 0d30 ; C, an octave up, held twice as long 0d71 0d15 ; B 0d67 0d15 ; G 0d64 0d15 ; E 0d60 0d45 ; and home 0x00 #Vectors Boot start Device 0x30 frame