/**************************************************************************** * * * Project64-audio - A Nintendo 64 audio plugin. * * http://www.pj64-emu.com/ * * Copyright (C) 2017 Project64. All rights reserved. * * Copyright (C) 2000-2015 Azimer. All rights reserved. * * * * License: * * GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html * * * ****************************************************************************/ #include "SoundBase.h" #include #include #include #include #include SoundDriverBase::SoundDriverBase() : m_MaxBufferSize(MAX_SIZE), m_AI_DMAPrimaryBuffer(NULL), m_AI_DMASecondaryBuffer(NULL), m_AI_DMAPrimaryBytes(0), m_AI_DMASecondaryBytes(0), m_CurrentReadLoc(0), m_CurrentWriteLoc(0), m_BufferRemaining(0) { memset(&m_Buffer, 0, sizeof(m_Buffer)); } bool SoundDriverBase::Initialize() { return true; } void SoundDriverBase::AI_SetFrequency(uint32_t Frequency, uint32_t BufferSize) { SetFrequency(Frequency, BufferSize); m_MaxBufferSize = (BufferSize * 4); if (g_settings->TinyBuffer() == true) { m_MaxBufferSize = m_MaxBufferSize * 2; } m_BufferRemaining = 0; m_CurrentReadLoc = m_CurrentWriteLoc = m_BufferRemaining = 0; } void SoundDriverBase::AI_LenChanged(uint8_t *start, uint32_t length) { WriteTrace(TraceAudioDriver, TraceDebug, "Start"); // Bleed off some of this buffer to smooth out audio if (length < m_MaxBufferSize && g_settings->SyncAudio()) { while ((m_BufferRemaining) == m_MaxBufferSize) { pjutil::Sleep(1); } } CGuard guard(m_CS); BufferAudio(); if (m_AI_DMASecondaryBuffer != NULL) { WriteTrace(TraceAudioDriver, TraceDebug, "Discarding previous secondary buffer"); } m_AI_DMASecondaryBuffer = start; m_AI_DMASecondaryBytes = length; if (m_AI_DMAPrimaryBytes == 0) { m_AI_DMAPrimaryBuffer = m_AI_DMASecondaryBuffer; m_AI_DMASecondaryBuffer = NULL; m_AI_DMAPrimaryBytes = m_AI_DMASecondaryBytes; m_AI_DMASecondaryBytes = 0; } *g_AudioInfo.AI_STATUS_REG = AI_STATUS_DMA_BUSY; if (m_AI_DMAPrimaryBytes > 0 && m_AI_DMASecondaryBytes > 0) { *g_AudioInfo.AI_STATUS_REG = (uint32_t)(AI_STATUS_DMA_BUSY | AI_STATUS_FIFO_FULL); } BufferAudio(); WriteTrace(TraceAudioDriver, TraceDebug, "Done"); } void SoundDriverBase::AI_Startup() { WriteTrace(TraceAudioDriver, TraceDebug, "Start"); m_AI_DMAPrimaryBytes = m_AI_DMASecondaryBytes = 0; m_AI_DMAPrimaryBuffer = m_AI_DMASecondaryBuffer = NULL; m_MaxBufferSize = MAX_SIZE; m_CurrentReadLoc = m_CurrentWriteLoc = m_BufferRemaining = 0; if (Initialize()) { StartAudio(); } WriteTrace(TraceAudioDriver, TraceDebug, "Start"); } void SoundDriverBase::AI_Shutdown() { StopAudio(); } void SoundDriverBase::AI_Update(bool Wait) { m_AiUpdateEvent.IsTriggered(Wait ? SyncEvent::INFINITE_TIMEOUT : 0); } uint32_t SoundDriverBase::AI_ReadLength() { CGuard guard(m_CS); return m_AI_DMAPrimaryBytes & ~0x7; } void SoundDriverBase::LoadAiBuffer(uint8_t *start, uint32_t length) { uint8_t nullBuff[MAX_SIZE]; uint8_t *ptrStart = start != NULL ? start : nullBuff; uint32_t writePtr = 0, bytesToMove = length; if (bytesToMove > m_MaxBufferSize) { memset(ptrStart, 0, 100); return; } bool DMAEnabled = (*g_AudioInfo.AI_CONTROL_REG & AI_CONTROL_DMA_ON) == AI_CONTROL_DMA_ON; if (!DMAEnabled) { WriteTrace(TraceAudioDriver, TraceVerbose, "Return silence -- DMA is disabled"); memset(ptrStart, 0, bytesToMove); return; } CGuard guard(m_CS); WriteTrace(TraceAudioDriver, TraceVerbose, "Step 0: Replace depleted stored buffer for next run"); BufferAudio(); WriteTrace(TraceAudioDriver, TraceVerbose, "Step 1: Deplete stored buffer (bytesToMove: 0x%08X m_BufferRemaining: 0x%08X)", bytesToMove, m_BufferRemaining); while (bytesToMove > 0 && m_BufferRemaining > 0) { *(uint32_t *)(ptrStart + writePtr) = *(uint32_t *)(m_Buffer + m_CurrentReadLoc); m_CurrentReadLoc += 4; writePtr += 4; m_CurrentReadLoc %= m_MaxBufferSize; m_BufferRemaining -= 4; bytesToMove -= 4; } WriteTrace(TraceAudioDriver, TraceVerbose, "Step 2: Fill bytesToMove (0x%08X) with silence", bytesToMove); while (bytesToMove > 0) { *(uint8_t *)(ptrStart + writePtr) = 0; writePtr += 1; bytesToMove -= 1; } WriteTrace(TraceAudioDriver, TraceVerbose, "Step 3: Replace depleted stored buffer for next run"); BufferAudio(); } void SoundDriverBase::BufferAudio() { WriteTrace(TraceAudioDriver, TraceVerbose, "Start (m_BufferRemaining: 0x%08X m_MaxBufferSize: 0x%08X m_AI_DMAPrimaryBytes: 0x%08X m_AI_DMASecondaryBytes: 0x%08X)", m_BufferRemaining, m_MaxBufferSize, m_AI_DMAPrimaryBytes, m_AI_DMASecondaryBytes); while ((m_BufferRemaining < m_MaxBufferSize) && (m_AI_DMAPrimaryBytes > 0 || m_AI_DMASecondaryBytes > 0)) { *(uint16_t *)(m_Buffer + m_CurrentWriteLoc) = *(uint16_t *)(m_AI_DMAPrimaryBuffer + 2); *(uint16_t *)(m_Buffer + m_CurrentWriteLoc + 2) = *(uint16_t *)m_AI_DMAPrimaryBuffer; m_CurrentWriteLoc += 4; m_AI_DMAPrimaryBuffer += 4; m_CurrentWriteLoc %= m_MaxBufferSize; m_BufferRemaining += 4; m_AI_DMAPrimaryBytes -= 4; if (m_AI_DMAPrimaryBytes == 0) { WriteTrace(TraceAudioDriver, TraceVerbose, "Emptied Primary Buffer"); m_AI_DMAPrimaryBytes = m_AI_DMASecondaryBytes; m_AI_DMAPrimaryBuffer = m_AI_DMASecondaryBuffer; // Switch m_AI_DMASecondaryBytes = 0; m_AI_DMASecondaryBuffer = NULL; *g_AudioInfo.AI_STATUS_REG = AI_STATUS_DMA_BUSY; *g_AudioInfo.AI_STATUS_REG &= ~AI_STATUS_FIFO_FULL; *g_AudioInfo.MI_INTR_REG |= MI_INTR_AI; g_AudioInfo.CheckInterrupts(); if (m_AI_DMAPrimaryBytes == 0) { *g_AudioInfo.AI_STATUS_REG = 0; } } } WriteTrace(TraceAudioDriver, TraceVerbose, "Done (m_BufferRemaining: 0x%08X)", m_BufferRemaining); } void SoundDriverBase::SetFrequency(uint32_t /*Frequency*/, uint32_t /*Divider*/) { } void SoundDriverBase::StartAudio() { } void SoundDriverBase::StopAudio() { }