# Sampled sound

[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/sampled-sound

> Generate samples for a plucked sound, upload them into the Synth's Sample memory and play them at eight pitches by changing the playback rate.

The same program in: [M68K](https://asm-editor.specy.app/learn/courses/m68k/examples/sampled-sound.md), [MIPS](https://asm-editor.specy.app/learn/courses/mips/examples/sampled-sound.md), [x86-64](https://asm-editor.specy.app/learn/courses/x86/examples/sampled-sound.md)

The **sample** waveform plays a sound from a list of numbers. Each **sample** is the sound's level
at one instant: a signed byte from -128 to 127, with 0 for silence. This program computes those
bytes, copies them into the **Synth**, the sound chip this editor adds at `0xffff0100`, and plays
them as a scale. RARS does not have the Synth.

The buffer holds 4,000 samples. Each cycle has 16 positive samples followed by 16 negative ones,
making a square wave. Its **amplitude**, the size of the swing, falls by one every 32 samples to
make the sound fade. In the `fill` loop, `t1` is the sample index: `srli` divides it by 32 to count
cycles, and `andi` tests bit 4 to choose the positive or negative half. Indices 0, 16 and 32
therefore produce levels 125, -125 and 124. `sb` stores the low byte of each value; the Synth
interprets that byte as signed.

For this waveform, the frequency register sets **samples per second**. At 8,000 samples a second,
the 32-sample cycle repeats `8000 / 32 = 250` times a second: a 250 Hz tone. The 4,000 samples
would last half a second. Playing them twice as fast raises the pitch an octave and halves their
duration. The table selects eight notes of a major scale, from 250 Hz to 500 Hz.

Choose **Build**, then **Run**, with your sound on. Build resets the Synth's registers to zero;
this example leaves attack, decay, release and loop start at that default.

```riscv
.data
rates:                      # samples a second for each note: 8000 plays the sound as computed
    .half 8000, 8980, 10079, 10679, 11986, 13454, 15102, 16000, 0
pluck:
    .space 4000             # 4,000 signed samples: half a second at 8,000 a second

.text
.globl main
main:
    # fill the buffer with a fading square wave: 32 samples per cycle
    la t0, pluck
    li t1, 0               # sample index
    li t6, 4000
fill:
    srli t2, t1, 5        # cycle number: sample index / 32
    li t3, 125
    sub t3, t3, t2       # amplitude falls by 1 per cycle
    andi t4, t1, 16       # the first half of each cycle is high,
    beqz t4, store
    sub t3, zero, t3     # the second half low
store:
    add t5, t0, t1
    sb t3, 0(t5)
    addi t1, t1, 1
    blt t1, t6, fill

    # copy the samples into the Synth's Sample memory
    li s0, 0xffff0100
    sw t0, 0x180(s0)      # from the buffer
    sh t6, 0x184(s0)      # 4,000 bytes
    sh zero, 0x186(s0)    # to the start of Sample memory
    sb zero, 0x188(s0)    # trigger the copy; the written value does not matter

    # channel 0 plays them
    sh zero, 0x0a(s0)     # from sample 0
    sh t6, 0x0c(s0)       # for 4,000 samples, then silence
    li t1, 6
    sb t1, 0x11(s0)       # waveform 6: sample
    li t1, 120
    sb t1, 0x12(s0)       # volume
    li t1, 127
    sb t1, 0x13(s0)       # sustain: the samples fade by themselves

    la s1, rates
    li s2, 1               # the gate bit
next:
    lhu t1, 0(s1)         # the next rate
    beqz t1, done
    sb zero, 0x10(s0)     # gate off, then on: from the first sample again
    sh t1, 0(s0)
    sb s2, 0x10(s0)       # write the gate bit to channel 0's control register
    li a7, 32
    li a0, 300             # wait 300 ms before starting the next note
    ecall
    addi s1, s1, 2
    j next

done:
    li a7, 10
    ecall
```

The `pluck` buffer is in the program's memory. Playback reads the Synth's own **Sample memory**,
shared by all its channels. An **upload** copies the buffer into Sample memory: write the source address, byte
count and destination offset, then write to `0x188(s0)` to copy the bytes at that moment.
Changing `pluck` afterwards has no effect on playback until another upload copies it again.

Channel 0 selects waveform 6 and plays from sample 0 for 4,000 samples, then goes silent. Sustain
127 keeps the chip's envelope at full level while the gate is on, leaving the stored samples to
provide the fade. Each note turns the gate off and on to restart at the first sample with a new
rate. The 300 ms wait means the slower notes restart before their full fade finishes; at 16,000
samples a second, playback finishes after 250 ms, before the next note starts.

## Try it

Make the sound loop: change `li s2, 1` to `li s2, 3`. This loads 3 into the CPU register `s2`;
the existing `sb s2, 0x10(s0)` writes it to the Synth's control register. Value 3 sets both bit 0
(gate) and bit 1 (loop). Also change `li a0, 300` to `li a0, 800` so every note has time to reach
the end and loop before the next note starts.

The loop bit makes the channel return to its **loop start**, the halfword at offset `0x0e`, when
it reaches the end. Its default of 0 repeats the whole fade. To repeat only the last 160 samples,
insert `li t1, 3840` followed by `sh t1, 0x0e(s0)` immediately after `sh t6, 0x0c(s0)`. After the
initial fade, samples 3,840 through 3,999 repeat as a quiet held tone.

Then change the sound itself. `andi t4, t1, 16` splits each cycle of 32 samples in two
halves; `andi t4, t1, 8` makes each cycle 16 samples long, raising the pitch an octave. The
[Synth chapter](https://asm-editor.specy.app/documentation/risc-v/synth/samples.md) has the other registers.

## Instructions in this Lecture

- RISC-V: [`la`](https://asm-editor.specy.app/documentation/risc-v/instruction/la.md), [`li`](https://asm-editor.specy.app/documentation/risc-v/instruction/li.md), [`srli`](https://asm-editor.specy.app/documentation/risc-v/instruction/srli.md), [`sub`](https://asm-editor.specy.app/documentation/risc-v/instruction/sub.md), [`andi`](https://asm-editor.specy.app/documentation/risc-v/instruction/andi.md), [`beqz`](https://asm-editor.specy.app/documentation/risc-v/instruction/beqz.md), [`add`](https://asm-editor.specy.app/documentation/risc-v/instruction/add.md), [`sb`](https://asm-editor.specy.app/documentation/risc-v/instruction/sb.md), [`addi`](https://asm-editor.specy.app/documentation/risc-v/instruction/addi.md), [`blt`](https://asm-editor.specy.app/documentation/risc-v/instruction/blt.md), [`sw`](https://asm-editor.specy.app/documentation/risc-v/instruction/sw.md), [`sh`](https://asm-editor.specy.app/documentation/risc-v/instruction/sh.md), [`lhu`](https://asm-editor.specy.app/documentation/risc-v/instruction/lhu.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)

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