SSI263 - support for core functionality (#175, PR #1301)

. A phoneme will continue playing back infinitely; unless the phoneme is changed or CTL=1.
  . Reset doesn't affect SSI263/SC01 (so phonemes continue to play).
. CTL=1 sets "PD" (Power Down / "standby") mode, also set at power-on.
. CTL=0 brings device out of "PD" mode, the mode will be set to DR1,DR0 and the phoneme P5-P0 will play.
. Setting mode to DR1:0 = %00 just disables A/!R (ie. disables interrupts), but otherwise retains the previous DR1:0 mode.
. RESET is not connected to !PD/!RST pin 18.
 . Support edge-case where RESET can enable ints & assert IRQ.
. Power-on: PD=1 (so D7=0), reg4 (Filter Freq)=0xFF.
. Support SSI263 IRQ and D7 on a Phasor mode change (including Echo+).
. $Csxx I/O mapping (same for Mockingboard and Phasor mode).

Other:
. SSI263 save-state: support SC01 as a sub-unit of the card.
. 6522: Fix reg $F (ORA w/HS) to be identical to reg $1 (ORA).
This commit is contained in:
TomCh 2024-06-07 21:10:33 +01:00 committed by GitHub
parent ae7e5a63a9
commit a88040c3ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 274 additions and 107 deletions

View File

@ -153,6 +153,7 @@ void SY6522::Write(BYTE nReg, BYTE nValue)
m_regs.ORB = nValue & m_regs.DDRB;
break;
case 0x01: // ORA
case 0x0f: // ORA_NO_HS
m_regs.ORA = nValue & m_regs.DDRA;
break;
case 0x02: // DDRB
@ -220,8 +221,6 @@ void SY6522::Write(BYTE nReg, BYTE nValue)
m_syncEvent[1]->m_canAssertIRQ = (m_regs.IER & IxR_TIMER2) ? true : false;
UpdateIFR(0);
break;
case 0x0f: // ORA_NO_HS
break;
}
}
@ -348,6 +347,7 @@ BYTE SY6522::Read(BYTE nReg)
if (m_isMegaAudio) nValue = 0x00; // MegaAudio: IRB just reads as $00
break;
case 0x01: // IRA
case 0x0f: // IRA_NO_HS
nValue = m_regs.ORA | (m_isBusDriven ? 0x00 : (m_regs.DDRA ^ 0xff)); // NB. Inputs bits driven by AY8913 if in PSG READ mode
if (m_isMegaAudio) nValue = 0x00; // MegaAudio: IRA just reads as $00
break;
@ -400,9 +400,6 @@ BYTE SY6522::Read(BYTE nReg)
if (m_isMegaAudio)
nValue &= 0x7F;
break;
case 0x0f: // ORA_NO_HS
nValue = m_regs.ORA;
break;
}
return nValue;

View File

