From Sound Chip to Beep
Control the Sega Genesis / Mega Drive YM2612 with JavaScript, one register at a time.
This is the browser companion to the chip basics and C++ beep tutorial. We will use one channel and one audible operator. You can edit and run the complete example below in Playground.
1. What does the chip do?
The YM2612 makes sound from settings written by a program. It has six FM channels, each with four operators. An operator produces a sine-based signal and controls its loudness over time with an envelope. Operators can become audible output or modulate other operators.
For our first beep, we only need a steady tone: one audible operator, no modulation, and no feedback.
On the original hardware, the CPU sends bytes through the chip's data bus and a clock drives the chip. Here, Playground runs the emulator and sends its generated samples to browser audio. We only need to supply the settings and the timing.
2. A register is a small setting
A register has an address and holds a byte. The address chooses what to change; the byte supplies the setting.
JavaScript's 0x prefix means hexadecimal: 0x07 is 7 and 0x7f is 127.
write(0xb0, 0x07); // CH1: algorithm 7, feedback 0
This write(address, value) helper is provided by Playground. It combines the two bus writes
used in the C++ tutorial: select a register, then send its value.
// C++ / chip bus interface:
chip.write(0, 0xb0); // Select a register in bank 0.
chip.write(1, 0x07); // Send its value.
// JavaScript / Playground:
write(0xb0, 0x07);
Most channel registers are divided into two banks: bank 0 for CH1–CH3 and bank 1 for CH4–CH6.
Playground calls this bank argument port. You can make it explicit with
write(0, 0xb0, 0x07). The two-argument form defaults to bank 0.
The C++ bus offsets are different: 0 and 1 select/write bank 0; 2 and 3 select/write bank 1. For this beep, all writes use bank 0.
3. Choose a simple voice
Algorithm 7 connects all four operators directly to the output, in parallel. We make OP1–OP3 very quiet and leave OP4 audible.
OP1–OP3 quiet · OP4 audible · FB off
The four hardware slot offsets are 0, 4, 8, 12.
Adding an offset to a register group's base address selects a slot.
The last offset, 12 (0x0c), selects OP4; for example, its total level is at 0x4c.
These are hardware address offsets, not Playground's OP1–OP4 constants.
The two middle slots have a different logical order; they receive identical settings here.
| CH1 register group | Setting | Our beep |
|---|---|---|
0x30 + offset | DT / MULTI: detune and frequency multiplier | 0x01: no detune, ×1 |
0x40 + offset | TL: attenuation | 0x7f for quiet slots; 0x00 for OP4. Higher means quieter. |
0x50 + offset | AR: attack rate | 0x1f: fastest attack, rate scaling off |
0x80 + offset | SL / RR: sustain level threshold and release rate | 0x0f: SL 0, fastest release |
0xb0 | Algorithm / feedback | 0x07: algorithm 7, feedback off |
0xb4 | Stereo output / LFO sensitivity | 0xc0: left and right enabled, sensitivity 0 |
We begin with fm.reset(). This leaves the two decay rates, D1R and D2R, at zero,
so the tone stays steady until key off. Release then makes it fade quickly.
4. Set the pitch, then press the key
The chip stores pitch as a range called BLOCK and an 11-bit number called FNUM.
The C++ beep uses BLOCK 4 and FNUM 0x269. They occupy two registers:
write(0xa4, 0x22); // High: (BLOCK 4 << 3) | upper FNUM bits 2
write(0xa0, 0x69); // Low: remaining eight FNUM bits
Write the high part first, then the low part. The low write commits the combined pitch. These values reproduce the tutorial's pitch; they are not a frequency in Hz.
Finally, register 0x28 controls key on and key off.
In 0xf0, the upper four bits turn on all four operators and the lower bits select CH1.
Key on starts the envelopes; it does not choose the voice or pitch.
write(0x28, 0xf0); // CH1: key on
await sleep(3); // Let it sound for three seconds.
write(0x28, 0x00); // CH1: key off
5. Play the beep
Press Run in Playground. This is the complete JavaScript version of the register setup in
ex03_beep.cpp, followed by a timed key off.
Playground provides fm, write, and sleep.
Change one thing and listen
- Change OP4's TL from
0x00to0x20in the conditional expression: a quieter beep. - Change pitch high from
0x22to0x2a: BLOCK rises from 4 to 5, one octave higher. - Change
sleep(3)tosleep(0.3): a shorter beep. - Change stereo output from
0xc0to0x80or0x40: left only or right only.
6. From register bytes to musical controls
Once the register version makes sense, Playground's named methods let you express the same channel settings more directly:
fm.setAlgo(CH1, 7, 0); // write(0xb0, 0x07)
fm.setPan(CH1, true, true); // write(0xb4, 0xc0)
fm.setFrequency(CH1, 4, 0x269); // writes 0xa4 and 0xa0
fm.keyOn(CH1); // write(0x28, 0xf0)
await sleep(3);
fm.keyOff(CH1); // write(0x28, 0x00)
This fragment replaces the channel and playback lines; it still needs the operator setup above. The sound comes from the same sequence: configure the voice, set the pitch, key on, wait, key off.
Next, explore operators and FM sound design, or use one tone to explore tuning and musical intervals.