Files
SplitBit-Emulator/Programs/Examples/tune.asm
T
AnachronautandClaude Opus 5 b160c66110 The arpeggio's first chord is the note its comment says it is
tune.asm wrote 0x2A four times, labelled "; Gs" each time. 0x2A is F#2.
G# is 0x2C. The file has disagreed with itself since it was written.

It is not a near miss. The progression is Ab, Bb, C - a flat six and a
flat seven resolving home - and the other two chords are right: 0x2E is
A# and 0x30 is C. With F# the first one is a tritone against its own
third and stops being major at all.

Found by the user transcribing the piece into the new tune language,
where writing the notes out again by ear is what made the wrong one
audible. The number was carried across faithfully; the intent was not in
the number.

Nothing churns: it is one value and not a length, so the binary is the
same size and the recordings are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-05 22:15:58 -04:00

300 lines
8.9 KiB
NASM

; 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 timer is the other half. A period in cycles, a repeat bit, and a line when one has gone
; by: a beat this program SETS rather than one it borrows. Every duration below is a count of
; ticks, and the tick is named 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.
;
; ---- The tick is a sixteenth note, and that is the whole point ----
;
; 125,000 cycles, which is a sixteenth note at 120 beats a minute.
;
; This program used to count the SCREEN's frames, because until the timer existed the frame
; was the only regular beat on the machine. A frame is 16,667 cycles, so a sixteenth note is
; seven and a half of them and could not be asked for at all. The arpeggio below was written
; as seven frames, the nearest whole one, which is six and a half per cent fast; and the way
; round the rest of it was to pick a tempo whose subdivisions happened to land on whole
; frames, which is making the tune fit the machine rather than the other way round.
;
; SO THE DURATIONS HERE ARE NOT A CONVERSION OF THE OLD ONES. They are what the music wanted
; in the first place, now that it can be written down: an eighth is two ticks, a quarter is
; four, and the arpeggio is one each rather than a fast approximation of one.
;
; The reason the screen should not have been the clock is not that a frame is slow. It is that
; a display refresh and a music routine have no reason to share a rate.
;
; ---- 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.
#Include services.asm
#Program
#Base 0x5000
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 ----
;
; The period first and the control byte second, because writing the control byte with the
; run bit set is what LOADS the period. Doing it the other way round starts a timer on
; whatever it was holding before.
INIA 0x01
OUTA 0x52
INIA 0xE8
OUTA 0x53
INIA 0x48
OUTA 0x54 ; 0x01E848 is 125,000.
INIA 0x07
OUTA 0x51 ; Run, repeat, interrupt.
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 Theme
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 ticks it lasts.
INCD.0
holdNote:
WAIT ; Nothing at all until the timer says a tick 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 give the timer back - stopping
; it before taking away what catches it, because this program is about to stop existing and
; an interrupt with no handler installed is a fault.
INIB 0d6
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.
;
; Nothing reads the status port here, and that is not an oversight. Taking the interrupt is
; what brings the line down. A program that POLLED the timer instead would have to read 0x50,
; because looking at it is the only thing that answers a tick nobody was interrupted by.
tick:
RETI
#Data
#Base 0x3000
; ---- Notes and how long they last ----
;
; Pairs: a MIDI note, then a count of ticks. 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 0d2 ; C
0d64 0d2 ; E
0d67 0d2 ; G
0d72 0d4 ; C, an octave up, held twice as long
0d71 0d2 ; B
0d67 0d2 ; G
0d64 0d2 ; E
0d60 0d6 ; and home
0x00
Theme:
0x30 0d2 ; C
0x35 0d2 ; F
0x3C 0d2 ; C+
0x40 0d4 ; E+
0x48 0d4 ; C++
0x45 0d4 ; A+
0x47 0d4 ; B+
0x43 0d6 ; G+
; repeat four times.
0x2C 0d1 ; Gs
0x33 0d1 ; Ef
0x3C 0d1 ; C+
;
0x2C 0d1 ; Gs
0x33 0d1 ; Ef
0x3C 0d1 ; C+
;
0x2C 0d1 ; Gs
0x33 0d1 ; Ef
0x3C 0d1 ; C+
;
0x2C 0d1 ; Gs
0x33 0d1 ; Ef
0x3C 0d1 ; C+
; next chord
0x2E 0d1 ; Bf
0x35 0d1 ; F
0x3E 0d1 ; D+
; next chord
0x2E 0d1 ; Bf
0x35 0d1 ; F
0x3E 0d1 ; D+
;
; next chord
0x2E 0d1 ; Bf
0x35 0d1 ; F
0x3E 0d1 ; D+
;
; next chord
0x2E 0d1 ; Bf
0x35 0d1 ; F
0x3E 0d1 ; D+
; Finally on the C major
0x30 0d1 ; C
0x37 0d1 ; G
0x40 0d1 ; E+
;
0x30 0d1 ; C
0x37 0d1 ; G
0x40 0d1 ; E+
;
0x30 0d1 ; C
0x37 0d1 ; G
0x40 0d1 ; E+
;
0x30 0d1 ; C
0x37 0d1 ; G
0x40 0d1 ; E+
;
0x18 0d8 ; C bass
0x00
#Vectors
Boot start
Device 0x50 tick