@ -405,9 +405,9 @@ bool MockingboardCard::Is6522IRQ(void)
irq |= m_MBSubUnit[i].sy6522.GetReg(SY6522::rIFR) & 0x80 ? true : false;
// NB. Mockingboard generates IRQ on both 6522s:
// . SSI263's IRQ (A/!R) is routed via the 2nd 6522 (at $Cn80) and must generate a 6502 IRQ (not NMI)
// - NB. 2nd SSI263's IRQ is routed via the 1st 6522 (at $Cn00) and again generates a 6502 IRQ
// . SC-01's IRQ (A/!R) is routed via the 6522 at $Cn00 (NB. Only the Mockingboard "Sound/Speech I" card supports the SC-01)
// . SSI263's IRQ (A/!R) is routed via the 2nd 6522's CA1 input (at $Cn80) and must generate a 6502 IRQ (not NMI)
// - NB. 2nd SSI263's IRQ is routed via the 1st 6522's CA1 input (at $Cn00) and again generates a 6502 IRQ
// . SC-01's IRQ (!A/R) is routed via the 6522 at $Cn00 (NB. Only the Mockingboard "Sound/Speech I" card supports the SC-01)
// Phasor's SSI263 IRQ (A/!R) line is *also* wired directly to the 6502's IRQ (as well as the 6522's CA1)
return irq;
@ -576,7 +576,7 @@ void MockingboardCard::Reset(const bool powerCycle) // CTRL+RESET or power-cycle
m_MBSubUnit[subunit].Reset(QueryType());
m_MBSubUnit[subunit].ssi263.SetCardMode(PH_Mockingboard); // Revert to PH_Mockingboard mode
m_MBSubUnit[subunit].ssi263.Reset();
m_MBSubUnit[subunit].ssi263.Reset(powerCycle, m_phasorEnable);
}
// Reset state
@ -668,7 +668,7 @@ BYTE MockingboardCard::IOReadInternal(WORD PC, WORD nAddr, BYTE bWrite, BYTE nVa
return MemReadFloatingBus(nExecutedCycles);
#endif
// NB. Mockingboard: SSI263.bit7 not readable (TODO: check this with real h/w)
// NB. Mockingboard: SSI263.bit7 not readable
const BYTE subunit = QueryType() == CT_SDMusic ? SY6522_DEVICE_A : !(nAddr & 0x80) ? SY6522_DEVICE_A : SY6522_DEVICE_B;
const BYTE reg = nAddr & 0xf;
return m_MBSubUnit[subunit].sy6522.Read(reg);
@ -751,14 +751,13 @@ BYTE MockingboardCard::IOWriteInternal(WORD PC, WORD nAddr, BYTE bWrite, BYTE nV
WriteToORB(SY6522_DEVICE_B);
}
bool CS_SSI263_A = (m_phasorMode == PH_Phasor) ? !(nAddr & 0x80) && (nAddr & 0x40) // SSI263 at $Cn4x, $Cn6x
: nAddr & 0x40; // SSI263 at $Cn4x-Cn7x, $CnCx-CnFx
bool CS_SSI263_B = (m_phasorMode == PH_Phasor) ? !(nAddr & 0x80) && (nAddr & 0x20) // SSI263 at $Cn2x, $Cn6x
: nAddr & 0x20; // SSI263 at $Cn2x-Cn3x, $Cn6x-Cn7x, $CnAx-CnBx, $CnEx-CnFx
if (m_phasorMode == PH_Mockingboard || m_phasorMode == PH_Phasor) // No SSI263 for Echo+
{
// Confirmed that Phasor has no extra logic to map SSI263 (it's the same as Mockingboard's)
bool CS_SSI263_A = nAddr & 0x40; // SSI263 at $Cn4x-Cn7x, $CnCx-CnFx
bool CS_SSI263_B = nAddr & 0x20; // SSI263 at $Cn2x-Cn3x, $Cn6x-Cn7x, $CnAx-CnBx, $CnEx-CnFx
// NB. Mockingboard mode: writes to $Cn4x/SSI263 also get written to 1st 6522 (have confirmed on real Phasor h/w)
if (CS_SSI263_A) // Primary SSI263
m_MBSubUnit[1].ssi263.Write(nAddr&0x7, nValue); // 2nd 6522 is used for 1st speech chip
@ -839,6 +838,8 @@ BYTE MockingboardCard::PhasorIOInternal(WORD PC, WORD nAddr, BYTE bWrite, BYTE n
m_phasorClockScaleFactor = 1;
else if (m_phasorMode == PH_Phasor)
m_phasorClockScaleFactor = 2;
else // undefined mode
m_phasorClockScaleFactor = 1; // TODO: Check for undefined Phasor mode
if (m_phasorMode == PH_Mockingboard)
{
@ -849,7 +850,7 @@ BYTE MockingboardCard::PhasorIOInternal(WORD PC, WORD nAddr, BYTE bWrite, BYTE n
AY8910_InitClock((int)(Get6502BaseClock() * m_phasorClockScaleFactor));
for (UINT i = 0; i < NUM_SSI263; i++)
m_MBSubUnit[i].ssi263.SetCardMode(m_phasorMode);
m_MBSubUnit[i].ssi263.SetCardMode(m_phasorMode); // TODO: Check for undefined Phasor mode
#if DBG_SUPPORT_ECHOPLUS
if (m_phasorMode == PH_EchoPlus && (nAddr & 0xf) == 0)
@ -1155,7 +1156,9 @@ UINT MockingboardCard::AY8910_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, BYTE
// "Reg Address Latch Valid A" + "Reg Address Latch Valid B"
// Changed at AppleWin 1.30.14
//11: Added: "Bus Driven by AY"
const UINT kUNIT_VERSION = 11;
//12: Added: SSI263: SC01 phoneme & active
// Current Mode changed (added bit5 = enableInts)
const UINT kUNIT_VERSION = 12;
#define SS_YAML_KEY_MB_UNIT "Unit"
#define SS_YAML_KEY_AY_CURR_REG "AY Current Register"
@ -1231,6 +1234,8 @@ void MockingboardCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper)
pMB->sy6522.SaveSnapshot(yamlSaveHelper);
AY8910_SaveSnapshot(yamlSaveHelper, subunit, AY8913_DEVICE_A, std::string(""));
pMB->ssi263.SaveSnapshot(yamlSaveHelper);
if (subunit == 0) // has SC01
pMB->ssi263.SC01_SaveSnapshot(yamlSaveHelper);
yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_MB_UNIT_STATE, pMB->state[0]);
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_AY_CURR_REG, pMB->nAYCurrentRegister[0]); // save all 8 bits (even though top 4 bits should be 0)
@ -1271,6 +1276,8 @@ bool MockingboardCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version
UpdateIFRandIRQ(pMB, 0, pMB->sy6522.GetReg(SY6522::rIFR)); // Assert any pending IRQs (GH#677)
AY8910_LoadSnapshot(yamlLoadHelper, subunit, AY8913_DEVICE_A, std::string(""));
pMB->ssi263.LoadSnapshot(yamlLoadHelper, PH_Mockingboard, version); // Pre: SetVotraxPhoneme()
if (subunit == 0) // has SC01
pMB->ssi263.SC01_LoadSnapshot(yamlLoadHelper, version);
pMB->nAYCurrentRegister[0] = yamlLoadHelper.LoadUint(SS_YAML_KEY_AY_CURR_REG);
@ -1337,6 +1344,8 @@ void MockingboardCard::Phasor_SaveSnapshot(YamlSaveHelper& yamlSaveHelper)
AY8910_SaveSnapshot(yamlSaveHelper, subunit, AY8913_DEVICE_A, std::string("-A"));
AY8910_SaveSnapshot(yamlSaveHelper, subunit, AY8913_DEVICE_B, std::string("-B"));
pMB->ssi263.SaveSnapshot(yamlSaveHelper);
if (subunit == 0) // has SC01
pMB->ssi263.SC01_SaveSnapshot(yamlSaveHelper);
yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_MB_UNIT_STATE, pMB->state[0]);
yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_MB_UNIT_STATE_B, pMB->state[1]);
@ -1405,6 +1414,8 @@ bool MockingboardCard::Phasor_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT
AY8910_LoadSnapshot(yamlLoadHelper, subunit, AY8913_DEVICE_B, std::string("-B"));
}
pMB->ssi263.LoadSnapshot(yamlLoadHelper, m_phasorMode, version); // Pre: SetVotraxPhoneme()
if (subunit == 0) // has SC01
pMB->ssi263.SC01_LoadSnapshot(yamlLoadHelper, version);
pMB->nAYCurrentRegister[0] = yamlLoadHelper.LoadUint(SS_YAML_KEY_AY_CURR_REG);
if (version >= 10)

