Square1 output
Square0 output
Triangle output
This assuming a NES ROM file with an extra 0x10, 16 bytes at the beginning.
0x0D70 is a list of
offsets to songs played in the game. These offsets point to parts of the full
songs and are in order for. For example the start of the overworld theme is
at 0x0D78 and runs to 0xD7E (inclusive). These offsets are relative to
0x0D70 (start of the list offsets.
08 09 0A 0B 0C 0D 0E 0F
0D70 75 7D 85 95 7D 8D 95 9D
The value at 0x0D78 of 0x75 (in hex) points to
0x0D70 + 0x75 = 0x0DE5
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
0DE0 91 46 22 00 80 10 70 8E 32 5D 8E 01 80 10 0F 8F
10708E325D0101800x8E70 = (hi << 8) | lo
RAM address - 0X8000 + 0x10; nearly same address but with and additional 0x8000 as this data is copied directly from the ROM to RAM. The extra 0x10 is a result of the ROM having an NES header. 0xE800x8EA2 = 0x8E70 + 0x32
0x0EB20x8ECD = 0x8E70 + 0x5D
0x0EDD0x8EFE = 0x8E70 + 0x8E
0x0F0E0x1FE1
in the ROM
note & 0x80
The Assembly code actually checks if the "note" is negative. Then using only
the lower 3 bits note & 0x07 or note & 0b0000_0111
and adding the Note Length Table Base (from the header) or
(node & 0x07) + NoteLengthTableBase.
0xF0 store the number of repeats of a collection of notes
until a 0xF0. The number of repeats is repeats = value - 0xF0
These are only used in the triangle generator
A note is immediately read after a length or repeat is found.
where frequency of the CPU (fcpu) = 1789773.0 and
t = (NotePeriodTable[note] << 8) | NotePeriodTable[note+1].
The period, t, is just a 16-bit integer. The triangle wave has
an extra factor of 2 in the conversion
The triangle generator can also produce sounds well above the audible range.
A value of 8 is a rest and a value of 0 ends the song.
The value of 0 only appears for Square1 and stops the other generators from
continuing to read data. Everything is done with counters and a specific
clock cycle for the NES, 60 frames per second. The APU apppears to operate
on this same cycle.
0x0E80 in the song is a length value of 0x85,
the 7th bit is set. The value becomes (0x85 & 0x07) + 0x10 = (0x05 + 0x10) = 0x15,
with a resulting length of NoteLengthTable[0x15] = 0x30 = 48 cycles or 0.8
seconds assuming 1/60 seconds per cycle.
The first note is 0x32 at 0x0E81. Looking this up in
NotePeriodTable gives NotePeriodTable[0x32-0x33] = [0x00, 0xEF] = 0x00EF
resulting in a frequency of f = fcpu / (16 * (0x00EF + 1)) = 466.086
or a B 4th octave flat, or A 4th octave sharp. This is the frequency of a square wave
produced by the APU.
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
0E80 85 32 82 08 08 32 32 32 32 32 08 2E 83 32 82 08
0E90 08 32 32 32 32 32 08 2E 83 32 82 08 08 32 32 32
0EA0 32 80 32 08 28 28 28 08 28 28 28 08 28 28 81 28
0EBo 28 00 83 1A 82 1A 1A 1A 83 1A 82 1A 1A 1A 83 16
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
1FE0 11 03 0A 01 14 05 28 3C 70 07 1B 35 14 0D 28 3C
1FF0 50 06 0C 08 18 24 30 48 10 07 0D 09 1B 24 36 48
2000 10 3C 50 0A 05 14 0D 28 0E FF FF FF FF FF FF FF
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
1F10 00 23 00 6A 03 27 00 97 00 00 02 F9 02 CF 02 A6
1F20 02 80 02 5C 02 3A 02 1A 01 FC 01 DF 01 C4 01 AB
1F30 01 93 01 7C 01 67 01 53 01 40 01 2E 01 1D 01 0D
1F40 00 FE 00 EF 00 E2 00 D5 00 C9 00 BE 00 B3 00 A9
1F50 00 A0 00 8E 00 86 00 77 00 7E 00 71 00 54 00 64
1F60 00 5F 00 59 00 50 00 47 00 43 00 3F 00 38 00 32
1F70 00 21 05 4D 05 01 04 B9 04 35 03 F8 03 BF 03 89
1F80 03 57 AD 19 06 A9 20 A2 82 A0 7F 60 AD 19 06 10
Within the browswer, we use OscillatorNode within an AudioContext. Following the example of schedeuling audio generators along with the associated functions that come before, we can schedule the notes at the correct time and for the correct duration.
We need to be careful at the transition between song parts as the Square0 and Triangle notes
do not include and ending indicator 00. Here we pre-determine the length of the
song part, then tell the generators to not exceed this length by counting beats.
It all works surprisingly well. The Envelopes Selectors are not included to shape the volume. The songs are great and composed incredibly well. I would not have done this if the songs had not stuck in my head for so many years.