diff --git a/include/audio/aac_decoder.hpp b/include/audio/aac_decoder.hpp index 6538bce9..8964e1f2 100644 --- a/include/audio/aac_decoder.hpp +++ b/include/audio/aac_decoder.hpp @@ -18,7 +18,8 @@ namespace Audio::AAC { public: // Decode function. Takes in a reference to the AAC response & request, and a callback for paddr -> pointer conversions - void decode(AAC::Message& response, const AAC::Message& request, PaddrCallback paddrCallback); + // We also allow for optionally muting the AAC output (setting all of it to 0) instead of properly decoding it, for debug/research purposes + void decode(AAC::Message& response, const AAC::Message& request, PaddrCallback paddrCallback, bool enableAudio = true); ~Decoder(); }; } // namespace Audio::AAC \ No newline at end of file diff --git a/include/audio/dsp_core.hpp b/include/audio/dsp_core.hpp index 5f5efa22..f180e717 100644 --- a/include/audio/dsp_core.hpp +++ b/include/audio/dsp_core.hpp @@ -14,6 +14,7 @@ // The DSP core must have access to the DSP service to be able to trigger interrupts properly class DSPService; class Memory; +struct EmulatorConfig; namespace Audio { // There are 160 stereo samples in 1 audio frame, so 320 samples total @@ -31,6 +32,7 @@ namespace Audio { Memory& mem; Scheduler& scheduler; DSPService& dspService; + EmulatorConfig& settings; Samples sampleBuffer; bool audioEnabled = false; @@ -39,7 +41,8 @@ namespace Audio { public: enum class Type { Null, Teakra, HLE }; - DSPCore(Memory& mem, Scheduler& scheduler, DSPService& dspService) : mem(mem), scheduler(scheduler), dspService(dspService) {} + DSPCore(Memory& mem, Scheduler& scheduler, DSPService& dspService, EmulatorConfig& settings) + : mem(mem), scheduler(scheduler), dspService(dspService), settings(settings) {} virtual ~DSPCore() {} virtual void reset() = 0; @@ -62,5 +65,5 @@ namespace Audio { virtual void setAudioEnabled(bool enable) { audioEnabled = enable; } }; - std::unique_ptr makeDSPCore(DSPCore::Type type, Memory& mem, Scheduler& scheduler, DSPService& dspService); + std::unique_ptr makeDSPCore(EmulatorConfig& config, Memory& mem, Scheduler& scheduler, DSPService& dspService); } // namespace Audio \ No newline at end of file diff --git a/include/audio/hle_core.hpp b/include/audio/hle_core.hpp index 32bbaae8..0e3b8636 100644 --- a/include/audio/hle_core.hpp +++ b/include/audio/hle_core.hpp @@ -206,7 +206,7 @@ namespace Audio { SampleBuffer decodeADPCM(const u8* data, usize sampleCount, Source& source); public: - HLE_DSP(Memory& mem, Scheduler& scheduler, DSPService& dspService); + HLE_DSP(Memory& mem, Scheduler& scheduler, DSPService& dspService, EmulatorConfig& config); ~HLE_DSP() override {} void reset() override; diff --git a/include/audio/miniaudio_device.hpp b/include/audio/miniaudio_device.hpp index f3b212e2..516c1602 100644 --- a/include/audio/miniaudio_device.hpp +++ b/include/audio/miniaudio_device.hpp @@ -3,6 +3,7 @@ #include #include +#include "config.hpp" #include "helpers.hpp" #include "miniaudio.h" #include "ring_buffer.hpp" @@ -12,12 +13,13 @@ class MiniAudioDevice { static constexpr ma_uint32 sampleRate = 32768; // 3DS sample rate static constexpr ma_uint32 channelCount = 2; // Audio output is stereo + ma_device device; ma_context context; ma_device_config deviceConfig; - ma_device device; - ma_resampler resampler; Samples* samples = nullptr; + const AudioDeviceConfig& audioSettings; + bool initialized = false; bool running = false; @@ -26,7 +28,8 @@ class MiniAudioDevice { std::vector audioDevices; public: - MiniAudioDevice(); + MiniAudioDevice(const AudioDeviceConfig& audioSettings); + // If safe is on, we create a null audio device void init(Samples& samples, bool safe = false); void close(); diff --git a/include/audio/null_core.hpp b/include/audio/null_core.hpp index 7a0f368d..e7ae12dc 100644 --- a/include/audio/null_core.hpp +++ b/include/audio/null_core.hpp @@ -23,7 +23,7 @@ namespace Audio { bool loaded = false; // Have we loaded a component? public: - NullDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService) : DSPCore(mem, scheduler, dspService) {} + NullDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService, EmulatorConfig& config) : DSPCore(mem, scheduler, dspService, config) {} ~NullDSP() override {} void reset() override; diff --git a/include/audio/teakra_core.hpp b/include/audio/teakra_core.hpp index 17104985..3fd5abc3 100644 --- a/include/audio/teakra_core.hpp +++ b/include/audio/teakra_core.hpp @@ -77,7 +77,7 @@ namespace Audio { } public: - TeakraDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService); + TeakraDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService, EmulatorConfig& config); ~TeakraDSP() override {} void reset() override; diff --git a/include/config.hpp b/include/config.hpp index 81a03385..9364a33d 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -4,6 +4,19 @@ #include "audio/dsp_core.hpp" #include "renderer.hpp" +struct AudioDeviceConfig { + float volumeRaw = 1.0f; + bool muteAudio = false; + + float getVolume() const { + if (muteAudio) { + return 0.0f; + } + + return volumeRaw; + } +}; + // Remember to initialize every field here to its default value otherwise bad things will happen struct EmulatorConfig { // Only enable the shader JIT by default on platforms where it's completely tested @@ -41,6 +54,7 @@ struct EmulatorConfig { bool audioEnabled = false; bool vsyncEnabled = true; + bool aacEnabled = true; // Enable AAC audio? bool enableRenderdoc = false; bool printAppVersion = true; @@ -70,6 +84,7 @@ struct EmulatorConfig { }; WindowSettings windowSettings; + AudioDeviceConfig audioDeviceConfig; EmulatorConfig(const std::filesystem::path& path); void load(); diff --git a/src/config.cpp b/src/config.cpp index 4cd18858..833a1b4e 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -96,7 +96,14 @@ void EmulatorConfig::load() { auto dspCoreName = toml::find_or(audio, "DSPEmulation", "HLE"); dspType = Audio::DSPCore::typeFromString(dspCoreName); + audioEnabled = toml::find_or(audio, "EnableAudio", false); + aacEnabled = toml::find_or(audio, "EnableAACAudio", true); + + audioDeviceConfig.muteAudio = toml::find_or(audio, "MuteAudio", false); + // Our volume ranges from 0.0 (muted) to 2.0 (boosted, using a logarithmic scale). 1.0 is the "default" volume, ie we don't adjust the PCM + // samples at all. + audioDeviceConfig.volumeRaw = float(std::clamp(toml::find_or(audio, "AudioVolume", 1.0), 0.0, 2.0)); } } @@ -167,6 +174,9 @@ void EmulatorConfig::save() { data["Audio"]["DSPEmulation"] = std::string(Audio::DSPCore::typeToString(dspType)); data["Audio"]["EnableAudio"] = audioEnabled; + data["Audio"]["EnableAACAudio"] = aacEnabled; + data["Audio"]["MuteAudio"] = audioDeviceConfig.muteAudio; + data["Audio"]["AudioVolume"] = double(audioDeviceConfig.volumeRaw); data["Battery"]["ChargerPlugged"] = chargerPlugged; data["Battery"]["BatteryPercentage"] = batteryPercentage; diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 142ff8c8..ddec3a36 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -370,12 +370,11 @@ void ShaderEmitter::storeRegister(Xmm source, const PICAShader& shader, u32 dest } else if (haveSSE4_1) { // Bit reverse the write mask because that is what blendps expects u32 adjustedMask = ((writeMask >> 3) & 0b1) | ((writeMask >> 1) & 0b10) | ((writeMask << 1) & 0b100) | ((writeMask << 3) & 0b1000); - // Don't accidentally overwrite scratch1 if that is what we're writing derp - Xmm temp = (source == scratch1) ? scratch2 : scratch1; - movaps(temp, xword[statePointer + offset]); // Read current value of dest - blendps(temp, source, adjustedMask); // Blend with source - movaps(xword[statePointer + offset], temp); // Write back + // Blend current value of dest with source. We have to invert the bits of the mask, as we do blendps source, dest instead of dest, source + // Note: This destroys source + blendps(source, xword[statePointer + offset], adjustedMask ^ 0xF); + movaps(xword[statePointer + offset], source); // Write back } else { // Blend algo referenced from Citra const u8 selector = (((writeMask & 0b1000) ? 1 : 0) << 0) | diff --git a/src/core/audio/aac_decoder.cpp b/src/core/audio/aac_decoder.cpp index 281539d8..af88485c 100644 --- a/src/core/audio/aac_decoder.cpp +++ b/src/core/audio/aac_decoder.cpp @@ -5,7 +5,7 @@ #include using namespace Audio; -void AAC::Decoder::decode(AAC::Message& response, const AAC::Message& request, AAC::Decoder::PaddrCallback paddrCallback) { +void AAC::Decoder::decode(AAC::Message& response, const AAC::Message& request, AAC::Decoder::PaddrCallback paddrCallback, bool enableAudio) { // Copy the command and mode fields of the request to the response response.command = request.command; response.mode = request.mode; @@ -95,9 +95,16 @@ void AAC::Decoder::decode(AAC::Message& response, const AAC::Message& request, A } } - for (int sample = 0; sample < info->frameSize; sample++) { + if (enableAudio) { + for (int sample = 0; sample < info->frameSize; sample++) { + for (int stream = 0; stream < channels; stream++) { + audioStreams[stream].push_back(frame[(sample * channels) + stream]); + } + } + } else { + // If audio is not enabled, push 0s for (int stream = 0; stream < channels; stream++) { - audioStreams[stream].push_back(frame[(sample * channels) + stream]); + audioStreams[stream].resize(audioStreams[stream].size() + info->frameSize, 0); } } } else { diff --git a/src/core/audio/dsp_core.cpp b/src/core/audio/dsp_core.cpp index 01cee11e..c793fcf8 100644 --- a/src/core/audio/dsp_core.cpp +++ b/src/core/audio/dsp_core.cpp @@ -8,17 +8,17 @@ #include "audio/null_core.hpp" #include "audio/teakra_core.hpp" -std::unique_ptr Audio::makeDSPCore(DSPCore::Type type, Memory& mem, Scheduler& scheduler, DSPService& dspService) { +std::unique_ptr Audio::makeDSPCore(EmulatorConfig& config, Memory& mem, Scheduler& scheduler, DSPService& dspService) { std::unique_ptr core; - switch (type) { - case DSPCore::Type::Null: core = std::make_unique(mem, scheduler, dspService); break; - case DSPCore::Type::Teakra: core = std::make_unique(mem, scheduler, dspService); break; - case DSPCore::Type::HLE: core = std::make_unique(mem, scheduler, dspService); break; + switch (config.dspType) { + case DSPCore::Type::Null: core = std::make_unique(mem, scheduler, dspService, config); break; + case DSPCore::Type::Teakra: core = std::make_unique(mem, scheduler, dspService, config); break; + case DSPCore::Type::HLE: core = std::make_unique(mem, scheduler, dspService, config); break; default: Helpers::warn("Invalid DSP core selected!"); - core = std::make_unique(mem, scheduler, dspService); + core = std::make_unique(mem, scheduler, dspService, config); break; } diff --git a/src/core/audio/hle_core.cpp b/src/core/audio/hle_core.cpp index 7e82a139..73d39adb 100644 --- a/src/core/audio/hle_core.cpp +++ b/src/core/audio/hle_core.cpp @@ -8,6 +8,7 @@ #include "audio/aac_decoder.hpp" #include "audio/dsp_simd.hpp" +#include "config.hpp" #include "services/dsp.hpp" namespace Audio { @@ -20,7 +21,8 @@ namespace Audio { }; } - HLE_DSP::HLE_DSP(Memory& mem, Scheduler& scheduler, DSPService& dspService) : DSPCore(mem, scheduler, dspService) { + HLE_DSP::HLE_DSP(Memory& mem, Scheduler& scheduler, DSPService& dspService, EmulatorConfig& config) + : DSPCore(mem, scheduler, dspService, config) { // Set up source indices for (int i = 0; i < sources.size(); i++) { sources[i].index = i; @@ -702,24 +704,9 @@ namespace Audio { AAC::Message response; switch (request.command) { - case AAC::Command::EncodeDecode: { - // Dummy response to stop games from hanging - response.resultCode = AAC::ResultCode::Success; - response.decodeResponse.channelCount = 2; - response.decodeResponse.sampleCount = 1024; - response.decodeResponse.size = 0; - response.decodeResponse.sampleRate = AAC::SampleRate::Rate48000; - - response.command = request.command; - response.mode = request.mode; - - // TODO: Make this a toggle in config.toml. Currently we have it on by default. - constexpr bool enableAAC = true; - if (enableAAC) { - aacDecoder->decode(response, request, [this](u32 paddr) { return getPointerPhys(paddr); }); - } + case AAC::Command::EncodeDecode: + aacDecoder->decode(response, request, [this](u32 paddr) { return getPointerPhys(paddr); }, settings.aacEnabled); break; - } case AAC::Command::Init: case AAC::Command::Shutdown: diff --git a/src/core/audio/miniaudio_device.cpp b/src/core/audio/miniaudio_device.cpp index cd537aef..b513450e 100644 --- a/src/core/audio/miniaudio_device.cpp +++ b/src/core/audio/miniaudio_device.cpp @@ -1,10 +1,14 @@ #include "audio/miniaudio_device.hpp" +#include +#include #include +#include #include "helpers.hpp" -MiniAudioDevice::MiniAudioDevice() : initialized(false), running(false), samples(nullptr) {} +MiniAudioDevice::MiniAudioDevice(const AudioDeviceConfig& audioSettings) + : initialized(false), running(false), samples(nullptr), audioSettings(audioSettings) {} void MiniAudioDevice::init(Samples& samples, bool safe) { this->samples = &samples; @@ -106,6 +110,40 @@ void MiniAudioDevice::init(Samples& samples, bool safe) { std::memcpy(&self->lastStereoSample[0], &output[(samplesWritten - 1) * 2], sizeof(lastStereoSample)); } + // Adjust the volume of our samples based on the emulator's volume slider + float audioVolume = self->audioSettings.getVolume(); + // If volume is 1.0 we don't need to do anything + if (audioVolume != 1.0f) { + s16* sample = output; + + // If our volume is > 1.0 then we boost samples using a logarithmic scale, + // In this case we also have to clamp samples to make sure they don't wrap around + if (audioVolume > 1.0f) { + audioVolume = 0.6 + 20 * std::log10(audioVolume); + + constexpr s32 min = s32(std::numeric_limits::min()); + constexpr s32 max = s32(std::numeric_limits::max()); + + for (usize i = 0; i < samplesWritten; i += 2) { + s16 l = s16(std::clamp(s32(float(sample[0]) * audioVolume), min, max)); + s16 r = s16(std::clamp(s32(float(sample[1]) * audioVolume), min, max)); + + *sample++ = l; + *sample++ = r; + } + } else { + // If our volume is in [0.0, 1.0) then just multiply by the volume. No need to clamp, since there is no danger of our samples wrapping + // around due to overflow + for (usize i = 0; i < samplesWritten; i += 2) { + s16 l = s16(float(sample[0]) * audioVolume); + s16 r = s16(float(sample[1]) * audioVolume); + + *sample++ = l; + *sample++ = r; + } + } + } + // If underruning, copy the last output sample { s16* pointer = &output[samplesWritten * 2]; diff --git a/src/core/audio/teakra_core.cpp b/src/core/audio/teakra_core.cpp index da2e5a5a..cf1484f8 100644 --- a/src/core/audio/teakra_core.cpp +++ b/src/core/audio/teakra_core.cpp @@ -36,8 +36,8 @@ struct Dsp1 { Segment segments[10]; }; -TeakraDSP::TeakraDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService) - : DSPCore(mem, scheduler, dspService), pipeBaseAddr(0), running(false) { +TeakraDSP::TeakraDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService, EmulatorConfig& config) + : DSPCore(mem, scheduler, dspService, config), pipeBaseAddr(0), running(false) { // Set up callbacks for Teakra Teakra::AHBMCallback ahbm; diff --git a/src/emulator.cpp b/src/emulator.cpp index 9851adb1..fc25eacb 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -20,7 +20,7 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1; Emulator::Emulator() : config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel, *this), gpu(memory, config), memory(cpu.getTicksRef(), config), - cheats(memory, kernel.getServiceManager().getHID()), lua(*this), running(false) + cheats(memory, kernel.getServiceManager().getHID()), audioDevice(config.audioDeviceConfig), lua(*this), running(false) #ifdef PANDA3DS_ENABLE_HTTP_SERVER , httpServer(this) @@ -28,7 +28,7 @@ Emulator::Emulator() { DSPService& dspService = kernel.getServiceManager().getDSP(); - dsp = Audio::makeDSPCore(config.dspType, memory, scheduler, dspService); + dsp = Audio::makeDSPCore(config, memory, scheduler, dspService); dspService.setDSPCore(dsp.get()); audioDevice.init(dsp->getSamples()); diff --git a/src/libretro_core.cpp b/src/libretro_core.cpp index 319b0a72..1e5b532d 100644 --- a/src/libretro_core.cpp +++ b/src/libretro_core.cpp @@ -172,12 +172,16 @@ static void configInit() { {"panda3ds_use_vsync", "Enable VSync; enabled|disabled"}, {"panda3ds_dsp_emulation", "DSP emulation; HLE|LLE|Null"}, {"panda3ds_use_audio", "Enable audio; disabled|enabled"}, + {"panda3ds_audio_volume", "Audio volume; 100|0|10|20|40|60|80|90|100|120|140|150|180|200"}, + {"panda3ds_mute_audio", "Mute audio; disabled|enabled"}, + {"panda3ds_enable_aac", "Enable AAC audio; enabled|disabled"}, + + {"panda3ds_ubershader_lighting_override", "Force shadergen when rendering lights; enabled|disabled"}, + {"panda3ds_ubershader_lighting_override_threshold", "Light threshold for forcing shadergen; 1|2|3|4|5|6|7|8"}, {"panda3ds_use_virtual_sd", "Enable virtual SD card; enabled|disabled"}, {"panda3ds_write_protect_virtual_sd", "Write protect virtual SD card; disabled|enabled"}, {"panda3ds_battery_level", "Battery percentage; 5|10|20|30|50|70|90|100"}, {"panda3ds_use_charger", "Charger plugged; enabled|disabled"}, - {"panda3ds_ubershader_lighting_override", "Force shadergen when rendering lights; enabled|disabled"}, - {"panda3ds_ubershader_lighting_override_threshold", "Light threshold for forcing shadergen; 1|2|3|4|5|6|7|8"}, {nullptr, nullptr}, }; @@ -194,6 +198,10 @@ static void configUpdate() { config.batteryPercentage = fetchVariableRange("panda3ds_battery_level", 5, 100); config.dspType = Audio::DSPCore::typeFromString(fetchVariable("panda3ds_dsp_emulation", "null")); config.audioEnabled = fetchVariableBool("panda3ds_use_audio", false); + config.aacEnabled = fetchVariableBool("panda3ds_enable_aac", true); + config.audioDeviceConfig.muteAudio = fetchVariableBool("panda3ds_mute_audio", false); + config.audioDeviceConfig.volumeRaw = float(fetchVariableRange("panda3ds_audio_volume", 0, 200)) / 100.0f; + config.sdCardInserted = fetchVariableBool("panda3ds_use_virtual_sd", true); config.sdWriteProtected = fetchVariableBool("panda3ds_write_protect_virtual_sd", false); config.accurateShaderMul = fetchVariableBool("panda3ds_accurate_shader_mul", false);