View File

@ -4,7 +4,7 @@ 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
Copyright (C) 2006-2024, 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
@ -23,7 +23,24 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/* Description: SSI263 emulation
*
* Author: Various
* Extra "spec" that's not obvious from the datasheet: (GH#175)
* . Writes to regs 0,1,2 (and reg3.CTL=1) all de-assert the IRQ (and writes to reg3.CTL=0 and regs 4..7 don't) (GH#1197)
* . A phoneme will continue playing back infinitely; unless the phoneme is changed or CTL=1.
* . NB. if silenced (Amplitude=0) it's still playing.
* . The IRQ is set at the end of the phoneme.
* . If IRQ is then cleared, a new IRQ will occur when the phoneme completes again (but need to clear IRQ with a write to reg0, 1 or 2, even for Mockingboard-C).
* . CTL=1 sets "PD" (Power Down / "standby") mode, also set at power-on.
* . Registers can still be changed in this mode.
* . IRQ de-asserted & D7=0.
* . CTL=0 brings device out of "PD" mode, the mode will be set to DR1,DR0 and the phoneme P5-P0 will play.
* . Setting mode to DR1:0 = %00 just disables A/!R (ie. disables interrupts), but otherwise retains the previous DR1:0 mode.
* . If an IRQ was previously asserted then to set DR1:0=%00, you must go via CTL=1, which de-asserts the IRQ.
* . Mockingboard-C: CTRL+RESET is not connected to !PD/!RST pin 18.
* . Phasor: TODO: check with a 'scope.
* . Phasor: with SSI263 ints disabled & reg0's DR1:0 != %00, then CTRL+RESET will cause SSI263 to enable ints & assert IRQ.
* . it's as if the SSI263 does a CTL H->L to pick-up the new DR1:0. (Bug in SSI263? Assume it should remain in PD mode.)
* . but if CTL=1, then CTRL+RESET has no effect.
* . Power-on: PD=1 (so D7=0), reg4 (Filter Freq)=0xFF (other regs are seemingly random?).
*/
#include "StdAfx.h"
@ -54,26 +71,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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
@ -129,7 +126,7 @@ BYTE SSI263::Read(ULONG nExecutedCycles)
// . inverted "A/!R" is high for REQ (ie. Request, as phoneme nearly complete)
// NB. this doesn't clear the IRQ
return MemReadFloatingBus(m_currentMode & 1, nExecutedCycles);
return MemReadFloatingBus(m_currentMode.D7, nExecutedCycles);
}
void SSI263::Write(BYTE nReg, BYTE nValue)
@ -141,30 +138,20 @@ void SSI263::Write(BYTE nReg, BYTE nValue)
ssiRegs[nReg] = nValue;
#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.
// . Empirically writes to regs 0,1 & 2 all clear the IRQ (and writes to 3,4..7 don't) (GH#1197)
// NB. For Mockingboard, A/!R is ack'ed by 6522's PCR handshake and D7 is cleared.
if (m_cardMode == PH_Phasor && nReg <= SSI_RATEINF)
// SSI263 datasheet is not clear, but a write to DURPHON de-asserts the IRQ and clears D7.
// . Empirically writes to regs 0,1,2 (and reg3.CTL=1) all de-assert the IRQ (and writes to reg3.CTL=0 and regs 4..7 don't) (GH#1197)
// NB. The same for Mockingboard as there's no automatic handshake from the 6522 (CA2 isn't connected to the SSI263). So writes to regs 0, 1 or 2 complete the "handshake".
if (nReg <= SSI_RATEINF)
{
CpuIrqDeassert(IS_SPEECH);
m_currentMode &= ~1; // Clear SSI263's D7 pin
m_currentMode.D7 = 0;
}
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);
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
@ -172,25 +159,27 @@ void SSI263::Write(BYTE nReg, BYTE nValue)
#endif
m_durationPhoneme = nValue;
m_isVotraxPhoneme = false;
Play(nValue & PHONEME_MASK);
if ((m_ctrlArtAmp & CONTROL_MASK) == 0)
Play(m_durationPhoneme & PHONEME_MASK); // Play phoneme when *not* in power-down / standby mode
break;
case SSI_INFLECT:
#if LOG_SSI263
if(g_fh) fprintf(g_fh, "INF = 0x%02X\n", nValue);
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);
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&AMPLITUDE_MASK);
if (g_fh) fprintf(g_fh, "CTRL = %d, ART = 0x%02X, AMP=0x%02X\n", nValue>>7, (nValue&ARTICULATION_MASK)>>4, nValue&AMPLITUDE_MASK);
//
{
bool H2L = (m_ctrlArtAmp & CONTROL_MASK) && !(nValue & CONTROL_MASK);
@ -202,23 +191,25 @@ void SSI263::Write(BYTE nReg, BYTE nValue)
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
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);
}
// NB. Just changed from CTL=1 (power-down) - where IRQ was already de-asserted & D7=0
// . So CTL H->L never affects IRQ or D7
SetDeviceModeAndInts();
// Device out of power down / "standby" mode, so play phoneme
m_isVotraxPhoneme = false;
Play(m_durationPhoneme & PHONEME_MASK);
}
m_ctrlArtAmp = nValue;
// "Setting the Control bit (CTL) to a logic one puts the device into Power Down mode..." (SSI263 datasheet)
// . this silences the phoneme - actually "turns off the excitation sources and analog circuits"
if (m_ctrlArtAmp & CONTROL_MASK)
{
// CpuIrqDeassert(IS_SPEECH);
// m_currentMode &= ~1; // Clear SSI263's D7 pin
CpuIrqDeassert(IS_SPEECH);
m_currentMode.D7 = 0;
}
break;
case SSI_FILFREQ: // RegAddr.b2=1 (b1 & b0 are: don't care)
@ -231,6 +222,20 @@ void SSI263::Write(BYTE nReg, BYTE nValue)
}
}
void SSI263::SetDeviceModeAndInts(void)
{
if ((m_durationPhoneme & DURATION_MODE_MASK) != MODE_IRQ_DISABLED)
{
m_currentMode.function = (m_durationPhoneme & DURATION_MODE_MASK) >> DURATION_MODE_SHIFT;
m_currentMode.enableInts = 1;
}
else
{
// "Disables A/!R output only; does not change previous A/!R response" (SSI263 datasheet)
m_currentMode.enableInts = 0;
}
}
//-----------------------------------------------------------------------------
const BYTE SSI263::m_Votrax2SSI263[/*64*/] =
@ -310,6 +315,7 @@ void SSI263::Votrax_Write(BYTE value)
LogOutput("SC01: %02X (= SSI263: %02X)\n", value, m_Votrax2SSI263[value & PHONEME_MASK]);
#endif
m_isVotraxPhoneme = true;
m_votraxPhoneme = value & PHONEME_MASK;
// !A/R: Acknowledge receipt of phoneme data (signal goes from high to low)
UpdateIFR(m_device, SY6522::IxR_VOTRAX, 0);
@ -317,7 +323,7 @@ void SSI263::Votrax_Write(BYTE value)
// NB. Don't set reg0.DUR, as SC01's phoneme duration doesn't change with pitch (empirically determined from MAME's SC01 emulation)
//m_durationPhoneme = value; // Set reg0.DUR = I1:0 (inflection or pitch)
m_durationPhoneme = 0;
Play(m_Votrax2SSI263[value & PHONEME_MASK]);
Play(m_Votrax2SSI263[m_votraxPhoneme]);
}
//-----------------------------------------------------------------------------
@ -366,6 +372,7 @@ void SSI263::Play(unsigned int nPhoneme)
m_phonemeAccurateLengthRemaining = m_phonemeLengthRemaining;
m_phonemePlaybackAndDebugger = (g_nAppMode == MODE_STEPPING || g_nAppMode == MODE_DEBUG);
m_phonemeCompleteByFullSpeed = false;
m_phonemeLeadoutLength = m_phonemeLengthRemaining / 10; // Arbitrary! (TODO: determine a more accurate factor)
if (bPause)
{
@ -581,18 +588,22 @@ void SSI263::Update(void)
//-------------
const double amplitude = !m_isVotraxPhoneme ? (double)(m_ctrlArtAmp & AMPLITUDE_MASK) / (double)AMPLITUDE_MASK : 1.0;
const double amplitude = m_isVotraxPhoneme ? 1.0
: m_ctrlArtAmp & CONTROL_MASK ? 0.0 // Power-down / standby
: m_filterFreq == FILTER_FREQ_SILENCE ? 0.0
: (double)(m_ctrlArtAmp & AMPLITUDE_MASK) / (double)AMPLITUDE_MASK;
bool bSpeechIRQ = false;
{
const BYTE DUR = m_durationPhoneme >> DURATION_SHIFT;
const BYTE DUR = (m_currentMode.function == (MODE_FRAME_IMMEDIATE_INFLECTION >> DURATION_MODE_SHIFT)) ? 3 // Frame timing mode
: m_durationPhoneme >> DURATION_MODE_SHIFT; // Phoneme timing mode
const BYTE numSamplesToAvg = (DUR <= 1) ? 1 :
(DUR == 2) ? 2 :
4;
short* pMixBuffer = &m_mixBufferSSI263[0];
int zeroSize = nNumSamples;
UINT zeroSize = nNumSamples;
if (m_phonemeLengthRemaining && !prefillBufferOnInit)
{
@ -633,7 +644,12 @@ void SSI263::Update(void)
}
if (zeroSize)
{
memset(pMixBuffer, 0, zeroSize * sizeof(short));
if (!prefillBufferOnInit)
m_phonemeLeadoutLength -= (m_phonemeLeadoutLength > zeroSize) ? zeroSize : m_phonemeLeadoutLength;
}
}
//
@ -666,8 +682,24 @@ void SSI263::Update(void)
{
// 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 _DEBUG
if (m_phonemePlaybackAndDebugger)
{
_ASSERT(m_phonemeAccurateLengthRemaining); // Check this!
}
#endif
if (!m_phonemePlaybackAndDebugger /*|| m_phonemeAccurateLengthRemaining*/) // superfluous, so commented out (see above)
{
UpdateIRQ();
}
}
if (m_phonemeLeadoutLength == 0)
{
if (!m_isVotraxPhoneme)
Play(m_durationPhoneme & PHONEME_MASK); // Repeat this phoneme again
else
Play(m_Votrax2SSI263[m_votraxPhoneme]); // Votrax phoneme repeats too (tested in MAME 0.262)
}
}
@ -689,7 +721,7 @@ void SSI263::UpdateAccurateLength(void)
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 BYTE DUR = m_durationPhoneme >> DURATION_MODE_SHIFT;
const UINT numSamples = nNumSamplesPerPeriod * (DUR+1);
if (m_phonemeAccurateLengthRemaining > numSamples)
@ -733,32 +765,37 @@ void SSI263::UpdateIRQ(void)
// Pre: m_isVotraxPhoneme, m_cardMode, m_device
void SSI263::SetSpeechIRQ(void)
{
if (!m_isVotraxPhoneme)
if (!m_isVotraxPhoneme && (m_ctrlArtAmp & CONTROL_MASK) == 0)
{
// 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_currentMode.enableInts)
{
if (m_cardMode == PH_Mockingboard)
{
if ((GetPCR(m_device) & 1) == 0) // CA1 Latch/Input = 0 (Negative active edge)
UpdateIFR(m_device, 0, SY6522::IxR_SSI263);
if (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
if (m_currentMode.D7 == 0)
{
// 6522's PCR = 0x0C (all SSI263 speech routine use this value, but 0x00 will do equally as well!)
// . b3:1 CA2 Control = b#110 (Low output) - not connected
// . b0 CA1 Latch/Input = 0 (Negative active edge) - input from SSI263's A/!R
if ((GetPCR(m_device) & 1) == 0) // Level change from SSI263's A/!R, latch this as an interrupt
UpdateIFR(m_device, 0, SY6522::IxR_SSI263);
}
}
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)
else if (m_cardMode == PH_Phasor)
{
// Phasor (in native mode): SSI263 IRQ (A/!R) pin is connected directly to the 6502's IRQ
// . And Mockingboard mode: A/!R is connected to the 6522's CA1
CpuIrqAssert(IS_SPEECH);
}
else
{
_ASSERT(0);
_ASSERT(m_cardMode == PH_EchoPlus);
// SSI263 not visible from Echo+ mode, but still continues to operate
}
}
// Always set SSI263's D7 pin regardless of SSI263 mode (DR1:0), including when SSI263 ints are disabled (via MODE_IRQ_DISABLED)
// NB. Don't set D7 when in power-down / standby mode.
m_currentMode.D7 = 1;
}
//
@ -766,15 +803,34 @@ void SSI263::SetSpeechIRQ(void)
if (m_isVotraxPhoneme && GetPCR(m_device) == 0xB0)
{
// !A/R: Time-out of old phoneme (signal goes from low to high)
UpdateIFR(m_device, 0, SY6522::IxR_VOTRAX);
m_isVotraxPhoneme = false;
}
}
//-----------------------------------------------------------------------------
void SSI263::SetCardMode(PHASOR_MODE mode)
{
const PHASOR_MODE oldCardMode = m_cardMode;
m_cardMode = mode;
if (oldCardMode == m_cardMode)
return;
// mode change
if (m_currentMode.D7 == 1)
{
m_currentMode.D7 = 0; // So that \PH_Mockingboard\ path sets IFR. Post: D7=1
SetSpeechIRQ();
}
if (m_cardMode != PH_Phasor)
CpuIrqDeassert(IS_SPEECH);
}
//-----------------------------------------------------------------------------
bool SSI263::DSInit(void)
{
//
@ -811,10 +867,27 @@ void SSI263::DSUninit(void)
//-----------------------------------------------------------------------------
void SSI263::Reset(void)
// SSI263 phoneme continues to play after CTRL+RESET (tested on real h/w)
// Votrax phoneme continues to play after CTRL+RESET (tested on MAME 0.262)
void SSI263::Reset(const bool powerCycle, const bool isPhasorCard)
{
if (!powerCycle)
{
if (isPhasorCard)
{
// Empirically observed it does CTL H->L to enable ints (and set the device mode?) (GH#175)
// NB. CTRL+RESET doesn't clear m_ctrlArtAmp.CTL (ie. if the device is in power-down/standby mode then ignore RST)
// Speculate that there's a bug in the SSI263 and that RST should put the device into power-down/standby mode (ie. silence the device)
// TODO: Stick a 'scope on !PD/!RST pin 18 to see what the Phasor h/w does.
if ((m_ctrlArtAmp & CONTROL_MASK) == 0)
SetDeviceModeAndInts();
}
return;
}
Stop();
ResetState();
ResetState(powerCycle);
CpuIrqDeassert(IS_SPEECH);
}
@ -884,7 +957,7 @@ void SSI263::SaveSnapshot(YamlSaveHelper& yamlSaveHelper)
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_CURRENT_MODE, m_currentMode);
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SSI263_CURRENT_MODE, m_currentMode.mode);
yamlSaveHelper.SaveBool(SS_YAML_KEY_SSI263_ACTIVE_PHONEME, IsPhonemeActive());
}
@ -898,10 +971,23 @@ void SSI263::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, PHASOR_MODE mode, UINT
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_CURRENT_MODE);
m_currentMode.mode = yamlLoadHelper.LoadUint(SS_YAML_KEY_SSI263_CURRENT_MODE);
bool activePhoneme = (version >= 7) ? yamlLoadHelper.LoadBool(SS_YAML_KEY_SSI263_ACTIVE_PHONEME) : false;
m_currentActivePhoneme = !activePhoneme ? -1 : 0x00; // Not important which phoneme, since UpdateIRQ() resets this
if (version < 12)
{
if (m_currentMode.function == 0) // invalid function (but in older versions this was accepted)
{
m_currentMode.function = MODE_PHONEME_TRANSITIONED_INFLECTION >> DURATION_MODE_SHIFT; // Typically this is used
m_currentMode.enableInts = 0;
}
else
{
m_currentMode.enableInts = 1;
}
}
yamlLoadHelper.PopMap();
//
@ -910,7 +996,7 @@ void SSI263::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, PHASOR_MODE mode, UINT
SetCardMode(mode);
// Only need to directly assert IRQ for Phasor mode (for Mockingboard mode it's done via UpdateIFR() in parent)
if (m_cardMode == PH_Phasor && (m_currentMode & DURATION_MODE_MASK) != MODE_IRQ_DISABLED && (m_currentMode & 1))
if (m_cardMode == PH_Phasor && (m_ctrlArtAmp & CONTROL_MASK) == 0 && m_currentMode.enableInts && m_currentMode.D7 == 1)
CpuIrqAssert(IS_SPEECH);
if (IsPhonemeActive())
@ -918,3 +1004,30 @@ void SSI263::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, PHASOR_MODE mode, UINT
m_lastUpdateCycle = GetLastCumulativeCycles();
}
//=============================================================================
#define SS_YAML_KEY_SC01 "SC01"
// NB. No version - this is determined by the parent "Mockingboard C" or "Phasor" unit
#define SS_YAML_KEY_SC01_PHONEME "SC01 Phoneme"
#define SS_YAML_KEY_SC01_ACTIVE_PHONEME "SC01 Active Phoneme"
void SSI263::SC01_SaveSnapshot(YamlSaveHelper& yamlSaveHelper)
{
YamlSaveHelper::Label label(yamlSaveHelper, "%s:\n", SS_YAML_KEY_SC01);
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SC01_PHONEME, m_votraxPhoneme);
yamlSaveHelper.SaveBool(SS_YAML_KEY_SC01_ACTIVE_PHONEME, m_isVotraxPhoneme);
}
void SSI263::SC01_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version)
{
if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_SC01))
throw std::runtime_error("Card: Expected key: " SS_YAML_KEY_SC01);
m_votraxPhoneme = yamlLoadHelper.LoadUint(SS_YAML_KEY_SC01_PHONEME);
m_isVotraxPhoneme = yamlLoadHelper.LoadBool(SS_YAML_KEY_SC01_ACTIVE_PHONEME);
yamlLoadHelper.PopMap();
}

