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.
This commit is contained in:
Jeremy Rand 2020-03-04 22:23:09 -05:00
parent 72fc265e5a
commit 20ad7dda1c
10 changed files with 315 additions and 192 deletions

View File

@ -71,7 +71,6 @@ SRCDIRS+=
# If you want to add arguments to the compile commandline, add them
# to this variable:
# CFLAGS += -DTOTAL_REPLAY_BUILD
CFLAGS += -Os
# If you want to add arguments to the assembly commandline, add them

View File

@ -16,20 +16,8 @@
// Implementation
#ifdef TOTAL_REPLAY_BUILD
void totalReplayQuit(void)
{
__asm__ ("BIT $C082");
__asm__ ("JMP $100");
}
#endif
int main(void)
{
#ifdef TOTAL_REPLAY_BUILD
atexit(totalReplayQuit);
#endif
initUI();
printInstructions();

View File

@ -46,6 +46,9 @@ static uint8_t *gMockDataDirA[NUM_SOUND_CHIPS] = { (uint8_t *)0xc003, (uint8_t *
static uint8_t gMockingBoardInitialized = false;
static uint8_t gMockingBoardSpeechInitialized = false;
static uint8_t gMockingBoardSearchDone = false;
static tSlot gMockingBoardSlot = 0;
static bool gMockingBoardHasSpeech = false;
// Implementation
@ -64,29 +67,47 @@ static uint8_t *mapIOPointer(tSlot slot, uint8_t *ptr)
}
void mockingBoardInit(tSlot slot, bool hasSpeechChip)
bool mockingBoardInit(void)
{
tMockingBoardSoundChip soundChip;
if (!gMockingBoardSearchDone)
{
gMockingBoardSlot = getMockingBoardSlot();
if ((gMockingBoardSlot & 0x80) != 0)
gMockingBoardHasSpeech = true;
gMockingBoardSlot &= 0x7;
gMockingBoardSearchDone = true;
}
if (gMockingBoardSlot == 0)
return false;
if (gMockingBoardInitialized)
return true;
if (sizeof(tMockingSoundRegisters) != 16) {
printf("The sound registers must be 16 bytes long!\n");
}
for (soundChip = SOUND_CHIP_1; soundChip < NUM_SOUND_CHIPS; soundChip++) {
gMockPortB[soundChip] = mapIOPointer(slot, gMockPortB[soundChip]);
gMockPortA[soundChip] = mapIOPointer(slot, gMockPortA[soundChip]);
gMockDataDirB[soundChip] = mapIOPointer(slot, gMockDataDirB[soundChip]);
gMockDataDirA[soundChip] = mapIOPointer(slot, gMockDataDirA[soundChip]);
gMockPortB[soundChip] = mapIOPointer(gMockingBoardSlot, gMockPortB[soundChip]);
gMockPortA[soundChip] = mapIOPointer(gMockingBoardSlot, gMockPortA[soundChip]);
gMockDataDirB[soundChip] = mapIOPointer(gMockingBoardSlot, gMockDataDirB[soundChip]);
gMockDataDirA[soundChip] = mapIOPointer(gMockingBoardSlot, gMockDataDirA[soundChip]);
*(gMockDataDirA[soundChip]) = 0xff; // Set port A for output
*(gMockDataDirB[soundChip]) = 0x7; // Set port B for output
}
if (hasSpeechChip) {
if (gMockingBoardHasSpeech) {
if (gMockingBoardSpeechInitialized) {
mockingBoardSpeechShutdown();
}
mockingBoardSpeechInit(slot);
mockingBoardSpeechInit(gMockingBoardSlot);
gMockingBoardSpeechInitialized = true;
} else if (gMockingBoardSpeechInitialized) {
mockingBoardSpeechShutdown();
@ -94,6 +115,7 @@ void mockingBoardInit(tSlot slot, bool hasSpeechChip)
}
gMockingBoardInitialized = true;
return true;
}
@ -108,6 +130,18 @@ void mockingBoardShutdown(void)
}
tSlot mockingBoardSlot(void)
{
return gMockingBoardSlot;
}
bool mockingBoardHasSpeechChip(void)
{
return gMockingBoardHasSpeech;
}
static void writeCommand(tMockingBoardSoundChip soundChip, uint8_t command)
{
volatile uint8_t *ptr = gMockPortB[soundChip];

View File

@ -117,13 +117,15 @@ typedef struct tMockingSoundRegisters {
// API
extern uint8_t getMockingBoardSlot(void);
extern void mockingBoardInit(tSlot slot, bool hasSpeechChip);
extern bool mockingBoardInit(void);
extern void mockingBoardShutdown(void);
extern tSlot mockingBoardSlot(void);
extern void mockingBoardPlaySound(tMockingBoardSpeaker speaker, tMockingSoundRegisters *registers);
extern void mockingBoardStopSound(tMockingBoardSpeaker speaker);
extern bool mockingBoardHasSpeechChip(void);
extern bool mockingBoardSpeechIsBusy(void);
extern bool mockingBoardSpeechIsPlaying(void);
extern bool mockingBoardSpeak(uint8_t *data, uint16_t dataLen);

View File

@ -18,6 +18,7 @@ extern uint16_t mockingBoardSpeechLen;
extern uint8_t mockingBoardSpeechBusy;
extern uint8_t mockingBoardSpeechPlaying;
extern uint8_t getMockingBoardSlot(void);
extern void mockingBoardSpeechInit(uint8_t slot);
extern void mockingBoardSpeechShutdown(void);
extern void mockingBoardSpeakPriv(void);

View File

@ -74,37 +74,128 @@ readChip:
; in: none
; accelerators should be off
; out:
; if card was found, A = #$n where n is the slot number of the card, otherwise #$00
; if card was found, A = #$?n where n is the slot number of the card, otherwise #$00
; and bit 6 = 0 if Mockingboard Sound I found
; or bit 6 = 1 if Mockingboard Sound II or "A" found
; and bit 7 = 1 if Mockingboard Sound/Speech I or "C" found
; flags clobbered
; zp $80-$82 clobbered
; (jrand - and then restored because I am chicken and afraid of breaking cc65 runtime)
; X/Y clobbered
;------------------------------------------------------------------------------
.proc _getMockingBoardSlot
php
lda $80
sta zp80Backup
lda $81
sta zp81Backup
lda $82
sta zp82Backup
lda $3fe
sta irq1Backup
lda $3ff
sta irq2Backup
lda #$00
sta $80
ldx #$C7
sta $82 ; type
ldx #$C1
@slotLoop:
stx $81
ldy #$04 ; 6522 #1 $Cx04
jsr timercheck
bne @nextSlot
ldy #$84 ; 6522 #2 $Cx84
jsr timercheck
bne @nextSlot
beq @cleanup
@nextSlot:
dex
cpx #$C0
jsr @timercheck
beq @foundI
inx
cpx #$C8
bne @slotLoop
ldx #$00 ; not found
ldx #00 ; not found
jmp @cleanup
@foundI: ; sound I or better
ldy #$84 ; 6522 #2 $Cx84
jsr @timercheck
beq @foundII
ldy #$0C
sty @mb_smc3 + 1
iny
sty @mb_smc8 + 1
iny
sty @mb_smc7 + 1
sty @mb_smc11 + 1
.BYTE $2C ; Hide next 2 bytes using a BIT opcode
@foundII: ; stereo
ror $82
lda $81
sta @mb_smc1 + 2
sta @mb_smc2 + 2
sta @mb_smc3 + 2
sta @mb_smc4 + 2
sta @mb_smc5 + 2
sta @mb_smc6 + 2
sta @mb_smc7 + 2
sta @mb_smc8 + 2
sta @mb_smc9 + 2
sta @mb_smc10 + 2
sta @mb_smc11 + 2
sta @mb_smc12 + 2
sta @mb_smc13 + 2
; detect speech chip
sei
lda #<@mb_irq
sta $3fe
lda #>@mb_irq
sta $3ff
lda #0
@mb_smc1:
sta $c403
@mb_smc2:
sta $c402
lda #$0c
@mb_smc3:
sta $c48c
lda #$80
@mb_smc4:
sta $c443
lda #$c0
@mb_smc5:
sta $c440
lda #$70
@mb_smc6:
sta $c443
lda #$82
@mb_smc7:
sta $c48e
ldx #0
ldy #0
sec
cli
@wait_irq:
lda $80
bne @got_irq
iny
bne @wait_irq
inx
bne @wait_irq
clc
@got_irq:
sei
ror $82
lda $81
and #7
ora $82
tax
@cleanup:
lda zp80Backup
sta $80
@ -112,25 +203,53 @@ readChip:
sta $81
lda zp82Backup
sta $82
lda irq1Backup
sta $3fe
lda irq2Backup
sta $3ff
txa
and #$07
ldx #$00
plp
rts
timercheck:
lda ($80),y ; read 6522 timer low byte
sta $82
lda ($80),y ; second time
@timercheck:
sec
sbc $82
cmp #$F8 ; looking for (-)8 cycles between reads
lda ($80),y ; read 6522 timer low byte
sbc ($80),y ; second time
cmp #5 ; looking for (-)8 cycles between reads
beq :+
cmp #$F7 ; FastChip //e clock is different
cmp #6 ; FastChip //e clock is different
: rts
@mb_irq:
lda #2
@mb_smc8:
sta $c48d
lda #0
@mb_smc9:
sta $c440
lda #$70
@mb_smc10:
sta $c443
sta $80
lda #2
@mb_smc11:
sta $c48e
lda #$ff
@mb_smc12:
sta $c403
lda #7
@mb_smc13:
sta $c402
lda $45
rti
; Locals
zp80Backup: .BYTE $00
zp81Backup: .BYTE $00
zp82Backup: .BYTE $00
irq1Backup: .BYTE $00
irq2Backup: .BYTE $00
.endproc

View File

@ -185,6 +185,7 @@ static uint8_t gSpeakIncredible[] = {
static bool gSoundEnabled = false;
static bool gMockingBoardInitialized = false;
static bool gMockingBoardEnabled = false;
static bool gMockingBoardSpeechEnabled = false;
@ -194,35 +195,30 @@ static tMockingBoardSpeaker gMockingBoardSoundSpeaker = SPEAKER_BOTH;
// Implementation
void soundInit(tSlot mockingBoardSlot, bool enableSpeechChip)
void soundInit(bool enableSounds, bool enableMockingBoard, bool enableSpeechChip)
{
gSoundEnabled = true;
if (mockingBoardSlot > 0) {
mockingBoardInit(mockingBoardSlot, enableSpeechChip);
gMockingBoardEnabled = true;
gMockingBoardSpeechEnabled = enableSpeechChip;
// 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 (enableSpeechChip) {
gMockingBoardSoundSpeaker = SPEAKER_RIGHT;
} else {
gMockingBoardSoundSpeaker = SPEAKER_BOTH;
}
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 {
if (gMockingBoardEnabled) {
mockingBoardShutdown();
}
gMockingBoardEnabled = false;
gMockingBoardSpeechEnabled = false;
gMockingBoardSoundSpeaker = SPEAKER_BOTH;
}
}
void soundShutdown(void)
{
if (gMockingBoardEnabled) {
if (gMockingBoardInitialized) {
mockingBoardShutdown();
gMockingBoardInitialized = false;
gMockingBoardEnabled = false;
gMockingBoardSpeechEnabled = false;
}
@ -367,6 +363,7 @@ void speakNoMoreMoves(void)
mockingBoardSpeak(gSpeakNoMoreMoves, sizeof(gSpeakNoMoreMoves));
}
bool speakGood(void)
{
if (!gMockingBoardSpeechEnabled)
@ -392,6 +389,7 @@ bool speakExcellent(void)
return true;
}
bool speakIncredible(void)
{
if (!gMockingBoardSpeechEnabled)

View File

@ -16,7 +16,7 @@
#include "mockingboard.h"
extern void soundInit(tSlot mockingBoardSlot, bool enableSpeechChip);
extern void soundInit(bool enableSounds, bool enableMockingBoard, bool enableSpeechChip);
extern void soundShutdown(void);
extern void beginClearGemSound(void);

View File

@ -26,24 +26,20 @@
// Defines
#define SAVE_OPTIONS_FILE "A2BEJWLD.OPTS"
#define BASE_VERSION "v2.3"
#ifdef TOTAL_REPLAY_BUILD
#define VERSION BASE_VERSION ".tr"
#else
#define VERSION BASE_VERSION
#endif
#define VERSION "v2.4.a3"
#define OPTIONS_VERSION_UNSAVED 0
#define OPTIONS_VERSION 2
// Typedefs
typedef struct tGameOptions {
bool optionsSaved;
uint8_t optionsVersion;
bool enableJoystick;
bool enableMouse;
bool enableSound;
tSlot mockingBoardSlot;
bool enableSpeechChip;
bool enableMockingboard;
bool enableMockingboardSpeech;
} tGameOptions;
@ -107,12 +103,12 @@ static tMouseCallbacks gMouseCallbacks = {
static bool gShouldSave = false;
static tGameOptions gGameOptions = {
false, // optionsSaved
false, // enableJoystick
true, // enableMouse
true, // enableSound
0, // mockingBoardSlot
false // enableSpeechChip
OPTIONS_VERSION_UNSAVED, // optionsVersion
false, // enableJoystick
true, // enableMouse
true, // enableSound
true, // enableMockingboard
true // enableMockingboardSpeech
};
@ -161,7 +157,7 @@ static void saveOptions(void)
FILE *optionsFile = fopen(SAVE_OPTIONS_FILE, "wb");
if (optionsFile != NULL) {
gGameOptions.optionsSaved = true;
gGameOptions.optionsVersion = OPTIONS_VERSION;
fwrite(&gGameOptions, sizeof(gGameOptions), 1, optionsFile);
fclose(optionsFile);
}
@ -183,6 +179,17 @@ static bool loadOptions(void)
fclose(optionsFile);
// If we are upgrading from v1 of the options file, then:
// - There used to be a tSlot of the mockingboard where we now have the enableMockingboard boolean.
// Overwrite it with true, forcing mockingboard sound to be on if one is detected.
// - There used to be a boolean to enable/disable the speech chip on the mockingboard. It was only
// true if the user enabled it. Now that we can detect the speech chip, the value is default true
// and the user can disable speech if they want.
if (gGameOptions < OPTIONS_VERSION) {
gGameOptions.enableMockingboard = true;
gGameOptions.enableMockingboardSpeech = true;
}
return true;
}
@ -196,19 +203,17 @@ static void applyNewOptions(tGameOptions *newOptions)
return;
}
printString("\n\n\n Saving options...");
if ((gGameOptions.enableSound != newOptions->enableSound) ||
(gGameOptions.mockingBoardSlot != newOptions->mockingBoardSlot) ||
(gGameOptions.enableSpeechChip != gGameOptions.enableSpeechChip)) {
// If the sound parameters have changed, then re-init or shutdown sounds
if (newOptions->enableSound) {
soundInit(newOptions->mockingBoardSlot, newOptions->enableSpeechChip);
} else {
soundShutdown();
}
(gGameOptions.enableMockingboard != newOptions->enableMockingboard) ||
(gGameOptions.enableMockingboardSpeech != newOptions->enableMockingboardSpeech)) {
// If the sound parameters have changed, then re-init sounds
soundInit(newOptions->enableSound, newOptions->enableMockingboard, newOptions->enableMockingboardSpeech);
}
memcpy(&gGameOptions, newOptions, sizeof(gGameOptions));
gGameOptions.optionsSaved = false;
gGameOptions.optionsVersion = OPTIONS_VERSION_UNSAVED;
if (oldEnableMouse != gGameOptions.enableMouse) {
if (gGameOptions.enableMouse) {
gGameOptions.enableMouse = initMouse(&gMouseCallbacks);
@ -231,67 +236,62 @@ static void replaceCursor(char ch)
}
static void getSoundOptions(tGameOptions *newOptions)
static bool yorn(void)
{
char ch;
bool result = true;
showCursor();
while (true) {
ch = cgetc();
if ((ch == 'N') ||
(ch == 'n')) {
result = false;
break;
}
if ((ch == 'Y') ||
(ch == 'y'))
break;
badThingHappened();
}
replaceCursor(ch);
return result;
}
static void getSoundOptions(tGameOptions *newOptions)
{
tSlot slot;
printString("\n\nEnable sounds? (Y/N) ");
showCursor();
while (true) {
ch = cgetc();
if ((ch == 'N') ||
(ch == 'n')) {
newOptions->enableSound = false;
newOptions->mockingBoardSlot = 0;
newOptions->enableSpeechChip = false;
return;
}
if ((ch == 'Y') ||
(ch == 'y')) {
replaceCursor(ch);
newOptions->enableSound = true;
break;
}
badThingHappened();
}
newOptions->enableSound = yorn();
if (!newOptions->enableSound)
return;
printString("\nMockingBoard slot number (0 for none) ");
showCursor();
while (true) {
ch = cgetc();
if (ch == '0') {
newOptions->mockingBoardSlot = 0;
newOptions->enableSpeechChip = false;
return;
}
if ((ch >= '1') &&
(ch <= '7')) {
replaceCursor(ch);
newOptions->mockingBoardSlot = (ch - '0');
break;
}
badThingHappened();
}
// If no mockingboard present, don't bother to ask whether to enable/disable it.
slot = mockingBoardSlot();
if (slot == 0)
return;
printString("\nMockingBoard has a speech chip? (Y/N) ");
showCursor();
while (true) {
ch = cgetc();
if ((ch == 'N') ||
(ch == 'n')) {
newOptions->enableSpeechChip = false;
break;
}
if ((ch == 'Y') ||
(ch == 'y')) {
newOptions->enableSpeechChip = true;
break;
}
badThingHappened();
}
printString("\nEnable MockingBoard sound found in slot ");
printInteger(slot);
printString("? (Y/N) ");
newOptions->enableMockingboard = yorn();
if (!newOptions->enableMockingboard)
return;
// If the mockingboard does not have a speech chip, do not prompt whether to
// enable/disable it.
if (!mockingBoardHasSpeechChip())
return;
printString("\nEnable speech on the Mockingboard? (Y/N) ");
newOptions->enableMockingboardSpeech = yorn();
}
@ -314,29 +314,35 @@ static void selectOptions(void)
" Options\n"
"\n"
" J - Joystick control - ");
printString(newOptions.enableJoystick ? "Enable\n" : "Disabled\n");
printString(newOptions.enableJoystick ? "Enabled\n" : "Disabled\n");
printString(
" M - Mouse control - ");
printString(newOptions.enableMouse ? "Enable\n" : "Disabled\n");
printString(newOptions.enableMouse ? "Enabled\n" : "Disabled\n");
printString(
" S - Sound - ");
printString(newOptions.enableSound ? "Enable\n" : "Disabled\n");
printString(newOptions.enableSound ? "Enabled\n" : "Disabled\n");
if (newOptions.enableSound) {
if (newOptions.mockingBoardSlot > 0) {
tSlot slot = mockingBoardSlot();
if (slot != 0) {
printString(
// 0000000001111111111222222222233333333334444444444555555555566666666667
// 1234567890123456789012345678901234567890123456789012345678901234567890
" MockingBoard - Slot ");
printInteger(newOptions.mockingBoardSlot);
printString("\n"
" Speech Chip - ");
printString(newOptions.enableSpeechChip ? "Enable\n" : "Disable\n");
} else {
printString(
// 0000000001111111111222222222233333333334444444444555555555566666666667
// 1234567890123456789012345678901234567890123456789012345678901234567890
" MockingBoard - Disabled\n");
" MockingBoard - ");
printString(newOptions.enableMockingboard ? "Enabled (Slot " : "Disabled (Slot ");
printInteger(slot);
printString(")\n");
if ((newOptions.enableMockingboard) &&
(mockingBoardHasSpeechChip()))
{
printString(
// 0000000001111111111222222222233333333334444444444555555555566666666667
// 1234567890123456789012345678901234567890123456789012345678901234567890
" Speech - ");
printString(newOptions.enableMockingboardSpeech ? "Enabled\n" : "Disabled\n");
}
}
}
printString(
@ -716,6 +722,8 @@ void initUI(void)
optionsLoaded = loadOptions();
soundInit(gGameOptions.enableSound, gGameOptions.enableMockingboard, gGameOptions.enableMockingboardSpeech);
initGameEngine(&gCallbacks);
mouseInitialized = initMouse(&gMouseCallbacks);
@ -723,19 +731,12 @@ void initUI(void)
if ((!mouseInitialized) &&
(gGameOptions.enableMouse)) {
gGameOptions.enableMouse = false;
gGameOptions.optionsSaved = false;
gGameOptions.optionsVersion = OPTIONS_VERSION_UNSAVED;
}
initJoystick(&gJoyCallbacks);
if (gGameOptions.enableSound) {
if (!optionsLoaded) {
gGameOptions.mockingBoardSlot = getMockingBoardSlot();
}
soundInit(gGameOptions.mockingBoardSlot, gGameOptions.enableSpeechChip);
}
if (!gGameOptions.optionsSaved) {
if (gGameOptions.optionsVersion == OPTIONS_VERSION_UNSAVED) {
saveOptions();
}
}
@ -957,33 +958,14 @@ void playGame(void)
printString("\n\nChecking for a saved game...");
if (loadGame()) {
bool gotAnswer = false;
bool loadSavedGame = false;
printString("\n\nYou have a saved game!\n Would you like to continue it (Y/N) ");
showCursor();
while (!gotAnswer) {
ch = cgetc();
switch (ch) {
case 'y':
case 'Y':
replaceCursor(ch);
printString("\n\nLoading your saved puzzle");
gotAnswer = true;
gShouldSave = true;
gameLoaded = true;
break;
case 'n':
case 'N':
replaceCursor(ch);
gotAnswer = true;
break;
default:
badThingHappened();
break;
}
loadSavedGame = yorn();
if (loadSavedGame) {
printString("\n\nLoading your saved puzzle");
gShouldSave = true;
gameLoaded = true;
}
}