# Chiptune

[Courses](https://asm-editor.specy.app/learn/courses.md) › [RISC-V assembly](https://asm-editor.specy.app/learn/courses/risc-v.md) › Examples

Web page: https://asm-editor.specy.app/learn/courses/risc-v/examples/chiptune

> Play a short tune on the Synth: a pulse lead, a triangle bass and noise drums on three channels. Read the notes from a table in memory.

The same program in: [M68K](https://asm-editor.specy.app/learn/courses/m68k/examples/chiptune.md), [MIPS](https://asm-editor.specy.app/learn/courses/mips/examples/chiptune.md), [Z80](https://asm-editor.specy.app/learn/courses/z80/examples/chiptune.md)

This program plays a short tune on the **Synth**, the editor's sound chip at `0xffff0100`.
The Synth is not available in RARS. Three channels play together: a pulse wave for the melody, a triangle
wave for the bass, and noise for the drums. Choose **Build**, then **Run**, with your sound on.

Each channel has the same register layout, and the channels start `0x20` bytes apart. The program
stores the Synth's base address in `s0`, so channels 0, 1 and 2 start at `s0`, `s0 + 0x20` and
`s0 + 0x40`. Add a setting's offset to the channel's starting address to find its register. For
example, decay is at offset `0x06`, so the bass's decay register is at `0x26(s0)`. `sh` writes
two-byte settings, and `sb` writes one-byte settings.

Pulse **width** sets how much of each cycle the wave stays high, in units of 1/4096 of a cycle.
A value of 1024 keeps it high for a quarter of the cycle. A value of 2048 keeps it high for half
the cycle, producing a square wave.

An **envelope** shapes a note's volume. Turning the gate on starts the **attack**, which raises
the volume to full. The **decay** then lowers it to the **sustain** level, where it stays while
the gate is on.
Turning the gate off starts the **release**, which fades to silence. Sustain ranges from 0 (silence) to 127
(the channel's full volume). Attack, decay and release use ticks of `1/1024` second, about one
millisecond. Decay and release values describe a fall across the whole volume range; smaller
drops take proportionally less time. A zero time makes that phase immediate. Registers start at
zero, so the bass's unset attack and decay are immediate, and the drum's unset sustain is silent.

```riscv
.data
# Each triple is a step, an eighth of a bar: the frequencies in Hz of the lead, bass and drum's
# noise. 0 leaves that channel as it is; a lead of 0xffff ends the song.
song:
    .half 523, 131, 1200,   659, 0, 0,   784, 0, 24000,   659, 0, 0
    .half 523, 131, 7000,   659, 0, 0,   784, 0, 24000,   1047, 0, 0
    .half 440, 110, 1200,   523, 0, 0,   659, 0, 24000,   523, 0, 0
    .half 440, 110, 7000,   523, 0, 0,   659, 0, 24000,   880, 0, 0
    .half 349, 87, 1200,   440, 0, 0,   523, 0, 24000,   440, 0, 0
    .half 349, 87, 7000,   440, 0, 0,   523, 0, 24000,   698, 0, 0
    .half 392, 98, 1200,   494, 0, 0,   587, 0, 24000,   494, 0, 0
    .half 392, 98, 7000,   494, 0, 0,   587, 0, 24000,   784, 0, 0
    .half 523, 131, 1200
    .half -1                # stored as 0xffff: the end of the song

.text
.globl main
main:
    li s0, 0xffff0100     # the Synth: channel 0 at +0x00, 1 at +0x20, 2 at +0x40

    # channel 0, the lead: a narrow pulse, struck, then held a little quieter
    sb zero, 0x11(s0)     # waveform 0: pulse
    li t0, 1024
    sh t0, 0x02(s0)       # pulse width: high a quarter of the time
    li t0, 4
    sh t0, 0x04(s0)       # attack
    li t0, 200
    sh t0, 0x06(s0)       # decay
    li t0, 60
    sh t0, 0x08(s0)       # release
    li t0, 80
    sb t0, 0x12(s0)       # volume
    li t0, 70
    sb t0, 0x13(s0)       # sustain

    # channel 1, the bass: a triangle, held at full level
    li t0, 1
    sb t0, 0x31(s0)       # waveform 1: triangle
    li t0, 40
    sh t0, 0x28(s0)       # release
    li t0, 127
    sb t0, 0x32(s0)       # volume
    sb t0, 0x33(s0)       # sustain

    # channel 2, the drums: noise that decays to silence, its sustain left at 0
    li t0, 4
    sb t0, 0x51(s0)       # waveform 4: noise
    li t0, 90
    sh t0, 0x46(s0)       # decay
    li t0, 100
    sb t0, 0x52(s0)       # volume

    la s1, song
    li s2, 1               # the gate bit
    li s3, 0xffff          # the end of the song
step:
    lhu t0, 0(s1)         # the lead
    beq t0, s3, done
    mv   a1, s0           # channel 0's registers
    jal strike
    lhu t0, 2(s1)         # the bass
    addi a1, s0, 0x20
    jal strike
    lhu t0, 4(s1)         # the drum
    addi a1, s0, 0x40
    jal strike
    li a7, 32
    li a0, 150             # an eighth of a bar: 150 milliseconds
    ecall
    addi s1, s1, 6        # the next three-halfword step
    j step

# Plays the frequency in t0 on the channel whose registers start at a1; 0 leaves it alone.
strike:
    beqz t0, held
    sb zero, 0x10(a1)     # gate off, then on: restart the attack from its current level
    sh t0, 0(a1)
    sb s2, 0x10(a1)
held:
    jr ra

done:
    li a7, 32
    li a0, 600             # let the last chord ring
    ecall
    li a7, 10                  # the program's end releases every gate
    ecall
```

Each table step is three halfwords: lead, bass and drum frequencies in Hz. `523, 131, 1200` is
the first step; the first eight data lines pack four steps each. `lhu` reads their two-byte
values at offsets 0, 2 and 4; adding 6 to `s1` reaches the next triple. A zero tells `strike` to
leave that channel alone.

Service 32 waits 150 milliseconds per step. The first 32 steps make four bars of eight steps
each. The separate 33rd triple plays the closing chord. The following `.half -1` stores `0xffff`, the end
marker checked in the lead position. `done` lets that chord ring for another 600 milliseconds
before exiting. Noise frequency controls the drum's brightness: 1200 gives a low thud, 7000 a
snare-like sound and 24000 a hi-hat-like hiss.

To call `strike`, put the frequency in `t0` and the channel's starting address in `a1`. The
routine reads both registers and uses the value 1 in `s2` to turn the gate on. `jal strike`
saves the return address in `ra`; `jr ra` jumps back to it, just like `ret`. For a nonzero
frequency, the routine turns the gate off, stores the frequency, then turns the gate on. This
restarts attack from the current envelope level. The bass's immediate attack and full sustain
may therefore make repeated notes sound continuous.

## Try it

For a brighter, brassier lead, replace `sb zero, 0x11(s0)` with `li t0, 2` followed by
`sb t0, 0x11(s0)` to select a sawtooth. Run and listen. Restore the original waveform store,
then change `li t0, 1024` before the pulse-width store to `li t0, 2048`. Predict how the
half-width pulse will sound, then run it to compare the hollower square wave with the original.

Then make the bass shorter. After its waveform store, add `li t0, 150` followed by
`sh t0, 0x26(s0)` for decay, and replace `sb t0, 0x33(s0)` with `sb zero, 0x33(s0)` for zero
sustain. Predict which bass notes will now have silence between them, then run and listen:
each bass note dies away in about 146 milliseconds, before the next 150-millisecond step. The
[Synth chapter](https://asm-editor.specy.app/documentation/risc-v/synth.md) has the other registers.

## Instructions in this Lecture

- RISC-V: [`li`](https://asm-editor.specy.app/documentation/risc-v/instruction/li.md), [`sb`](https://asm-editor.specy.app/documentation/risc-v/instruction/sb.md), [`sh`](https://asm-editor.specy.app/documentation/risc-v/instruction/sh.md), [`la`](https://asm-editor.specy.app/documentation/risc-v/instruction/la.md), [`lhu`](https://asm-editor.specy.app/documentation/risc-v/instruction/lhu.md), [`beq`](https://asm-editor.specy.app/documentation/risc-v/instruction/beq.md), [`mv`](https://asm-editor.specy.app/documentation/risc-v/instruction/mv.md), [`jal`](https://asm-editor.specy.app/documentation/risc-v/instruction/jal.md), [`addi`](https://asm-editor.specy.app/documentation/risc-v/instruction/addi.md), [`ecall`](https://asm-editor.specy.app/documentation/risc-v/instruction/ecall.md), [`j`](https://asm-editor.specy.app/documentation/risc-v/instruction/j.md), [`beqz`](https://asm-editor.specy.app/documentation/risc-v/instruction/beqz.md), [`jr`](https://asm-editor.specy.app/documentation/risc-v/instruction/jr.md)

Previous: [Flappy ASM in C](https://asm-editor.specy.app/learn/courses/risc-v/examples/flappy-asm-in-c.md)

Next: [Play a tune](https://asm-editor.specy.app/learn/courses/risc-v/examples/play-a-tune.md)