View File

@ -11,17 +11,18 @@ public:
m_cardMode = PH_Mockingboard;
m_pPhonemeData00 = NULL;
ResetState();
ResetState(true);
}
~SSI263(void)
{
delete [] m_pPhonemeData00;
}
void ResetState(void)
void ResetState(const bool powerCycle)
{
m_currentActivePhoneme = -1;
m_isVotraxPhoneme = false;
m_votraxPhoneme = 0;
m_cyclesThisAudioFrame = 0;
//
@ -34,6 +35,7 @@ public:
m_phonemeAccurateLengthRemaining = 0;
m_phonemePlaybackAndDebugger = false;
m_phonemeCompleteByFullSpeed = false;
m_phonemeLeadoutLength = 0;
//
@ -45,13 +47,19 @@ public:
//
m_durationPhoneme = 0;
// After a chip power-on, if the first thing done is CTL=0, then empirically it can be observed that:
// . enableInts = 1 (mostly an SSI263 interrupt occurs, but not always)
// . DR1:0 != b#00 (since enableInts is set to 1, except when no interrupt => DR1:0 == b#00)
m_durationPhoneme = MODE_PHONEME_TRANSITIONED_INFLECTION; // Typical function & phoneme=$00
m_inflection = 0;
m_rateInflection = 0;
m_ctrlArtAmp = 0;
m_filterFreq = 0;
m_ctrlArtAmp = powerCycle ? CONTROL_MASK : 0; // Chip power-on, so CTL=1 (power-down / standby)
m_filterFreq = powerCycle ? FILTER_FREQ_SILENCE : 0; // Empirically observed at chip power-on (GH#1302)
m_currentMode = 0;
m_currentMode.mode = 0;
m_currentMode.function = 0; // Set at runtime when CTL=0
m_currentMode.enableInts = 0; // Set at runtime when CTL=0
m_currentMode.D7 = 0; // Since in power-down mode (as per SSI263 datasheet)
//
@ -60,12 +68,12 @@ public:
}
void SetDevice(UINT device) { m_device = device; }
void SetCardMode(PHASOR_MODE mode) { m_cardMode = mode; }
void SetCardMode(PHASOR_MODE mode);
bool DSInit(void);
void DSUninit(void);
void Reset(void);
void Reset(const bool powerCycle, const bool isPhasorCard);
bool IsPhonemeActive(void) { return m_currentActivePhoneme >= 0; }
BYTE Read(ULONG nExecutedCycles);
@ -85,12 +93,15 @@ public:
void SaveSnapshot(class YamlSaveHelper& yamlSaveHelper);
void LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, PHASOR_MODE mode, UINT version);
void SC01_SaveSnapshot(YamlSaveHelper& yamlSaveHelper);
void SC01_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version);
private:
void Play(unsigned int nPhoneme);
void Stop(void);
void UpdateIRQ(void);
void UpdateAccurateLength(void);
void SetDeviceModeAndInts(void);
UINT64 GetLastCumulativeCycles(void);
void UpdateIFR(BYTE nDevice, BYTE clr_mask, BYTE set_mask);
@ -105,13 +116,37 @@ private:
//
// Duration/Phonome
static const BYTE DURATION_MODE_MASK = 0xC0;
static const BYTE DURATION_MODE_SHIFT = 6;
static const BYTE PHONEME_MASK = 0x3F;
static const BYTE MODE_PHONEME_TRANSITIONED_INFLECTION = 0xC0;
static const BYTE MODE_PHONEME_IMMEDIATE_INFLECTION = 0x80;
static const BYTE MODE_FRAME_IMMEDIATE_INFLECTION = 0x40;
static const BYTE MODE_IRQ_DISABLED = 0x00; // disable interrupts, but retains one of the 3 modes
// Rate/Inflection
static const BYTE RATE_MASK = 0xF0;
static const BYTE INFLECTION_MASK_H = 0x08; // I11
static const BYTE INFLECTION_MASK_L = 0x07; // I2..I0
// Ctrl/Art/Amp
static const BYTE ARTICULATION_MASK = 0x70;
static const BYTE AMPLITUDE_MASK = 0x0F;
static const BYTE CONTROL_MASK = 0x80;
// Filter frequency range
static const BYTE FILTER_FREQ_SILENCE = 0xFF;
UINT m_slot;
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;
int m_currentActivePhoneme; // -1 (if none) or SSI263 or SC01 phoneme
bool m_isVotraxPhoneme;
BYTE m_votraxPhoneme;
UINT m_cyclesThisAudioFrame;
//
@ -124,6 +159,7 @@ private:
UINT m_phonemeAccurateLengthRemaining; // length in samples, decremented by cycles executed
bool m_phonemePlaybackAndDebugger;
bool m_phonemeCompleteByFullSpeed;
UINT m_phonemeLeadoutLength; // length in samples, decremented after \m_phonemeLengthRemaining\ goes to zero. Delay until phoneme repeats
//
@ -140,7 +176,17 @@ private:
BYTE m_ctrlArtAmp;
BYTE m_filterFreq;
BYTE m_currentMode; // b7:6=Mode; b0=D7 pin (for IRQ)
union
{
struct
{
BYTE D7 : 1; // b0=D7 pin (for IRQ)
BYTE reserved : 4;
BYTE enableInts : 1; // b5 = enable A/!R (ie. interrupts)
BYTE function : 2; // b7:6 = function
};
BYTE mode;
} m_currentMode;
// Debug
bool m_dbgFirst;