// Part of SAASound copyright 2020 Dave Hooper // // SAADevice.cpp: connecting the subcomponents of the SAA1099 together. // This class handles device inputs and outputs (clocking, data and // address bus, and simulated output) // ////////////////////////////////////////////////////////////////////// #include "SAASound.h" #include "types.h" #include "SAAEnv.h" #include "SAANoise.h" #include "SAAFreq.h" #include "SAAAmp.h" #include "SAASound.h" #include "SAAImpl.h" #include "defns.h" ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CSAADevice::CSAADevice() : m_nCurrentSaaReg(0), m_bOutputEnabled(false), m_bSync(false), m_nOversample(0), m_Noise0(0xffffffff), m_Noise1(0xffffffff), m_Env0(), m_Env1(), m_Osc0(&m_Noise0, NULL), m_Osc1(NULL, &m_Env0), m_Osc2(NULL, NULL), m_Osc3(&m_Noise1, NULL), m_Osc4(NULL, &m_Env1), m_Osc5(NULL, NULL), m_Amp0(&m_Osc0, &m_Noise0, NULL), m_Amp1(&m_Osc1, &m_Noise0, NULL), m_Amp2(&m_Osc2, &m_Noise0, &m_Env0), m_Amp3(&m_Osc3, &m_Noise1, NULL), m_Amp4(&m_Osc4, &m_Noise1, NULL), m_Amp5(&m_Osc5, &m_Noise1, &m_Env1) { // Create and link up the objects that make up the emulator Noise[0] = &m_Noise0; Noise[1] = &m_Noise1; Env[0] = &m_Env0; Env[1] = &m_Env1; // Create oscillators (tone generators) and link to noise generators and // envelope controllers Osc[0] = &m_Osc0; Osc[1] = &m_Osc1; Osc[2] = &m_Osc2; Osc[3] = &m_Osc3; Osc[4] = &m_Osc4; Osc[5] = &m_Osc5; // Create amplification/mixing stages and link to appropriate oscillators, // noise generators and envelope controllers Amp[0] = &m_Amp0; Amp[1] = &m_Amp1; Amp[2] = &m_Amp2; Amp[3] = &m_Amp3; Amp[4] = &m_Amp4; Amp[5] = &m_Amp5; _SetClockRate(EXTERNAL_CLK_HZ); _SetOversample(DEFAULT_OVERSAMPLE); } CSAADevice::~CSAADevice() { } ////////////////////////////////////////////////////////////////////// // CSAASound members ////////////////////////////////////////////////////////////////////// void CSAADevice::_SetClockRate(unsigned int nClockRate) { m_Osc0._SetClockRate(nClockRate); m_Osc1._SetClockRate(nClockRate); m_Osc2._SetClockRate(nClockRate); m_Osc3._SetClockRate(nClockRate); m_Osc4._SetClockRate(nClockRate); m_Osc5._SetClockRate(nClockRate); m_Noise0._SetClockRate(nClockRate); m_Noise1._SetClockRate(nClockRate); } void CSAADevice::_SetSampleRate(unsigned int nSampleRate) { m_Osc0._SetSampleRate(nSampleRate); m_Osc1._SetSampleRate(nSampleRate); m_Osc2._SetSampleRate(nSampleRate); m_Osc3._SetSampleRate(nSampleRate); m_Osc4._SetSampleRate(nSampleRate); m_Osc5._SetSampleRate(nSampleRate); m_Noise0._SetSampleRate(nSampleRate); m_Noise1._SetSampleRate(nSampleRate); } void CSAADevice::_SetOversample(unsigned int nOversample) { if (nOversample != (unsigned int)m_nOversample) { m_nOversample = nOversample; m_Osc0._SetOversample(nOversample); m_Osc1._SetOversample(nOversample); m_Osc2._SetOversample(nOversample); m_Osc3._SetOversample(nOversample); m_Osc4._SetOversample(nOversample); m_Osc5._SetOversample(nOversample); m_Noise0._SetOversample(nOversample); m_Noise1._SetOversample(nOversample); } } void CSAADevice::_WriteData(BYTE nData) { #if defined(DEBUG) || defined(DEBUGSAA) m_Reg[m_nCurrentSaaReg] = nData; #endif // route nData to the appropriate place switch (m_nCurrentSaaReg) { // Amplitude data (==> Amp) case 0: m_Amp0.SetAmpLevel(nData); break; case 1: m_Amp1.SetAmpLevel(nData); break; case 2: m_Amp2.SetAmpLevel(nData); break; case 3: m_Amp3.SetAmpLevel(nData); break; case 4: m_Amp4.SetAmpLevel(nData); break; case 5: m_Amp5.SetAmpLevel(nData); break; // Freq data (==> Osc) case 8: m_Osc0.SetFreqOffset(nData); break; case 9: m_Osc1.SetFreqOffset(nData); break; case 10: m_Osc2.SetFreqOffset(nData); break; case 11: m_Osc3.SetFreqOffset(nData); break; case 12: m_Osc4.SetFreqOffset(nData); break; case 13: m_Osc5.SetFreqOffset(nData); break; // Freq octave data (==> Osc) for channels 0,1 case 16: m_Osc0.SetFreqOctave(nData & 0x07); m_Osc1.SetFreqOctave((nData >> 4) & 0x07); break; // Freq octave data (==> Osc) for channels 2,3 case 17: m_Osc2.SetFreqOctave(nData & 0x07); m_Osc3.SetFreqOctave((nData >> 4) & 0x07); break; // Freq octave data (==> Osc) for channels 4,5 case 18: m_Osc4.SetFreqOctave(nData & 0x07); m_Osc5.SetFreqOctave((nData >> 4) & 0x07); break; // Tone mixer control (==> Amp) case 20: m_Amp0.SetToneMixer(nData & 0x01); m_Amp1.SetToneMixer(nData & 0x02); m_Amp2.SetToneMixer(nData & 0x04); m_Amp3.SetToneMixer(nData & 0x08); m_Amp4.SetToneMixer(nData & 0x10); m_Amp5.SetToneMixer(nData & 0x20); break; // Noise mixer control (==> Amp) case 21: m_Amp0.SetNoiseMixer(nData & 0x01); m_Amp1.SetNoiseMixer(nData & 0x02); m_Amp2.SetNoiseMixer(nData & 0x04); m_Amp3.SetNoiseMixer(nData & 0x08); m_Amp4.SetNoiseMixer(nData & 0x10); m_Amp5.SetNoiseMixer(nData & 0x20); break; // Noise frequency/source control (==> Noise) case 22: m_Noise0.SetSource(nData & 0x03); m_Noise1.SetSource((nData >> 4) & 0x03); break; // Envelope control data (==> Env) for envelope controller #0 case 24: m_Env0.SetEnvControl(nData); break; // Envelope control data (==> Env) for envelope controller #1 case 25: m_Env1.SetEnvControl(nData); break; // Global enable and reset (sync) controls case 28: { // Reset (sync) bit bool bSync = bool(nData & 0x02); if (bSync != m_bSync) { // Sync all devices // This amounts to telling them all to reset to a // known state, which is also a state that doesn't change // (i.e. no audio output, although there are some exceptions) // bSync=true => all devices are sync (aka reset); // bSync=false => all devices are allowed to run and generate changing output m_Osc0.Sync(bSync); m_Osc1.Sync(bSync); m_Osc2.Sync(bSync); m_Osc3.Sync(bSync); m_Osc4.Sync(bSync); m_Osc5.Sync(bSync); m_Noise0.Sync(bSync); m_Noise1.Sync(bSync); m_Amp0.Sync(bSync); m_Amp1.Sync(bSync); m_Amp2.Sync(bSync); m_Amp3.Sync(bSync); m_Amp4.Sync(bSync); m_Amp5.Sync(bSync); m_bSync = bSync; } // Global mute bit bool bOutputEnabled = bool(nData & 0x01); if (bOutputEnabled != m_bOutputEnabled) { // unmute all amps - sound 'enabled' m_Amp0.Mute(!bOutputEnabled); m_Amp1.Mute(!bOutputEnabled); m_Amp2.Mute(!bOutputEnabled); m_Amp3.Mute(!bOutputEnabled); m_Amp4.Mute(!bOutputEnabled); m_Amp5.Mute(!bOutputEnabled); m_bOutputEnabled = bOutputEnabled; } } break; default: // anything else means data is being written to a register // that is not used within the SAA-1099 architecture // hence, we ignore it. {} } } void CSAADevice::_WriteAddress(BYTE nReg) { m_nCurrentSaaReg = nReg & 31; if (m_nCurrentSaaReg == 24) { m_Env0.ExternalClock(); } else if (m_nCurrentSaaReg == 25) { m_Env1.ExternalClock(); } } #if defined(DEBUG) || defined(DEBUGSAA) || defined(USE_CONFIG_FILE) BYTE CSAADevice::_ReadAddress(void) { // Not a real hardware function of the SAA-1099, which is write-only // However, this is used by SAAImpl to generate debug logs (if enabled) return(m_nCurrentSaaReg); } #endif #if defined(DEBUG) BYTE CSAADevice::_ReadData(void) { // Not a real hardware function of the SAA-1099, which is write-only // This is only compiled for Debug builds return(m_Reg[m_nCurrentSaaReg]); } #endif void CSAADevice::_TickAndOutputStereo(unsigned int& left_mixed, unsigned int& right_mixed, DivDispatchOscBuffer** oscBuf) { unsigned int temp_left, temp_right; unsigned int accum_left = 0, accum_right = 0; for (int i = 1 << m_nOversample; i > 0; i--) { m_Noise0.Tick(); m_Noise1.Tick(); m_Amp0.TickAndOutputStereo(temp_left, temp_right); oscBuf[0]->data[oscBuf[0]->needle++]=(temp_left+temp_right)<<5; accum_left += temp_left; accum_right += temp_right; m_Amp1.TickAndOutputStereo(temp_left, temp_right); oscBuf[1]->data[oscBuf[1]->needle++]=(temp_left+temp_right)<<5; accum_left += temp_left; accum_right += temp_right; m_Amp2.TickAndOutputStereo(temp_left, temp_right); oscBuf[2]->data[oscBuf[2]->needle++]=(temp_left+temp_right)<<5; accum_left += temp_left; accum_right += temp_right; m_Amp3.TickAndOutputStereo(temp_left, temp_right); oscBuf[3]->data[oscBuf[3]->needle++]=(temp_left+temp_right)<<5; accum_left += temp_left; accum_right += temp_right; m_Amp4.TickAndOutputStereo(temp_left, temp_right); oscBuf[4]->data[oscBuf[4]->needle++]=(temp_left+temp_right)<<5; accum_left += temp_left; accum_right += temp_right; m_Amp5.TickAndOutputStereo(temp_left, temp_right); oscBuf[5]->data[oscBuf[5]->needle++]=(temp_left+temp_right)<<5; accum_left += temp_left; accum_right += temp_right; } left_mixed = accum_left; right_mixed = accum_right; } void CSAADevice::_TickAndOutputSeparate(unsigned int& left_mixed, unsigned int& right_mixed, unsigned int& left0, unsigned int& right0, unsigned int& left1, unsigned int& right1, unsigned int& left2, unsigned int& right2, unsigned int& left3, unsigned int& right3, unsigned int& left4, unsigned int& right4, unsigned int& left5, unsigned int& right5 ) { unsigned int temp_left, temp_right; unsigned int accum_left = 0, accum_right = 0; left0 = left1 = left2 = left3 = left4 = left5 = 0; right0 = right1 = right2 = right3 = right4 = right5 = 0; for (int i = 1 << m_nOversample; i > 0; i--) { m_Noise0.Tick(); m_Noise1.Tick(); m_Amp0.TickAndOutputStereo(temp_left, temp_right); left0 += temp_left; right0 += temp_right; accum_left += temp_left; accum_right += temp_right; m_Amp1.TickAndOutputStereo(temp_left, temp_right); left1 += temp_left; right1 += temp_right; accum_left += temp_left; accum_right += temp_right; m_Amp2.TickAndOutputStereo(temp_left, temp_right); left2 += temp_left; right2 += temp_right; accum_left += temp_left; accum_right += temp_right; m_Amp3.TickAndOutputStereo(temp_left, temp_right); left3 += temp_left; right3 += temp_right; accum_left += temp_left; accum_right += temp_right; m_Amp4.TickAndOutputStereo(temp_left, temp_right); left4 += temp_left; right4 += temp_right; accum_left += temp_left; accum_right += temp_right; m_Amp5.TickAndOutputStereo(temp_left, temp_right); left5 += temp_left; right5 += temp_right; accum_left += temp_left; accum_right += temp_right; } left_mixed = accum_left; right_mixed = accum_right; }