Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audio: Add more settings #649

Merged
merged 6 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion include/audio/aac_decoder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 5 additions & 2 deletions include/audio/dsp_core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,6 +32,7 @@ namespace Audio {
Memory& mem;
Scheduler& scheduler;
DSPService& dspService;
EmulatorConfig& settings;

Samples sampleBuffer;
bool audioEnabled = false;
Expand All @@ -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;
Expand All @@ -62,5 +65,5 @@ namespace Audio {
virtual void setAudioEnabled(bool enable) { audioEnabled = enable; }
};

std::unique_ptr<DSPCore> makeDSPCore(DSPCore::Type type, Memory& mem, Scheduler& scheduler, DSPService& dspService);
std::unique_ptr<DSPCore> makeDSPCore(EmulatorConfig& config, Memory& mem, Scheduler& scheduler, DSPService& dspService);
} // namespace Audio
2 changes: 1 addition & 1 deletion include/audio/hle_core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 6 additions & 3 deletions include/audio/miniaudio_device.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <string>
#include <vector>

#include "config.hpp"
#include "helpers.hpp"
#include "miniaudio.h"
#include "ring_buffer.hpp"
Expand All @@ -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;

Expand All @@ -26,7 +28,8 @@ class MiniAudioDevice {
std::vector<std::string> 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();
Expand Down
2 changes: 1 addition & 1 deletion include/audio/null_core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion include/audio/teakra_core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
15 changes: 15 additions & 0 deletions include/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -41,6 +54,7 @@ struct EmulatorConfig {

bool audioEnabled = false;
bool vsyncEnabled = true;
bool aacEnabled = true; // Enable AAC audio?

bool enableRenderdoc = false;
bool printAppVersion = true;
Expand Down Expand Up @@ -70,6 +84,7 @@ struct EmulatorConfig {
};

WindowSettings windowSettings;
AudioDeviceConfig audioDeviceConfig;

EmulatorConfig(const std::filesystem::path& path);
void load();
Expand Down
10 changes: 10 additions & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,14 @@ void EmulatorConfig::load() {

auto dspCoreName = toml::find_or<std::string>(audio, "DSPEmulation", "HLE");
dspType = Audio::DSPCore::typeFromString(dspCoreName);

audioEnabled = toml::find_or<toml::boolean>(audio, "EnableAudio", false);
aacEnabled = toml::find_or<toml::boolean>(audio, "EnableAACAudio", true);

audioDeviceConfig.muteAudio = toml::find_or<toml::boolean>(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<toml::floating>(audio, "AudioVolume", 1.0), 0.0, 2.0));
}
}

Expand Down Expand Up @@ -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;
Expand Down
9 changes: 4 additions & 5 deletions src/core/PICA/dynapica/shader_rec_emitter_x64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand Down
13 changes: 10 additions & 3 deletions src/core/audio/aac_decoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include <vector>
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;
Expand Down Expand Up @@ -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 {
Expand Down
12 changes: 6 additions & 6 deletions src/core/audio/dsp_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@
#include "audio/null_core.hpp"
#include "audio/teakra_core.hpp"

std::unique_ptr<Audio::DSPCore> Audio::makeDSPCore(DSPCore::Type type, Memory& mem, Scheduler& scheduler, DSPService& dspService) {
std::unique_ptr<Audio::DSPCore> Audio::makeDSPCore(EmulatorConfig& config, Memory& mem, Scheduler& scheduler, DSPService& dspService) {
std::unique_ptr<DSPCore> core;

switch (type) {
case DSPCore::Type::Null: core = std::make_unique<NullDSP>(mem, scheduler, dspService); break;
case DSPCore::Type::Teakra: core = std::make_unique<TeakraDSP>(mem, scheduler, dspService); break;
case DSPCore::Type::HLE: core = std::make_unique<HLE_DSP>(mem, scheduler, dspService); break;
switch (config.dspType) {
case DSPCore::Type::Null: core = std::make_unique<NullDSP>(mem, scheduler, dspService, config); break;
case DSPCore::Type::Teakra: core = std::make_unique<TeakraDSP>(mem, scheduler, dspService, config); break;
case DSPCore::Type::HLE: core = std::make_unique<HLE_DSP>(mem, scheduler, dspService, config); break;

default:
Helpers::warn("Invalid DSP core selected!");
core = std::make_unique<NullDSP>(mem, scheduler, dspService);
core = std::make_unique<NullDSP>(mem, scheduler, dspService, config);
break;
}

Expand Down
23 changes: 5 additions & 18 deletions src/core/audio/hle_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "audio/aac_decoder.hpp"
#include "audio/dsp_simd.hpp"
#include "config.hpp"
#include "services/dsp.hpp"

namespace Audio {
Expand All @@ -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;
Expand Down Expand Up @@ -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<u8>(paddr); });
}
case AAC::Command::EncodeDecode:
aacDecoder->decode(response, request, [this](u32 paddr) { return getPointerPhys<u8>(paddr); }, settings.aacEnabled);
break;
}

case AAC::Command::Init:
case AAC::Command::Shutdown:
Expand Down
40 changes: 39 additions & 1 deletion src/core/audio/miniaudio_device.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
#include "audio/miniaudio_device.hpp"

#include <algorithm>
#include <cmath>
#include <cstring>
#include <limits>

#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;
Expand Down Expand Up @@ -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<s16>::min());
constexpr s32 max = s32(std::numeric_limits<s16>::max());

for (usize i = 0; i < samplesWritten; i += 2) {
s16 l = s16(std::clamp<s32>(s32(float(sample[0]) * audioVolume), min, max));
s16 r = s16(std::clamp<s32>(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];
Expand Down
4 changes: 2 additions & 2 deletions src/core/audio/teakra_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
4 changes: 2 additions & 2 deletions src/emulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ __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)
#endif
{
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());
Expand Down
12 changes: 10 additions & 2 deletions src/libretro_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};

Expand All @@ -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);
Expand Down
Loading