186 lines
5.2 KiB
C
186 lines
5.2 KiB
C
// Space Taxi -- audio sink for the simulation's SID events.
|
|
//
|
|
// The C64 drives three SID voices: voice 1 carries the SFX programs
|
|
// (landing, gear, crash, fuel) and the thrust/crash frequency sweep,
|
|
// voice 2 the level gimmick tones (laser hum, puzzle chime), voice 3
|
|
// the jet noise. JoeyLib's audio HAL is a PSG-style tone/noise
|
|
// interface, so each SID program is mapped to its pitch and release
|
|
// time; envelopes and waveforms are approximated with an attenuation
|
|
// ramp. The speech samples of the original are not extracted; each
|
|
// spoken character becomes a short chirp so the cues still land.
|
|
|
|
#include "spacetaxi.h"
|
|
|
|
|
|
// SID voice index in a program's byte 8 -> JoeyLib voice slot.
|
|
#define ST_VOICE_SFX 1u // SID voice 1
|
|
#define ST_VOICE_TONE 2u // SID voice 2
|
|
#define ST_VOICE_NOISE 3u // SID voice 3 (noise)
|
|
|
|
#define ST_ATTEN_LOUD 4u
|
|
#define ST_ATTEN_SOFT 8u
|
|
#define ST_ATTEN_OFF 15u
|
|
|
|
// NTSC SID: Hz = word * 1022727 / 16777216 = word * 0.06096.
|
|
#define ST_SID_HZ_NUM 1022727UL
|
|
#define ST_SID_HZ_DEN 16777216UL
|
|
|
|
// Speech chirp: one short tone per character, pitched by the character.
|
|
#define ST_SPEECH_TICKS 3u
|
|
#define ST_SPEECH_BASE_HZ 300u
|
|
#define ST_SPEECH_STEP_HZ 9u
|
|
|
|
// Thrust sweep register value -> Hz: the C64 writes the same byte to
|
|
// both frequency bytes, so the word is value * 257.
|
|
#define ST_SWEEP_WORD_SCALE 257u
|
|
|
|
|
|
typedef struct {
|
|
uint16_t ticksLeft; // release countdown (game ticks); 0 = idle
|
|
uint8_t voice;
|
|
} StSfxSlotT;
|
|
|
|
|
|
static StSfxSlotT gSfx; // SID voice 1 (programs + sweep)
|
|
static StSfxSlotT gTone; // SID voice 2
|
|
static bool gNoiseOn;
|
|
static uint16_t gSpeechTicks;
|
|
|
|
|
|
static uint16_t sidHz(uint16_t word);
|
|
static void voiceOff(StSfxSlotT *slot);
|
|
|
|
|
|
static uint16_t sidHz(uint16_t word) {
|
|
uint32_t hz = ((uint32_t)word * ST_SID_HZ_NUM) / ST_SID_HZ_DEN;
|
|
|
|
if (hz > 20000u) {
|
|
hz = 20000u;
|
|
}
|
|
return (uint16_t)hz;
|
|
}
|
|
|
|
|
|
static void voiceOff(StSfxSlotT *slot) {
|
|
if (slot->ticksLeft != 0u) {
|
|
jlAudioVoice(slot->voice, 0u, ST_ATTEN_OFF);
|
|
slot->ticksLeft = 0u;
|
|
}
|
|
}
|
|
|
|
|
|
// Once per game tick: run the release timers ($4320 sfxEnvelopeTick).
|
|
void stAudioTick(void) {
|
|
if (gSfx.ticksLeft != 0u) {
|
|
gSfx.ticksLeft--;
|
|
if (gSfx.ticksLeft == 0u) {
|
|
jlAudioVoice(gSfx.voice, 0u, ST_ATTEN_OFF);
|
|
}
|
|
}
|
|
if (gTone.ticksLeft != 0u) {
|
|
gTone.ticksLeft--;
|
|
if (gTone.ticksLeft == 0u) {
|
|
jlAudioVoice(gTone.voice, 0u, ST_ATTEN_OFF);
|
|
}
|
|
}
|
|
if (gSpeechTicks != 0u) {
|
|
gSpeechTicks--;
|
|
if (gSpeechTicks == 0u) {
|
|
jlAudioVoice(ST_VOICE_TONE, 0u, ST_ATTEN_OFF);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void stAudioInit(void) {
|
|
(void)jlAudioInit();
|
|
gSfx.voice = ST_VOICE_SFX;
|
|
gSfx.ticksLeft = 0u;
|
|
gTone.voice = ST_VOICE_TONE;
|
|
gTone.ticksLeft = 0u;
|
|
gNoiseOn = false;
|
|
gSpeechTicks = 0u;
|
|
}
|
|
|
|
|
|
void stAudioShutdown(void) {
|
|
voiceOff(&gSfx);
|
|
voiceOff(&gTone);
|
|
jlAudioNoise(0u, ST_ATTEN_OFF);
|
|
jlAudioShutdown();
|
|
}
|
|
|
|
|
|
// $6D6A / $6A3B -- voice 3 jet noise gate.
|
|
void stAudioNoise(bool on) {
|
|
if (on == gNoiseOn) {
|
|
return;
|
|
}
|
|
gNoiseOn = on;
|
|
if (on) {
|
|
jlAudioNoise(31u, ST_ATTEN_SOFT);
|
|
} else {
|
|
jlAudioNoise(0u, ST_ATTEN_OFF);
|
|
}
|
|
}
|
|
|
|
|
|
// $42E9 -- a 9-byte SID program: freq lo/hi, pulse lo/hi, control,
|
|
// AD, SR, release ticks, voice.
|
|
void stAudioSfx(const uint8_t *program9) {
|
|
uint16_t word = (uint16_t)(program9[0] | ((uint16_t)program9[1] << 8));
|
|
uint8_t ctrl = program9[4];
|
|
uint8_t ticks = program9[7];
|
|
StSfxSlotT *slot = (program9[8] == 1u) ? &gTone : &gSfx;
|
|
|
|
if ((ctrl & 0x80u) != 0u) {
|
|
// Noise waveform: a burst on the noise generator.
|
|
jlAudioNoise(31u, ST_ATTEN_LOUD);
|
|
gNoiseOn = true;
|
|
return;
|
|
}
|
|
jlAudioVoice(slot->voice, sidHz(word), ST_ATTEN_LOUD);
|
|
slot->ticksLeft = (uint16_t)(ticks + 1u);
|
|
}
|
|
|
|
|
|
// $6C7F / $6CAA -- voices 1 and 2 gated off.
|
|
void stAudioSilence(void) {
|
|
voiceOff(&gSfx);
|
|
voiceOff(&gTone);
|
|
}
|
|
|
|
|
|
// $9802 -- one spoken character (a chirp here).
|
|
void stAudioSpeech(uint8_t ch) {
|
|
jlAudioVoice(ST_VOICE_TONE, (uint16_t)(ST_SPEECH_BASE_HZ + (uint16_t)(ch & 0x1Fu) * ST_SPEECH_STEP_HZ), ST_ATTEN_LOUD);
|
|
gSpeechTicks = ST_SPEECH_TICKS;
|
|
}
|
|
|
|
|
|
// $6A8A -- the crash scream: voice 1 frequency written from the sweep.
|
|
void stAudioThrustSweep(uint8_t value) {
|
|
jlAudioVoice(ST_VOICE_SFX, sidHz((uint16_t)(value * ST_SWEEP_WORD_SCALE)), ST_ATTEN_LOUD);
|
|
gSfx.ticksLeft = 3u;
|
|
}
|
|
|
|
|
|
// $6E71 -- the fuel pump's rising note on voice 1.
|
|
void stAudioVoice1Freq(uint8_t value) {
|
|
jlAudioVoice(ST_VOICE_SFX, sidHz((uint16_t)(value * ST_SWEEP_WORD_SCALE)), ST_ATTEN_LOUD);
|
|
gSfx.ticksLeft = 2u;
|
|
}
|
|
|
|
|
|
// Level hooks poke voice 2 directly (laser hum, puzzle chime).
|
|
void stAudioVoice2(uint8_t freqLo, uint8_t freqHi, uint8_t ctrl) {
|
|
uint16_t word = (uint16_t)(freqLo | ((uint16_t)freqHi << 8));
|
|
|
|
if ((ctrl & 0x80u) != 0u) {
|
|
jlAudioNoise((uint8_t)(freqHi & 31u), ST_ATTEN_SOFT);
|
|
gNoiseOn = true;
|
|
return;
|
|
}
|
|
jlAudioVoice(ST_VOICE_TONE, sidHz(word), ST_ATTEN_SOFT);
|
|
gTone.ticksLeft = 2u;
|
|
}
|