mirror of
https://github.com/jeremysrand/a2bejwld.git
synced 2024-06-25 23:29:27 +00:00
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.
404 lines
12 KiB
C
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;
|
|
}
|