// sound.h // The Voyager's sound device. // Written by Anachronaut #ifndef SOUND_H #define SOUND_H #include // ---- What this is ---- // // Four channels, each one a full soundThing voice: two oscillators, two envelopes and a // filter. A channel is asked for by number and keeps its patch between notes, which is what // makes it hardware rather than a keyboard - channel two is channel two. // // ---- Why it is not thirty ports ---- // // A voice has some forty parameters and the machine has 256 ports, so giving each one a port // of its own would spend a sixth of the whole address space on one device. Instead there is a // SELECTOR AND A VALUE: say which channel, say which parameter, write it. Three writes to // change one thing, which is the right price for something a program does when it loads a // patch and not when it plays a note. // // What a program does per NOTE is cheap on purpose, because that happens in a music routine's // inner loop: select the channel, write the note, write the gate. Three writes and no // parameter machinery at all. #define PORT_SOUND 0x40 #define PORT_SOUND_TOP 0x4F #define SOUND_STATUS 0x40 #define SOUND_CHANNEL 0x41 #define SOUND_PARAMETER 0x42 #define SOUND_VALUE 0x43 #define SOUND_NOTE 0x44 #define SOUND_GATE 0x45 #define SOUND_VOLUME 0x46 // Set while any channel is still sounding, so a routine can wait for a note to finish // rather than counting. #define SOUND_STATUS_SOUNDING 0x01 #define SOUND_CHANNELS 4 // ---- The parameters ---- // // Grouped so that the number says which part of a voice it belongs to: the high nibble picks // the part and the low one picks the setting. Everything is a byte, because everything on // this machine is - what each byte means is in the manual and in soundParameter below. #define SP_OSC0 0x00 // 0x00-0x0F, and 0x10-0x1F for the second oscillator #define SP_OSC1 0x10 #define SP_OSC_WAVE 0x00 #define SP_OSC_GAIN 0x01 #define SP_OSC_DUTY 0x02 #define SP_OSC_DETUNE 0x03 #define SP_OSC_OCTAVE 0x04 #define SP_OSC_ACTIVE 0x05 #define SP_OSC_PWM_SRC 0x06 #define SP_OSC_PWM_DEPTH 0x07 #define SP_OSC_DET_SRC 0x08 #define SP_OSC_DET_DEPTH 0x09 #define SP_OSC_GAIN_SRC 0x0A #define SP_OSC_GAIN_DEPTH 0x0B #define SP_AMPENV 0x20 // 0x20-0x2F amp, 0x30-0x3F mod #define SP_MODENV 0x30 #define SP_ENV_ATTACK 0x00 #define SP_ENV_DECAY 0x01 #define SP_ENV_SUSTAIN 0x02 #define SP_ENV_RELEASE 0x03 #define SP_FILTER 0x40 #define SP_FILTER_ACTIVE 0x40 #define SP_FILTER_TYPE 0x41 #define SP_FILTER_CUTOFF 0x42 #define SP_FILTER_RES 0x43 #define SP_FILTER_CUT_SRC 0x44 #define SP_FILTER_CUT_DEP 0x45 #define SP_FILTER_RES_SRC 0x46 #define SP_FILTER_RES_DEP 0x47 // Which source shapes the channel's level: 0 none, 1 envelope 0, 2 envelope 1, 3 and 4 the // LFOs. Nought is the one that could not be said before - see synth.h. #define SP_LEVEL_SOURCE 0x50 // ---- Whether a note waits to be let go of ---- // // 0 is gated and 1 is triggered. Gated is what a keyboard wants: the sound lasts as long as // the finger does, and dropping the gate starts the release. Triggered is what a GAME wants - // a bang, a pickup, a door - where the note is struck and then plays its own length, and // nothing has to remember to end it. Sustain and release have no meaning in a triggered // voice, because both are answers to a question about a key that is not being asked. // // A program driving one-shots does not need port 0x45 at all once this is set. #define SP_VOICE_GATE 0x51 // The LFOs belong to the whole device rather than to a channel, so these ignore whichever // channel is selected. #define SP_LFO0 0x60 // 0x60-0x6F and 0x70-0x7F #define SP_LFO1 0x70 #define SP_LFO_ACTIVE 0x00 #define SP_LFO_WAVE 0x01 #define SP_LFO_RATE 0x02 // ---- Whether it starts over when a voice does ---- // // 0 free and 1 retriggered. Free is one cycle running under everything, which is what vibrato // across a held chord wants. Retriggered starts at the beginning of its shape every time a // voice begins, which is the only way a one-shot sounds the SAME twice: a free LFO is wherever // the wall clock left it, so the same drum caught at a different moment is a different drum. // // The mode belongs to the LFO and the cycle belongs to the voice, so retriggering costs // nothing to a channel that is not using it. #define SP_LFO_MODE 0x03 // ---- Samples come from the machine's clock ---- // // Forty-eight thousand a second against a million cycles: one sample every twenty and five // sixths, worked out in whole numbers so it never drifts. THE HOST'S CLOCK IS NOT INVOLVED, // which is what makes a recorded sound something a test can compare - the same program makes // the same samples in the same cycles however fast anything really ran. #define SOUND_SAMPLE_RATE 48000 void soundReset(void); // Asks the device to keep every sample it makes, for soundWriteSamples. Off unless something // wants a file, because a long run makes millions of them. void soundKeepSamples(void); // Called with the machine's clock, and generates whatever samples are due by now. void soundTick(unsigned long now); uint8_t soundWrite(uint8_t value, uint8_t port); uint8_t soundRead(uint8_t port); // Takes up to `wanted` samples for something that is going to play them, and says how many // there were. A front end with a speaker calls this; nothing else has to. int soundTake(int16_t *into, int wanted); // Writes every sample generated so far to a file, as raw signed 16 bit. What --screen is for // a picture: the only way to check a sound on a machine with no speaker. int soundWriteSamples(const char *path); #endif // SOUND_H