Give the machine a sound device
Four channels on ports 0x40 to 0x4F, each one a whole soundThing voice:
two oscillators, two envelopes, a filter and the routing between them. A
channel keeps its patch between notes, so a program sets an instrument up
once and then plays it.
Six ports rather than forty, because a voice has around forty settings and
four of them would spend more than half the port space on one device.
There is a selector and a value instead: say which channel, say which
setting, write it. That is three writes to change a setting and two to
play a note, which is the right way round - patches are loaded, notes are
played in an inner loop.
Samples come from the machine's clock and not the host's: 48,000 a second
of emulated time, worked out in whole numbers so it never drifts. A
million cycles is exactly 48,000 samples on any host at any speed, which
is what makes a sound something a test can compare. --sound writes them
out, the way --screen writes a picture, for the same reason: the suite has
no speaker.
Tests/sound.sh is 22 checks and found three real defects the first time it
ran, all the same shape - a synthesizer written for a patch editor, wired
up as hardware and inheriting the editor's assumptions:
- Only one voice had an oscillator switched on, so three of the four
channels could not make a sound whatever was written to them.
- That voice's oscillator arrived at full gain and every other one
arrived at nothing, an asymmetry with no reason behind it.
- A note with no sustain is silent but not over, so the obvious way to
wait for a sound to finish waits for ever.
The first two are fixed by the device defining its own power-on state
rather than inheriting synthInit's: every channel arrives able to make a
sound, so writing a note number is the whole of playing a note. The third
was already written into the manual as advice, an hour before the check
existed. The check disagreed with the documentation and the check was
right; the manual now says the one rule, which is that a note sounds until
the gate is dropped.
Programs/Examples/tune.asm plays eight notes, taking its tempo from the
screen's frame interrupt because that is the only regular beat this
machine has. It spends 99.8% of its cycles asleep in WAIT.
Voyager has no speaker yet - this is the device and its tests. Playing the
samples out of the window is the next commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
b0d06aa6e5
commit
d388cd3122
@@ -513,6 +513,7 @@ If nothing is installed for the vector a device refused with, the machine stops
|
||||
| 0x13 | The machine itself. Writing 1 asks it to start over: whatever put the first instruction in memory does it again, and the CPU begins where the boot vector points. A port rather than a service, because a reset has to work when the system does not - and a program that owns the whole machine has no system to ask. The disk is not unplugged and keeps what was written to it; the vector table is cleared, because a handler left behind would aim an interrupt into a program that is no longer running. | 0x04 |
|
||||
| 0x12 | A device that owns 256 bytes of memory. Writing to its port fills that memory with the byte written, standing in for a disk controller reading a sector. Its memory is unreachable until it is registered as a bank. | 0x12 |
|
||||
| 0x30 - 0x3F | The screen. See The Screen. It brings video memory, which is unreachable until it is registered as a bank. | 0x14 |
|
||||
| 0x40 - 0x4F | The sound device. See Making A Noise. Four channels, played by writing to ports; it brings no memory. | 0x15 |
|
||||
| 0xE0 - 0xEF | The memory controller. See The Memory Controller. | 0x03 |
|
||||
| 0xFF | The bus registry. See Asking What Is There. | 0x01 |
|
||||
|
||||
@@ -677,6 +678,200 @@ What a program on the other end of an actual serial line sees is a different que
|
||||
|
||||
**It is one screen.** A program that writes its own tiles and its own map has taken the screen, and a console still writing characters into it will scribble on what that program drew. This is not an oversight to be worked around - it is what one screen means, and it is why a program that wants the screen takes it.
|
||||
|
||||
## Making A Noise:
|
||||
|
||||
Four channels on ports 0x40 to 0x4F. Each one is a whole voice - two oscillators, two
|
||||
envelopes, a filter and the routing between them - and it keeps its settings between notes.
|
||||
Channel two is channel two: a program sets up a sound once and then plays it, the same way it
|
||||
sets up a tile once and then places it.
|
||||
|
||||
### Why It Is Six Ports And Not Forty:
|
||||
|
||||
A voice has around forty settings and there are four of them, so a port for each would spend
|
||||
more than half of the machine's whole port space on one device. Instead there is a **selector
|
||||
and a value**: say which channel, say which setting, write it. Three writes to change one
|
||||
thing.
|
||||
|
||||
That is the right price because of *when* a program pays it. Patches are loaded; notes are
|
||||
played. Changing a setting happens when a program starts or when an instrument changes, and
|
||||
three writes there costs nothing anybody can hear. Playing a note happens in the inner loop of
|
||||
a music routine, and that is two writes with no selector machinery at all.
|
||||
|
||||
### Registers:
|
||||
|
||||
| Port | Register |
|
||||
| --- | --- |
|
||||
| 0x40 | Status. Bit 0, some channel is still sounding. |
|
||||
| 0x41 | Channel, 0 to 3. Anything larger wraps, so a program cannot select a channel that is not there. |
|
||||
| 0x42 | Which setting the next write to 0x43 means. |
|
||||
| 0x43 | The value of that setting, for the selected channel. |
|
||||
| 0x44 | Note. Writing a MIDI note number **starts it**: 60 is middle C, and every 12 is an octave. |
|
||||
| 0x45 | Gate. Writing zero releases the note and lets it fade; writing anything else starts the last note again. |
|
||||
| 0x46 | Volume, for the whole device. |
|
||||
|
||||
Reading 0x41, 0x42 and 0x44 gives back what is in them, so a routine can save and restore the
|
||||
selection around an interrupt.
|
||||
|
||||
### The Shortest Program That Makes A Sound:
|
||||
|
||||
```
|
||||
RSTA
|
||||
OUTA 0x41 ; Channel 0
|
||||
INIA 0d60
|
||||
OUTA 0x44 ; Middle C, which starts it
|
||||
```
|
||||
|
||||
**Every channel arrives able to make a sound**: one oscillator switched on at full gain, a
|
||||
plain triangle wave, an envelope that fades in and holds. Writing a note number is the whole
|
||||
of playing a note, and a program only reaches for the settings when it wants a different
|
||||
sound rather than a sound at all.
|
||||
|
||||
The second oscillator arrives switched off, and that is not the same as arriving silent. **The
|
||||
two oscillators are averaged rather than added**, so switching the second one on halves the
|
||||
first whatever gain it has - which is what keeps two of them from clipping, and which means
|
||||
there is no setting of `active` that costs nothing. One oscillator is the plain case, and
|
||||
asking for two is something a program says out loud:
|
||||
|
||||
```
|
||||
INIA 0x15
|
||||
OUTA 0x42 ; Oscillator 1, on
|
||||
INIA 0x01
|
||||
OUTA 0x43
|
||||
INIA 0x11
|
||||
OUTA 0x42 ; and how loud
|
||||
INIA 0xC0
|
||||
OUTA 0x43
|
||||
```
|
||||
|
||||
### Settings:
|
||||
|
||||
The high nibble says which part of the voice, the low nibble which setting of it.
|
||||
|
||||
| Number | Part |
|
||||
| --- | --- |
|
||||
| 0x00 - 0x0F | Oscillator 0. |
|
||||
| 0x10 - 0x1F | Oscillator 1. |
|
||||
| 0x20 - 0x2F | The amplitude envelope. |
|
||||
| 0x30 - 0x3F | The modulation envelope. |
|
||||
| 0x40 - 0x4F | The filter. |
|
||||
| 0x50 | What shapes the channel's level. |
|
||||
| 0x60 - 0x6F | LFO 0. |
|
||||
| 0x70 - 0x7F | LFO 1. |
|
||||
|
||||
| Oscillator | Setting |
|
||||
| --- | --- |
|
||||
| 0 | Waveform: 0 sine, 1 triangle, 2 saw, 3 ramp, 4 pulse, 5 noise. Anything larger wraps. |
|
||||
| 1 | Gain. Silent at zero, which is where it starts. |
|
||||
| 2 | Pulse width, for the pulse wave. |
|
||||
| 3 | Detune, centred on 128, an octave either way. A step is about nine cents. |
|
||||
| 4 | Octave, centred on 128, two either way. |
|
||||
| 5 | On, or off at zero. |
|
||||
| 6, 7 | What modulates the pulse width, and how much. |
|
||||
| 8, 9 | What modulates the detune, and how much. |
|
||||
| 10, 11 | What modulates the gain, and how much. |
|
||||
|
||||
| Envelope | Setting |
|
||||
| --- | --- |
|
||||
| 0 | Attack. |
|
||||
| 1 | Decay. |
|
||||
| 2 | Sustain, the level it holds at while the note is held. |
|
||||
| 3 | Release. |
|
||||
|
||||
| Filter | Setting |
|
||||
| --- | --- |
|
||||
| 0x40 | On, or off at zero. |
|
||||
| 0x41 | Type: 0 low pass, 1 high pass, 2 band pass. Anything larger wraps. |
|
||||
| 0x42 | Cutoff. |
|
||||
| 0x43 | Resonance. |
|
||||
| 0x44, 0x45 | What modulates the cutoff, and how much. |
|
||||
| 0x46, 0x47 | What modulates the resonance, and how much. |
|
||||
|
||||
| LFO | Setting |
|
||||
| --- | --- |
|
||||
| 0 | On, or off at zero. |
|
||||
| 1 | Waveform, from the same six. |
|
||||
| 2 | Rate. |
|
||||
|
||||
Anywhere a setting asks *what modulates* something, the answer is one of these:
|
||||
|
||||
| Value | Source |
|
||||
| --- | --- |
|
||||
| 0 | Nothing. |
|
||||
| 1 | The amplitude envelope. |
|
||||
| 2 | The modulation envelope. |
|
||||
| 3 | LFO 0. |
|
||||
| 4 | LFO 1. |
|
||||
|
||||
**The two LFOs belong to the device and not to a channel**, so writing 0x60 to 0x7F ignores
|
||||
whichever channel is selected. That is what makes them useful: a vibrato that every voice
|
||||
shares is one wobble rather than four that drift apart.
|
||||
|
||||
### What A Byte Means:
|
||||
|
||||
Everything here is a byte, and a synthesizer wants seconds and hertz. How the one becomes the
|
||||
other is chosen for **where the useful part of the range is**, not for whatever arithmetic is
|
||||
tidiest.
|
||||
|
||||
| Kind of setting | 0 to 255 becomes |
|
||||
| --- | --- |
|
||||
| Times: attack, decay, release | Nought to four seconds, squared. |
|
||||
| Levels: gain, sustain, resonance, volume | Nought to the most there is, evenly. |
|
||||
| Cutoff, LFO rate | 20 Hz to 20 kHz, and 0.05 Hz to 20 Hz: exponential. |
|
||||
| Detune, octave, and every modulation depth | Centred on 128, so half is no change and either side is a direction. |
|
||||
|
||||
Times are squared because the difference between five and fifty milliseconds is the whole
|
||||
character of a percussive sound, and the difference between three seconds and four is nothing
|
||||
anybody can hear. A byte spread evenly over four seconds would spend nine tenths of itself on
|
||||
the part that does not matter. Cutoff and rate are exponential for the same reason, since
|
||||
pitch is logarithmic and so is where a filter sounds like it is.
|
||||
|
||||
### Level:
|
||||
|
||||
Setting 0x50 says what shapes the channel's level, out of the same list of sources. It is
|
||||
normally the amplitude envelope, which is what an amplitude envelope is for, and it can be set
|
||||
to **nothing** - a channel whose level nothing shapes plays flat out until it is gated off.
|
||||
|
||||
That sounds like a small thing and is not. Without it the amplitude envelope is welded to the
|
||||
output, so an envelope routed somewhere useful - opening the filter, bending a pitch - still
|
||||
has to be shaped like something you would want to hear, and a snare that wants a click of
|
||||
filter sweep and a flat body cannot have both.
|
||||
|
||||
### Knowing When It Has Finished:
|
||||
|
||||
The status port's bit 0 is set while any channel is still sounding, so a routine can wait for
|
||||
a sound to end rather than counting cycles.
|
||||
|
||||
There is one rule about when a note ends, and it is worth stating on its own because the
|
||||
obvious guess is wrong. **A note sounds until the gate is dropped.** What the envelope is
|
||||
doing does not come into it.
|
||||
|
||||
In particular, a note whose sustain is nothing goes quiet and *keeps sounding*. Silence and
|
||||
being finished look identical from outside and are not the same thing: the voice is holding at
|
||||
nothing, which is exactly what a held key does on any instrument. A program that plays such a
|
||||
note and then waits for the status bit waits for ever.
|
||||
|
||||
So a routine that means to wait for a sound does this, in this order: play the note, wait
|
||||
however long the note is meant to last, write nothing to the gate at 0x45, and *then* wait for
|
||||
the bit to come down - which it does when the release has finished. `Programs/Examples/tune.asm`
|
||||
is that loop with the waiting done on the screen's frame.
|
||||
|
||||
### The Sound Comes From The Machine's Clock:
|
||||
|
||||
Samples are made against cycles, not against however fast the host really ran: forty-eight
|
||||
thousand a second of *emulated* time, worked out in whole numbers so it never drifts. Three
|
||||
million cycles make exactly one hundred and forty-four thousand samples.
|
||||
|
||||
This is the same decision as the screen writing a picture out, and it buys the same thing. A
|
||||
sound is **something a test can compare**: the same program makes the same samples every time,
|
||||
on any host, at any speed, and `Tests/sound.sh` reads them back and measures the pitch. It
|
||||
also means a machine that is paused makes no sound rather than a held note, which is right - a
|
||||
stopped machine's oscillators are stopped too.
|
||||
|
||||
**The device does not interrupt.** Nothing about a note finishing needs the CPU's attention
|
||||
urgently enough to be worth a line, and a program that wants to play in time has the screen's
|
||||
frame interrupt, which is 60 a second and already there. A programmable timer is the proper
|
||||
answer and is a device that does not exist yet.
|
||||
|
||||
## Asking What Is There:
|
||||
|
||||
A program that only ever runs on one machine can be told where everything is. A program meant to run on more than one has to ask, and the bus registry on port 0xFF is what it asks.
|
||||
@@ -718,7 +913,9 @@ One thing to be careful of: the registry remembers which port it was asked about
|
||||
| 0x11 | Test device, which refuses everything. |
|
||||
| 0x12 | Test device, which owns memory. |
|
||||
| 0x13 | Disk. |
|
||||
| 0x14 - 0xFF | Peripherals. |
|
||||
| 0x14 | Screen. |
|
||||
| 0x15 | Sound. |
|
||||
| 0x16 - 0xFF | Peripherals. |
|
||||
|
||||
## The Memory Controller:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user