From 3d47fa08b8a460d766f77f260cc08702ff21fac6 Mon Sep 17 00:00:00 2001 From: Arisotura Date: Sun, 19 May 2024 23:58:14 +0200 Subject: [PATCH] migrate AudioInOut inside EmuInstance --- src/frontend/FrontendUtil.h | 24 -- src/frontend/Util_Audio.cpp | 136 ------- src/frontend/qt_sdl/AudioInOut.cpp | 390 -------------------- src/frontend/qt_sdl/AudioInOut.h | 49 --- src/frontend/qt_sdl/CMakeLists.txt | 5 +- src/frontend/qt_sdl/EmuInstance.cpp | 4 + src/frontend/qt_sdl/EmuInstance.h | 40 ++ src/frontend/qt_sdl/EmuInstanceAudio.cpp | 451 +++++++++++++++++++++++ src/frontend/qt_sdl/EmuThread.cpp | 15 +- src/frontend/qt_sdl/Input.h | 1 + src/frontend/qt_sdl/Window.cpp | 7 +- src/frontend/qt_sdl/main.cpp | 3 +- 12 files changed, 508 insertions(+), 617 deletions(-) delete mode 100644 src/frontend/Util_Audio.cpp delete mode 100644 src/frontend/qt_sdl/AudioInOut.cpp delete mode 100644 src/frontend/qt_sdl/AudioInOut.h create mode 100644 src/frontend/qt_sdl/EmuInstanceAudio.cpp diff --git a/src/frontend/FrontendUtil.h b/src/frontend/FrontendUtil.h index 6f09d4b7..2b8f128a 100644 --- a/src/frontend/FrontendUtil.h +++ b/src/frontend/FrontendUtil.h @@ -95,30 +95,6 @@ int GetScreenTransforms(float* out, int* kind); // on the bottom screen bool GetTouchCoords(int& x, int& y, bool clamp); - -// initialize the audio utility -void Init_Audio(int outputfreq); - -// get how many samples to read from the core audio output -// based on how many are needed by the frontend (outlen in samples) -int AudioOut_GetNumSamples(int outlen); - -// resample audio from the core audio output to match the frontend's -// output frequency, and apply specified volume -// note: this assumes the output buffer is interleaved stereo -void AudioOut_Resample(s16* inbuf, int inlen, s16* outbuf, int outlen, int volume); - -// feed silence to the microphone input -void Mic_FeedSilence(NDS& nds); - -// feed random noise to the microphone input -void Mic_FeedNoise(NDS& nds); - -// feed an external buffer to the microphone input -// buffer should be mono -void Mic_FeedExternalBuffer(NDS& nds); -void Mic_SetExternalBuffer(s16* buffer, u32 len); - } #endif // FRONTENDUTIL_H diff --git a/src/frontend/Util_Audio.cpp b/src/frontend/Util_Audio.cpp deleted file mode 100644 index 02b3026e..00000000 --- a/src/frontend/Util_Audio.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/* - Copyright 2016-2023 melonDS team - - This file is part of melonDS. - - melonDS 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 3 of the License, or (at your option) - any later version. - - melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/. -*/ - -#include -#include -#include -#include - -#include "FrontendUtil.h" - -#include "NDS.h" - -#include "mic_blow.h" - -using namespace melonDS; - -namespace Frontend -{ - -int AudioOut_Freq; -float AudioOut_SampleFrac; - -s16* MicBuffer; -u32 MicBufferLength; -u32 MicBufferReadPos; - - -void Init_Audio(int outputfreq) -{ - AudioOut_Freq = outputfreq; - AudioOut_SampleFrac = 0; - - MicBuffer = nullptr; - MicBufferLength = 0; - MicBufferReadPos = 0; -} - - -int AudioOut_GetNumSamples(int outlen) -{ - float f_len_in = (outlen * 32823.6328125) / (float)AudioOut_Freq; - f_len_in += AudioOut_SampleFrac; - int len_in = (int)floor(f_len_in); - AudioOut_SampleFrac = f_len_in - len_in; - - return len_in; -} - -void AudioOut_Resample(s16* inbuf, int inlen, s16* outbuf, int outlen, int volume) -{ - float res_incr = inlen / (float)outlen; - float res_timer = 0; - int res_pos = 0; - - for (int i = 0; i < outlen; i++) - { - outbuf[i*2 ] = (inbuf[res_pos*2 ] * volume) >> 8; - outbuf[i*2+1] = (inbuf[res_pos*2+1] * volume) >> 8; - - res_timer += res_incr; - while (res_timer >= 1.0) - { - res_timer -= 1.0; - res_pos++; - } - } -} - - -void Mic_FeedSilence(NDS& nds) -{ - MicBufferReadPos = 0; - nds.MicInputFrame(NULL, 0); -} - -void Mic_FeedNoise(NDS& nds) -{ - int sample_len = sizeof(mic_blow) / sizeof(u16); - static int sample_pos = 0; - - s16 tmp[735]; - - for (int i = 0; i < 735; i++) - { - tmp[i] = mic_blow[sample_pos]; - sample_pos++; - if (sample_pos >= sample_len) sample_pos = 0; - } - - nds.MicInputFrame(tmp, 735); -} - -void Mic_FeedExternalBuffer(NDS& nds) -{ - if (!MicBuffer) return Mic_FeedSilence(nds); - - if ((MicBufferReadPos + 735) > MicBufferLength) - { - s16 tmp[735]; - u32 len1 = MicBufferLength - MicBufferReadPos; - memcpy(&tmp[0], &MicBuffer[MicBufferReadPos], len1*sizeof(s16)); - memcpy(&tmp[len1], &MicBuffer[0], (735 - len1)*sizeof(s16)); - - nds.MicInputFrame(tmp, 735); - MicBufferReadPos = 735 - len1; - } - else - { - nds.MicInputFrame(&MicBuffer[MicBufferReadPos], 735); - MicBufferReadPos += 735; - } -} - -void Mic_SetExternalBuffer(s16* buffer, u32 len) -{ - MicBuffer = buffer; - MicBufferLength = len; - MicBufferReadPos = 0; -} - -} diff --git a/src/frontend/qt_sdl/AudioInOut.cpp b/src/frontend/qt_sdl/AudioInOut.cpp deleted file mode 100644 index 1f1ee1c5..00000000 --- a/src/frontend/qt_sdl/AudioInOut.cpp +++ /dev/null @@ -1,390 +0,0 @@ -/* - Copyright 2016-2023 melonDS team - - This file is part of melonDS. - - melonDS 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 3 of the License, or (at your option) - any later version. - - melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/. -*/ - -#include "AudioInOut.h" - -#include - -#include "FrontendUtil.h" -#include "Config.h" -#include "NDS.h" -#include "SPU.h" -#include "Platform.h" -#include "Input.h" -#include "main.h" - -using namespace melonDS; -namespace AudioInOut -{ - -SDL_AudioDeviceID audioDevice; -int audioFreq; -bool audioMuted; -SDL_cond* audioSync; -SDL_mutex* audioSyncLock; - -SDL_AudioDeviceID micDevice; -s16 micExtBuffer[2048]; -u32 micExtBufferWritePos; - -u32 micWavLength; -s16* micWavBuffer; - -void AudioCallback(void* data, Uint8* stream, int len) -{ - len /= (sizeof(s16) * 2); - - // resample incoming audio to match the output sample rate - - int len_in = Frontend::AudioOut_GetNumSamples(len); - s16 buf_in[1024*2]; - int num_in; - - EmuThread* emuThread = (EmuThread*)data; - SDL_LockMutex(audioSyncLock); - num_in = emuThread->NDS->SPU.ReadOutput(buf_in, len_in); - SDL_CondSignal(audioSync); - SDL_UnlockMutex(audioSyncLock); - - if ((num_in < 1) || audioMuted) - { - memset(stream, 0, len*sizeof(s16)*2); - return; - } - - int margin = 6; - if (num_in < len_in-margin) - { - int last = num_in-1; - - for (int i = num_in; i < len_in-margin; i++) - ((u32*)buf_in)[i] = ((u32*)buf_in)[last]; - - num_in = len_in-margin; - } - - Frontend::AudioOut_Resample(buf_in, num_in, (s16*)stream, len, Config::AudioVolume); -} - -void MicCallback(void* data, Uint8* stream, int len) -{ - s16* input = (s16*)stream; - len /= sizeof(s16); - - int maxlen = sizeof(micExtBuffer) / sizeof(s16); - - if ((micExtBufferWritePos + len) > maxlen) - { - u32 len1 = maxlen - micExtBufferWritePos; - memcpy(&micExtBuffer[micExtBufferWritePos], &input[0], len1*sizeof(s16)); - memcpy(&micExtBuffer[0], &input[len1], (len - len1)*sizeof(s16)); - micExtBufferWritePos = len - len1; - } - else - { - memcpy(&micExtBuffer[micExtBufferWritePos], input, len*sizeof(s16)); - micExtBufferWritePos += len; - } -} - -void AudioMute(QMainWindow* mainWindow) -{ - int inst = Platform::InstanceID(); - audioMuted = false; - - switch (Config::MPAudioMode) - { - case 1: // only instance 1 - if (inst > 0) audioMuted = true; - break; - - case 2: // only currently focused instance - if (mainWindow != nullptr) - audioMuted = !mainWindow->isActiveWindow(); - break; - } -} - - -void MicOpen() -{ - if (Config::MicInputType != micInputType_External) - { - micDevice = 0; - return; - } - - int numMics = SDL_GetNumAudioDevices(1); - if (numMics == 0) - return; - - SDL_AudioSpec whatIwant, whatIget; - memset(&whatIwant, 0, sizeof(SDL_AudioSpec)); - whatIwant.freq = 44100; - whatIwant.format = AUDIO_S16LSB; - whatIwant.channels = 1; - whatIwant.samples = 1024; - whatIwant.callback = MicCallback; - const char* mic = NULL; - if (Config::MicDevice != "") - { - mic = Config::MicDevice.c_str(); - } - micDevice = SDL_OpenAudioDevice(mic, 1, &whatIwant, &whatIget, 0); - if (!micDevice) - { - Platform::Log(Platform::LogLevel::Error, "Mic init failed: %s\n", SDL_GetError()); - } - else - { - SDL_PauseAudioDevice(micDevice, 0); - } -} - -void MicClose() -{ - if (micDevice) - SDL_CloseAudioDevice(micDevice); - - micDevice = 0; -} - -void MicLoadWav(const std::string& name) -{ - SDL_AudioSpec format; - memset(&format, 0, sizeof(SDL_AudioSpec)); - - if (micWavBuffer) delete[] micWavBuffer; - micWavBuffer = nullptr; - micWavLength = 0; - - u8* buf; - u32 len; - if (!SDL_LoadWAV(name.c_str(), &format, &buf, &len)) - return; - - const u64 dstfreq = 44100; - - int srcinc = format.channels; - len /= ((SDL_AUDIO_BITSIZE(format.format) / 8) * srcinc); - - micWavLength = (len * dstfreq) / format.freq; - if (micWavLength < 735) micWavLength = 735; - micWavBuffer = new s16[micWavLength]; - - float res_incr = len / (float)micWavLength; - float res_timer = 0; - int res_pos = 0; - - for (int i = 0; i < micWavLength; i++) - { - u16 val = 0; - - switch (SDL_AUDIO_BITSIZE(format.format)) - { - case 8: - val = buf[res_pos] << 8; - break; - - case 16: - if (SDL_AUDIO_ISBIGENDIAN(format.format)) - val = (buf[res_pos*2] << 8) | buf[res_pos*2 + 1]; - else - val = (buf[res_pos*2 + 1] << 8) | buf[res_pos*2]; - break; - - case 32: - if (SDL_AUDIO_ISFLOAT(format.format)) - { - u32 rawval; - if (SDL_AUDIO_ISBIGENDIAN(format.format)) - rawval = (buf[res_pos*4] << 24) | (buf[res_pos*4 + 1] << 16) | (buf[res_pos*4 + 2] << 8) | buf[res_pos*4 + 3]; - else - rawval = (buf[res_pos*4 + 3] << 24) | (buf[res_pos*4 + 2] << 16) | (buf[res_pos*4 + 1] << 8) | buf[res_pos*4]; - - float fval = *(float*)&rawval; - s32 ival = (s32)(fval * 0x8000); - ival = std::clamp(ival, -0x8000, 0x7FFF); - val = (s16)ival; - } - else if (SDL_AUDIO_ISBIGENDIAN(format.format)) - val = (buf[res_pos*4] << 8) | buf[res_pos*4 + 1]; - else - val = (buf[res_pos*4 + 3] << 8) | buf[res_pos*4 + 2]; - break; - } - - if (SDL_AUDIO_ISUNSIGNED(format.format)) - val ^= 0x8000; - - micWavBuffer[i] = val; - - res_timer += res_incr; - while (res_timer >= 1.0) - { - res_timer -= 1.0; - res_pos += srcinc; - } - } - - SDL_FreeWAV(buf); -} - -void MicProcess(melonDS::NDS& nds) -{ - int type = Config::MicInputType; - bool cmd = Input::HotkeyDown(HK_Mic); - - if (type != micInputType_External && !cmd) - { - type = micInputType_Silence; - } - - switch (type) - { - case micInputType_Silence: // no mic - Frontend::Mic_FeedSilence(nds); - break; - - case micInputType_External: // host mic - case micInputType_Wav: // WAV - Frontend::Mic_FeedExternalBuffer(nds); - break; - - case micInputType_Noise: // blowing noise - Frontend::Mic_FeedNoise(nds); - break; - } -} - -void SetupMicInputData() -{ - if (micWavBuffer != nullptr) - { - delete[] micWavBuffer; - micWavBuffer = nullptr; - micWavLength = 0; - } - - switch (Config::MicInputType) - { - case micInputType_Silence: - case micInputType_Noise: - Frontend::Mic_SetExternalBuffer(NULL, 0); - break; - case micInputType_External: - Frontend::Mic_SetExternalBuffer(micExtBuffer, sizeof(micExtBuffer)/sizeof(s16)); - break; - case micInputType_Wav: - MicLoadWav(Config::MicWavPath); - Frontend::Mic_SetExternalBuffer(micWavBuffer, micWavLength); - break; - } -} - -void Init(EmuThread* thread) -{ - audioMuted = false; - audioSync = SDL_CreateCond(); - audioSyncLock = SDL_CreateMutex(); - - audioFreq = 48000; // TODO: make configurable? - SDL_AudioSpec whatIwant, whatIget; - memset(&whatIwant, 0, sizeof(SDL_AudioSpec)); - whatIwant.freq = audioFreq; - whatIwant.format = AUDIO_S16LSB; - whatIwant.channels = 2; - whatIwant.samples = 1024; - whatIwant.callback = AudioCallback; - whatIwant.userdata = thread; - audioDevice = SDL_OpenAudioDevice(NULL, 0, &whatIwant, &whatIget, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); - if (!audioDevice) - { - Platform::Log(Platform::LogLevel::Error, "Audio init failed: %s\n", SDL_GetError()); - } - else - { - audioFreq = whatIget.freq; - Platform::Log(Platform::LogLevel::Info, "Audio output frequency: %d Hz\n", audioFreq); - SDL_PauseAudioDevice(audioDevice, 1); - } - - micDevice = 0; - - memset(micExtBuffer, 0, sizeof(micExtBuffer)); - micExtBufferWritePos = 0; - micWavBuffer = nullptr; - - Frontend::Init_Audio(audioFreq); - - SetupMicInputData(); -} - -void DeInit() -{ - if (audioDevice) SDL_CloseAudioDevice(audioDevice); - audioDevice = 0; - MicClose(); - - if (audioSync) SDL_DestroyCond(audioSync); - audioSync = nullptr; - - if (audioSyncLock) SDL_DestroyMutex(audioSyncLock); - audioSyncLock = nullptr; - - if (micWavBuffer) delete[] micWavBuffer; - micWavBuffer = nullptr; -} - -void AudioSync(NDS& nds) -{ - if (audioDevice) - { - SDL_LockMutex(audioSyncLock); - while (nds.SPU.GetOutputSize() > 1024) - { - int ret = SDL_CondWaitTimeout(audioSync, audioSyncLock, 500); - if (ret == SDL_MUTEX_TIMEDOUT) break; - } - SDL_UnlockMutex(audioSyncLock); - } -} - -void UpdateSettings(NDS& nds) -{ - MicClose(); - - nds.SPU.SetInterpolation(static_cast(Config::AudioInterp)); - SetupMicInputData(); - - MicOpen(); -} - -void Enable() -{ - if (audioDevice) SDL_PauseAudioDevice(audioDevice, 0); - MicOpen(); -} - -void Disable() -{ - if (audioDevice) SDL_PauseAudioDevice(audioDevice, 1); - MicClose(); -} - -} diff --git a/src/frontend/qt_sdl/AudioInOut.h b/src/frontend/qt_sdl/AudioInOut.h deleted file mode 100644 index 0bf36540..00000000 --- a/src/frontend/qt_sdl/AudioInOut.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - Copyright 2016-2023 melonDS team - - This file is part of melonDS. - - melonDS 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 3 of the License, or (at your option) - any later version. - - melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/. -*/ - -#ifndef AUDIO_INOUT_H -#define AUDIO_INOUT_H - -#include "types.h" - -#include - -class EmuThread; -namespace melonDS -{ -class NDS; -} -namespace AudioInOut -{ - -void Init(EmuThread* thread); -void DeInit(); - -void MicProcess(melonDS::NDS& nds); -void AudioMute(QMainWindow* mainWindow); - -void AudioSync(melonDS::NDS& nds); - -void UpdateSettings(melonDS::NDS& nds); - -void Enable(); -void Disable(); - -} - -#endif diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 90caea20..58cfce2c 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -8,6 +8,7 @@ set(SOURCES_QT_SDL Screen.cpp Window.cpp EmuInstance.cpp + EmuInstanceAudio.cpp EmuThread.cpp CheatsDialog.cpp Config.cpp @@ -39,13 +40,11 @@ set(SOURCES_QT_SDL QPathInput.h SaveManager.cpp CameraManager.cpp - AudioInOut.cpp ArchiveUtil.h ArchiveUtil.cpp ../Util_Video.cpp - ../Util_Audio.cpp ../FrontendUtil.h ../mic_blow.h @@ -56,8 +55,6 @@ set(SOURCES_QT_SDL CLI.h CLI.cpp - EmuInstance.cpp - EmuInstance.h ) if (APPLE) diff --git a/src/frontend/qt_sdl/EmuInstance.cpp b/src/frontend/qt_sdl/EmuInstance.cpp index ad89aebd..4b33af1f 100644 --- a/src/frontend/qt_sdl/EmuInstance.cpp +++ b/src/frontend/qt_sdl/EmuInstance.cpp @@ -66,6 +66,8 @@ EmuInstance::EmuInstance(int inst) : instanceID(inst), globalCfg(Config::GetGlobalTable()), localCfg(Config::GetLocalTable(inst)) { + audioInit(); + emuThread = new EmuThread(this); numWindows = 0; @@ -87,6 +89,8 @@ EmuInstance::~EmuInstance() emuThread->emuStop(); emuThread->wait(); delete emuThread; + + audioDeInit(); } diff --git a/src/frontend/qt_sdl/EmuInstance.h b/src/frontend/qt_sdl/EmuInstance.h index e7854bab..185ba695 100644 --- a/src/frontend/qt_sdl/EmuInstance.h +++ b/src/frontend/qt_sdl/EmuInstance.h @@ -19,6 +19,8 @@ #ifndef EMUINSTANCE_H #define EMUINSTANCE_H +#include + #include "NDS.h" #include "EmuThread.h" #include "Window.h" @@ -105,6 +107,26 @@ private: bool gbaCartInserted(); QString gbaCartLabel(); + void audioInit(); + void audioDeInit(); + void audioEnable(); + void audioDisable(); + void audioMute(); + void audioSync(); + void audioUpdateSettings(); + + void micOpen(); + void micClose(); + void micLoadWav(const std::string& name); + void micProcess(); + void setupMicInputData(); + + int audioGetNumSamplesOut(int outlen); + void audioResample(melonDS::s16* inbuf, int inlen, melonDS::s16* outbuf, int outlen, int volume); + + static void audioCallback(void* data, Uint8* stream, int len); + static void micCallback(void* data, Uint8* stream, int len); + int instanceID; EmuThread* emuThread; @@ -142,6 +164,24 @@ private: melonDS::ARCodeFile* cheatFile; bool cheatsOn; + SDL_AudioDeviceID audioDevice; + int audioFreq; + float audioSampleFrac; + bool audioMuted; + SDL_cond* audioSyncCond; + SDL_mutex* audioSyncLock; + + SDL_AudioDeviceID micDevice; + melonDS::s16 micExtBuffer[2048]; + melonDS::u32 micExtBufferWritePos; + + melonDS::u32 micWavLength; + melonDS::s16* micWavBuffer; + + melonDS::s16* micBuffer; + melonDS::u32 micBufferLength; + melonDS::u32 micBufferReadPos; + friend class EmuThread; friend class MainWindow; }; diff --git a/src/frontend/qt_sdl/EmuInstanceAudio.cpp b/src/frontend/qt_sdl/EmuInstanceAudio.cpp new file mode 100644 index 00000000..a9689703 --- /dev/null +++ b/src/frontend/qt_sdl/EmuInstanceAudio.cpp @@ -0,0 +1,451 @@ +/* + Copyright 2016-2023 melonDS team + + This file is part of melonDS. + + melonDS 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 3 of the License, or (at your option) + any later version. + + melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include "Config.h" +#include "NDS.h" +#include "SPU.h" +#include "Platform.h" +#include "Input.h" +#include "main.h" + +#include "mic_blow.h" + +using namespace melonDS; + + +int EmuInstance::audioGetNumSamplesOut(int outlen) +{ + float f_len_in = (outlen * 32823.6328125) / (float)audioFreq; + f_len_in += audioSampleFrac; + int len_in = (int)floor(f_len_in); + audioSampleFrac = f_len_in - len_in; + + return len_in; +} + +void EmuInstance::audioResample(s16* inbuf, int inlen, s16* outbuf, int outlen, int volume) +{ + float res_incr = inlen / (float)outlen; + float res_timer = 0; + int res_pos = 0; + + for (int i = 0; i < outlen; i++) + { + outbuf[i*2 ] = (inbuf[res_pos*2 ] * volume) >> 8; + outbuf[i*2+1] = (inbuf[res_pos*2+1] * volume) >> 8; + + res_timer += res_incr; + while (res_timer >= 1.0) + { + res_timer -= 1.0; + res_pos++; + } + } +} + +void EmuInstance::audioCallback(void* data, Uint8* stream, int len) +{ + EmuInstance* inst = (EmuInstance*)data; + len /= (sizeof(s16) * 2); + + // resample incoming audio to match the output sample rate + + int len_in = inst->audioGetNumSamplesOut(len); + s16 buf_in[1024*2]; + int num_in; + + SDL_LockMutex(inst->audioSyncLock); + num_in = inst->nds->SPU.ReadOutput(buf_in, len_in); + SDL_CondSignal(inst->audioSyncCond); + SDL_UnlockMutex(inst->audioSyncLock); + + if ((num_in < 1) || inst->audioMuted) + { + memset(stream, 0, len*sizeof(s16)*2); + return; + } + + int margin = 6; + if (num_in < len_in-margin) + { + int last = num_in-1; + + for (int i = num_in; i < len_in-margin; i++) + ((u32*)buf_in)[i] = ((u32*)buf_in)[last]; + + num_in = len_in-margin; + } + + inst->audioResample(buf_in, num_in, (s16*)stream, len, Config::AudioVolume); +} + +void EmuInstance::micCallback(void* data, Uint8* stream, int len) +{ + EmuInstance* inst = (EmuInstance*)data; + s16* input = (s16*)stream; + len /= sizeof(s16); + + int maxlen = sizeof(micExtBuffer) / sizeof(s16); + + if ((inst->micExtBufferWritePos + len) > maxlen) + { + u32 len1 = maxlen - inst->micExtBufferWritePos; + memcpy(&inst->micExtBuffer[inst->micExtBufferWritePos], &input[0], len1*sizeof(s16)); + memcpy(&inst->micExtBuffer[0], &input[len1], (len - len1)*sizeof(s16)); + inst->micExtBufferWritePos = len - len1; + } + else + { + memcpy(&inst->micExtBuffer[inst->micExtBufferWritePos], input, len*sizeof(s16)); + inst->micExtBufferWritePos += len; + } +} + +void EmuInstance::audioMute() +{ + int inst = Platform::InstanceID(); + audioMuted = false; + + switch (Config::MPAudioMode) + { + case 1: // only instance 1 + if (inst > 0) audioMuted = true; + break; + + case 2: // only currently focused instance + //if (mainWindow != nullptr) + // audioMuted = !mainWindow->isActiveWindow(); + // TODO!! + printf("TODO!! audioMute mode 2\n"); + break; + } +} + + +void EmuInstance::micOpen() +{ + if (Config::MicInputType != micInputType_External) + { + micDevice = 0; + return; + } + + int numMics = SDL_GetNumAudioDevices(1); + if (numMics == 0) + return; + + SDL_AudioSpec whatIwant, whatIget; + memset(&whatIwant, 0, sizeof(SDL_AudioSpec)); + whatIwant.freq = 44100; + whatIwant.format = AUDIO_S16LSB; + whatIwant.channels = 1; + whatIwant.samples = 1024; + whatIwant.callback = micCallback; + whatIwant.userdata = this; + const char* mic = NULL; + if (Config::MicDevice != "") + { + mic = Config::MicDevice.c_str(); + } + micDevice = SDL_OpenAudioDevice(mic, 1, &whatIwant, &whatIget, 0); + if (!micDevice) + { + Platform::Log(Platform::LogLevel::Error, "Mic init failed: %s\n", SDL_GetError()); + } + else + { + SDL_PauseAudioDevice(micDevice, 0); + } +} + +void EmuInstance::micClose() +{ + if (micDevice) + SDL_CloseAudioDevice(micDevice); + + micDevice = 0; +} + +void EmuInstance::micLoadWav(const std::string& name) +{ + SDL_AudioSpec format; + memset(&format, 0, sizeof(SDL_AudioSpec)); + + if (micWavBuffer) delete[] micWavBuffer; + micWavBuffer = nullptr; + micWavLength = 0; + + u8* buf; + u32 len; + if (!SDL_LoadWAV(name.c_str(), &format, &buf, &len)) + return; + + const u64 dstfreq = 44100; + + int srcinc = format.channels; + len /= ((SDL_AUDIO_BITSIZE(format.format) / 8) * srcinc); + + micWavLength = (len * dstfreq) / format.freq; + if (micWavLength < 735) micWavLength = 735; + micWavBuffer = new s16[micWavLength]; + + float res_incr = len / (float)micWavLength; + float res_timer = 0; + int res_pos = 0; + + for (int i = 0; i < micWavLength; i++) + { + u16 val = 0; + + switch (SDL_AUDIO_BITSIZE(format.format)) + { + case 8: + val = buf[res_pos] << 8; + break; + + case 16: + if (SDL_AUDIO_ISBIGENDIAN(format.format)) + val = (buf[res_pos*2] << 8) | buf[res_pos*2 + 1]; + else + val = (buf[res_pos*2 + 1] << 8) | buf[res_pos*2]; + break; + + case 32: + if (SDL_AUDIO_ISFLOAT(format.format)) + { + u32 rawval; + if (SDL_AUDIO_ISBIGENDIAN(format.format)) + rawval = (buf[res_pos*4] << 24) | (buf[res_pos*4 + 1] << 16) | (buf[res_pos*4 + 2] << 8) | buf[res_pos*4 + 3]; + else + rawval = (buf[res_pos*4 + 3] << 24) | (buf[res_pos*4 + 2] << 16) | (buf[res_pos*4 + 1] << 8) | buf[res_pos*4]; + + float fval = *(float*)&rawval; + s32 ival = (s32)(fval * 0x8000); + ival = std::clamp(ival, -0x8000, 0x7FFF); + val = (s16)ival; + } + else if (SDL_AUDIO_ISBIGENDIAN(format.format)) + val = (buf[res_pos*4] << 8) | buf[res_pos*4 + 1]; + else + val = (buf[res_pos*4 + 3] << 8) | buf[res_pos*4 + 2]; + break; + } + + if (SDL_AUDIO_ISUNSIGNED(format.format)) + val ^= 0x8000; + + micWavBuffer[i] = val; + + res_timer += res_incr; + while (res_timer >= 1.0) + { + res_timer -= 1.0; + res_pos += srcinc; + } + } + + SDL_FreeWAV(buf); +} + +void EmuInstance::micProcess() +{ + int type = Config::MicInputType; + bool cmd = Input::HotkeyDown(HK_Mic); + + if (type != micInputType_External && !cmd) + { + type = micInputType_Silence; + } + + switch (type) + { + case micInputType_Silence: // no mic + micBufferReadPos = 0; + nds->MicInputFrame(nullptr, 0); + break; + + case micInputType_External: // host mic + case micInputType_Wav: // WAV + if (micBuffer) + { + if ((micBufferReadPos + 735) > micBufferLength) + { + s16 tmp[735]; + u32 len1 = micBufferLength - micBufferReadPos; + memcpy(&tmp[0], &micBuffer[micBufferReadPos], len1*sizeof(s16)); + memcpy(&tmp[len1], &micBuffer[0], (735 - len1)*sizeof(s16)); + + nds->MicInputFrame(tmp, 735); + micBufferReadPos = 735 - len1; + } + else + { + nds->MicInputFrame(&micBuffer[micBufferReadPos], 735); + micBufferReadPos += 735; + } + } + else + { + micBufferReadPos = 0; + nds->MicInputFrame(nullptr, 0); + } + break; + + case micInputType_Noise: // blowing noise + { + int sample_len = sizeof(mic_blow) / sizeof(u16); + static int sample_pos = 0; + + s16 tmp[735]; + + for (int i = 0; i < 735; i++) + { + tmp[i] = mic_blow[sample_pos]; + sample_pos++; + if (sample_pos >= sample_len) sample_pos = 0; + } + + nds->MicInputFrame(tmp, 735); + } + break; + } +} + +void EmuInstance::setupMicInputData() +{ + if (micWavBuffer != nullptr) + { + delete[] micWavBuffer; + micWavBuffer = nullptr; + micWavLength = 0; + } + + switch (Config::MicInputType) + { + case micInputType_Silence: + case micInputType_Noise: + micBuffer = nullptr; + micBufferLength = 0; + break; + case micInputType_External: + micBuffer = micExtBuffer; + micBufferLength = sizeof(micExtBuffer)/sizeof(s16); + break; + case micInputType_Wav: + micLoadWav(Config::MicWavPath); + micBuffer = micWavBuffer; + micBufferLength = micWavLength; + break; + } + + micBufferReadPos = 0; +} + +void EmuInstance::audioInit() +{ + audioMuted = false; + audioSyncCond = SDL_CreateCond(); + audioSyncLock = SDL_CreateMutex(); + + audioFreq = 48000; // TODO: make configurable? + SDL_AudioSpec whatIwant, whatIget; + memset(&whatIwant, 0, sizeof(SDL_AudioSpec)); + whatIwant.freq = audioFreq; + whatIwant.format = AUDIO_S16LSB; + whatIwant.channels = 2; + whatIwant.samples = 1024; + whatIwant.callback = audioCallback; + whatIwant.userdata = this; + audioDevice = SDL_OpenAudioDevice(NULL, 0, &whatIwant, &whatIget, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + if (!audioDevice) + { + Platform::Log(Platform::LogLevel::Error, "Audio init failed: %s\n", SDL_GetError()); + } + else + { + audioFreq = whatIget.freq; + Platform::Log(Platform::LogLevel::Info, "Audio output frequency: %d Hz\n", audioFreq); + SDL_PauseAudioDevice(audioDevice, 1); + } + + audioSampleFrac = 0; + + micDevice = 0; + + memset(micExtBuffer, 0, sizeof(micExtBuffer)); + micExtBufferWritePos = 0; + micWavBuffer = nullptr; + + micBuffer = nullptr; + micBufferLength = 0; + micBufferReadPos = 0; + + setupMicInputData(); +} + +void EmuInstance::audioDeInit() +{ + if (audioDevice) SDL_CloseAudioDevice(audioDevice); + audioDevice = 0; + micClose(); + + if (audioSyncCond) SDL_DestroyCond(audioSyncCond); + audioSyncCond = nullptr; + + if (audioSyncLock) SDL_DestroyMutex(audioSyncLock); + audioSyncLock = nullptr; + + if (micWavBuffer) delete[] micWavBuffer; + micWavBuffer = nullptr; +} + +void EmuInstance::audioSync() +{ + if (audioDevice) + { + SDL_LockMutex(audioSyncLock); + while (nds->SPU.GetOutputSize() > 1024) + { + int ret = SDL_CondWaitTimeout(audioSyncCond, audioSyncLock, 500); + if (ret == SDL_MUTEX_TIMEDOUT) break; + } + SDL_UnlockMutex(audioSyncLock); + } +} + +void EmuInstance::audioUpdateSettings() +{ + micClose(); + + nds->SPU.SetInterpolation(static_cast(Config::AudioInterp)); + setupMicInputData(); + + micOpen(); +} + +void EmuInstance::audioEnable() +{ + if (audioDevice) SDL_PauseAudioDevice(audioDevice, 0); + micOpen(); +} + +void EmuInstance::audioDisable() +{ + if (audioDevice) SDL_PauseAudioDevice(audioDevice, 1); + micClose(); +} diff --git a/src/frontend/qt_sdl/EmuThread.cpp b/src/frontend/qt_sdl/EmuThread.cpp index d05661c0..2ebc5408 100644 --- a/src/frontend/qt_sdl/EmuThread.cpp +++ b/src/frontend/qt_sdl/EmuThread.cpp @@ -30,7 +30,6 @@ #include "main.h" #include "Input.h" -#include "AudioInOut.h" #include "types.h" #include "version.h" @@ -329,7 +328,7 @@ void EmuThread::run() } // microphone input - //AudioInOut::MicProcess(emuInstance->nds); + emuInstance->micProcess(); // auto screen layout if (Config::ScreenSizing == Frontend::screenSizing_Auto) @@ -423,8 +422,8 @@ void EmuThread::run() Config::AudioVolume = volumeLevel * (256.0 / 31.0); } - //if (Config::AudioSync && !fastforward) - // AudioInOut::AudioSync(emuInstance->nds); + if (Config::AudioSync && !fastforward) + emuInstance->audioSync(); double frametimeStep = nlines / (60.0 * 263.0); @@ -554,7 +553,7 @@ void EmuThread::emuRun() // checkme emit windowEmuStart(); - AudioInOut::Enable(); + emuInstance->audioEnable(); } void EmuThread::initContext() @@ -578,7 +577,7 @@ void EmuThread::emuPause() EmuRunning = emuStatus_Paused; while (EmuStatus != emuStatus_Paused); - AudioInOut::Disable(); + emuInstance->audioDisable(); } void EmuThread::emuUnpause() @@ -590,7 +589,7 @@ void EmuThread::emuUnpause() EmuRunning = PrevEmuStatus; - AudioInOut::Enable(); + emuInstance->audioEnable(); } void EmuThread::emuStop() @@ -598,7 +597,7 @@ void EmuThread::emuStop() EmuRunning = emuStatus_Exit; EmuPauseStack = EmuPauseStackRunning; - AudioInOut::Disable(); + emuInstance->audioDisable(); } void EmuThread::emuFrameStep() diff --git a/src/frontend/qt_sdl/Input.h b/src/frontend/qt_sdl/Input.h index 2dfa4a77..dd979143 100644 --- a/src/frontend/qt_sdl/Input.h +++ b/src/frontend/qt_sdl/Input.h @@ -20,6 +20,7 @@ #define INPUT_H #include +#include #include "types.h" diff --git a/src/frontend/qt_sdl/Window.cpp b/src/frontend/qt_sdl/Window.cpp index 80c0f9b0..5304a3e9 100644 --- a/src/frontend/qt_sdl/Window.cpp +++ b/src/frontend/qt_sdl/Window.cpp @@ -68,7 +68,6 @@ #include "RAMInfoDialog.h" #include "TitleManagerDialog.h" #include "PowerManagement/PowerManagementDialog.h" -#include "AudioInOut.h" #include "Platform.h" #include "Config.h" @@ -941,12 +940,12 @@ void MainWindow::dropEvent(QDropEvent* event) void MainWindow::focusInEvent(QFocusEvent* event) { - AudioInOut::AudioMute(mainWindow); + emuInstance->audioMute(); } void MainWindow::focusOutEvent(QFocusEvent* event) { - AudioInOut::AudioMute(mainWindow); + emuInstance->audioMute(); } void MainWindow::onAppStateChanged(Qt::ApplicationState state) @@ -1828,7 +1827,7 @@ void MainWindow::onOpenMPSettings() void MainWindow::onMPSettingsFinished(int res) { - AudioInOut::AudioMute(mainWindow); + emuInstance->audioMute(); LocalMP::SetRecvTimeout(Config::MPRecvTimeout); emuThread->emuUnpause(); diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index df1f72f7..965a008f 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -73,7 +73,6 @@ #include "RAMInfoDialog.h" #include "TitleManagerDialog.h" #include "PowerManagement/PowerManagementDialog.h" -#include "AudioInOut.h" #include "types.h" #include "version.h" @@ -428,7 +427,7 @@ int main(int argc, char** argv) Input::CloseJoystick(); - AudioInOut::DeInit(); + //AudioInOut::DeInit(); delete camManager[0]; delete camManager[1];