# Sampled sound

[Courses](https://asm-editor.specy.app/learn/courses.md) › [MIPS assembly](https://asm-editor.specy.app/learn/courses/mips.md) › Examples

Web page: https://asm-editor.specy.app/learn/courses/mips/examples/sampled-sound

> Generate a plucked sound in memory, upload it to the Synth and play it 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), [RISC-V](https://asm-editor.specy.app/learn/courses/risc-v/examples/sampled-sound.md), [x86-64](https://asm-editor.specy.app/learn/courses/x86/examples/sampled-sound.md)

This program computes a plucked sound, copies it into the editor's **Synth** at `0xffff0100`,
and plays it at eight pitches. MARS does not have the Synth. Choose **Build**, then **Run**, with
your sound on.

A **sample** is a number giving the sound's level at one instant. Here each sample occupies one
signed byte, with values from -128 to 127. A value of 0 is the centre level. The `pluck` buffer reserves
4,000 bytes in the program's memory, one per sample.

The `fill` loop makes a square wave that fades. `$t1` counts sample positions from 0 to 3,999.
Shifting it right by 5 divides it by 32, discarding the remainder, to find the current period.
Subtracting that period number from 125 gives the **amplitude**, the height of the wave's swing:
it starts at 125 and falls by one every 32 samples. The mask `andi $t4, $t1, 16` is zero for the
first 16 positions of each period and nonzero for the next 16. The loop keeps the amplitude
positive for the first half and negates it for the second. `sb` writes the low byte of that signed
result at `pluck + $t1`.

The Synth plays from its own **Sample memory**, shared by all eight channels. The program
**uploads** a copy of `pluck` there, then selects waveform 6 to play those bytes as signed levels.
For this waveform, the frequency register sets the **playback rate in samples per second**.
At a rate of 8,000, the 32-sample wave repeats `8000 / 32 = 250` times a second: 250 Hz.
The whole buffer takes `4000 / 8000 = 0.5` seconds to play, fading like a plucked string.

```mips
.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:
    # compute a plucked string: a square wave of 32 samples a period, fading away
    la $t0, pluck
    li $t1, 0               # the sample's number
    li $t6, 4000
fill:
    srl $t2, $t1, 5         # which period it is in: its number / 32
    li $t3, 125
    sub $t3, $t3, $t2       # the amplitude, falling by 1 a period
    andi $t4, $t1, 16       # the first half of each period 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)    # now

    # 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)
    li $v0, 32
    li $a0, 300             # 300 milliseconds a note
    syscall
    addi $s1, $s1, 2
    j next

done:
    li $v0, 10
    syscall
```

The upload uses four device registers, all at offsets from the Synth base in `$s0`:

| Offset  | Write                 | Meaning                                          |
| ------- | --------------------- | ------------------------------------------------ |
| `0x180` | word from `$t0`       | Source address: `pluck` in the program's memory. |
| `0x184` | halfword from `$t6`   | Length: 4,000 bytes.                             |
| `0x186` | halfword from `$zero` | Destination: offset 0 in Sample memory.          |
| `0x188` | byte from `$zero`     | Start the copy. Writing any value triggers it.   |

The copy captures the buffer at that moment. Changing `pluck` afterwards leaves the uploaded
sound unchanged until another upload replaces it.

Channel 0 starts at offset 0 in Sample memory and plays 4,000 samples. A sustain value of 127 holds the
channel's envelope at full level, leaving the shrinking sample values to make the fade. Build
starts the other Synth registers at 0, so attack, decay, and release are immediate and looping
is off. At the buffer's end, playback becomes silent. Switching the gate off and on restarts
from the first sample.

The `rates` table holds eight playback rates followed by a zero end marker. Each halfword takes
two bytes, so `addi $s1, $s1, 2` moves to the next rate. The rates form a major scale starting
from the buffer's 250 Hz tone. Playing the same samples faster raises the pitch and finishes
the buffer sooner: 16,000 is twice the initial rate, giving a tone one octave higher at 500 Hz
and a buffer duration of a quarter of a second.

Service 32 waits 300 milliseconds between gate restarts. At the lower rates, this cuts the
sound short: the first buffer needs 500 milliseconds to finish. At 16,000, the whole buffer
finishes in 250 milliseconds, leaving a short silence before the wait ends.

## Try it

First replace `li $a0, 300` with `li $a0, 800`. This gives every note time to reach the buffer's
end and lets you hear the looping experiments below.

Make the whole fade repeat by replacing `li $s2, 1` with `li $s2, 3`. This loads 3 into the CPU
register `$s2`; the existing `sb $s2, 0x10($s0)` then writes it to the Synth's control register.
The value 3 sets both bit 0, the gate, and bit 1, looping. When playback reaches the end, it
returns to the **loop start**, the halfword at offset `0x0e`. Its default value is 0, so the
whole fade repeats until the next gate restart.

Then loop only the quiet tail. After `li $s0, 0xffff0100`, add `li $t1, 3840` followed by
`sh $t1, 0x0e($s0)`. Keep `$s2` at 3 and the wait at 800 milliseconds. Each note first plays
the whole buffer, then repeats samples 3,840 through 3,999: the last 160 samples, heard as a
quiet held tone.

Finally change the sound itself. `andi $t4, $t1, 16` splits each period of 32 samples into two
halves; changing the mask to `andi $t4, $t1, 8` makes the wave repeat every 16 samples, an octave
higher at the same playback rate. The [Synth chapter](https://asm-editor.specy.app/documentation/mips/synth/samples.md) lists
the other registers.

## Instructions in this Lecture

- MIPS: [`la`](https://asm-editor.specy.app/documentation/mips/instruction/la.md), [`li`](https://asm-editor.specy.app/documentation/mips/instruction/li.md), [`srl`](https://asm-editor.specy.app/documentation/mips/instruction/srl.md), [`sub`](https://asm-editor.specy.app/documentation/mips/instruction/sub.md), [`andi`](https://asm-editor.specy.app/documentation/mips/instruction/andi.md), [`beqz`](https://asm-editor.specy.app/documentation/mips/instruction/beqz.md), [`add`](https://asm-editor.specy.app/documentation/mips/instruction/add.md), [`sb`](https://asm-editor.specy.app/documentation/mips/instruction/sb.md), [`addi`](https://asm-editor.specy.app/documentation/mips/instruction/addi.md), [`blt`](https://asm-editor.specy.app/documentation/mips/instruction/blt.md), [`sw`](https://asm-editor.specy.app/documentation/mips/instruction/sw.md), [`sh`](https://asm-editor.specy.app/documentation/mips/instruction/sh.md), [`lhu`](https://asm-editor.specy.app/documentation/mips/instruction/lhu.md), [`syscall`](https://asm-editor.specy.app/documentation/mips/instruction/syscall.md), [`j`](https://asm-editor.specy.app/documentation/mips/instruction/j.md)

Previous: [Chiptune](https://asm-editor.specy.app/learn/courses/mips/examples/chiptune.md)
