mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-09-10 11:05:07 -04:00
Prepare audio system and UI for surround
This commit is contained in:
@@ -173,11 +173,7 @@ void JASChannel::updateEffectorParam(JASDsp::TChannel* i_channel, u16* i_mixerVo
|
||||
|
||||
f32 pan = 0.5f;
|
||||
f32 dolby = 0.0f;
|
||||
#if TARGET_PC
|
||||
u32 effectiveOutputMode = dusk::audio::EnableHrtf ? JAS_OUTPUT_SURROUND : JASDriver::getOutputMode();
|
||||
#else
|
||||
u32 effectiveOutputMode = JASDriver::getOutputMode();
|
||||
#endif
|
||||
switch (effectiveOutputMode) {
|
||||
case JAS_OUTPUT_MONO:
|
||||
break;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "JSystem/JAudio2/JASDriverIF.h"
|
||||
#include "JSystem/JAudio2/JASAiCtrl.h"
|
||||
#include "JSystem/JAudio2/JASDSPInterface.h"
|
||||
#include "dusk/settings.h"
|
||||
#include <os.h>
|
||||
|
||||
void JASDriver::setDSPLevel(f32 param_0) {
|
||||
@@ -30,7 +31,18 @@ void JASDriver::setOutputMode(u32 mode) {
|
||||
}
|
||||
|
||||
u32 JASDriver::getOutputMode() {
|
||||
#ifdef TARGET_PC
|
||||
switch (dusk::getSettings().audio.outputMode) {
|
||||
case dusk::AudioOutputMode::StereoSpeakers:
|
||||
return JAS_OUTPUT_STEREO;
|
||||
case dusk::AudioOutputMode::StereoHeadphones:
|
||||
case dusk::AudioOutputMode::Surround6ch:
|
||||
case dusk::AudioOutputMode::Surround8ch:
|
||||
return JAS_OUTPUT_SURROUND;
|
||||
}
|
||||
#else
|
||||
return JASDriver::JAS_SYSTEM_OUTPUT_MODE;
|
||||
#endif
|
||||
}
|
||||
|
||||
void JASDriver::waitSubFrame() {
|
||||
|
||||
@@ -745,16 +745,26 @@ f32 Z2Audience::calcRelPosPan(const Vec& param_0, int camID) {
|
||||
f32 Z2Audience::calcRelPosDolby(const Vec& param_0, int camID) {
|
||||
f32 fVar1 = param_0.z + mAudioCamera[camID].getDolbyCenterZ();
|
||||
#if TARGET_PC
|
||||
if (dusk::audio::EnableHrtf) {
|
||||
const auto mode = dusk::getSettings().audio.outputMode.getValue();
|
||||
if (mode >= dusk::AudioOutputMode::StereoHeadphones) {
|
||||
// Normalize the direction so result is purely front/back orientation,
|
||||
// independent of how far away the sound is
|
||||
f32 lenSq = param_0.x * param_0.x + param_0.y * param_0.y + param_0.z * param_0.z;
|
||||
f32 lenSq = param_0.x * param_0.x + param_0.z * param_0.z;
|
||||
if (mode == dusk::AudioOutputMode::StereoHeadphones) {
|
||||
// original HRTF math
|
||||
lenSq += param_0.y * param_0.y;
|
||||
}
|
||||
if (lenSq < 0.0001f) {
|
||||
return 0.5f;
|
||||
}
|
||||
f32 zNorm = param_0.z / sqrtf(lenSq);
|
||||
f32 t = (zNorm + 1.0f) * 0.5f;
|
||||
return 0.5f - 0.5f * cosf(t * static_cast<f32>(M_PI));
|
||||
if (mode == dusk::AudioOutputMode::StereoHeadphones) {
|
||||
// original HRTF math
|
||||
return 0.5f - 0.5f * cosf(t * static_cast<f32>(M_PI));
|
||||
} else {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (fVar1 > mSetting.field_0x48) {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
using namespace dusk::audio;
|
||||
|
||||
static OutputSubframe OutBuffer;
|
||||
static std::array<f32, DSP_SUBFRAME_SIZE * OutputSubframe::NUM_CHANNELS> OutInterleaveBuffer;
|
||||
static std::array<f32, DSP_SUBFRAME_SIZE * OutputSubframe::NUM_CHANNELS> OutInterleaveBufferFull;
|
||||
|
||||
static SDL_AudioStream* PlaybackStream;
|
||||
|
||||
@@ -42,21 +42,55 @@ static int RenderNewAudioFrame();
|
||||
/**
|
||||
* Render an audio subframe and output it to SDL3.
|
||||
*/
|
||||
static void RenderAudioSubframe();
|
||||
static int RenderAudioSubframe();
|
||||
|
||||
static void InitSDL3Output() {
|
||||
SDL_Init(SDL_INIT_AUDIO);
|
||||
static size_t GetChannelCountForOutputMode(dusk::AudioOutputMode config) {
|
||||
switch (config) {
|
||||
default:
|
||||
case dusk::AudioOutputMode::StereoSpeakers:
|
||||
case dusk::AudioOutputMode::StereoHeadphones:
|
||||
return 2;
|
||||
case dusk::AudioOutputMode::Surround6ch:
|
||||
return 6;
|
||||
case dusk::AudioOutputMode::Surround8ch:
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr SDL_AudioSpec spec = {
|
||||
static bool InitSDL3Output() {
|
||||
const auto speakerConfig = dusk::getSettings().audio.outputMode.getValue();
|
||||
const auto desiredChannelCount = GetChannelCountForOutputMode(speakerConfig);
|
||||
const bool hrtf = speakerConfig == dusk::AudioOutputMode::StereoHeadphones;
|
||||
|
||||
if (PlaybackStream && desiredChannelCount == OutChannelCount) {
|
||||
JASCriticalSection section;
|
||||
EnableHrtf = hrtf;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PlaybackStream) {
|
||||
SDL_PauseAudioStreamDevice(PlaybackStream);
|
||||
SDL_DestroyAudioStream(PlaybackStream);
|
||||
} else {
|
||||
SDL_Init(SDL_INIT_AUDIO);
|
||||
}
|
||||
|
||||
const SDL_AudioSpec spec = {
|
||||
SDL_AUDIO_F32,
|
||||
2,
|
||||
static_cast<int>(desiredChannelCount),
|
||||
SampleRate,
|
||||
};
|
||||
PlaybackStream = SDL_OpenAudioDeviceStream(
|
||||
SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
|
||||
&spec,
|
||||
&GetNewAudio,
|
||||
nullptr);
|
||||
SDL_AudioStream* newStream =
|
||||
SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, &GetNewAudio, nullptr);
|
||||
|
||||
{
|
||||
JASCriticalSection section;
|
||||
EnableHrtf = hrtf;
|
||||
OutChannelCount = desiredChannelCount;
|
||||
PlaybackStream = newStream;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void dusk::audio::Initialize() {
|
||||
@@ -71,6 +105,12 @@ void dusk::audio::Initialize() {
|
||||
SDL_ResumeAudioStreamDevice(PlaybackStream);
|
||||
}
|
||||
|
||||
void dusk::audio::Reinitialize() {
|
||||
if (InitSDL3Output()) {
|
||||
SDL_ResumeAudioStreamDevice(PlaybackStream);
|
||||
}
|
||||
}
|
||||
|
||||
void dusk::audio::SetMasterVolume(const f32 value) {
|
||||
JASCriticalSection section;
|
||||
|
||||
@@ -112,53 +152,58 @@ int RenderNewAudioFrame() {
|
||||
ZoneScoped;
|
||||
JASCriticalSection section;
|
||||
const u32 countSubframes = JASDriver::getSubFrames();
|
||||
int bytesWritten = 0;
|
||||
|
||||
JASAudioThread::setDSPSyncCount(countSubframes);
|
||||
|
||||
for (u32 i = 0; i < countSubframes; i++) {
|
||||
RenderAudioSubframe();
|
||||
bytesWritten += RenderAudioSubframe();
|
||||
|
||||
JASAudioThread::snIntCount -= 1;
|
||||
}
|
||||
|
||||
return static_cast<u16>(countSubframes) * sizeof(OutputSubframe);
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
static void InterleaveOutputData(const OutputSubframe& data, std::span<f32> target) {
|
||||
assert(target.size() >= data.channels[0].size() * OutputSubframe::NUM_CHANNELS);
|
||||
assert(target.size() >= data.channels[0].size() * OutChannelCount);
|
||||
|
||||
size_t outPos = 0;
|
||||
for (size_t inPos = 0; inPos < data.channels[0].size(); inPos++) {
|
||||
for (size_t channelIdx = 0; channelIdx < OutputSubframe::NUM_CHANNELS; channelIdx++) {
|
||||
for (size_t channelIdx = 0; channelIdx < OutChannelCount; channelIdx++) {
|
||||
target[outPos++] = data.channels[channelIdx][inPos];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderAudioSubframe() {
|
||||
int RenderAudioSubframe() {
|
||||
ZoneScoped;
|
||||
OutBuffer = {};
|
||||
|
||||
JASDriver::updateDSP();
|
||||
DspRender(OutBuffer);
|
||||
|
||||
std::span<f32> OutInterleaveBuffer{OutInterleaveBufferFull.data(), static_cast<size_t>(DSP_SUBFRAME_SIZE * OutChannelCount)};
|
||||
InterleaveOutputData(OutBuffer, OutInterleaveBuffer);
|
||||
|
||||
if (JASDriver::extMixCallback != nullptr && JASDriver::sMixMode == MIX_MODE_INTERLEAVE) {
|
||||
static_assert(OutputSubframe::NUM_CHANNELS == 2); // This code only works with Stereo so far.
|
||||
// NOTE: In the real game, this gets called on the entire audio frame, rather than the subframe.
|
||||
// That's probably more efficient, but I didn't wanna change the code to calculate the
|
||||
// entire audio buffers at once.
|
||||
// This is only used for the movie player, and it seems to work fine with the smaller calls.
|
||||
const auto mixData = JASDriver::extMixCallback(DSP_SUBFRAME_SIZE);
|
||||
if (mixData) {
|
||||
for (int i = 0; i < OutInterleaveBuffer.size(); i++) {
|
||||
OutInterleaveBuffer[i] += static_cast<f32>(mixData[i]) / static_cast<f32>(0x7FFF);
|
||||
for (int i = 0; i < DSP_SUBFRAME_SIZE; i++) {
|
||||
const auto oi = i * OutChannelCount;
|
||||
OutInterleaveBuffer[oi] += static_cast<f32>(mixData[i * 2]) / 32767.0f;
|
||||
OutInterleaveBuffer[oi + 1] += static_cast<f32>(mixData[i * 2 + 1]) / 32767.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_PutAudioStreamData(PlaybackStream, &OutInterleaveBuffer, sizeof(OutInterleaveBuffer));
|
||||
auto bytesToWrite = OutInterleaveBuffer.size_bytes();
|
||||
SDL_PutAudioStreamData(PlaybackStream, OutInterleaveBuffer.data(), bytesToWrite);
|
||||
return bytesToWrite;
|
||||
}
|
||||
|
||||
u32 dusk::audio::GetResetCount(int channelIdx) {
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace dusk::audio {
|
||||
*/
|
||||
void Initialize();
|
||||
|
||||
void Reinitialize();
|
||||
|
||||
void SetEnableReverb(bool value);
|
||||
|
||||
void SetMasterVolume(f32 value);
|
||||
|
||||
@@ -50,7 +50,7 @@ bool dusk::audio::EnableReverb = true;
|
||||
bool dusk::audio::DumpAudio = false;
|
||||
bool dusk::audio::EnableHrtf = false;
|
||||
f32 dusk::audio::HrtfGain = 0.5f;
|
||||
|
||||
u8 dusk::audio::OutChannelCount = 0;
|
||||
|
||||
// 3dB at 5kHz.
|
||||
static constexpr f32 HRTF_LP_K = 0.75f;
|
||||
@@ -512,10 +512,10 @@ static void ApplyPanning(
|
||||
|
||||
switch (outChannel.mBusConnect) {
|
||||
case 0x0D00:
|
||||
ch = OutputChannel::LEFT;
|
||||
ch = OutputChannel::FRONT_LEFT;
|
||||
break;
|
||||
case 0x0D60:
|
||||
ch = OutputChannel::RIGHT;
|
||||
ch = OutputChannel::FRONT_RIGHT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -12,8 +12,15 @@ namespace dusk::audio {
|
||||
constexpr int SampleRate = 32000;
|
||||
|
||||
enum class OutputChannel : u8 {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
// same as SDL channel layout for 7.1
|
||||
FRONT_LEFT,
|
||||
FRONT_RIGHT,
|
||||
FRONT_CENTER,
|
||||
LFE,
|
||||
REAR_LEFT,
|
||||
REAR_RIGHT,
|
||||
SURROUND_LEFT,
|
||||
SURROUND_RIGHT,
|
||||
OutputChannel_MAX
|
||||
};
|
||||
|
||||
@@ -126,4 +133,5 @@ namespace dusk::audio {
|
||||
extern bool DumpAudio;
|
||||
extern bool EnableHrtf;
|
||||
extern f32 HrtfGain;
|
||||
extern u8 OutChannelCount;
|
||||
}
|
||||
|
||||
+2
-1
@@ -316,6 +316,7 @@ template class ConfigImpl<BloomMode>;
|
||||
template class ConfigImpl<DepthOfFieldMode>;
|
||||
template class ConfigImpl<DiscVerificationState>;
|
||||
template class ConfigImpl<GameLanguage>;
|
||||
template class ConfigImpl<AudioOutputMode>;
|
||||
|
||||
template <>
|
||||
void ConfigImpl<FrameInterpMode>::loadFromJson(
|
||||
@@ -639,4 +640,4 @@ void shutdown() {
|
||||
s_activeChangeNotifications.clear();
|
||||
}
|
||||
|
||||
} // namespace dusk::config
|
||||
} // namespace dusk::config
|
||||
|
||||
@@ -25,13 +25,13 @@ UserSettings g_userSettings = {
|
||||
},
|
||||
|
||||
.audio = {
|
||||
.outputMode {"audio.outputMode", AudioOutputMode::StereoSpeakers},
|
||||
.masterVolume {"audio.masterVolume", 60},
|
||||
.mainMusicVolume {"audio.mainMusicVolume", 100},
|
||||
.subMusicVolume {"audio.subMusicVolume", 100},
|
||||
.soundEffectsVolume {"audio.soundEffectsVolume", 100},
|
||||
.fanfareVolume {"audio.fanfareVolume", 100},
|
||||
.enableReverb {"audio.enableReverb", true},
|
||||
.enableHrtf {"audio.enableHrtf", false},
|
||||
.menuSounds {"audio.menuSounds", true},
|
||||
},
|
||||
|
||||
@@ -256,13 +256,13 @@ void registerSettings() {
|
||||
[](const int&, const int&) { dusk::ui::apply_scale(); });
|
||||
|
||||
// Audio
|
||||
Register(g_userSettings.audio.outputMode);
|
||||
Register(g_userSettings.audio.masterVolume);
|
||||
Register(g_userSettings.audio.mainMusicVolume);
|
||||
Register(g_userSettings.audio.subMusicVolume);
|
||||
Register(g_userSettings.audio.soundEffectsVolume);
|
||||
Register(g_userSettings.audio.fanfareVolume);
|
||||
Register(g_userSettings.audio.enableReverb);
|
||||
Register(g_userSettings.audio.enableHrtf);
|
||||
Register(g_userSettings.audio.menuSounds);
|
||||
|
||||
// Game
|
||||
|
||||
+14
-1
@@ -68,6 +68,13 @@ enum class MagicArmorMode : u8 {
|
||||
COSMETIC = 4,
|
||||
};
|
||||
|
||||
enum class AudioOutputMode : u8 {
|
||||
StereoSpeakers = 0,
|
||||
StereoHeadphones = 1, // spatial audio
|
||||
Surround6ch = 2, // discrete 5.1
|
||||
Surround8ch = 3, // discrete 7.1
|
||||
};
|
||||
|
||||
namespace config {
|
||||
template <>
|
||||
struct ConfigEnumRange<BloomMode> {
|
||||
@@ -123,6 +130,12 @@ struct ConfigEnumRange<MagicArmorMode> {
|
||||
static constexpr auto max = MagicArmorMode::COSMETIC;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ConfigEnumRange<AudioOutputMode> {
|
||||
static constexpr auto min = AudioOutputMode::StereoSpeakers;
|
||||
static constexpr auto max = AudioOutputMode::Surround8ch;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ConfigValueTraits<ui::ControlLayout> {
|
||||
static constexpr bool enabled = true;
|
||||
@@ -150,13 +163,13 @@ struct UserSettings {
|
||||
|
||||
struct {
|
||||
// Audio
|
||||
ConfigVar<AudioOutputMode> outputMode;
|
||||
ConfigVar<int> masterVolume;
|
||||
ConfigVar<int> mainMusicVolume;
|
||||
ConfigVar<int> subMusicVolume;
|
||||
ConfigVar<int> soundEffectsVolume;
|
||||
ConfigVar<int> fanfareVolume;
|
||||
ConfigVar<bool> enableReverb;
|
||||
ConfigVar<bool> enableHrtf;
|
||||
ConfigVar<bool> menuSounds;
|
||||
} audio;
|
||||
|
||||
|
||||
@@ -72,6 +72,13 @@ constexpr std::array kInterpolationModes = {
|
||||
"Unlimited",
|
||||
};
|
||||
|
||||
constexpr std::array kAudioOutputModeNames = {
|
||||
"Stereo (Speakers)",
|
||||
"Stereo (Headphones)",
|
||||
"5.1 Surround",
|
||||
"7.1 Surround",
|
||||
};
|
||||
|
||||
constexpr std::array kTouchTargetingLabels = {
|
||||
"Hybrid",
|
||||
"Hold",
|
||||
@@ -1044,6 +1051,35 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
leftPane.add_section("Output");
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Output Mode",
|
||||
.getValue = [] {
|
||||
const auto idx = static_cast<int>(getSettings().audio.outputMode.getValue());
|
||||
return Rml::String{kAudioOutputModeNames[idx]};
|
||||
},
|
||||
.isModified = [] {
|
||||
const auto& setting = getSettings().audio.outputMode;
|
||||
return setting.getValue() != setting.getDefaultValue();
|
||||
},
|
||||
}), rightPane, [](Pane& pane) {
|
||||
for (int i = 0; i < static_cast<int>(kAudioOutputModeNames.size()); ++i) {
|
||||
pane.add_button({
|
||||
.text = kAudioOutputModeNames[i],
|
||||
.isSelected = [i] {
|
||||
const auto& setting = getSettings().audio.outputMode;
|
||||
return setting.getValue() == static_cast<AudioOutputMode>(i);
|
||||
},
|
||||
}).on_pressed([i] {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
getSettings().audio.outputMode.setValue(static_cast<AudioOutputMode>(i));
|
||||
config::save();
|
||||
audio::Reinitialize();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Individual sliders for Main Music, Sub Music, Sound Effects, and Fanfare.
|
||||
leftPane.add_section("Volume");
|
||||
leftPane.register_control(
|
||||
@@ -1076,13 +1112,6 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
.helpText = "Enables the reverb effect in game audio.",
|
||||
.onChange = [](bool value) { audio::SetEnableReverb(value); },
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().audio.enableHrtf,
|
||||
{
|
||||
.key = "Enable Spatial Sound",
|
||||
.helpText =
|
||||
"Emulate surround sound via HRTF. Recommended only for use with headphones!",
|
||||
.onChange = [](bool value) { audio::EnableHrtf = value; },
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().audio.menuSounds,
|
||||
{
|
||||
.key = "Dusklight Menu Sounds",
|
||||
|
||||
@@ -753,7 +753,6 @@ int game_main(int argc, char* argv[]) {
|
||||
|
||||
dusk::audio::SetMasterVolume(dusk::audio::MasterVolumeToLinear(dusk::getSettings().audio.masterVolume / 100.0f));
|
||||
dusk::audio::SetEnableReverb(dusk::getSettings().audio.enableReverb);
|
||||
dusk::audio::EnableHrtf = dusk::getSettings().audio.enableHrtf;
|
||||
|
||||
// Run ImGui UI loop if Aurora couldn't initialize a backend
|
||||
if (auroraInfo.backend == BACKEND_NULL) {
|
||||
|
||||
Reference in New Issue
Block a user