/* 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-2022, 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: Mockingboard/Phasor Card Manager * * Author: Various * */ #include "StdAfx.h" #include "MockingboardCardManager.h" #include "Core.h" #include "CardManager.h" #include "Mockingboard.h" #include "MockingboardDefs.h" #include "Riff.h" //#define DBG_MB_UPDATE bool MockingboardCardManager::IsMockingboard(UINT slot) { SS_CARDTYPE type = GetCardMgr().QuerySlot(slot); return type == CT_MockingboardC || type == CT_Phasor; } void MockingboardCardManager::ReinitializeClock(void) { for (UINT i = SLOT0; i < NUM_SLOTS; i++) { if (IsMockingboard(i)) dynamic_cast(GetCardMgr().GetRef(i)).ReinitializeClock(); } } void MockingboardCardManager::InitializeForLoadingSnapshot(void) { if (g_bDisableDirectSound || g_bDisableDirectSoundMockingboard) return; if (!m_mockingboardVoice.lpDSBvoice) return; DSVoiceStop(&m_mockingboardVoice); // Reason: 'MB voice is playing' then loading a save-state where 'no MB present' (GH#609) } void MockingboardCardManager::MuteControl(bool mute) { for (UINT i = SLOT0; i < NUM_SLOTS; i++) { if (IsMockingboard(i)) dynamic_cast(GetCardMgr().GetRef(i)).MuteControl(mute); } if (mute) { if (m_mockingboardVoice.bActive && !m_mockingboardVoice.bMute) { m_mockingboardVoice.lpDSBvoice->SetVolume(DSBVOLUME_MIN); m_mockingboardVoice.bMute = true; } } else { if (m_mockingboardVoice.bActive && m_mockingboardVoice.bMute) { m_mockingboardVoice.lpDSBvoice->SetVolume(m_mockingboardVoice.nVolume); m_mockingboardVoice.bMute = false; } } } void MockingboardCardManager::SetCumulativeCycles(void) { for (UINT i = SLOT0; i < NUM_SLOTS; i++) { if (IsMockingboard(i)) dynamic_cast(GetCardMgr().GetRef(i)).SetCumulativeCycles(); } } void MockingboardCardManager::UpdateCycles(ULONG executedCycles) { for (UINT i = SLOT0; i < NUM_SLOTS; i++) { if (IsMockingboard(i)) dynamic_cast(GetCardMgr().GetRef(i)).UpdateCycles(executedCycles); } } bool MockingboardCardManager::IsActive(void) { if (!m_mockingboardVoice.bActive) return false; for (UINT i = SLOT0; i < NUM_SLOTS; i++) { if (IsMockingboard(i)) if (dynamic_cast(GetCardMgr().GetRef(i)).IsActive()) return true; // if any card is true then the condition for active is true } return false; } DWORD MockingboardCardManager::GetVolume(void) { return m_userVolume; } void MockingboardCardManager::SetVolume(DWORD volume, DWORD volumeMax) { m_userVolume = volume; m_mockingboardVoice.dwUserVolume = volume; m_mockingboardVoice.nVolume = NewVolume(volume, volumeMax); if (m_mockingboardVoice.bActive && !m_mockingboardVoice.bMute) m_mockingboardVoice.lpDSBvoice->SetVolume(m_mockingboardVoice.nVolume); for (UINT i = SLOT0; i < NUM_SLOTS; i++) { if (IsMockingboard(i)) dynamic_cast(GetCardMgr().GetRef(i)).SetVolume(volume, volumeMax); } } #ifdef _DEBUG void MockingboardCardManager::CheckCumulativeCycles(void) { for (UINT i = SLOT0; i < NUM_SLOTS; i++) { if (IsMockingboard(i)) dynamic_cast(GetCardMgr().GetRef(i)).CheckCumulativeCycles(); } } void MockingboardCardManager::Get6522IrqDescription(std::string& desc) { for (UINT i = SLOT0; i < NUM_SLOTS; i++) { if (IsMockingboard(i)) dynamic_cast(GetCardMgr().GetRef(i)).Get6522IrqDescription(desc); } } #endif // Called by CardManager::Destroy() void MockingboardCardManager::Destroy(void) { // NB. All cards (including any Mockingboard cards) have just been destroyed by CardManager if (m_mockingboardVoice.lpDSBvoice && m_mockingboardVoice.bActive) DSVoiceStop(&m_mockingboardVoice); DSReleaseSoundBuffer(&m_mockingboardVoice); } // Called by ContinueExecution() at the end of every execution period (~1000 cycles or ~3 cycles when MODE_STEPPING) // NB. Required for FT's TEST LAB #1 player void MockingboardCardManager::Update(const ULONG executedCycles) { // NB. CardManager has just called each card's Update() bool active = false; for (UINT i = SLOT0; i < NUM_SLOTS; i++) { if (IsMockingboard(i)) active |= dynamic_cast(GetCardMgr().GetRef(i)).IsAnyTimer1Active(); } if (active) return; // No 6522 TIMER1's are active, so periodically update AY8913's here... const UINT kCyclesPerAudioFrame = 1000; m_cyclesThisAudioFrame += executedCycles; if (m_cyclesThisAudioFrame < kCyclesPerAudioFrame) return; m_cyclesThisAudioFrame %= kCyclesPerAudioFrame; UpdateSoundBuffer(); } // Called by: // . MB_SyncEventCallback() on a TIMER1 (not TIMER2) underflow - when IsAnyTimer1Active() == true // . Update() - when IsAnyTimer1Active() == false void MockingboardCardManager::UpdateSoundBuffer(void) { #ifdef LOG_PERF_TIMINGS extern UINT64 g_timeMB_NoTimer; extern UINT64 g_timeMB_Timer; PerfMarker perfMarker(!IsAnyTimer1Active() ? g_timeMB_NoTimer : g_timeMB_Timer); #endif if (!m_mockingboardVoice.lpDSBvoice) { if (g_bDisableDirectSound || g_bDisableDirectSoundMockingboard) return; if (!Init()) return; } if (!m_mockingboardVoice.bActive) { // Sound buffer may have been stopped by MB_InitializeForLoadingSnapshot(). // NB. DSZeroVoiceBuffer() also zeros the sound buffer, so it's better than directly calling IDirectSoundBuffer::Play(): // - without zeroing, then the previous sound buffer can be heard for a fraction of a second // - eg. when doing Mockingboard playback, then loading a save-state which is also doing Mockingboard playback DSZeroVoiceBuffer(&m_mockingboardVoice, SOUNDBUFFER_SIZE); // ... and Play() } UINT numSamples = GenerateAllSoundData(); if (numSamples) MixAllAndCopyToRingBuffer(numSamples); } bool MockingboardCardManager::Init(void) { if (!g_bDSAvailable) return false; const DWORD SAMPLE_RATE = 44100; // Use a base freq so that DirectX (or sound h/w) doesn't have to up/down-sample HRESULT hr = DSGetSoundBuffer(&m_mockingboardVoice, DSBCAPS_CTRLVOLUME, SOUNDBUFFER_SIZE, SAMPLE_RATE, NUM_MB_CHANNELS, "MB"); LogFileOutput("MBCardMgr: DSGetSoundBuffer(), hr=0x%08X\n", hr); if (FAILED(hr)) { LogFileOutput("MBCardMgr: DSGetSoundBuffer failed (%08X)\n", hr); return false; } bool bRes = DSZeroVoiceBuffer(&m_mockingboardVoice, SOUNDBUFFER_SIZE); // ... and Play() LogFileOutput("MBCardMgr: DSZeroVoiceBuffer(), res=%d\n", bRes ? 1 : 0); if (!bRes) return false; m_mockingboardVoice.bActive = true; // Volume might've been setup from value in Registry if (!m_mockingboardVoice.nVolume) m_mockingboardVoice.nVolume = DSBVOLUME_MAX; hr = m_mockingboardVoice.lpDSBvoice->SetVolume(m_mockingboardVoice.nVolume); LogFileOutput("MBCardMgr: SetVolume(), hr=0x%08X\n", hr); return true; } UINT MockingboardCardManager::GenerateAllSoundData(void) { UINT nNumSamples = 0; for (UINT slot = SLOT0; slot < NUM_SLOTS; slot++) { if (!IsMockingboard(slot)) continue; MockingboardCard& MB = dynamic_cast(GetCardMgr().GetRef(slot)); MB.SetNumSamplesError(m_numSamplesError); nNumSamples = MB.MB_Update(); m_numSamplesError = MB.GetNumSamplesError(); } // DWORD dwCurrentPlayCursor, dwCurrentWriteCursor; HRESULT hr = m_mockingboardVoice.lpDSBvoice->GetCurrentPosition(&dwCurrentPlayCursor, &dwCurrentWriteCursor); if (FAILED(hr)) return 0; if (m_byteOffset == (DWORD)-1) { // First time in this func m_byteOffset = dwCurrentWriteCursor; } else { // Check that our offset isn't between Play & Write positions if (dwCurrentWriteCursor > dwCurrentPlayCursor) { // |-----PxxxxxW-----| if ((m_byteOffset > dwCurrentPlayCursor) && (m_byteOffset < dwCurrentWriteCursor)) { #ifdef DBG_MB_UPDATE double fTicksSecs = (double)GetTickCount() / 1000.0; LogOutput("%010.3f: [MBUpdt] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X xxx\n", fTicksSecs, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor - dwCurrentPlayCursor, dwByteOffset, nNumSamples); #endif m_byteOffset = dwCurrentWriteCursor; m_numSamplesError = 0; } } else { // |xxW----------Pxxx| if ((m_byteOffset > dwCurrentPlayCursor) || (m_byteOffset < dwCurrentWriteCursor)) { #ifdef DBG_MB_UPDATE double fTicksSecs = (double)GetTickCount() / 1000.0; LogOutput("%010.3f: [MBUpdt] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X XXX\n", fTicksSecs, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor - dwCurrentPlayCursor, dwByteOffset, nNumSamples); #endif m_byteOffset = dwCurrentWriteCursor; m_numSamplesError = 0; } } } int nBytesRemaining = m_byteOffset - dwCurrentPlayCursor; if (nBytesRemaining < 0) nBytesRemaining += SOUNDBUFFER_SIZE; // Calc correction factor so that play-buffer doesn't under/overflow const int nErrorInc = SoundCore_GetErrorInc(); if (nBytesRemaining < SOUNDBUFFER_SIZE / 4) m_numSamplesError += nErrorInc; // < 0.25 of buffer remaining else if (nBytesRemaining > SOUNDBUFFER_SIZE / 2) m_numSamplesError -= nErrorInc; // > 0.50 of buffer remaining else m_numSamplesError = 0; // Acceptable amount of data in buffer #ifdef DBG_MB_UPDATE double fTicksSecs = (double)GetTickCount() / 1000.0; LogOutput("%010.3f: [MBUpdt] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X, NSE=%08X, Interval=%f\n", fTicksSecs, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor - dwCurrentPlayCursor, dwByteOffset, nNumSamples, nNumSamplesError, updateInterval); #endif return nNumSamples; } void MockingboardCardManager::MixAllAndCopyToRingBuffer(UINT nNumSamples) { // const double fAttenuation = g_bPhasorEnable ? 2.0 / 3.0 : 1.0; const double fAttenuation = true ? 2.0 / 3.0 : 1.0; short** slotAYVoiceBuffers[NUM_SLOTS] = {0}; for (UINT slot = SLOT0; slot < NUM_SLOTS; slot++) { if (IsMockingboard(slot)) slotAYVoiceBuffers[slot] = dynamic_cast(GetCardMgr().GetRef(slot)).GetVoiceBuffers(); } for (UINT i = 0; i < nNumSamples; i++) { // Mockingboard stereo (all voices on an AY8910 wire-or'ed together) // L = Address.b7=0, R = Address.b7=1 int nDataL = 0, nDataR = 0; for (UINT slot = SLOT0; slot < NUM_SLOTS; slot++) { if (!slotAYVoiceBuffers[slot]) continue; for (UINT j = 0; j < NUM_VOICES_PER_AY8913; j++) { short** ppAYVoiceBuffer = slotAYVoiceBuffers[slot]; // Regular MB-C AY's nDataL += (int)((double)ppAYVoiceBuffer[0 * NUM_VOICES_PER_AY8913 + j][i] * fAttenuation); nDataR += (int)((double)ppAYVoiceBuffer[2 * NUM_VOICES_PER_AY8913 + j][i] * fAttenuation); // Extra Phasor AY's nDataL += (int)((double)ppAYVoiceBuffer[1 * NUM_VOICES_PER_AY8913 + j][i] * fAttenuation); nDataR += (int)((double)ppAYVoiceBuffer[3 * NUM_VOICES_PER_AY8913 + j][i] * fAttenuation); } } // Cap the superpositioned output if (nDataL < WAVE_DATA_MIN) nDataL = WAVE_DATA_MIN; else if (nDataL > WAVE_DATA_MAX) nDataL = WAVE_DATA_MAX; if (nDataR < WAVE_DATA_MIN) nDataR = WAVE_DATA_MIN; else if (nDataR > WAVE_DATA_MAX) nDataR = WAVE_DATA_MAX; m_mixBuffer[i * NUM_MB_CHANNELS + 0] = (short)nDataL; // L m_mixBuffer[i * NUM_MB_CHANNELS + 1] = (short)nDataR; // R } // DWORD dwDSLockedBufferSize0, dwDSLockedBufferSize1; SHORT* pDSLockedBuffer0, * pDSLockedBuffer1; HRESULT hr = DSGetLock(m_mockingboardVoice.lpDSBvoice, m_byteOffset, (DWORD)nNumSamples * sizeof(short) * NUM_MB_CHANNELS, &pDSLockedBuffer0, &dwDSLockedBufferSize0, &pDSLockedBuffer1, &dwDSLockedBufferSize1); if (FAILED(hr)) return; memcpy(pDSLockedBuffer0, &m_mixBuffer[0], dwDSLockedBufferSize0); if (pDSLockedBuffer1) memcpy(pDSLockedBuffer1, &m_mixBuffer[dwDSLockedBufferSize0 / sizeof(short)], dwDSLockedBufferSize1); // Commit sound buffer hr = m_mockingboardVoice.lpDSBvoice->Unlock((void*)pDSLockedBuffer0, dwDSLockedBufferSize0, (void*)pDSLockedBuffer1, dwDSLockedBufferSize1); m_byteOffset = (m_byteOffset + (DWORD)nNumSamples * sizeof(short) * NUM_MB_CHANNELS) % SOUNDBUFFER_SIZE; if (m_outputToRiff) RiffPutSamples(&m_mixBuffer[0], nNumSamples); }