SIDin #4 — Martin Galway's Arkanoid music routine, the explanation (Stefano Tognon, 2003)
Tognon's prose around his reverse-engineering of arkanoid — the explanation of the galway-player that the code alone does not give: why he picked this tune, what he thinks of the code, which instructions exist on which voice, the instrument table and its running copy, the four-phase adder engine that makes vibrato, portamento, pulse sweeps and filter envelopes from the same idea, how sound effects bypass the streams, and why the "samples" were, in his reading, deliberately hidden. The listing itself is covered by s-realdmx-galway-arkanoid-player.
Key claims#
Why Arkanoid, and the verdict on the code
- Chosen "as the tune contains the new sample music system in it"; what struck him was "a self modified code to mask the use of volume register for sample, and the use of 6502 JMP/JSR opcode as music pattern … I think that understand the sample routine at that time using the available tools was very difficult due to the use of all this kind of masked code."
- "each voice has its code that are perfectly equal each other, so it may be coded using some indexed addressing mode for reducing size … However, as all effects are coded into tables, this made the Galway engine very powerful: maybe having left each voice separate could be a Martin choice for not complicating more the engine."
- Some instructions exist for one voice only: "this seems me that Martin had added some instruction when needed: maybe other version of the engine has other implemented instructions. Otherwise, he removed the code not used by one voice for reducing memory size."
Songs, streams, notes
- A song is three voice-stream pointers and one byte, "the minim duration used for a note" (
$09in his example; the listing's eight tunes use$03,$04,$07,$09,$0Band$0D); raising it lengthens only the notes timed through the table. - "there are not the division in tracks and patterns … there are other methods to take advantage of the use of patterns: subroutine call" — "programming a track is like creating a music program using a high level language"; an approach "very powerful if it is compiled by hand, but too complicated by an editor program". The mnemonics are his: "maybe Martin should had used directly the hex value instead" (sid-player-routine, orderlist).
- His instruction table, with the voices each exists on:
00–5F nnnote with table duration (5Frest) and60–BF nnnote −$60with the duration in the byte (BFrest) — all voices;C0RTS ("Also used for terminating a track"),C2 lo hiJSR,C4 lo hiJMP,CA id vaSET ("all offset of the table can be used"),CC nnFOR,CENEXT,D2 nn lo hiSETNI ("the first nn item of the instrument table"),D4 lo hiINSTR ("5 instrument parameters … control/ADSR"),D6 nn vaSETCI,DE id v1 v2SET2CI,F0 lo hiSETFI (copies the frequency program to offsets$00–$0D; he counts "13 values") — all voices;C6 ht lo hiJSRT (call withhthalftones added) — voices 1 and 3;DC id v1 v2SET2I — voices 2 and 3;D8 lo hiEXCT,E0LF3 ("low filter on voice 3 with max resonance"),E2 lo hiFILTA — voice 3 only. - "the note 12h is equivalent to note 72h" (with an immediate duration); "something like 6 octaves … so the max notes to use is 5Eh (BEh)".
The instrument (29 bytes) and its running copy (34 bytes) (instrument-tables)
- Definition
$00–$1C:$00–$07four signed frequency adders ("low/high to add in each cycle of phase 1–4"),$08–$0Bthe cycle counts,$0Cinitial delay,$0Dflag;$0E–$0Ftwo "wave (pulsation amplitude)" — pulse-width — cycle counts,$10delay,$11flag,$12–$15two pulse adders,$16–$17the pulse width;$18control register,$19AD,$1ASR;$1B"Duration before apply release ($FF means not apply release) [?]";$1C"Duration before an hard restart (after the end of event of previous 1Bh register)" — the table says "0 means no effect to apply [?]", the prose "A $FF value means no hard restart". "not all the meaning of this flags I was able to understand … try to found by yourself the answers :(" (hard-restart). - The running copy (
$00–$22) is whatSETCI/SET2CIedit: the same bytes to$17, then the current frequency at$18–$19, control$1A, the two durations$1B–$1C, and the live counters —$1D–$20"number of cycles of phase 1–4" for frequency,$21–$22for the two pulse phases; the definition's$08–$0B/$0E–$0Fare the values "to reload" into them. - "The main part of the instruments table is the AD/SR (19h-1Ah offsets), control (18h offset), and wave pitch for rectangular waveform (16h-17h offsets) values (as usual). All the other values are for pitch control".
- Test bit in the control byte: "it is tested if 1Bh table value is below current duration before apply a release (even if test bit is selected, the effective control putted into sid register is the one without the test bit)".
- Voice 2 only: "if bit 3 is 1, other task are performed: very important task for the timbre as it heavy used into tune 1" — the arpeggio mode identified from the code in s-realdmx-galway-arkanoid-player (chord-arpeggio).
Frequency timbre: four phases (vibrato, pitch-slide-and-portamento)
- In each phase the value the SID is producing gets the phase's 16-bit adder added every cycle for the phase's count; "the added value are in two complement logic, so you can produce even a subtraction";
$0Cdelays the first phase; the flag "must be different from 0 for having the frequency timbre engaged". - The flag, from his table (
xyyy yyyyz):x(bit 7) = 1 → "reload the freq. cycles with freq. value of instrument table again" (restart from the note's stored frequency);z(bit 0) = 1 → "reload the freq. cycles but use the actual frequency value";y= 1 → "continue with actual freq., no more cycle". His prose says "bit 1" for the last case; the listing testsand #$81at the end of phase 4 andand #$02only during the initial delay, where bit 1 applies adder 4 every tick — the slide-into-the-note reading of the realdmx summary. - Vibrato:
$14,$00 / $EC,$FF / $14,$00 / $00,$00 / $03,$06 / $03,$00 / $1E,$05— "after an initial delay of 1Eh cycles, there are 3 cycles where 14h is added to currently frequencies, then for 6 cycles -14h is added (and so subtracted), finally for 3 cycles 14h is added. Now, the cycles can restart (05h) continuing with the actual frequencies." His figure shows "why cycle 2° is double longer (this is necessary for having the correct up/down sequence)" — up from the centre, across to the bottom, back to the centre. - Portamento:
$01,$00 / $00,$00 / $00,$00 / $00,$00 / $35,$00 / $00,$00 / $00,$08— "a long up portamento with little frequency increment": +1 per cycle for$35cycles, then (flag$08, no bit 7 or 0) hold. "Just changing the phases, you can reach very complex frequencies tasks: look at the source for more examples of real Martin use of the phases."
Rectangular wave timbre: two phases (pulse-width-modulation)
- Same logic with two phases and the same three flag cases (bit 7 restart from the stored width, bit 0 restart from the current one, other bits hold). "a classical up/down pulse amplitude effect":
$32,$32 / $14,$05 / $0A,$00 / $F6,$FF— 50 cycles of +10, 50 of −10, after a 20-cycle delay, looping from the current width.
Filter table: 16 bytes (filter-programming)
- "The Martin engine have a very complex filter manipulation":
$00–$07four cutoff adders,$08–$0Bcycles,$0Cdelay,$0Dflag (same three cases),$0E"Filter low value (8 bit)",$0F"Filter high value (3 bit)" — "analogue to what we can achieve with frequencies table". The Arkanoid table:$4D,$01 / $D3,$FF / $FB,$FF / $FF,$FF / $03,$14,$0A,$32 / $00 / $04 / $01,$00. Loaded byFILTA; "even the FL3 (that set the resonance of filter to max) and some calling to EXCT of custom code are used for better controlling the filter generation".
Game sound effects
- "a sound effect is activated by compiling an effect instrument table, and then activating the same methods (makeTimbreVx) used by the engine. The sound instruments table is a perfect copy of the instrument table, but with two extra bytes at the end: the low/high value of the frequency to use." No note, no duration byte — "the duration is taken according by the timbre effect the instrument table is executing"; "all the 3 voices can be used for generating the sound effect (and else, this is the way used in the game)".
Sample (chip-samples)
- "If you are thinking that sample generation used by Martin is the 'reproduction of sample' … you are not in the right way. If you were a ripper, maybe you know … the PSID specific extension that were introduced for managing this kind of 'sample'." Six procedures each "reproduce a particular timbre" by volume variation, called by "a flow instruction control":
81–86 xxplay sample 1–6 forxx,87 xxnothing,20 lo hiJSR,40NEXT,60RTS,49 xxFOR,4C lo hiJMP — "exactly the same opcodes as 6510 instructions … This can be a coincidence, or maybe a way to make the part related to sample generation a bit harder to understand". The pointer tables "are located in two area very far", the six routines "into very sparse memory area" with self-modifying code — "maybe some suspicious of a way to hidden the sample generation seems to be present into Martin engine" (or "a memory restriction of the game"). PSample1, printed in full:ldy #$05(six delays, read from the end),ldx #$0Crepeats per delay, a busy loop countingdelayTabP1[y]down,$DEincremented by 1,and #$0F,sta $DD18— whose operand is unmasked to$D418on entry (lda #$94 / clc / adc #$40 / sta Vol1+2) and masked again on exit (lda #$DD; the other five routines store$94). "Each call to the Psample1 routine will generate a sample sound witch timbre is governed by two parameters: a repeat value and a table of delays" — the volume climbs and "restart from low level when the max level is reached"; the tables contain "5 or more delay values" (delayTabP1: $40,$1E,$3E,$19,$3C,$14;delayTabP3: $20,$10,$08,$04,$02,$01).- Why it sounds different in a player: "Now you probably should be able to understand why the Arkanoid tune will play so different into a sidplayer (with extended sid register) instead of the real machine (or today RSID rip). PSID extended register try to emulated this kind of sound generation that is quite an unusual way for common samples sound." (sid-format)
- The engine (p. 98):
playSamplecounts$DCdown, reads the stream at($DA); a byte ≥$80is a routine index ($DF) followed by a duration, anything else is looked up inoperTable($20,$40,$60,$49,$4C) and dispatched; after a hitlda #$0F / ora TEMP / sta $D418— "turn the volume to max / turn filter to the store value". - Zero page, from the listing's comments (p. 33): 8-deep call stacks (lo, hi, repeat count) at
$31/$39/$40for voice 1,$A9/$B1/$B9voice 2,$61/$69/$71voice 3,$78/$80/$89the sample stream;$DA–$DBsample stream pointer,$DCits duration,$DDits stack index,$DFroutine index;$E0–$E5voice stream pointers,$E6–$E8note durations,$E9–$EBstack indexes,$EC–$EE"halftone to add to current note";$F1–$F6pulse width,$F7–$FCfrequency,$FD–$FEtable pointer,$FFcurrent note.
Conclusion (p. 98): "It is a very clean rip, where the ripper had manage a complete code initialization that take order especially into sound effects generation. However in the rip there other part of Martin Engine (a voice 1 and 2 part) … probably they are part of the relocation of the code into upper memory." The header: RSID version 2, 20 songs, init $0801, "Arkanoid / Martin Galway / 1986 Imagine".
Practical takeaways#
- One mechanism, three sizes: a phase is a signed 16-bit adder and a cycle count; pitch gets four phases, pulse width two, cutoff four; each program has a delay and a flag that says what happens at the end — restart from the stored value (bit 7), restart from where you are (bit 0), or hold (anything else). Vibrato = 3/6/3 cycles of +/−/+; portamento = one phase and hold; a pulse sweep = two phases looping; a filter pluck = a fast rise and three slowing falls.
- The song header's minimum-duration byte is a tempo control that leaves immediate-duration notes untouched — write ornaments with
+ $60notes and they keep their length when the tune is slowed. - A sound effect is an instrument plus a pitch and nothing else: the modulation program is the effect, and it works on any voice.
- Galway's drums are a repeat count and six delays per timbre; a PSID-era player's sample emulation gets them wrong — listen as RSID or on hardware.
Notable quotes#
"as all effects are coded into tables, this made the Galway engine very powerful"
"programming a track is like creating a music program using a high level language"
Relevance to the wiki#
The explanation layer for galway-player and arkanoid: per-voice instruction sets, the running-instrument copy, the flag semantics, SFX as instrument + pitch, the "hidden" sample synth and the PSID/RSID consequence for sid-format; concrete recipes for vibrato, pitch-slide-and-portamento, pulse-width-modulation and filter-programming; Tognon's verdict on Galway's code for martin-galway.
To verify#
- (unverified) Tognon's table lists
SETCI(D6) for all three voices; the listing's dispatch (checked in the realdmx copy) has handlersinst_D6_v1andinst_D6_v2only.SET2I(DC) on voices 2 and 3 andJSRT(C6) on 1 and 3 agree with the listing. - (unverified) "something like 6 octaves":
$5Eis 94 semitones above note$00, nearly eight octaves; the wiki keeps the range$00–$5Efrom the code summary. - The excerpt of
PSample1on p. 32 readslda delayTabP1-1,y, the listing (p. 97 and the realdmx copy)lda delayTabP1,y— a typo in the excerpt. - (unverified) His example song header
$09is labelledtune1; in the realdmx copytune1carries$04andtune3$09— one of the two relabelled the tunes.
Pages touched#
arkanoid · galway-player · martin-galway · sid-format · filter-programming · pulse-width-modulation · vibrato · pitch-slide-and-portamento · chip-samples · instrument-tables · sid-player-routine · hard-restart · chord-arpeggio · orderlist · sidin · sound-effects