a2bejwld/a2bejwld/sound.c
Jeremy Rand 20ad7dda1c Add code from Total Replay to detect the sound chip automatically on startup. This then leads to a complete rethinking about how to save/load the options for the game. It used to ask what slot the mockingboard was in (if any) and whether it had a speech chip. Now, the game should just know this information. So, I turned the first boolean in the save file into an options file version byte and bumped it to "2". The boolean was always true and was kind of a very simple "magic number" to say that the contents was valid. Now it is a version number of the contents. The slot number and boolean for the speech chip is are now each turned into booleans - one to say whether to enable a mockingboard if found and the other to enable the speech chip if found.
This means there are three booleans in the options related to sound now.  The first one enables/disables sound entirely and is default on.  The second is only relevant if the sound is enabled and says "use a mockingboard if present".  Again this is true by default.  Finally, the this says "use a speech chip on the mockingboard if present" and is only relevant if the mockingboard is also enabled.

So, the basic approach now is to default the "best" sound options and auto-detect the sound HW at launch.  The user can then use the options to downgrade their sound all the way to basic Apple // sound or turn off the sound entirely.
2020-03-04 22:23:09 -05:00

404 lines
12 KiB
C

//
// sound.c
// a2bejwld
//
// Created by Jeremy Rand on 2016-12-18.
// Copyright © 2016 Jeremy Rand. All rights reserved.
//
#include "sound.h"
#include "mockingboard.h"
#include <stdint.h>
// Defines
#define CLEAR_GEM_SOUND_NORMAL 0
#define CLEAR_GEM_SOUND_STAR 1
#define CLEAR_GEM_SOUND_SPECIAL 2
#define CLEAR_GEM_SOUND_EXPLODE 3
#define NUM_CLEAR_GEM_SOUNDS 4
// Globals
static uint8_t gSoundClearGem = CLEAR_GEM_SOUND_NORMAL;
static uint8_t gClearGemSoundFreq[NUM_CLEAR_GEM_SOUNDS][8] = {
{ // CLEAR_GEM_SOUND_NORMAL
30, 25, 20, 30, 30, 30, 30, 0 },
{ // CLEAR_GEM_SOUND_STAR
10, 9, 8, 7, 6, 5, 4, 0 },
{ // CLEAR_GEM_SOUND_SPECIAL
4, 6, 8, 10, 8, 6, 4, 0 },
{ // CLEAR_GEM_SOUND_EXPLODE
50, 60, 50, 60, 50, 60, 50, 0 }
};
static uint8_t gClearGemSoundDuration[NUM_CLEAR_GEM_SOUNDS][8] = {
{ // CLEAR_GEM_SOUND_NORMAL
10, 15, 20, 10, 10, 10, 10, 0 },
{ // CLEAR_GEM_SOUND_STAR
30, 31, 32, 33, 34, 35, 36, 0 },
{ // CLEAR_GEM_SOUND_SPECIAL
36, 34, 32, 30, 32, 34, 36, 0 },
{ // CLEAR_GEM_SOUND_EXPLODE
8, 8, 8, 8, 8, 8, 8, 0 },
};
static tMockingSoundRegisters gClearGemMockSounds[NUM_CLEAR_GEM_SOUNDS] = {
// Normal
{
{ TONE_PERIOD_C(6), TONE_PERIOD_G(6), 0 }, // Tone period for the three channels
0, // Noise period
ENABLE_CHANNEL(TONE_CHANNEL_A | TONE_CHANNEL_B), // Enable
{ VARIABLE_AMPLITUDE, VARIABLE_AMPLITUDE, MIN_AMPLITUDE }, // Amplitude for the three channels
MILLISEC_TO_ENVELOP_PERIOD(960), // Envelope period
ENVELOPE_SHAPE_ONE_SHOT_DECAY, // Envelope shape
0, // Dummy1
0 // Dummy2
},
// Star
{
{ TONE_PERIOD_C(5), TONE_PERIOD_G(5), TONE_PERIOD_E(5) }, // Tone period for the three channels
10, // Noise period
ENABLE_ALL_CHANNELS, // Enable
{ VARIABLE_AMPLITUDE, VARIABLE_AMPLITUDE, MIN_AMPLITUDE }, // Amplitude for the three channels
MILLISEC_TO_ENVELOP_PERIOD(960), // Envelope period
ENVELOPE_SHAPE_ONE_SHOT_DECAY, // Envelope shape
0, // Dummy1
0 // Dummy2
},
// Special
{
{ 100, 97, 98 }, // Tone period for the three channels
4, // Noise period
ENABLE_ALL_CHANNELS, // Enable
{ VARIABLE_AMPLITUDE, VARIABLE_AMPLITUDE, VARIABLE_AMPLITUDE }, // Amplitude for the three channels
MILLISEC_TO_ENVELOP_PERIOD(1280), // Envelope period
ENVELOPE_SHAPE_ONE_SHOT_DECAY, // Envelope shape
0, // Dummy1
0 // Dummy2
},
// Explosion
{
{ 0, 0, 0 }, // Tone period for the three channels
16, // Noise period
ENABLE_CHANNEL(NOISE_CHANNEL_A), // Enable
{ VARIABLE_AMPLITUDE, MIN_AMPLITUDE, MIN_AMPLITUDE }, // Amplitude for the three channels
MILLISEC_TO_ENVELOP_PERIOD(3000), // Envelope period
ENVELOPE_SHAPE_ONE_SHOT_DECAY, // Envelope shape
0, // Dummy1
0 // Dummy2
}
};
static tMockingSoundRegisters gGemLandSound = {
{ 0, 0, 0 }, // Tone period for the three channels
30, // Noise period
ENABLE_CHANNEL(NOISE_CHANNEL_A), // Enable
{ VARIABLE_AMPLITUDE, VARIABLE_AMPLITUDE, VARIABLE_AMPLITUDE }, // Amplitude for the three channels
MILLISEC_TO_ENVELOP_PERIOD(20), // Envelope period
ENVELOPE_SHAPE_ONE_SHOT_DECAY, // Envelope shape
0, // Dummy1
0 // Dummy2
};
static uint8_t gSpeakGood[] = {
0xE8, 0x7B, 0x88, 0x3A, 0xE9, 0xE8, 0x7B, 0x88,
0x3A, 0xE6, 0xE8, 0x7B, 0x88, 0x3A, 0x52, 0xE8,
0x7B, 0x88, 0x3A, 0x52, 0xEA, 0x7F, 0x68, 0x4A,
0x65, 0xEA, 0x7F, 0x68, 0x4A, 0xC0
};
static uint8_t gSpeakGo[] = {
0xE8, 0x7B, 0x88, 0x3A, 0xE9, 0xE8, 0x7B, 0x88,
0x3A, 0xE6, 0xE8, 0x7B, 0x88, 0x3A, 0x11, 0xE8,
0x7B, 0x88, 0x3A, 0x63
};
static uint8_t gSpeakLevelComplete[] = {
0xE8, 0x7B, 0x88, 0x3A, 0x60, 0xE8, 0x7B, 0x88,
0x3A, 0x0A, 0xE8, 0x7B, 0x88, 0x3A, 0x33, 0xE8,
0x7B, 0x88, 0x3A, 0xEC, 0xE8, 0x7B, 0x88, 0x3A,
0x4A, 0xE8, 0x7B, 0x88, 0x3A, 0x4A, 0xE8, 0x7B,
0x88, 0x3A, 0x60, 0xE8, 0x7B, 0x88, 0x3A, 0x29,
0xE8, 0x7B, 0x88, 0x3A, 0xAD, 0xE8, 0x7B, 0x88,
0x3A, 0x5A, 0xE8, 0x7B, 0x88, 0x3A, 0x5A, 0xE8,
0x7B, 0x88, 0x3A, 0x37, 0xE8, 0x7B, 0x88, 0x3A,
0x27, 0xE8, 0x7B, 0x88, 0x3A, 0x60, 0xE8, 0x7B,
0x88, 0x3A, 0x01, 0xE8, 0x7B, 0x88, 0x3A, 0x68,
0xE8, 0x7B, 0x88, 0x3A, 0xC0
};
static uint8_t gSpeakGetReady[] = {
0xE8, 0x7B, 0x88, 0x3A, 0xE6, 0xE8, 0x7B, 0x88,
0x3A, 0xC0, 0xE8, 0x7B, 0x88, 0x3A, 0x0A, 0xE8,
0x7B, 0x88, 0x3A, 0x68, 0xE8, 0x7B, 0x88, 0x3A,
0xC0, 0xE8, 0x7B, 0x88, 0x3A, 0x1D, 0xE8, 0x7B,
0x88, 0x3A, 0x4A, 0xE8, 0x7B, 0x88, 0x3A, 0x4A,
0xE8, 0x7B, 0x88, 0x3A, 0x25, 0xE8, 0x7B, 0x88,
0x3A, 0x01
};
static uint8_t gSpeakNoMoreMoves[] = {
0xE8, 0x7B, 0x88, 0x3A, 0x78, 0xE8, 0x7B, 0x88,
0x3A, 0x11, 0xE8, 0x7B, 0x88, 0x3A, 0x63, 0xE8,
0x7B, 0x88, 0x3A, 0x37, 0xE8, 0x7B, 0x88, 0x3A,
0x11, 0xE8, 0x7B, 0x88, 0x3A, 0x5C, 0xE8, 0x7B,
0x88, 0x3A, 0x37, 0xE8, 0x7B, 0x88, 0x3A, 0x16,
0xE8, 0x7B, 0x88, 0x3A, 0x33, 0xE8, 0x7B, 0x88,
0x3A, 0x2F
};
static uint8_t gSpeakExcellent[] = {
0xE8, 0x7B, 0x88, 0x3A, 0x4A, 0xE8, 0x7B, 0x88,
0x3A, 0x29, 0xE8, 0x7B, 0x88, 0x3A, 0x30, 0xE8,
0x7B, 0x88, 0x3A, 0x0A, 0xE8, 0x7B, 0x88, 0x3A,
0x4A, 0xE8, 0x7B, 0x88, 0x3A, 0x60, 0xE8, 0x7B,
0x88, 0x3A, 0x60, 0xE8, 0x7B, 0x88, 0x3A, 0x4A,
0xE8, 0x7B, 0x88, 0x3A, 0x4A, 0xE8, 0x7B, 0x88,
0x3A, 0x78, 0xEA, 0x7F, 0x68, 0x4A, 0x68, 0xEA,
0x7F, 0x68, 0x4A, 0xC0
};
static uint8_t gSpeakIncredible[] = {
0xE8, 0x7B, 0x88, 0x3A, 0x47, 0xE8, 0x7B, 0x88,
0x3A, 0x78, 0xE8, 0x7B, 0x88, 0x3A, 0x29, 0xE8,
0x7B, 0x88, 0x3A, 0x1D, 0xE8, 0x7B, 0x88, 0x3A,
0x0A, 0xE8, 0x7B, 0x88, 0x3A, 0x65, 0xE8, 0x7B,
0x88, 0x3A, 0x1B, 0xE8, 0x7B, 0x88, 0x3A, 0x64,
0xE8, 0x7B, 0x88, 0x3A, 0x60
};
static bool gSoundEnabled = false;
static bool gMockingBoardInitialized = false;
static bool gMockingBoardEnabled = false;
static bool gMockingBoardSpeechEnabled = false;
static tMockingBoardSpeaker gMockingBoardSoundSpeaker = SPEAKER_BOTH;
// Implementation
void soundInit(bool enableSounds, bool enableMockingBoard, bool enableSpeechChip)
{
gMockingBoardInitialized = mockingBoardInit();
gSoundEnabled = enableSounds;
gMockingBoardEnabled = ((gSoundEnabled) && (gMockingBoardInitialized) && (enableMockingBoard));
gMockingBoardSpeechEnabled = ((enableSpeechChip) && (gMockingBoardEnabled) && (mockingBoardHasSpeechChip()));
// When the speech chip is on, sound effects go out the right speaker
// only and the left speaker is used for speech. If the speech chip is
// off, then sound effects go to both speakers.
if (gMockingBoardSpeechEnabled) {
gMockingBoardSoundSpeaker = SPEAKER_RIGHT;
} else {
gMockingBoardSoundSpeaker = SPEAKER_BOTH;
}
}
void soundShutdown(void)
{
if (gMockingBoardInitialized) {
mockingBoardShutdown();
gMockingBoardInitialized = false;
gMockingBoardEnabled = false;
gMockingBoardSpeechEnabled = false;
}
gSoundEnabled = false;
}
static void playSound(int8_t startFreq, int8_t duration)
{
int8_t freq;
while (duration > 0) {
asm ("STA %w", 0xc030);
freq = startFreq;
while (freq > 0) {
freq--;
}
duration--;
}
}
void beginClearGemSound(void)
{
gSoundClearGem = CLEAR_GEM_SOUND_NORMAL;
}
void playSoundForExplodingGem(void)
{
if (gSoundClearGem < CLEAR_GEM_SOUND_EXPLODE)
gSoundClearGem = CLEAR_GEM_SOUND_EXPLODE;
}
void playSoundForStarringGem(void)
{
if (gSoundClearGem < CLEAR_GEM_SOUND_STAR)
gSoundClearGem = CLEAR_GEM_SOUND_STAR;
}
void playSoundForSpecialGem(void)
{
if (gSoundClearGem < CLEAR_GEM_SOUND_SPECIAL)
gSoundClearGem = CLEAR_GEM_SOUND_SPECIAL;
}
void playClearGemSound(uint8_t frame)
{
static uint8_t *clearGemSoundFreq;
static uint8_t *clearGemSoundDuration;
if (!gSoundEnabled)
return;
if (gMockingBoardEnabled) {
if (frame == 0) {
mockingBoardPlaySound(gMockingBoardSoundSpeaker, &(gClearGemMockSounds[gSoundClearGem]));
}
} else {
if (frame == 0) {
clearGemSoundFreq = &(gClearGemSoundFreq[gSoundClearGem][0]);
clearGemSoundDuration = &(gClearGemSoundDuration[gSoundClearGem][0]);
}
playSound(*clearGemSoundFreq, *clearGemSoundDuration);
clearGemSoundFreq++;
clearGemSoundDuration++;
}
}
void playLandingSound(uint8_t numLanded)
{
if (!gSoundEnabled)
return;
if (gMockingBoardEnabled) {
if (numLanded == 0) {
mockingBoardPlaySound(gMockingBoardSoundSpeaker, &gGemLandSound);
}
} else {
playSound(1, 1);
}
}
void speakGo(void)
{
if (!gMockingBoardSpeechEnabled)
return;
if (mockingBoardSpeechIsBusy()) {
while (mockingBoardSpeechIsPlaying())
;
}
mockingBoardSpeak(gSpeakGo, sizeof(gSpeakGo));
}
void speakLevelComplete(void)
{
if (!gMockingBoardSpeechEnabled)
return;
if (mockingBoardSpeechIsBusy()) {
while (mockingBoardSpeechIsPlaying())
;
}
mockingBoardSpeak(gSpeakLevelComplete, sizeof(gSpeakLevelComplete));
}
void speakGetReady(void)
{
if (!gMockingBoardSpeechEnabled)
return;
if (mockingBoardSpeechIsBusy()) {
while (mockingBoardSpeechIsPlaying())
;
}
mockingBoardSpeak(gSpeakGetReady, sizeof(gSpeakGetReady));
}
void speakNoMoreMoves(void)
{
if (!gMockingBoardSpeechEnabled)
return;
if (mockingBoardSpeechIsBusy()) {
while (mockingBoardSpeechIsPlaying())
;
}
mockingBoardSpeak(gSpeakNoMoreMoves, sizeof(gSpeakNoMoreMoves));
}
bool speakGood(void)
{
if (!gMockingBoardSpeechEnabled)
return true;
if (mockingBoardSpeechIsBusy())
return false;
mockingBoardSpeak(gSpeakGood, sizeof(gSpeakGood));
return true;
}
bool speakExcellent(void)
{
if (!gMockingBoardSpeechEnabled)
return true;
if (mockingBoardSpeechIsBusy())
return false;
mockingBoardSpeak(gSpeakExcellent, sizeof(gSpeakExcellent));
return true;
}
bool speakIncredible(void)
{
if (!gMockingBoardSpeechEnabled)
return true;
if (mockingBoardSpeechIsBusy())
return false;
mockingBoardSpeak(gSpeakIncredible, sizeof(gSpeakIncredible));
return true;
}