diff --git a/AppleWinExpress2008.vcproj b/AppleWinExpress2008.vcproj index c145816c..c5d8019b 100644 --- a/AppleWinExpress2008.vcproj +++ b/AppleWinExpress2008.vcproj @@ -789,6 +789,14 @@ RelativePath=".\source\Speech.h" > + + + + diff --git a/AppleWinExpress2019.vcxproj b/AppleWinExpress2019.vcxproj index c01f5177..1264320a 100644 --- a/AppleWinExpress2019.vcxproj +++ b/AppleWinExpress2019.vcxproj @@ -104,6 +104,7 @@ + @@ -195,6 +196,7 @@ + Create Create diff --git a/AppleWinExpress2019.vcxproj.filters b/AppleWinExpress2019.vcxproj.filters index dddf397c..4e5b5f10 100644 --- a/AppleWinExpress2019.vcxproj.filters +++ b/AppleWinExpress2019.vcxproj.filters @@ -220,6 +220,9 @@ Source Files\Windows + + Source Files\Emulator + Source Files\Debugger @@ -525,6 +528,9 @@ Source Files\Windows + + Source Files\Emulator + Source Files\Debugger diff --git a/source/CPU.cpp b/source/CPU.cpp index a94d167f..f069380a 100644 --- a/source/CPU.cpp +++ b/source/CPU.cpp @@ -436,7 +436,13 @@ static __forceinline void IRQ(ULONG& uExecutedCycles, BOOL& flagc, BOOL& flagn, UINT uExtraCycles = 0; // Needed for CYC(a) macro CYC(7) #if defined(_DEBUG) && LOG_IRQ_TAKEN_AND_RTI - LogOutput("IRQ\n"); + std::string irq6522; + MB_Get6522IrqDescription(irq6522); + const char* pSrc = (g_bmIRQ & 1) ? irq6522.c_str() : + (g_bmIRQ & 2) ? "SPEECH" : + (g_bmIRQ & 4) ? "SSC" : + (g_bmIRQ & 8) ? "MOUSE" : "UNKNOWN"; + LogOutput("IRQ (%08X) (%s)\n", (UINT)g_nCycleIrqStart, pSrc); #endif CheckSynchronousInterruptSources(7, uExecutedCycles); } diff --git a/source/Mockingboard.cpp b/source/Mockingboard.cpp index a8194deb..bb3e9d93 100644 --- a/source/Mockingboard.cpp +++ b/source/Mockingboard.cpp @@ -91,10 +91,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "Riff.h" #include "AY8910.h" -#include "SSI263Phonemes.h" - -#define LOG_SSI263 0 -#define LOG_SSI263B 0 // Alternate SSI263 logging (use in conjunction with CPU.cpp's LOG_IRQ_TAKEN_AND_RTI) +#include "SSI263.h" #define SY6522_DEVICE_A 0 @@ -114,7 +111,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // Chip offsets from card base. #define SY6522A_Offset 0x00 #define SY6522B_Offset 0x80 -#define SSI263_Offset 0x40 +#define SSI263B_Offset 0x20 +#define SSI263A_Offset 0x40 //#define Phasor_SY6522A_CS 4 //#define Phasor_SY6522B_CS 7 @@ -130,32 +128,18 @@ struct SY6522_AY8910 BYTE nAYCurrentRegister; bool bTimer1Active; bool bTimer2Active; - SSI263A SpeechChip; + SSI263 ssi263; MockingboardUnitState_e state; // Where a unit is a 6522+AY8910 pair MockingboardUnitState_e stateB; // Phasor: 6522 & 2nd AY8910 }; -// IFR & IER: -#define IxR_PERIPHERAL (1<<1) -#define IxR_VOTRAX (1<<4) // TO DO: Get proper name from 6522 datasheet! -#define IxR_TIMER2 (1<<5) -#define IxR_TIMER1 (1<<6) - // ACR: #define RUNMODE (1<<6) // 0 = 1-Shot Mode, 1 = Free Running Mode #define RM_ONESHOT (0<<6) #define RM_FREERUNNING (1<<6) -// SSI263A registers: -#define SSI_DURPHON 0x00 -#define SSI_INFLECT 0x01 -#define SSI_RATEINF 0x02 -#define SSI_CTTRAMP 0x03 -#define SSI_FILFREQ 0x04 - - // Support 2 MB's, each with 2x SY6522/AY8910 pairs. static SY6522_AY8910 g_MB[NUM_AY8910]; @@ -169,12 +153,6 @@ static const UINT kTIMERDEVICE_INVALID = -1; static UINT g_nMBTimerDevice = kTIMERDEVICE_INVALID; // SY6522 device# which is generating timer IRQ static UINT64 g_uLastCumulativeCycles = 0; -// SSI263 vars: -static USHORT g_nSSI263Device = 0; // SSI263 device# which is generating phoneme-complete IRQ -static volatile int g_nCurrentActivePhoneme = -1; // Modified by threads: main & SSI263Thread -static volatile bool g_bStopPhoneme = false; // Modified by threads: main & SSI263Thread -static bool g_bVotraxPhoneme = false; - static const DWORD SAMPLE_RATE = 44100; // Use a base freq so that DirectX (or sound h/w) doesn't have to up/down-sample static short* ppAYVoiceBuffer[NUM_VOICES] = {0}; @@ -183,36 +161,25 @@ static unsigned __int64 g_nMB_InActiveCycleCount = 0; static bool g_bMB_RegAccessedFlag = false; static bool g_bMB_Active = false; -static HANDLE g_hThread = NULL; - static bool g_bMBAvailable = false; // static SS_CARDTYPE g_SoundcardType = CT_Empty; // Use CT_Empty to mean: no soundcard static bool g_bPhasorEnable = false; -enum PHASOR_MODE {PH_Mockingboard=0, PH_UNDEF1, PH_UNDEF2, PH_UNDEF3, PH_UNDEF4, PH_Phasor/*=5*/, PH_UNDEF6, PH_EchoPlus/*=7*/}; static PHASOR_MODE g_phasorMode = PH_Mockingboard; static UINT g_PhasorClockScaleFactor = 1; // for save-state only //------------------------------------- static const unsigned short g_nMB_NumChannels = 2; - static const DWORD g_dwDSBufferSize = MAX_SAMPLES * sizeof(short) * g_nMB_NumChannels; static const SHORT nWaveDataMin = (SHORT)0x8000; static const SHORT nWaveDataMax = (SHORT)0x7FFF; static short g_nMixBuffer[g_dwDSBufferSize / sizeof(short)]; - - -static VOICE MockingboardVoice = {0}; -static VOICE SSI263Voice[64] = {0}; - -static const int g_nNumEvents = 2; -static HANDLE g_hSSI263Event[g_nNumEvents] = {NULL}; // 1: Phoneme finished playing, 2: Exit thread -static DWORD g_dwMaxPhonemeLen = 0; +static VOICE MockingboardVoice; static bool g_bCritSectionValid = false; // Deleting CritialSection when not valid causes crash on Win98 static CRITICAL_SECTION g_CriticalSection; // To guard 6522's IFR @@ -222,12 +189,42 @@ static UINT g_cyclesThisAudioFrame = 0; //--------------------------------------------------------------------------- // Forward refs: -static DWORD WINAPI SSI263Thread(LPVOID); -static void Votrax_Write(BYTE nDevice, BYTE nValue); static int MB_SyncEventCallback(int id, int cycles, ULONG uExecutedCycles); //--------------------------------------------------------------------------- +void MB_Get6522IrqDescription(std::string& desc) +{ + for (UINT i=0; ibTimer1Active = true; @@ -566,7 +563,7 @@ static USHORT SetTimerSyncEvent(UINT id, BYTE reg, USHORT timerLatch) static void UpdateIFR(SY6522_AY8910* pMB, BYTE clr_ifr, BYTE set_ifr=0) { - // Need critical section to avoid data-race: main thread & SSI263Thread can both access IFR + // Need critical section to avoid data-race: main thread & SSI263Thread can both access IFR -- no longer a SSI263Thread // . NB. Loading a save-state just directly writes into 6522.IFR (which is fine) if (g_bCritSectionValid) EnterCriticalSection(&g_CriticalSection); { @@ -614,7 +611,7 @@ static void SY6522_Write(BYTE nDevice, BYTE nReg, BYTE nValue) if( (pMB->sy6522.DDRB == 0xFF) && (pMB->sy6522.PCR == 0xB0) ) { // Votrax speech data - Votrax_Write(nDevice, nValue); + pMB->ssi263.Votrax_Write(nValue); break; } @@ -789,271 +786,6 @@ static BYTE SY6522_Read(BYTE nDevice, BYTE nReg) return nValue; } -//--------------------------------------------------------------------------- - -static void SSI263_Play(unsigned int nPhoneme); - -#if 0 -typedef struct -{ - BYTE DurationPhoneme; - BYTE Inflection; // I10..I3 - BYTE RateInflection; - BYTE CtrlArtAmp; - BYTE FilterFreq; - // - BYTE CurrentMode; -} SSI263A; -#endif - -//static SSI263A nSpeechChip; - -// Duration/Phonome -const BYTE DURATION_MODE_MASK = 0xC0; -const BYTE PHONEME_MASK = 0x3F; - -const BYTE MODE_PHONEME_TRANSITIONED_INFLECTION = 0xC0; // IRQ active -const BYTE MODE_PHONEME_IMMEDIATE_INFLECTION = 0x80; // IRQ active -const BYTE MODE_FRAME_IMMEDIATE_INFLECTION = 0x40; // IRQ active -const BYTE MODE_IRQ_DISABLED = 0x00; - -// Rate/Inflection -const BYTE RATE_MASK = 0xF0; -const BYTE INFLECTION_MASK_H = 0x08; // I11 -const BYTE INFLECTION_MASK_L = 0x07; // I2..I0 - -// Ctrl/Art/Amp -const BYTE CONTROL_MASK = 0x80; -const BYTE ARTICULATION_MASK = 0x70; -const BYTE AMPLITUDE_MASK = 0x0F; - -#if LOG_SSI263B -static int ssiRegs[5]={-1,-1,-1,-1,-1}; -static int totalDuration_ms = 0; - -void SSI_Output(void) -{ - int ssi0 = ssiRegs[SSI_DURPHON]; - int ssi2 = ssiRegs[SSI_RATEINF]; - - LogOutput("SSI: "); - for (int i=0; i<=4; i++) - { - char r[3]="--"; - if (ssiRegs[i]>=0) sprintf(r,"%02X",ssiRegs[i]); - LogOutput("%s ", r); - ssiRegs[i] = -1; - } - - if (ssi0 != -1 && ssi2 != -1) - { - int phonemeDuration_ms = (((16-(ssi2>>4))*4096)/1023) * (4-(ssi0>>6)); - totalDuration_ms += phonemeDuration_ms; - LogOutput("/ duration = %d (total = %d) ms", phonemeDuration_ms, totalDuration_ms); - } - - LogOutput("\n"); -} -#endif - -static BYTE SSI263_Read(BYTE nDevice, ULONG nExecutedCycles) -{ - SY6522_AY8910* pMB = &g_MB[nDevice]; - - // Regardless of register, just return inverted A/!R in bit7 - // . A/!R is low for IRQ - - return MemReadFloatingBus(pMB->SpeechChip.CurrentMode & 1, nExecutedCycles); -} - -static void SSI263_Write(BYTE nDevice, BYTE nReg, BYTE nValue) -{ - SY6522_AY8910* pMB = &g_MB[nDevice]; - -#if LOG_SSI263B - _ASSERT(nReg < 5); - if (nReg>4) nReg=4; - if (ssiRegs[nReg]>=0) SSI_Output(); // overwriting a reg - ssiRegs[nReg] = nValue; -#endif - - switch(nReg) - { - case SSI_DURPHON: -#if LOG_SSI263 - if(g_fh) fprintf(g_fh, "DUR = 0x%02X, PHON = 0x%02X\n\n", nValue>>6, nValue&PHONEME_MASK); - LogOutput("DUR = %d, PHON = 0x%02X\n", nValue>>6, nValue&PHONEME_MASK); -#endif -#if LOG_SSI263B - SSI_Output(); -#endif - - // Notes: - // . Phasor's text-to-speech playback has no CTL H->L - // - ISR just writes CTL=0 (and new ART+AMP values), and writes DUR=x (and new PHON) - // - since no CTL H->L, then DUR value doesn't take affect (so continue using previous) - // - so the write to DURPHON must clear the IRQ - // . Does a write of CTL=0 clear IRQ? (ie. CTL 0->0) - // . Does a write of CTL=1 clear IRQ? (ie. CTL 0->1) - // - SSI263 datasheet says: "Setting the Control bit (CTL) to a logic one puts the device into Power Down mode..." - // . Does phoneme output only happen when CTL=0? (Otherwise device is in PD mode) - - // SSI263 datasheet is not clear, but a write to DURPHON must clear the IRQ. - // NB. For Mockingboard, A/!R is ack'ed by 6522's PCR handshake. - if (g_bPhasorEnable && g_phasorMode == PH_Phasor) - CpuIrqDeassert(IS_SPEECH); - - pMB->SpeechChip.CurrentMode &= ~1; // Clear SSI263's D7 pin - - pMB->SpeechChip.DurationPhoneme = nValue; - - g_nSSI263Device = nDevice; - - SSI263_Play(nValue & PHONEME_MASK); - break; - case SSI_INFLECT: -#if LOG_SSI263 - if(g_fh) fprintf(g_fh, "INF = 0x%02X\n", nValue); -#endif - pMB->SpeechChip.Inflection = nValue; - break; - - case SSI_RATEINF: -#if LOG_SSI263 - if(g_fh) fprintf(g_fh, "RATE = 0x%02X, INF = 0x%02X\n", nValue>>4, nValue&0x0F); -#endif - pMB->SpeechChip.RateInflection = nValue; - break; - case SSI_CTTRAMP: -#if LOG_SSI263 - if(g_fh) fprintf(g_fh, "CTRL = %d, ART = 0x%02X, AMP=0x%02X\n", nValue>>7, (nValue&ARTICULATION_MASK)>>4, nValue&LITUDE_MASK); - // - { - bool H2L = (pMB->SpeechChip.CtrlArtAmp & CONTROL_MASK) && !(nValue & CONTROL_MASK); - char newMode[20]; - sprintf_s(newMode, sizeof(newMode), "(new mode=%d)", pMB->SpeechChip.DurationPhoneme>>6); - LogOutput("CTRL = %d->%d, ART = 0x%02X, AMP=0x%02X %s\n", pMB->SpeechChip.CtrlArtAmp>>7, nValue>>7, (nValue&ARTICULATION_MASK)>>4, nValue&LITUDE_MASK, H2L?newMode:""); - } -#endif -#if LOG_SSI263B - if ( ((pMB->SpeechChip.CtrlArtAmp & CONTROL_MASK) && !(nValue & CONTROL_MASK)) || ((nValue&0xF) == 0x0) ) // H->L or amp=0 - SSI_Output(); -#endif - if((pMB->SpeechChip.CtrlArtAmp & CONTROL_MASK) && !(nValue & CONTROL_MASK)) // H->L - { - pMB->SpeechChip.CurrentMode = pMB->SpeechChip.DurationPhoneme & DURATION_MODE_MASK; - if (pMB->SpeechChip.CurrentMode == MODE_IRQ_DISABLED) - { - // "Disables A/!R output only; does not change previous A/!R response" (SSI263 datasheet) -// CpuIrqDeassert(IS_SPEECH); - } - } - - pMB->SpeechChip.CtrlArtAmp = nValue; - - // "Setting the Control bit (CTL) to a logic one puts the device into Power Down mode..." (SSI263 datasheet) - if (pMB->SpeechChip.CtrlArtAmp & CONTROL_MASK) - { -// CpuIrqDeassert(IS_SPEECH); -// pMB->SpeechChip.CurrentMode &= ~1; // Clear SSI263's D7 pin - } - break; - case SSI_FILFREQ: // RegAddr.b2=1 (b1 & b0 are: don't care) - default: -#if LOG_SSI263 - if(g_fh) fprintf(g_fh, "FFREQ = 0x%02X\n", nValue); -#endif - pMB->SpeechChip.FilterFreq = nValue; - break; - } -} - -//------------------------------------- - -static BYTE Votrax2SSI263[64] = -{ - 0x02, // 00: EH3 jackEt -> E1 bEnt - 0x0A, // 01: EH2 Enlist -> EH nEst - 0x0B, // 02: EH1 hEAvy -> EH1 bElt - 0x00, // 03: PA0 no sound -> PA - 0x28, // 04: DT buTTer -> T Tart - 0x08, // 05: A2 mAde -> A mAde - 0x08, // 06: A1 mAde -> A mAde - 0x2F, // 07: ZH aZure -> Z Zero - 0x0E, // 08: AH2 hOnest -> AH gOt - 0x07, // 09: I3 inhibIt -> I sIx - 0x07, // 0A: I2 Inhibit -> I sIx - 0x07, // 0B: I1 inhIbit -> I sIx - 0x37, // 0C: M Mat -> More - 0x38, // 0D: N suN -> N NiNe - 0x24, // 0E: B Bag -> B Bag - 0x33, // 0F: V Van -> V Very - // - 0x32, // 10: CH* CHip -> SCH SHip (!) - 0x32, // 11: SH SHop -> SCH SHip - 0x2F, // 12: Z Zoo -> Z Zero - 0x10, // 13: AW1 lAWful -> AW Office - 0x39, // 14: NG thiNG -> NG raNG - 0x0F, // 15: AH1 fAther -> AH1 fAther - 0x13, // 16: OO1 lOOking -> OO lOOk - 0x13, // 17: OO bOOK -> OO lOOk - 0x20, // 18: L Land -> L Lift - 0x29, // 19: K triCK -> Kit - 0x25, // 1A: J* juDGe -> D paiD (!) - 0x2C, // 1B: H Hello -> HF Heart - 0x26, // 1C: G Get -> KV taG - 0x34, // 1D: F Fast -> F Four - 0x25, // 1E: D paiD -> D paiD - 0x30, // 1F: S paSS -> S Same - // - 0x08, // 20: A dAY -> A mAde - 0x09, // 21: AY dAY -> AI cAre - 0x03, // 22: Y1 Yard -> YI Year - 0x1B, // 23: UH3 missIOn -> UH3 nUt - 0x0E, // 24: AH mOp -> AH gOt - 0x27, // 25: P Past -> P Pen - 0x11, // 26: O cOld -> O stOre - 0x07, // 27: I pIn -> I sIx - 0x16, // 28: U mOve -> U tUne - 0x05, // 29: Y anY -> AY plEAse - 0x28, // 2A: T Tap -> T Tart - 0x1D, // 2B: R Red -> R Roof - 0x01, // 2C: E mEEt -> E mEEt - 0x23, // 2D: W Win -> W Water - 0x0C, // 2E: AE dAd -> AE dAd - 0x0D, // 2F: AE1 After -> AE1 After - // - 0x10, // 30: AW2 sAlty -> AW Office - 0x1A, // 31: UH2 About -> UH2 whAt - 0x19, // 32: UH1 Uncle -> UH1 lOve - 0x18, // 33: UH cUp -> UH wOnder - 0x11, // 34: O2 fOr -> O stOre - 0x11, // 35: O1 abOArd -> O stOre - 0x14, // 36: IU yOU -> IU yOU - 0x14, // 37: U1 yOU -> IU yOU - 0x35, // 38: THV THe -> THV THere - 0x36, // 39: TH THin -> TH wiTH - 0x1C, // 3A: ER bIrd -> ER bIrd - 0x0A, // 3B: EH gEt -> EH nEst - 0x01, // 3C: E1 bE -> E mEEt - 0x10, // 3D: AW cAll -> AW Office - 0x00, // 3E: PA1 no sound -> PA - 0x00, // 3F: STOP no sound -> PA -}; - -static void Votrax_Write(BYTE nDevice, BYTE nValue) -{ - g_bVotraxPhoneme = true; - - // !A/R: Acknowledge receipt of phoneme data (signal goes from high to low) - SY6522_AY8910* pMB = &g_MB[nDevice]; - UpdateIFR(pMB, IxR_VOTRAX); - - g_nSSI263Device = nDevice; - - SSI263_Play(Votrax2SSI263[nValue & PHONEME_MASK]); -} - //=========================================================================== //#define DBG_MB_UPDATE @@ -1282,194 +1014,6 @@ static void MB_Update(void) //----------------------------------------------------------------------------- -// Called by SSI263Thread(), MB_LoadSnapshot & Phasor_LoadSnapshot -// Pre: g_bVotraxPhoneme, g_bPhasorEnable, g_phasorMode -static void SetSpeechIRQ(SY6522_AY8910* pMB) -{ - if (!g_bVotraxPhoneme) - { - // Always set SSI263's D7 pin regardless of SSI263 mode (DR1:0), including MODE_IRQ_DISABLED - pMB->SpeechChip.CurrentMode |= 1; // Set SSI263's D7 pin - - if ((pMB->SpeechChip.CurrentMode & DURATION_MODE_MASK) != MODE_IRQ_DISABLED) - { - if (!g_bPhasorEnable || (g_bPhasorEnable && g_phasorMode == PH_Mockingboard)) - { - if ((pMB->sy6522.PCR & 1) == 0) // CA1 Latch/Input = 0 (Negative active edge) - UpdateIFR(pMB, 0, IxR_PERIPHERAL); - if (pMB->sy6522.PCR == 0x0C) // CA2 Control = b#110 (Low output) - pMB->SpeechChip.CurrentMode &= ~1; // Clear SSI263's D7 pin (cleared by 6522's PCR CA1/CA2 handshake) - - // NB. Don't set CTL=1, as Mockingboard(SMS) speech doesn't work (it sets MODE_IRQ_DISABLED mode during ISR) - //pMB->SpeechChip.CtrlArtAmp |= CONTROL_MASK; // 6522's CA2 sets Power Down mode (pin 18), which sets Control bit - } - else if (g_bPhasorEnable && g_phasorMode == PH_Phasor) // Phasor's SSI263 IRQ (A/!R) line is *also* wired directly to the 6502's IRQ (as well as the 6522's CA1) - { - CpuIrqAssert(IS_SPEECH); - } - } - } - - // - - if (g_bVotraxPhoneme && pMB->sy6522.PCR == 0xB0) - { - // !A/R: Time-out of old phoneme (signal goes from low to high) - - UpdateIFR(pMB, 0, IxR_VOTRAX); - - g_bVotraxPhoneme = false; - } -} - -static DWORD WINAPI SSI263Thread(LPVOID lpParameter) -{ - while(1) - { - DWORD dwWaitResult = WaitForMultipleObjects( - g_nNumEvents, // number of handles in array - g_hSSI263Event, // array of event handles - FALSE, // wait until any one is signaled - INFINITE); - - if((dwWaitResult < WAIT_OBJECT_0) || (dwWaitResult > WAIT_OBJECT_0+g_nNumEvents-1)) - continue; - - dwWaitResult -= WAIT_OBJECT_0; // Determine event # that signaled - - if(dwWaitResult == (g_nNumEvents-1)) // Termination event - break; - - // Phoneme completed playing - - if (g_bStopPhoneme) - { - g_bStopPhoneme = false; - continue; - } - -#if LOG_SSI263 - //if(g_fh) fprintf(g_fh, "IRQ: Phoneme complete (0x%02X)\n\n", g_nCurrentActivePhoneme); -#endif - - if (g_nCurrentActivePhoneme < 0) - continue; // On CTRL+RESET or power-cycle (during phoneme playback): ResetState() is called, which set g_nCurrentActivePhoneme=-1 - - SSI263Voice[g_nCurrentActivePhoneme].bActive = false; - g_nCurrentActivePhoneme = -1; - - // Phoneme complete, so generate IRQ if necessary - SY6522_AY8910* pMB = &g_MB[g_nSSI263Device]; - SetSpeechIRQ(pMB); - } - - return 0; -} - -//----------------------------------------------------------------------------- - -static void SSI263_Play(unsigned int nPhoneme) -{ -#if 1 - HRESULT hr; - - { - int nCurrPhoneme = g_nCurrentActivePhoneme; // local copy in case SSI263Thread sets it to -1 - if (nCurrPhoneme >= 0) - { - // A write to DURPHON before previous phoneme has completed - g_bStopPhoneme = true; - hr = SSI263Voice[nCurrPhoneme].lpDSBvoice->Stop(); - - // Busy-wait until ACK from SSI263Thread - // . required to avoid data-race - while ( g_bStopPhoneme && // wait for SSI263Thread to ACK the lpDSBVoice->Stop() - g_nCurrentActivePhoneme >= 0) // wait for SSI263Thread to get end of sample event - ; - - g_bStopPhoneme = false; - } - } - - g_nCurrentActivePhoneme = nPhoneme; - - hr = SSI263Voice[g_nCurrentActivePhoneme].lpDSBvoice->SetCurrentPosition(0); - if(FAILED(hr)) - return; - - hr = SSI263Voice[g_nCurrentActivePhoneme].lpDSBvoice->Play(0,0,0); // Not looping - if(FAILED(hr)) - return; - - SSI263Voice[g_nCurrentActivePhoneme].bActive = true; -#else - HRESULT hr; - bool bPause; - - if(nPhoneme == 1) - nPhoneme = 2; // Missing this sample, so map to phoneme-2 - - if(nPhoneme == 0) - { - bPause = true; - } - else - { -// nPhoneme--; - nPhoneme-=2; // Missing phoneme-1 - bPause = false; - } - - DWORD dwDSLockedBufferSize = 0; // Size of the locked DirectSound buffer - SHORT* pDSLockedBuffer; - - hr = SSI263Voice.lpDSBvoice->Stop(); - - if(!DSGetLock(SSI263Voice.lpDSBvoice, 0, 0, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, 0)) - return; - - unsigned int nPhonemeShortLength = g_nPhonemeInfo[nPhoneme].nLength; - unsigned int nPhonemeByteLength = g_nPhonemeInfo[nPhoneme].nLength * sizeof(SHORT); - - if(bPause) - { - // 'pause' length is length of 1st phoneme (arbitrary choice, since don't know real length) - memset(pDSLockedBuffer, 0, g_dwMaxPhonemeLen); - } - else - { - memcpy(pDSLockedBuffer, &g_nPhonemeData[g_nPhonemeInfo[nPhoneme].nOffset], nPhonemeByteLength); - memset(&pDSLockedBuffer[nPhonemeShortLength], 0, g_dwMaxPhonemeLen-nPhonemeByteLength); - } - -#if 0 - DSBPOSITIONNOTIFY PositionNotify; - - PositionNotify.dwOffset = nPhonemeByteLength - 1; // End of phoneme - PositionNotify.hEventNotify = g_hSSI263Event[0]; - - hr = SSI263Voice.lpDSNotify->SetNotificationPositions(1, &PositionNotify); - if(FAILED(hr)) - { - DirectSound_ErrorText(hr); - return; - } -#endif - - hr = SSI263Voice.lpDSBvoice->Unlock((void*)pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0); - if(FAILED(hr)) - return; - - hr = SSI263Voice.lpDSBvoice->Play(0,0,0); // Not looping - if(FAILED(hr)) - return; - - SSI263Voice.bActive = true; -#endif -} - -//----------------------------------------------------------------------------- - static bool MB_DSInit() { LogFileOutput("MB_DSInit\n", g_bMBAvailable); @@ -1483,21 +1027,18 @@ static bool MB_DSInit() // Create single Mockingboard voice // - DWORD dwDSLockedBufferSize = 0; // Size of the locked DirectSound buffer - SHORT* pDSLockedBuffer; - if(!g_bDSAvailable) return false; - HRESULT hr = DSGetSoundBuffer(&MockingboardVoice, DSBCAPS_CTRLVOLUME, g_dwDSBufferSize, SAMPLE_RATE, 2); + HRESULT hr = DSGetSoundBuffer(&MockingboardVoice, DSBCAPS_CTRLVOLUME, g_dwDSBufferSize, SAMPLE_RATE, g_nMB_NumChannels, "MB"); LogFileOutput("MB_DSInit: DSGetSoundBuffer(), hr=0x%08X\n", hr); if(FAILED(hr)) { - if(g_fh) fprintf(g_fh, "MB: DSGetSoundBuffer failed (%08X)\n",hr); + LogFileOutput("MB_DSInit: DSGetSoundBuffer failed (%08X)\n", hr); return false; } - bool bRes = DSZeroVoiceBuffer(&MockingboardVoice, "MB", g_dwDSBufferSize); + bool bRes = DSZeroVoiceBuffer(&MockingboardVoice, g_dwDSBufferSize); LogFileOutput("MB_DSInit: DSZeroVoiceBuffer(), res=%d\n", bRes ? 1 : 0); if (!bRes) return false; @@ -1513,135 +1054,12 @@ static bool MB_DSInit() //--------------------------------- - // - // Create SSI263 voice - // - -#if 0 - g_dwMaxPhonemeLen = 0; - for(int i=0; iQueryInterface(IID_IDirectSoundNotify, (LPVOID *)&SSI263Voice[i].lpDSNotify); - //LogFileOutput("MB_DSInit: (%02d) QueryInterface(), hr=0x%08X\n", i, hr); // WARNING: Lock acquired && doing heavy-weight logging - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "SSI263: QueryInterface failed (%08X)\n",hr); - return false; - } - - DSBPOSITIONNOTIFY PositionNotify; - -// PositionNotify.dwOffset = nPhonemeByteLength - 1; // End of buffer - PositionNotify.dwOffset = DSBPN_OFFSETSTOP; // End of buffer - PositionNotify.hEventNotify = g_hSSI263Event[0]; - - hr = SSI263Voice[i].lpDSNotify->SetNotificationPositions(1, &PositionNotify); - //LogFileOutput("MB_DSInit: (%02d) SetNotificationPositions(), hr=0x%08X\n", i, hr); // WARNING: Lock acquired && doing heavy-weight logging - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "SSI263: SetNotifyPos failed (%08X)\n",hr); - return false; - } - - hr = SSI263Voice[i].lpDSBvoice->Unlock((void*)pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0); - LogFileOutput("MB_DSInit: (%02d) Unlock(),hr=0x%08X\n", i, hr); - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "SSI263: DSUnlock failed (%08X)\n",hr); - return false; - } - - SSI263Voice[i].bActive = false; - SSI263Voice[i].nVolume = MockingboardVoice.nVolume; // Use same volume as MB - hr = SSI263Voice[i].lpDSBvoice->SetVolume(SSI263Voice[i].nVolume); - LogFileOutput("MB_DSInit: (%02d) SetVolume(), hr=0x%08X\n", i, hr); - } - - // - - DWORD dwThreadId; - - g_hThread = CreateThread(NULL, // lpThreadAttributes - 0, // dwStackSize - SSI263Thread, - NULL, // lpParameter - 0, // dwCreationFlags : 0 = Run immediately - &dwThreadId); // lpThreadId - LogFileOutput("MB_DSInit: CreateThread(), g_hThread=0x%08X\n", g_hThread); - - BOOL bRes2 = SetThreadPriority(g_hThread, THREAD_PRIORITY_TIME_CRITICAL); - LogFileOutput("MB_DSInit: SetThreadPriority(), bRes=%d\n", bRes2 ? 1 : 0); - return true; #endif // NO_DIRECT_X @@ -1649,63 +1067,15 @@ static bool MB_DSInit() static void MB_DSUninit() { - if(g_hThread) - { - DWORD dwExitCode; - SetEvent(g_hSSI263Event[g_nNumEvents-1]); // Signal to thread that it should exit - - do - { - if(GetExitCodeThread(g_hThread, &dwExitCode)) - { - if(dwExitCode == STILL_ACTIVE) - Sleep(10); - else - break; - } - } - while(1); - - CloseHandle(g_hThread); - g_hThread = NULL; - } - - // - if(MockingboardVoice.lpDSBvoice && MockingboardVoice.bActive) - { - MockingboardVoice.lpDSBvoice->Stop(); - MockingboardVoice.bActive = false; - } + DSVoiceStop(&MockingboardVoice); DSReleaseSoundBuffer(&MockingboardVoice); // - for(int i=0; i<64; i++) - { - if(SSI263Voice[i].lpDSBvoice && SSI263Voice[i].bActive) - { - SSI263Voice[i].lpDSBvoice->Stop(); - SSI263Voice[i].bActive = false; - } - - DSReleaseSoundBuffer(&SSI263Voice[i]); - } - - // - - if(g_hSSI263Event[0]) - { - CloseHandle(g_hSSI263Event[0]); - g_hSSI263Event[0] = NULL; - } - - if(g_hSSI263Event[1]) - { - CloseHandle(g_hSSI263Event[1]); - g_hSSI263Event[1] = NULL; - } + for (UINT i=0; iStop(); // Reason: 'MB voice is playing' then loading a save-state where 'no MB present' + DSVoiceStop(&MockingboardVoice); // Reason: 'MB voice is playing' then loading a save-state where 'no MB present' + + // NB. ssi263.Stop() already done by MB_Reset() } //----------------------------------------------------------------------------- @@ -1820,11 +1194,6 @@ static void ResetState() g_nMBTimerDevice = kTIMERDEVICE_INVALID; MB_SetCumulativeCycles(); - g_nSSI263Device = 0; - g_nCurrentActivePhoneme = -1; - g_bStopPhoneme = false; - g_bVotraxPhoneme = false; - g_nMB_InActiveCycleCount = 0; g_bMB_RegAccessedFlag = false; g_bMB_Active = false; @@ -1841,6 +1210,12 @@ static void ResetState() g_SynchronousEventMgr.Remove(id); } + for (UINT i=0; iMB_Reset() @@ -1916,9 +1291,9 @@ static BYTE __stdcall MB_Read(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULO { _ASSERT(!bAccessedDevice); if (nAddr & 0x40) // Primary SSI263 - nRes = SSI263_Read(nMB*2+1, nExecutedCycles); // SSI263 only drives bit7 + nRes = g_MB[nMB*2+1].ssi263.Read(nExecutedCycles); // SSI263 only drives bit7 if (nAddr & 0x20) // Secondary SSI263 - nRes = SSI263_Read(nMB*2+0, nExecutedCycles); // SSI263 only drives bit7 + nRes = g_MB[nMB*2+0].ssi263.Read(nExecutedCycles); // SSI263 only drives bit7 bAccessedDevice = true; } @@ -2011,9 +1386,9 @@ static BYTE __stdcall MB_Write(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, UL // NB. Mockingboard mode: writes to $Cn4x/SSI263 also get written to 1st 6522 (have confirmed on real Phasor h/w) _ASSERT( (g_phasorMode == PH_Mockingboard && (CS==0 || CS==1)) || (g_phasorMode == PH_Phasor && (CS==0)) ); if (nAddr & 0x40) // Primary SSI263 - SSI263_Write(nMB*2+1, nAddr&0x7, nValue); // 2nd 6522 is used for 1st speech chip + g_MB[nMB*2+1].ssi263.Write(nAddr&0x7, nValue); // 2nd 6522 is used for 1st speech chip if (nAddr & 0x20) // Secondary SSI263 - SSI263_Write(nMB*2+0, nAddr&0x7, nValue); // 1st 6522 is used for 2nd speech chip + g_MB[nMB*2+0].ssi263.Write(nAddr&0x7, nValue); // 1st 6522 is used for 2nd speech chip } return 0; @@ -2025,9 +1400,9 @@ static BYTE __stdcall MB_Write(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, UL SY6522_Write(nMB*NUM_DEVS_PER_MB + SY6522_DEVICE_B, nAddr&0xf, nValue); if (nAddr & 0x40) - SSI263_Write(nMB*2+1, nAddr&0x7, nValue); // 2nd 6522 is used for 1st speech chip + g_MB[nMB*2+1].ssi263.Write(nAddr&0x7, nValue); // 2nd 6522 is used for 1st speech chip if (nAddr & 0x20) - SSI263_Write(nMB*2+0, nAddr&0x7, nValue); // 1st 6522 is used for 2nd speech chip + g_MB[nMB*2+0].ssi263.Write(nAddr&0x7, nValue); // 1st 6522 is used for 2nd speech chip return 0; } @@ -2068,6 +1443,9 @@ static BYTE __stdcall PhasorIO(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, UL AY8910_InitClock((int)(Get6502BaseClock() * g_PhasorClockScaleFactor)); + for (UINT i=0; i= 0) - SSI263Voice[g_nCurrentActivePhoneme].lpDSBvoice->SetVolume(DSBVOLUME_MIN); + for (UINT i=0; i= 0) - SSI263Voice[g_nCurrentActivePhoneme].lpDSBvoice->SetVolume(SSI263Voice[g_nCurrentActivePhoneme].nVolume); + for (UINT i=0; iSetVolume(MockingboardVoice.nVolume); + + // + + for (UINT i=0; iUnit[i].RegsAY8910[j] = AYReadReg(nDeviceNum, j); } - memcpy(&pSS->Unit[i].RegsSSI263, &pMB->SpeechChip, sizeof(SSI263A)); + memset(&pSS->Unit[i].RegsSSI263, 0, sizeof(SSI263A)); // Not used by debugger pSS->Unit[i].nAYCurrentRegister = pMB->nAYCurrentRegister; pSS->Unit[i].bTimer1IrqPending = false; pSS->Unit[i].bTimer2IrqPending = false; @@ -2381,7 +1790,7 @@ void MB_GetSnapshot_v1(SS_CARD_MOCKINGBOARD_v1* const pSS, const DWORD dwSlot) } } -//=========================================================================== +//============================================================================= // Unit version history: // 2: Added: Timer1 & Timer2 active @@ -2391,7 +1800,8 @@ void MB_GetSnapshot_v1(SS_CARD_MOCKINGBOARD_v1* const pSS, const DWORD dwSlot) // 6: Changed SS_YAML_KEY_PHASOR_MODE from (0,1) to (0,5,7) // Added SS_YAML_KEY_VOTRAX_PHONEME // Removed: redundant SS_YAML_KEY_PHASOR_CLOCK_SCALE_FACTOR -const UINT kUNIT_VERSION = 6; +// 7: Added SS_YAML_KEY_SSI263_REG_ACTIVE_PHONEME to SSI263 sub-unit +const UINT kUNIT_VERSION = 7; const UINT NUM_MB_UNITS = 2; const UINT NUM_PHASOR_UNITS = 2; @@ -2411,13 +1821,6 @@ const UINT NUM_PHASOR_UNITS = 2; #define SS_YAML_KEY_SY6522_REG_PCR "PCR" #define SS_YAML_KEY_SY6522_REG_IFR "IFR" #define SS_YAML_KEY_SY6522_REG_IER "IER" -#define SS_YAML_KEY_SSI263 "SSI263" -#define SS_YAML_KEY_SSI263_REG_DUR_PHON "Duration / Phoneme" -#define SS_YAML_KEY_SSI263_REG_INF "Inflection" -#define SS_YAML_KEY_SSI263_REG_RATE_INF "Rate / Inflection" -#define SS_YAML_KEY_SSI263_REG_CTRL_ART_AMP "Control / Articulation / Amplitude" -#define SS_YAML_KEY_SSI263_REG_FILTER_FREQ "Filter Frequency" -#define SS_YAML_KEY_SSI263_REG_CURRENT_MODE "Current Mode" #define SS_YAML_KEY_AY_CURR_REG "AY Current Register" #define SS_YAML_KEY_MB_UNIT_STATE "Unit State" #define SS_YAML_KEY_MB_UNIT_STATE_B "Unit State-B" // Phasor only @@ -2469,18 +1872,6 @@ static void SaveSnapshotSY6522(YamlSaveHelper& yamlSaveHelper, SY6522& sy6522) // NB. No need to write ORA_NO_HS, since same data as ORA, just without handshake } -static void SaveSnapshotSSI263(YamlSaveHelper& yamlSaveHelper, SSI263A& ssi263) -{ - YamlSaveHelper::Label label(yamlSaveHelper, "%s:\n", SS_YAML_KEY_SSI263); - - yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SSI263_REG_DUR_PHON, ssi263.DurationPhoneme); - yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SSI263_REG_INF, ssi263.Inflection); - yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SSI263_REG_RATE_INF, ssi263.RateInflection); - yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SSI263_REG_CTRL_ART_AMP, ssi263.CtrlArtAmp); - yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SSI263_REG_FILTER_FREQ, ssi263.FilterFreq); - yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SSI263_REG_CURRENT_MODE, ssi263.CurrentMode); -} - void MB_SaveSnapshot(YamlSaveHelper& yamlSaveHelper, const UINT uSlot) { const UINT nMbCardNum = uSlot - SLOT4; @@ -2491,7 +1882,7 @@ void MB_SaveSnapshot(YamlSaveHelper& yamlSaveHelper, const UINT uSlot) YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE); - yamlSaveHelper.SaveBool(SS_YAML_KEY_VOTRAX_PHONEME, g_bVotraxPhoneme); + yamlSaveHelper.SaveBool(SS_YAML_KEY_VOTRAX_PHONEME, pMB->ssi263.GetVotraxPhoneme()); for(UINT i=0; isy6522); AY8910_SaveSnapshot(yamlSaveHelper, nDeviceNum, std::string("")); - SaveSnapshotSSI263(yamlSaveHelper, pMB->SpeechChip); + pMB->ssi263.SaveSnapshot(yamlSaveHelper); yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_MB_UNIT_STATE, pMB->state); yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_AY_CURR_REG, pMB->nAYCurrentRegister); @@ -2545,21 +1936,6 @@ static void LoadSnapshotSY6522(YamlLoadHelper& yamlLoadHelper, SY6522& sy6522, U yamlLoadHelper.PopMap(); } -static void LoadSnapshotSSI263(YamlLoadHelper& yamlLoadHelper, SSI263A& ssi263) -{ - if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_SSI263)) - throw std::string("Card: Expected key: ") + std::string(SS_YAML_KEY_SSI263); - - ssi263.DurationPhoneme = yamlLoadHelper.LoadUint(SS_YAML_KEY_SSI263_REG_DUR_PHON); - ssi263.Inflection = yamlLoadHelper.LoadUint(SS_YAML_KEY_SSI263_REG_INF); - ssi263.RateInflection = yamlLoadHelper.LoadUint(SS_YAML_KEY_SSI263_REG_RATE_INF); - ssi263.CtrlArtAmp = yamlLoadHelper.LoadUint(SS_YAML_KEY_SSI263_REG_CTRL_ART_AMP); - ssi263.FilterFreq = yamlLoadHelper.LoadUint(SS_YAML_KEY_SSI263_REG_FILTER_FREQ); - ssi263.CurrentMode = yamlLoadHelper.LoadUint(SS_YAML_KEY_SSI263_REG_CURRENT_MODE); - - yamlLoadHelper.PopMap(); -} - bool MB_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) { if (slot != 4 && slot != 5) // fixme @@ -2568,16 +1944,14 @@ bool MB_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) if (version < 1 || version > kUNIT_VERSION) throw std::string("Card: wrong version"); - g_bVotraxPhoneme = (version >= 6) ? yamlLoadHelper.LoadBool(SS_YAML_KEY_VOTRAX_PHONEME) : false; - AY8910UpdateSetCycles(); const UINT nMbCardNum = slot - SLOT4; UINT nDeviceNum = nMbCardNum*2; SY6522_AY8910* pMB = &g_MB[nDeviceNum]; - g_nSSI263Device = 0; - g_nCurrentActivePhoneme = -1; + bool isVotrax = (version >= 6) ? yamlLoadHelper.LoadBool(SS_YAML_KEY_VOTRAX_PHONEME) : false; + pMB->ssi263.SetVotraxPhoneme(isVotrax); for(UINT i=0; isy6522, version); UpdateIFR(pMB, 0, pMB->sy6522.IFR); // Assert any pending IRQs (GH#677) AY8910_LoadSnapshot(yamlLoadHelper, nDeviceNum, std::string("")); - LoadSnapshotSSI263(yamlLoadHelper, pMB->SpeechChip); + pMB->ssi263.LoadSnapshot(yamlLoadHelper, nDeviceNum, PH_Mockingboard, version); // Pre: SetVotraxPhoneme() pMB->nAYCurrentRegister = yamlLoadHelper.LoadUint(SS_YAML_KEY_AY_CURR_REG); yamlLoadHelper.LoadBool(SS_YAML_KEY_TIMER1_IRQ); // Consume @@ -2636,17 +2010,6 @@ bool MB_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) g_SynchronousEventMgr.Insert(pSyncEvent); } - // FIXME: currently only support a single speech chip - // NB. g_bVotraxPhoneme is never true, as the phoneme playback completes in SSI263Thread() before this point in the save-state. - // NB. SpeechChip.DurationPhoneme will mostly be non-zero during speech playback, as this is the SSI263 register, not whether the phonene is active. - // FIXME: So possible race-condition between saving-state & SSI263Thread() - if (pMB->SpeechChip.DurationPhoneme || g_bVotraxPhoneme) - { - g_nSSI263Device = nDeviceNum; - g_bPhasorEnable = false; - SetSpeechIRQ(pMB); - } - nDeviceNum++; pMB++; } @@ -2671,7 +2034,7 @@ void Phasor_SaveSnapshot(YamlSaveHelper& yamlSaveHelper, const UINT uSlot) YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE); yamlSaveHelper.SaveUint(SS_YAML_KEY_PHASOR_MODE, g_phasorMode); - yamlSaveHelper.SaveBool(SS_YAML_KEY_VOTRAX_PHONEME, g_bVotraxPhoneme); + yamlSaveHelper.SaveBool(SS_YAML_KEY_VOTRAX_PHONEME, pMB->ssi263.GetVotraxPhoneme()); for(UINT i=0; isy6522); AY8910_SaveSnapshot(yamlSaveHelper, nDeviceNum+0, std::string("-A")); AY8910_SaveSnapshot(yamlSaveHelper, nDeviceNum+1, std::string("-B")); - SaveSnapshotSSI263(yamlSaveHelper, pMB->SpeechChip); + pMB->ssi263.SaveSnapshot(yamlSaveHelper); yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_MB_UNIT_STATE, pMB->state); yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_MB_UNIT_STATE_B, pMB->stateB); @@ -2718,15 +2081,13 @@ bool Phasor_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version g_phasorMode = (PHASOR_MODE) phasorMode; g_PhasorClockScaleFactor = (g_phasorMode == PH_Phasor) ? 2 : 1; - g_bVotraxPhoneme = (version >= 6) ? yamlLoadHelper.LoadBool(SS_YAML_KEY_VOTRAX_PHONEME) : false; - AY8910UpdateSetCycles(); UINT nDeviceNum = 0; SY6522_AY8910* pMB = &g_MB[0]; - g_nSSI263Device = 0; - g_nCurrentActivePhoneme = -1; + bool isVotrax = (version >= 6) ? yamlLoadHelper.LoadBool(SS_YAML_KEY_VOTRAX_PHONEME) : false; + pMB->ssi263.SetVotraxPhoneme(isVotrax); for(UINT i=0; isy6522.IFR); // Assert any pending IRQs (GH#677) AY8910_LoadSnapshot(yamlLoadHelper, nDeviceNum+0, std::string("-A")); AY8910_LoadSnapshot(yamlLoadHelper, nDeviceNum+1, std::string("-B")); - LoadSnapshotSSI263(yamlLoadHelper, pMB->SpeechChip); + pMB->ssi263.LoadSnapshot(yamlLoadHelper, nDeviceNum, PH_Phasor, version); // Pre: SetVotraxPhoneme() pMB->nAYCurrentRegister = yamlLoadHelper.LoadUint(SS_YAML_KEY_AY_CURR_REG); yamlLoadHelper.LoadBool(SS_YAML_KEY_TIMER1_IRQ); // Consume @@ -2788,14 +2149,6 @@ bool Phasor_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version g_SynchronousEventMgr.Insert(pSyncEvent); } - // FIXME: currently only support a single speech chip - if (pMB->SpeechChip.DurationPhoneme || g_bVotraxPhoneme) - { - g_nSSI263Device = nDeviceNum+1; // +1 as speech is always 2nd device of the pair - g_bPhasorEnable = true; - SetSpeechIRQ(pMB); - } - nDeviceNum += 2; pMB++; } diff --git a/source/Mockingboard.h b/source/Mockingboard.h index 35445438..063f8fb7 100644 --- a/source/Mockingboard.h +++ b/source/Mockingboard.h @@ -2,6 +2,15 @@ #include "Card.h" +enum PHASOR_MODE {PH_Mockingboard=0, PH_UNDEF1, PH_UNDEF2, PH_UNDEF3, PH_UNDEF4, PH_Phasor/*=5*/, PH_UNDEF6, PH_EchoPlus/*=7*/}; + +// IFR & IER: +#define IxR_SSI263 (1<<1) +#define IxR_VOTRAX (1<<4) +#define IxR_TIMER2 (1<<5) +#define IxR_TIMER1 (1<<6) + + void MB_Initialize(); void MB_Reinitialize(); void MB_Destroy(); @@ -9,7 +18,7 @@ void MB_Reset(const bool powerCycle); void MB_InitializeForLoadingSnapshot(void); void MB_InitializeIO(LPBYTE pCxRomPeripheral, UINT uSlot4, UINT uSlot5); void MB_Mute(); -void MB_Demute(); +void MB_Unmute(); #ifdef _DEBUG void MB_CheckCumulativeCycles(); // DEBUG #endif @@ -21,6 +30,11 @@ SS_CARDTYPE MB_GetSoundcardType(); bool MB_IsActive(); DWORD MB_GetVolume(); void MB_SetVolume(DWORD dwVolume, DWORD dwVolumeMax); +void MB_Get6522IrqDescription(std::string& desc); + +UINT64 MB_GetLastCumulativeCycles(void); +void MB_UpdateIFR(BYTE nDevice, BYTE clr_mask, BYTE set_mask); +BYTE MB_GetPCR(BYTE nDevice); void MB_GetSnapshot_v1(struct SS_CARD_MOCKINGBOARD_v1* const pSS, const DWORD dwSlot); // For debugger std::string MB_GetSnapshotCardName(void); diff --git a/source/SSI263.cpp b/source/SSI263.cpp new file mode 100644 index 00000000..4719ac89 --- /dev/null +++ b/source/SSI263.cpp @@ -0,0 +1,868 @@ +/* +AppleWin : An Apple //e emulator for Windows + +Copyright (C) 1994-1996, Michael O'Brien +Copyright (C) 1999-2001, Oliver Schmidt +Copyright (C) 2002-2005, Tom Charlesworth +Copyright (C) 2006-2021, Tom Charlesworth, Michael Pohoreski, Nick Westgate + +AppleWin is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +AppleWin is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with AppleWin; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* Description: SSI263 emulation + * + * Author: Various + */ + +#include "StdAfx.h" + +#include "Core.h" +#include "CPU.h" +#include "Log.h" +#include "Memory.h" +#include "SoundCore.h" +#include "SSI263.h" +#include "SSI263Phonemes.h" + +#include "YamlHelper.h" + +#define LOG_SSI263 0 +#define LOG_SSI263B 0 // Alternate SSI263 logging (use in conjunction with CPU.cpp's LOG_IRQ_TAKEN_AND_RTI) + +// SSI263A registers: +#define SSI_DURPHON 0x00 +#define SSI_INFLECT 0x01 +#define SSI_RATEINF 0x02 +#define SSI_CTTRAMP 0x03 +#define SSI_FILFREQ 0x04 + +const DWORD SAMPLE_RATE_SSI263 = 22050; + +// Duration/Phonome +const BYTE DURATION_MODE_MASK = 0xC0; +const BYTE DURATION_SHIFT = 6; +const BYTE PHONEME_MASK = 0x3F; + +const BYTE MODE_PHONEME_TRANSITIONED_INFLECTION = 0xC0; // IRQ active +const BYTE MODE_PHONEME_IMMEDIATE_INFLECTION = 0x80; // IRQ active +const BYTE MODE_FRAME_IMMEDIATE_INFLECTION = 0x40; // IRQ active +const BYTE MODE_IRQ_DISABLED = 0x00; + +// Rate/Inflection +const BYTE RATE_MASK = 0xF0; +const BYTE INFLECTION_MASK_H = 0x08; // I11 +const BYTE INFLECTION_MASK_L = 0x07; // I2..I0 + +// Ctrl/Art/Amp +const BYTE CONTROL_MASK = 0x80; +const BYTE ARTICULATION_MASK = 0x70; +const BYTE AMPLITUDE_MASK = 0x0F; + +//----------------------------------------------------------------------------- + +#if LOG_SSI263B +static int ssiRegs[5]={-1,-1,-1,-1,-1}; +static int totalDuration_ms = 0; + +void SSI_Output(void) +{ + int ssi0 = ssiRegs[SSI_DURPHON]; + int ssi2 = ssiRegs[SSI_RATEINF]; + + LogOutput("SSI: "); + for (int i=0; i<=4; i++) + { + char r[3]="--"; + if (ssiRegs[i]>=0) sprintf(r,"%02X",ssiRegs[i]); + LogOutput("%s ", r); + ssiRegs[i] = -1; + } + + if (ssi0 != -1 && ssi2 != -1) + { + int phonemeDuration_ms = (((16-(ssi2>>4))*4096)/1023) * (4-(ssi0>>6)); + totalDuration_ms += phonemeDuration_ms; + LogOutput("/ duration = %d (total = %d) ms", phonemeDuration_ms, totalDuration_ms); + } + + LogOutput("\n"); +} +#endif + +//----------------------------------------------------------------------------- + +BYTE SSI263::Read(ULONG nExecutedCycles) +{ + // Regardless of register, just return inverted A/!R in bit7 + // . inverted "A/!R" is high for REQ (ie. Request, as phoneme nearly complete) + + return MemReadFloatingBus(m_currentMode & 1, nExecutedCycles); +} + +void SSI263::Write(BYTE nReg, BYTE nValue) +{ +#if LOG_SSI263B + _ASSERT(nReg < 5); + if (nReg>4) nReg=4; + if (ssiRegs[nReg]>=0) SSI_Output(); // overwriting a reg + ssiRegs[nReg] = nValue; +#endif + + switch(nReg) + { + case SSI_DURPHON: +#if LOG_SSI263 + if(g_fh) fprintf(g_fh, "DUR = 0x%02X, PHON = 0x%02X\n\n", nValue>>6, nValue&PHONEME_MASK); + LogOutput("DUR = %d, PHON = 0x%02X\n", nValue>>6, nValue&PHONEME_MASK); +#endif +#if LOG_SSI263B + SSI_Output(); +#endif + + // Notes: + // . Phasor's text-to-speech playback has no CTL H->L + // - ISR just writes CTL=0 (and new ART+AMP values), and writes DUR=x (and new PHON) + // - since no CTL H->L, then DUR value doesn't take affect (so continue using previous) + // - so the write to DURPHON must clear the IRQ + // . Does a write of CTL=0 clear IRQ? (ie. CTL 0->0) + // . Does a write of CTL=1 clear IRQ? (ie. CTL 0->1) + // - SSI263 datasheet says: "Setting the Control bit (CTL) to a logic one puts the device into Power Down mode..." + // . Does phoneme output only happen when CTL=0? (Otherwise device is in PD mode) + + // SSI263 datasheet is not clear, but a write to DURPHON must clear the IRQ. + // NB. For Mockingboard, A/!R is ack'ed by 6522's PCR handshake. + if (m_cardMode == PH_Phasor) + CpuIrqDeassert(IS_SPEECH); + + m_currentMode &= ~1; // Clear SSI263's D7 pin + + m_durationPhoneme = nValue; + + Play(nValue & PHONEME_MASK); + break; + case SSI_INFLECT: +#if LOG_SSI263 + if(g_fh) fprintf(g_fh, "INF = 0x%02X\n", nValue); +#endif + m_inflection = nValue; + break; + + case SSI_RATEINF: +#if LOG_SSI263 + if(g_fh) fprintf(g_fh, "RATE = 0x%02X, INF = 0x%02X\n", nValue>>4, nValue&0x0F); +#endif + m_rateInflection = nValue; + break; + case SSI_CTTRAMP: +#if LOG_SSI263 + if(g_fh) fprintf(g_fh, "CTRL = %d, ART = 0x%02X, AMP=0x%02X\n", nValue>>7, (nValue&ARTICULATION_MASK)>>4, nValue&LITUDE_MASK); + // + { + bool H2L = (m_ctrlArtAmp & CONTROL_MASK) && !(nValue & CONTROL_MASK); + char newMode[20]; + sprintf_s(newMode, sizeof(newMode), "(new mode=%d)", m_durationPhoneme>>6); + LogOutput("CTRL = %d->%d, ART = 0x%02X, AMP=0x%02X %s\n", m_ctrlArtAmp>>7, nValue>>7, (nValue&ARTICULATION_MASK)>>4, nValue&LITUDE_MASK, H2L?newMode:""); + } +#endif +#if LOG_SSI263B + if ( ((m_ctrlArtAmp & CONTROL_MASK) && !(nValue & CONTROL_MASK)) || ((nValue&0xF) == 0x0) ) // H->L or amp=0 + SSI_Output(); +#endif + if((m_ctrlArtAmp & CONTROL_MASK) && !(nValue & CONTROL_MASK)) // H->L + { + m_currentMode = m_durationPhoneme & DURATION_MODE_MASK; + if (m_currentMode == MODE_IRQ_DISABLED) + { + // "Disables A/!R output only; does not change previous A/!R response" (SSI263 datasheet) +// CpuIrqDeassert(IS_SPEECH); + } + } + + m_ctrlArtAmp = nValue; + + // "Setting the Control bit (CTL) to a logic one puts the device into Power Down mode..." (SSI263 datasheet) + if (m_ctrlArtAmp & CONTROL_MASK) + { +// CpuIrqDeassert(IS_SPEECH); +// m_currentMode &= ~1; // Clear SSI263's D7 pin + } + break; + case SSI_FILFREQ: // RegAddr.b2=1 (b1 & b0 are: don't care) + default: +#if LOG_SSI263 + if(g_fh) fprintf(g_fh, "FFREQ = 0x%02X\n", nValue); +#endif + m_filterFreq = nValue; + break; + } +} + +//----------------------------------------------------------------------------- + +const BYTE SSI263::m_Votrax2SSI263[/*64*/] = +{ + 0x02, // 00: EH3 jackEt -> E1 bEnt + 0x0A, // 01: EH2 Enlist -> EH nEst + 0x0B, // 02: EH1 hEAvy -> EH1 bElt + 0x00, // 03: PA0 no sound -> PA + 0x28, // 04: DT buTTer -> T Tart + 0x08, // 05: A2 mAde -> A mAde + 0x08, // 06: A1 mAde -> A mAde + 0x2F, // 07: ZH aZure -> Z Zero + 0x0E, // 08: AH2 hOnest -> AH gOt + 0x07, // 09: I3 inhibIt -> I sIx + 0x07, // 0A: I2 Inhibit -> I sIx + 0x07, // 0B: I1 inhIbit -> I sIx + 0x37, // 0C: M Mat -> More + 0x38, // 0D: N suN -> N NiNe + 0x24, // 0E: B Bag -> B Bag + 0x33, // 0F: V Van -> V Very + // + 0x32, // 10: CH* CHip -> SCH SHip (!) + 0x32, // 11: SH SHop -> SCH SHip + 0x2F, // 12: Z Zoo -> Z Zero + 0x10, // 13: AW1 lAWful -> AW Office + 0x39, // 14: NG thiNG -> NG raNG + 0x0F, // 15: AH1 fAther -> AH1 fAther + 0x13, // 16: OO1 lOOking -> OO lOOk + 0x13, // 17: OO bOOK -> OO lOOk + 0x20, // 18: L Land -> L Lift + 0x29, // 19: K triCK -> Kit + 0x25, // 1A: J* juDGe -> D paiD (!) + 0x2C, // 1B: H Hello -> HF Heart + 0x26, // 1C: G Get -> KV taG + 0x34, // 1D: F Fast -> F Four + 0x25, // 1E: D paiD -> D paiD + 0x30, // 1F: S paSS -> S Same + // + 0x08, // 20: A dAY -> A mAde + 0x09, // 21: AY dAY -> AI cAre + 0x03, // 22: Y1 Yard -> YI Year + 0x1B, // 23: UH3 missIOn -> UH3 nUt + 0x0E, // 24: AH mOp -> AH gOt + 0x27, // 25: P Past -> P Pen + 0x11, // 26: O cOld -> O stOre + 0x07, // 27: I pIn -> I sIx + 0x16, // 28: U mOve -> U tUne + 0x05, // 29: Y anY -> AY plEAse + 0x28, // 2A: T Tap -> T Tart + 0x1D, // 2B: R Red -> R Roof + 0x01, // 2C: E mEEt -> E mEEt + 0x23, // 2D: W Win -> W Water + 0x0C, // 2E: AE dAd -> AE dAd + 0x0D, // 2F: AE1 After -> AE1 After + // + 0x10, // 30: AW2 sAlty -> AW Office + 0x1A, // 31: UH2 About -> UH2 whAt + 0x19, // 32: UH1 Uncle -> UH1 lOve + 0x18, // 33: UH cUp -> UH wOnder + 0x11, // 34: O2 fOr -> O stOre + 0x11, // 35: O1 abOArd -> O stOre + 0x14, // 36: IU yOU -> IU yOU + 0x14, // 37: U1 yOU -> IU yOU + 0x35, // 38: THV THe -> THV THere + 0x36, // 39: TH THin -> TH wiTH + 0x1C, // 3A: ER bIrd -> ER bIrd + 0x0A, // 3B: EH gEt -> EH nEst + 0x01, // 3C: E1 bE -> E mEEt + 0x10, // 3D: AW cAll -> AW Office + 0x00, // 3E: PA1 no sound -> PA + 0x00, // 3F: STOP no sound -> PA +}; + +void SSI263::Votrax_Write(BYTE value) +{ + m_isVotraxPhoneme = true; + + // !A/R: Acknowledge receipt of phoneme data (signal goes from high to low) + MB_UpdateIFR(m_device, IxR_VOTRAX, 0); + + m_durationPhoneme = value; // Set reg0.DUR = I1:0 (inflection or pitch) + Play(m_Votrax2SSI263[value & PHONEME_MASK]); +} + +//----------------------------------------------------------------------------- + +void SSI263::Play(unsigned int nPhoneme) +{ + if (!SSI263SingleVoice.bActive) + { + bool bRes = DSZeroVoiceBuffer(&SSI263SingleVoice, m_kDSBufferSize); + LogFileOutput("SSI263::Play: DSZeroVoiceBuffer(), res=%d\n", bRes ? 1 : 0); + if (!bRes) + return; + } + + if (m_dbgFirst) + { + m_dbgStartTime = g_nCumulativeCycles; + LogOutput("1st phoneme = 0x%02X\n", nPhoneme); + } + + m_currentActivePhoneme = nPhoneme; + + bool bPause = false; + + if (nPhoneme == 1) + nPhoneme = 2; // Missing this sample, so map to phoneme-2 + + if (nPhoneme == 0) + bPause = true; + else + nPhoneme-=2; // Missing phoneme-1 + + m_phonemeLengthRemaining = g_nPhonemeInfo[nPhoneme].nLength; + + m_phonemeAccurateLengthRemaining = m_phonemeLengthRemaining; + m_phonemePlaybackAndDebugger = (g_nAppMode == MODE_STEPPING || g_nAppMode == MODE_DEBUG); + m_phonemeCompleteByFullSpeed = false; + + if (bPause) + { + if (!m_pPhonemeData00) + { + // 'pause' length is length of 1st phoneme (arbitrary choice, since don't know real length) + m_pPhonemeData00 = new short [m_phonemeLengthRemaining]; + memset(m_pPhonemeData00, 0x00, m_phonemeLengthRemaining*sizeof(short)); + } + + m_pPhonemeData = m_pPhonemeData00; + } + else + { + m_pPhonemeData = (const short*) &g_nPhonemeData[g_nPhonemeInfo[nPhoneme].nOffset]; + } + + m_currSampleSum = 0; + m_currNumSamples = 0; + m_currSampleMod4 = 0; +} + +void SSI263::Stop(void) +{ + if (SSI263SingleVoice.lpDSBvoice && SSI263SingleVoice.bActive) + DSVoiceStop(&SSI263SingleVoice); +} + +//----------------------------------------------------------------------------- + +//#define DBG_SSI263_UPDATE // NB. This outputs for all active SSI263 ring-buffers (eg. for mb-audit this may be 2 or 4) + +// Called by: +// . PeriodicUpdate() +void SSI263::Update(void) +{ + UpdateAccurateLength(); + + if (!SSI263SingleVoice.bActive) + return; + + if (g_bFullSpeed) // ie. only true when IsPhonemeActive() is true + { + if (m_phonemeLengthRemaining) + { + // Willy Byte does SSI263 detection with drive motor on + m_phonemeLengthRemaining = 0; + if (m_dbgFirst) LogOutput("1st phoneme short-circuited by fullspeed\n"); + + if (m_phonemeAccurateLengthRemaining) + m_phonemeCompleteByFullSpeed = true; // Let UpdateAccurateLength() call UpdateIRQ() + else + UpdateIRQ(); + } + + m_updateWasFullSpeed = true; + return; + } + + // + + const bool nowNormalSpeed = m_updateWasFullSpeed; // Just transitioned from full-speed to normal speed + m_updateWasFullSpeed = false; + + // NB. next call to this function: nowNormalSpeed = false + if (nowNormalSpeed) + m_byteOffset = (DWORD)-1; // ...which resets m_numSamplesError below + + //------------- + + DWORD dwCurrentPlayCursor, dwCurrentWriteCursor; + HRESULT hr = SSI263SingleVoice.lpDSBvoice->GetCurrentPosition(&dwCurrentPlayCursor, &dwCurrentWriteCursor); + if (FAILED(hr)) + return; + + bool prefillBufferOnInit = false; + + if (m_byteOffset == (DWORD)-1) + { + // First time in this func (or transitioned from full-speed to normal speed, or a ring-buffer reset) +#ifdef DBG_SSI263_UPDATE + double fTicksSecs = (double)GetTickCount() / 1000.0; + LogOutput("%010.3f: [SSUpdtInit%1d]PC=%08X, WC=%08X, Diff=%08X, Off=%08X xxx\n", fTicksSecs, m_device, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor - dwCurrentPlayCursor, m_byteOffset); +#endif + m_byteOffset = dwCurrentWriteCursor; + m_numSamplesError = 0; + prefillBufferOnInit = true; + } + else + { + // Check that our offset isn't between Play & Write positions + + if (dwCurrentWriteCursor > dwCurrentPlayCursor) + { + // |-----PxxxxxW-----| + if ((m_byteOffset > dwCurrentPlayCursor) && (m_byteOffset < dwCurrentWriteCursor)) + { +#ifdef DBG_SSI263_UPDATE + double fTicksSecs = (double)GetTickCount() / 1000.0; + LogOutput("%010.3f: [SSUpdt%1d] PC=%08X, WC=%08X, Diff=%08X, Off=%08X xxx\n", fTicksSecs, m_device, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor - dwCurrentPlayCursor, m_byteOffset); +#endif + m_byteOffset = dwCurrentWriteCursor; + m_numSamplesError = 0; + } + } + else + { + // |xxW----------Pxxx| + if ((m_byteOffset > dwCurrentPlayCursor) || (m_byteOffset < dwCurrentWriteCursor)) + { +#ifdef DBG_SSI263_UPDATE + double fTicksSecs = (double)GetTickCount() / 1000.0; + LogOutput("%010.3f: [SSUpdt%1d] PC=%08X, WC=%08X, Diff=%08X, Off=%08X XXX\n", fTicksSecs, m_device, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor - dwCurrentPlayCursor, m_byteOffset); +#endif + m_byteOffset = dwCurrentWriteCursor; + m_numSamplesError = 0; + } + } + } + + //------------- + + const UINT kMinBytesInBuffer = m_kDSBufferSize / 4; // 25% full + int nNumSamples = 0; + double updateInterval = 0.0; + + if (prefillBufferOnInit) + { + // Just prefill first 25% of buffer with zeros: + // . so we have a quarter buffer of silence/lag before the real sample data begins. + // . NB. this is fine, since it's the steady state; and it's likely that no actual data will ever occur during this initial time. + // This means that the '1st phoneme playback time' (in cycles) will be a bit longer for subsequent times. + + m_lastUpdateCycle = MB_GetLastCumulativeCycles(); + + nNumSamples = kMinBytesInBuffer / sizeof(short); + memset(&m_mixBufferSSI263[0], 0, nNumSamples); + } + else + { + // For small timer periods, wait for a period of 500cy before updating DirectSound ring-buffer. + // NB. A timer period of less than 24cy will yield nNumSamplesPerPeriod=0. + const double kMinimumUpdateInterval = 500.0; // Arbitary (500 cycles = 21 samples) + const double kMaximumUpdateInterval = (double)(0xFFFF + 2); // Max 6522 timer interval (1372 samples) + + if (m_lastUpdateCycle == 0) + m_lastUpdateCycle = MB_GetLastCumulativeCycles(); // Initial call to SSI263_Update() after reset/power-cycle + + _ASSERT(MB_GetLastCumulativeCycles() >= m_lastUpdateCycle); + updateInterval = (double)(MB_GetLastCumulativeCycles() - m_lastUpdateCycle); + if (updateInterval < kMinimumUpdateInterval) + return; + if (updateInterval > kMaximumUpdateInterval) + updateInterval = kMaximumUpdateInterval; + + m_lastUpdateCycle = MB_GetLastCumulativeCycles(); + + const double nIrqFreq = g_fCurrentCLK6502 / updateInterval + 0.5; // Round-up + const int nNumSamplesPerPeriod = (int)((double)(SAMPLE_RATE_SSI263) / nIrqFreq); // Eg. For 60Hz this is 367 + + nNumSamples = nNumSamplesPerPeriod + m_numSamplesError; // Apply correction + if (nNumSamples <= 0) + nNumSamples = 0; + if (nNumSamples > 2 * nNumSamplesPerPeriod) + nNumSamples = 2 * nNumSamplesPerPeriod; + + if (nNumSamples > m_kDSBufferSize) + nNumSamples = m_kDSBufferSize; // Clamp to prevent buffer overflow + +// if (nNumSamples) +// { /* Generate new sample data - ie. could merge from all the SSI263 sources */ } + + // + + int nBytesRemaining = m_byteOffset - dwCurrentPlayCursor; + if (nBytesRemaining < 0) + nBytesRemaining += m_kDSBufferSize; + + // Calc correction factor so that play-buffer doesn't under/overflow + const int nErrorInc = SoundCore_GetErrorInc(); + if (nBytesRemaining < kMinBytesInBuffer) + m_numSamplesError += nErrorInc; // < 0.25 of buffer remaining + else if (nBytesRemaining > m_kDSBufferSize / 2) + m_numSamplesError -= nErrorInc; // > 0.50 of buffer remaining + else + m_numSamplesError = 0; // Acceptable amount of data in buffer + } + +#if defined(DBG_SSI263_UPDATE) + double fTicksSecs = (double)GetTickCount() / 1000.0; + LogOutput("%010.3f: [SSUpdt%1d] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X, NSE=%08X, Interval=%f\n", fTicksSecs, m_device, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor - dwCurrentPlayCursor, m_byteOffset, nNumSamples, m_numSamplesError, updateInterval); +#endif + + if (nNumSamples == 0) + { + if (m_numSamplesError) + { + // Reset ring-buffer if we've had a major interruption, eg. F7 (enter debugger), F8 (configure), F11/12 (save-state), Pause, etc + // - this can cause Apple II SSI263 detection code to fail (when either timing one or a sequence of phonemes) + // When the AppleWin code restarts and reads the ring-buffer position it'll be at a random point, and maybe nearly full (>50% full) + // - so the code waits until it drains (nNumSamples=0 each time) + // - but it takes a large number of calls to this func to drain to an acceptable level + m_byteOffset = (DWORD)-1; +#if defined(DBG_SSI263_UPDATE) + double fTicksSecs = (double)GetTickCount() / 1000.0; + LogOutput("%010.3f: [SSUpdt%1d] Reset ring-buffer\n", fTicksSecs, m_device); +#endif + } + return; + } + + //------------- + + bool bSpeechIRQ = false; + + { + const BYTE DUR = m_durationPhoneme >> DURATION_SHIFT; + const BYTE numSamplesToAvg = (DUR <= 1) ? 1 : + (DUR == 2) ? 2 : + 4; + + short* pMixBuffer = &m_mixBufferSSI263[0]; + int zeroSize = nNumSamples; + + if (m_phonemeLengthRemaining && !prefillBufferOnInit) + { + UINT samplesWritten = 0; + while (samplesWritten < (UINT)nNumSamples) + { + m_currSampleSum += (int)*m_pPhonemeData; + m_currNumSamples++; + + m_pPhonemeData++; + m_phonemeLengthRemaining--; + + if (m_currNumSamples == numSamplesToAvg) + { + *pMixBuffer++ = (short)(m_currSampleSum / numSamplesToAvg); + samplesWritten++; + m_currSampleSum = 0; + m_currNumSamples = 0; + } + + m_currSampleMod4 = (m_currSampleMod4 + 1) & 3; + if (DUR == 1 && m_currSampleMod4 == 3 && m_phonemeLengthRemaining) + { + m_pPhonemeData++; + m_phonemeLengthRemaining--; + } + + if (!m_phonemeLengthRemaining) + { + bSpeechIRQ = true; + break; + } + } + + zeroSize = nNumSamples - samplesWritten; + _ASSERT(zeroSize >= 0); + } + + if (zeroSize) + memset(pMixBuffer, 0, zeroSize * sizeof(short)); + } + + // + + DWORD dwDSLockedBufferSize0, dwDSLockedBufferSize1; + short *pDSLockedBuffer0, *pDSLockedBuffer1; + + if (!DSGetLock(SSI263SingleVoice.lpDSBvoice, + m_byteOffset, (DWORD)nNumSamples*sizeof(short)*m_kNumChannels, + &pDSLockedBuffer0, &dwDSLockedBufferSize0, + &pDSLockedBuffer1, &dwDSLockedBufferSize1)) + return; + + memcpy(pDSLockedBuffer0, &m_mixBufferSSI263[0], dwDSLockedBufferSize0); + if(pDSLockedBuffer1) + memcpy(pDSLockedBuffer1, &m_mixBufferSSI263[dwDSLockedBufferSize0/sizeof(short)], dwDSLockedBufferSize1); + + // Commit sound buffer + hr = SSI263SingleVoice.lpDSBvoice->Unlock((void*)pDSLockedBuffer0, dwDSLockedBufferSize0, + (void*)pDSLockedBuffer1, dwDSLockedBufferSize1); + if (FAILED(hr)) + return; + + m_byteOffset = (m_byteOffset + (DWORD)nNumSamples*sizeof(short)*m_kNumChannels) % m_kDSBufferSize; + + // + + if (bSpeechIRQ) + { + // NB. if m_phonemePlaybackAndDebugger==true, then "m_phonemeAccurateLengthRemaining!=0" must be true. + // Since in UpdateAccurateLength(), (when m_phonemePlaybackAndDebugger==true) then m_phonemeAccurateLengthRemaining decs to zero. + if (!m_phonemePlaybackAndDebugger /*|| m_phonemeAccurateLengthRemaining*/) // superfluous, so commented out (see above) + UpdateIRQ(); + } +} + +//----------------------------------------------------------------------------- + +// The primary way for phonemes to generate IRQ is via the ring-buffer in Update(), +// but when single-stepping (eg. timing-sensitive SSI263 detection code), then this secondary method is used. +void SSI263::UpdateAccurateLength(void) +{ + if (!m_phonemeAccurateLengthRemaining) + return; + + if (m_lastUpdateCycle == 0) + return; + + double updateInterval = (double)(MB_GetLastCumulativeCycles() - m_lastUpdateCycle); + + const double nIrqFreq = g_fCurrentCLK6502 / updateInterval + 0.5; // Round-up + const int nNumSamplesPerPeriod = (int)((double)(SAMPLE_RATE_SSI263) / nIrqFreq); // Eg. For 60Hz this is 367 + + const BYTE DUR = m_durationPhoneme >> DURATION_SHIFT; + + const UINT numSamples = nNumSamplesPerPeriod * (DUR+1); + if (m_phonemeAccurateLengthRemaining > numSamples) + { + m_phonemeAccurateLengthRemaining -= numSamples; + } + else + { + m_phonemeAccurateLengthRemaining = 0; + if (m_phonemePlaybackAndDebugger || m_phonemeCompleteByFullSpeed) + UpdateIRQ(); + } +} + +// Called by: +// . Update() when m_phonemeLengthRemaining -> 0 +// . UpdateAccurateLength() when m_phonemeAccurateLengthRemaining -> 0 +// . LoadSnapshot() +void SSI263::UpdateIRQ(void) +{ + m_phonemeLengthRemaining = m_phonemeAccurateLengthRemaining = 0; // Prevent an IRQ from the other source + + _ASSERT(m_currentActivePhoneme != -1); + m_currentActivePhoneme = -1; + + if (m_dbgFirst && m_dbgStartTime) + { + UINT64 diff = g_nCumulativeCycles - m_dbgStartTime; + LogOutput("1st phoneme playback time = 0x%08X cy\n", (UINT32)diff); + m_dbgFirst = false; + } + + // Phoneme complete, so generate IRQ if necessary + SetSpeechIRQ(); +} + +//----------------------------------------------------------------------------- + +// Pre: m_isVotraxPhoneme, m_cardMode, m_device +void SSI263::SetSpeechIRQ(void) +{ + if (!m_isVotraxPhoneme) + { + // Always set SSI263's D7 pin regardless of SSI263 mode (DR1:0), including MODE_IRQ_DISABLED + m_currentMode |= 1; // Set SSI263's D7 pin + + if ((m_currentMode & DURATION_MODE_MASK) != MODE_IRQ_DISABLED) + { + if (m_cardMode == PH_Mockingboard) + { + if ((MB_GetPCR(m_device) & 1) == 0) // CA1 Latch/Input = 0 (Negative active edge) + MB_UpdateIFR(m_device, 0, IxR_SSI263); + if (MB_GetPCR(m_device) == 0x0C) // CA2 Control = b#110 (Low output) + m_currentMode &= ~1; // Clear SSI263's D7 pin (cleared by 6522's PCR CA1/CA2 handshake) + + // NB. Don't set CTL=1, as Mockingboard(SMS) speech doesn't work (it sets MODE_IRQ_DISABLED mode during ISR) + //pMB->SpeechChip.CtrlArtAmp |= CONTROL_MASK; // 6522's CA2 sets Power Down mode (pin 18), which sets Control bit + } + else if (m_cardMode == PH_Phasor) // Phasor's SSI263 IRQ (A/!R) line is *also* wired directly to the 6502's IRQ (as well as the 6522's CA1) + { + CpuIrqAssert(IS_SPEECH); + } + else + { + _ASSERT(0); + } + } + } + + // + + if (m_isVotraxPhoneme && MB_GetPCR(m_device) == 0xB0) + { + // !A/R: Time-out of old phoneme (signal goes from low to high) + + MB_UpdateIFR(m_device, 0, IxR_VOTRAX); + + m_isVotraxPhoneme = false; + } +} + +//----------------------------------------------------------------------------- + +bool SSI263::DSInit(void) +{ + // + // Create single SSI263 voice + // + + HRESULT hr = DSGetSoundBuffer(&SSI263SingleVoice, DSBCAPS_CTRLVOLUME, m_kDSBufferSize, SAMPLE_RATE_SSI263, m_kNumChannels, "SSI263"); + LogFileOutput("SSI263::DSInit: DSGetSoundBuffer(), hr=0x%08X\n", hr); + if (FAILED(hr)) + { + LogFileOutput("SSI263::DSInit: DSGetSoundBuffer failed (%08X)\n", hr); + return false; + } + + // Don't DirectSoundBuffer::Play() via DSZeroVoiceBuffer() - instead wait until this SSI263 is actually first used + // . different to Speaker & Mockingboard ring buffers + // . NB. we have 2x SSI263 per MB card, and it's rare if 1 is used (and *extremely* rare if 2 are used!) + + // Volume might've been setup from value in Registry + if (!SSI263SingleVoice.nVolume) + SSI263SingleVoice.nVolume = DSBVOLUME_MAX; + + hr = SSI263SingleVoice.lpDSBvoice->SetVolume(SSI263SingleVoice.nVolume); + LogFileOutput("SSI263::DSInit: SetVolume(), hr=0x%08X\n", hr); + + return true; +} + +void SSI263::DSUninit(void) +{ + Stop(); + DSReleaseSoundBuffer(&SSI263SingleVoice); +} + +//----------------------------------------------------------------------------- + +void SSI263::Reset(void) +{ + Stop(); + ResetState(); +} + +//----------------------------------------------------------------------------- + +void SSI263::Mute(void) +{ + if (SSI263SingleVoice.bActive && !SSI263SingleVoice.bMute) + { + SSI263SingleVoice.lpDSBvoice->SetVolume(DSBVOLUME_MIN); + SSI263SingleVoice.bMute = true; + } +} + +void SSI263::Unmute(void) +{ + if (SSI263SingleVoice.bActive && SSI263SingleVoice.bMute) + { + SSI263SingleVoice.lpDSBvoice->SetVolume(SSI263SingleVoice.nVolume); + SSI263SingleVoice.bMute = false; + } +} + +void SSI263::SetVolume(DWORD dwVolume, DWORD dwVolumeMax) +{ + SSI263SingleVoice.dwUserVolume = dwVolume; + + SSI263SingleVoice.nVolume = NewVolume(dwVolume, dwVolumeMax); + + if (SSI263SingleVoice.bActive && !SSI263SingleVoice.bMute) + SSI263SingleVoice.lpDSBvoice->SetVolume(SSI263SingleVoice.nVolume); +} + +//----------------------------------------------------------------------------- + +void SSI263::PeriodicUpdate(UINT executedCycles) +{ + const UINT kCyclesPerAudioFrame = 1000; + m_cyclesThisAudioFrame += executedCycles; + if (m_cyclesThisAudioFrame < kCyclesPerAudioFrame) + return; + + m_cyclesThisAudioFrame %= kCyclesPerAudioFrame; + + Update(); +} + +//============================================================================= + +#define SS_YAML_KEY_SSI263 "SSI263" +// NB. No version - this is determined by the parent "Mockingboard C" or "Phasor" unit + +#define SS_YAML_KEY_SSI263_REG_DUR_PHON "Duration / Phoneme" +#define SS_YAML_KEY_SSI263_REG_INF "Inflection" +#define SS_YAML_KEY_SSI263_REG_RATE_INF "Rate / Inflection" +#define SS_YAML_KEY_SSI263_REG_CTRL_ART_AMP "Control / Articulation / Amplitude" +#define SS_YAML_KEY_SSI263_REG_FILTER_FREQ "Filter Frequency" +#define SS_YAML_KEY_SSI263_REG_CURRENT_MODE "Current Mode" +#define SS_YAML_KEY_SSI263_REG_ACTIVE_PHONEME "Active Phoneme" + +void SSI263::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) +{ + YamlSaveHelper::Label label(yamlSaveHelper, "%s:\n", SS_YAML_KEY_SSI263); + + yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SSI263_REG_DUR_PHON, m_durationPhoneme); + yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SSI263_REG_INF, m_inflection); + yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SSI263_REG_RATE_INF, m_rateInflection); + yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SSI263_REG_CTRL_ART_AMP, m_ctrlArtAmp); + yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SSI263_REG_FILTER_FREQ, m_filterFreq); + yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SSI263_REG_CURRENT_MODE, m_currentMode); + yamlSaveHelper.SaveBool(SS_YAML_KEY_SSI263_REG_ACTIVE_PHONEME, IsPhonemeActive()); +} + +void SSI263::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT device, PHASOR_MODE mode, UINT version) +{ + if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_SSI263)) + throw std::string("Card: Expected key: ") + std::string(SS_YAML_KEY_SSI263); + + m_durationPhoneme = yamlLoadHelper.LoadUint(SS_YAML_KEY_SSI263_REG_DUR_PHON); + m_inflection = yamlLoadHelper.LoadUint(SS_YAML_KEY_SSI263_REG_INF); + m_rateInflection = yamlLoadHelper.LoadUint(SS_YAML_KEY_SSI263_REG_RATE_INF); + m_ctrlArtAmp = yamlLoadHelper.LoadUint(SS_YAML_KEY_SSI263_REG_CTRL_ART_AMP); + m_filterFreq = yamlLoadHelper.LoadUint(SS_YAML_KEY_SSI263_REG_FILTER_FREQ); + m_currentMode = yamlLoadHelper.LoadUint(SS_YAML_KEY_SSI263_REG_CURRENT_MODE); + bool activePhoneme = (version >= 7) ? yamlLoadHelper.LoadBool(SS_YAML_KEY_SSI263_REG_ACTIVE_PHONEME) : false; + m_currentActivePhoneme = !activePhoneme ? -1 : 0x00; // Not important which phoneme, since UpdateIRQ() resets this + + yamlLoadHelper.PopMap(); + + // + + _ASSERT(m_device != -1); + SetCardMode(mode); + + if (IsPhonemeActive()) + UpdateIRQ(); // Pre: m_device, m_cardMode + + m_lastUpdateCycle = MB_GetLastCumulativeCycles(); +} diff --git a/source/SSI263.h b/source/SSI263.h new file mode 100644 index 00000000..18f432bb --- /dev/null +++ b/source/SSI263.h @@ -0,0 +1,143 @@ +#pragma once + +#include "Mockingboard.h" // enum PHASOR_MODE + +class SSI263 +{ +public: + SSI263(void) + { + m_device = -1; // undefined + m_cardMode = PH_Mockingboard; + m_pPhonemeData00 = NULL; + + ResetState(); + } + ~SSI263(void) + { + delete [] m_pPhonemeData00; + } + + void ResetState(void) + { + m_currentActivePhoneme = -1; + m_isVotraxPhoneme = false; + m_cyclesThisAudioFrame = 0; + + // + + m_lastUpdateCycle = 0; + m_updateWasFullSpeed = false; + + m_pPhonemeData = NULL; + m_phonemeLengthRemaining = 0; + m_phonemeAccurateLengthRemaining = 0; + m_phonemePlaybackAndDebugger = false; + m_phonemeCompleteByFullSpeed = false; + + // + + m_numSamplesError = 0; + m_byteOffset = (DWORD)-1; + m_currSampleSum = 0; + m_currNumSamples = 0; + m_currSampleMod4 = 0; + + // + + m_durationPhoneme = 0; + m_inflection = 0; + m_rateInflection = 0; + m_ctrlArtAmp = 0; + m_filterFreq = 0; + + m_currentMode = 0; + + // + + m_dbgFirst = true; + m_dbgStartTime = 0; + } + + bool DSInit(void); + void DSUninit(void); + + void Reset(void); + bool IsPhonemeActive(void) { return m_currentActivePhoneme >= 0; } + + BYTE Read(ULONG nExecutedCycles); + void Write(BYTE nReg, BYTE nValue); + + void Mute(void); + void Unmute(void); + void SetVolume(DWORD dwVolume, DWORD dwVolumeMax); + + void PeriodicUpdate(UINT executedCycles); + void Update(void); + void SetSpeechIRQ(void); + + void Votrax_Write(BYTE nValue); + bool GetVotraxPhoneme(void) { return m_isVotraxPhoneme; } + void SetVotraxPhoneme(bool value) { m_isVotraxPhoneme = value; } + + void SetCardMode(PHASOR_MODE mode) { m_cardMode = mode; } + void SetDevice(UINT device) { m_device = device; } + + void SaveSnapshot(class YamlSaveHelper& yamlSaveHelper); + void LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT device, PHASOR_MODE mode, UINT version); + +private: + void Play(unsigned int nPhoneme); + void Stop(void); + void UpdateIRQ(void); + void UpdateAccurateLength(void); + + static const BYTE m_Votrax2SSI263[/*64*/]; + + static const unsigned short m_kNumChannels = 1; + static const DWORD m_kDSBufferSize = MAX_SAMPLES * sizeof(short) * m_kNumChannels; + short m_mixBufferSSI263[m_kDSBufferSize / sizeof(short)]; + VOICE SSI263SingleVoice; + + // + + BYTE m_device; // SSI263 device# which is generating phoneme-complete IRQ (and only required whilst Mockingboard isn't a class) + PHASOR_MODE m_cardMode; + short* m_pPhonemeData00; + + int m_currentActivePhoneme; + bool m_isVotraxPhoneme; + UINT m_cyclesThisAudioFrame; + + // + + UINT64 m_lastUpdateCycle; + bool m_updateWasFullSpeed; + + const short* m_pPhonemeData; + UINT m_phonemeLengthRemaining; // length in samples, decremented as space becomes available in the ring-buffer + UINT m_phonemeAccurateLengthRemaining; // length in samples, decremented by cycles executed + bool m_phonemePlaybackAndDebugger; + bool m_phonemeCompleteByFullSpeed; + + // + + int m_numSamplesError; + DWORD m_byteOffset; + int m_currSampleSum; + int m_currNumSamples; + UINT m_currSampleMod4; + + // Regs: + BYTE m_durationPhoneme; + BYTE m_inflection; // I10..I3 + BYTE m_rateInflection; + BYTE m_ctrlArtAmp; + BYTE m_filterFreq; + + BYTE m_currentMode; // b7:6=Mode; b0=D7 pin (for IRQ) + + // Debug + bool m_dbgFirst; + UINT64 m_dbgStartTime; +}; diff --git a/source/SoundCore.cpp b/source/SoundCore.cpp index 50190811..aa3d327e 100644 --- a/source/SoundCore.cpp +++ b/source/SoundCore.cpp @@ -48,7 +48,7 @@ static LPDIRECTSOUND g_lpDS = NULL; // Used for muting & fading: -static const UINT uMAX_VOICES = 66; // 64 phonemes + spkr + mockingboard +static const UINT uMAX_VOICES = 6; // 4x SSI263 + spkr + mockingboard static UINT g_uNumVoices = 0; static VOICE* g_pVoices[uMAX_VOICES] = {NULL}; @@ -167,8 +167,10 @@ bool DSGetLock(LPDIRECTSOUNDBUFFER pVoice, DWORD dwOffset, DWORD dwBytes, //----------------------------------------------------------------------------- -HRESULT DSGetSoundBuffer(VOICE* pVoice, DWORD dwFlags, DWORD dwBufferSize, DWORD nSampleRate, int nChannels) +HRESULT DSGetSoundBuffer(VOICE* pVoice, DWORD dwFlags, DWORD dwBufferSize, DWORD nSampleRate, int nChannels, const char* pszDevName) { + pVoice->name = pszDevName; + WAVEFORMATEX wavfmt; DSBUFFERDESC dsbdesc; @@ -226,29 +228,41 @@ void DSReleaseSoundBuffer(VOICE* pVoice) //----------------------------------------------------------------------------- -bool DSZeroVoiceBuffer(PVOICE Voice, const char* pszDevName, DWORD dwBufferSize) +bool DSVoiceStop(PVOICE Voice) { #ifdef NO_DIRECT_X - return false; +#else + _ASSERT(Voice->lpDSBvoice); + HRESULT hr = Voice->lpDSBvoice->Stop(); + if(FAILED(hr)) + { + if(g_fh) fprintf(g_fh, "%s: DSStop failed (%08X)\n", Voice->name.c_str(), hr); + return false; + } + Voice->bActive = false; + return true; +#endif // NO_DIRECT_X +} + +// Use this to Play() +bool DSZeroVoiceBuffer(PVOICE Voice, DWORD dwBufferSize) +{ +#ifdef NO_DIRECT_X + return false; #else DWORD dwDSLockedBufferSize = 0; // Size of the locked DirectSound buffer SHORT* pDSLockedBuffer; - _ASSERT(Voice->lpDSBvoice); - HRESULT hr = Voice->lpDSBvoice->Stop(); - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "%s: DSStop failed (%08X)\n",pszDevName,hr); + if (!DSVoiceStop(Voice)) return false; - } - hr = DSGetLock(Voice->lpDSBvoice, 0, 0, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, 0); + HRESULT hr = DSGetLock(Voice->lpDSBvoice, 0, 0, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, 0); if(FAILED(hr)) { - if(g_fh) fprintf(g_fh, "%s: DSGetLock failed (%08X)\n",pszDevName,hr); + if(g_fh) fprintf(g_fh, "%s: DSGetLock failed (%08X)\n", Voice->name.c_str(), hr); return false; } @@ -258,24 +272,25 @@ bool DSZeroVoiceBuffer(PVOICE Voice, const char* pszDevName, DWORD dwBufferSize) hr = Voice->lpDSBvoice->Unlock((void*)pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0); if(FAILED(hr)) { - if(g_fh) fprintf(g_fh, "%s: DSUnlock failed (%08X)\n",pszDevName,hr); + if(g_fh) fprintf(g_fh, "%s: DSUnlock failed (%08X)\n", Voice->name.c_str(), hr); return false; } hr = Voice->lpDSBvoice->Play(0,0,DSBPLAY_LOOPING); if(FAILED(hr)) { - if(g_fh) fprintf(g_fh, "%s: DSPlay failed (%08X)\n",pszDevName,hr); + if(g_fh) fprintf(g_fh, "%s: DSPlay failed (%08X)\n", Voice->name.c_str(), hr); return false; } + Voice->bActive = true; return true; #endif // NO_DIRECT_X } //----------------------------------------------------------------------------- -bool DSZeroVoiceWritableBuffer(PVOICE Voice, const char* pszDevName, DWORD dwBufferSize) +bool DSZeroVoiceWritableBuffer(PVOICE Voice, DWORD dwBufferSize) { DWORD dwDSLockedBufferSize0=0, dwDSLockedBufferSize1=0; SHORT *pDSLockedBuffer0, *pDSLockedBuffer1; @@ -287,7 +302,7 @@ bool DSZeroVoiceWritableBuffer(PVOICE Voice, const char* pszDevName, DWORD dwBuf &pDSLockedBuffer1, &dwDSLockedBufferSize1); if(FAILED(hr)) { - if(g_fh) fprintf(g_fh, "%s: DSGetLock failed (%08X)\n",pszDevName,hr); + if(g_fh) fprintf(g_fh, "%s: DSGetLock failed (%08X)\n", Voice->name.c_str(), hr); return false; } @@ -299,7 +314,7 @@ bool DSZeroVoiceWritableBuffer(PVOICE Voice, const char* pszDevName, DWORD dwBuf (void*)pDSLockedBuffer1, dwDSLockedBufferSize1); if(FAILED(hr)) { - if(g_fh) fprintf(g_fh, "%s: DSUnlock failed (%08X)\n",pszDevName,hr); + if(g_fh) fprintf(g_fh, "%s: DSUnlock failed (%08X)\n", Voice->name.c_str(), hr); return false; } @@ -428,7 +443,7 @@ void SoundCore_SetFade(eFADE FadeType) if(g_nAppMode == MODE_DEBUG) return; - // Fade in/out for speaker, the others are demuted/muted here + // Fade in/out for speaker, the others are unmuted/muted here if(FadeType != FADE_NONE) { for(UINT i=0; iStop(); - SpeakerVoice.bActive = false; - } + DSVoiceStop(&SpeakerVoice); DSReleaseSoundBuffer(&SpeakerVoice); } diff --git a/source/Speaker.h b/source/Speaker.h index f3d93e0e..1cc26de1 100644 --- a/source/Speaker.h +++ b/source/Speaker.h @@ -27,7 +27,7 @@ void SpkrUpdate_Timer(); DWORD SpkrGetVolume(); void SpkrSetVolume(DWORD dwVolume, DWORD dwVolumeMax); void Spkr_Mute(); -void Spkr_Demute(); +void Spkr_Unmute(); bool Spkr_IsActive(); bool Spkr_DSInit(); void SpkrSaveSnapshot(class YamlSaveHelper& yamlSaveHelper); diff --git a/source/Windows/AppleWin.cpp b/source/Windows/AppleWin.cpp index 9b481493..13bbc3ba 100644 --- a/source/Windows/AppleWin.cpp +++ b/source/Windows/AppleWin.cpp @@ -190,8 +190,8 @@ static void ContinueExecution(void) if (bWasFullSpeed) GetFrame().VideoRedrawScreenAfterFullSpeed(g_dwCyclesThisFrame); - // Don't call Spkr_Demute() - MB_Demute(); + // Don't call Spkr_Unmute() + MB_Unmute(); SysClk_StartTimerUsec(nExecutionPeriodUsec); // Switch to higher priority, eg. for audio (BUG #015394)