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
@@ -7,6 +7,7 @@
|
||||
#include "../Assembler/assembly.h" // For the fault vector numbers.
|
||||
#include "controller.h"
|
||||
#include "video.h"
|
||||
#include "sound.h"
|
||||
#include "font.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -898,6 +899,9 @@ void deviceTick(unsigned long now) {
|
||||
// The screen blinks its cursor on the machine's own clock rather than the host's, so the
|
||||
// picture is the same at the same cycle count however fast anything ran.
|
||||
videoTick(now);
|
||||
// And the sound, which makes whatever samples are due by now. On the machine's clock,
|
||||
// so the same program makes the same sound in the same cycles.
|
||||
soundTick(now);
|
||||
if (diskPending && now >= diskReadyAt) {
|
||||
uint8_t command = diskPending;
|
||||
diskPending = 0;
|
||||
@@ -962,6 +966,7 @@ static const DeviceRecord deviceTable[] = {
|
||||
{ PORT_MEMORY, DEVICE_MEMORY, DEVICE_FLAG_HAS_MEMORY },
|
||||
{ PORT_DISK, DEVICE_DISK, DEVICE_FLAG_HAS_MEMORY },
|
||||
{ PORT_VIDEO, DEVICE_VIDEO, DEVICE_FLAG_HAS_MEMORY },
|
||||
{ PORT_SOUND, DEVICE_SOUND, 0 },
|
||||
{ PORT_REGISTRY, DEVICE_REGISTRY, 0 },
|
||||
};
|
||||
static const int deviceCount = (int)(sizeof(deviceTable) / sizeof(deviceTable[0]));
|
||||
@@ -994,6 +999,9 @@ static const DeviceRecord *deviceOnPort(uint8_t port) {
|
||||
// Sixteen ports, one device, and the same rule again.
|
||||
return deviceOnPort(PORT_VIDEO);
|
||||
}
|
||||
if (port > PORT_SOUND && port <= PORT_SOUND_TOP) {
|
||||
return deviceOnPort(PORT_SOUND);
|
||||
}
|
||||
for (int i = 0; i < deviceCount; i++) {
|
||||
if (deviceTable[i].port == port) {
|
||||
return &deviceTable[i];
|
||||
@@ -1027,6 +1035,9 @@ uint8_t OutputHandler(uint8_t DataByte, uint8_t Address) {
|
||||
if (Address >= PORT_VIDEO && Address <= PORT_VIDEO_TOP) {
|
||||
return videoWrite(DataByte, Address);
|
||||
}
|
||||
if (Address >= PORT_SOUND && Address <= PORT_SOUND_TOP) {
|
||||
return soundWrite(DataByte, Address);
|
||||
}
|
||||
// This function sends the DataByte to the appropriate place based on the Port Address.
|
||||
switch(Address) {
|
||||
case CONSOLE_DATA:
|
||||
@@ -1121,6 +1132,9 @@ uint8_t InputHandler(uint8_t Address) {
|
||||
if (Address >= PORT_VIDEO && Address <= PORT_VIDEO_TOP) {
|
||||
return videoRead(Address);
|
||||
}
|
||||
if (Address >= PORT_SOUND && Address <= PORT_SOUND_TOP) {
|
||||
return soundRead(Address);
|
||||
}
|
||||
switch(Address) {
|
||||
case CONSOLE_DATA:
|
||||
// If data is sent here, it should be read from STDIN.
|
||||
|
||||
Reference in New Issue
Block a user