Additional 5ch options (#196)

* Added angle selector for surround channels, subwoofer threshold and rear music volume slider.

* Change comment.

* Integrate options in game.

* Added reset button for positions.

* Added option to show / hide menu for 5.1.

* Added mfpu=neon option to aarch64.

---------

Co-authored-by: Sonic Dreamcaster <alejandro.asenjo88@gmail.com>
This commit is contained in:
tortugaveloz
2025-05-20 22:58:35 +02:00
committed by GitHub
parent 1e65843326
commit ca1a5dd510
7 changed files with 152 additions and 22 deletions
+2
View File
@@ -574,6 +574,8 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
else()
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
set(CPU_OPTION -msse2 -mfpmath=sse)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
set(CPU_OPTION -mfpu=neon)
endif()
target_compile_options(${PROJECT_NAME} PRIVATE
+9 -11
View File
@@ -144,17 +144,13 @@ void Audio_InitNoteSub(Note* note, NoteAttributes* noteAttr) {
panVolumeCenter = 1.0f;
}
else if (stereo.s.is_sfx) { // SFX
float pan_angle = (float)(pan + 64) / 128 * 2 * M_PI;
float pan_angle = ((float) pan) / 128 * 2 * M_PI;
// Speaker angles in radians
const float front_left = -0.5236;
const float front_right = 0.5236;
const float rear_left = -1.92;
const float rear_right = 1.92;
// Normalize pan_angle to [0, 2π]
pan_angle = fmodf(pan_angle, 2 * M_PI);
if (pan_angle < 0) pan_angle += 2 * M_PI;
const float front_left = (CVarGetInteger("gPositionFrontLeft", 240) - 90) * (M_PI / 180.0f);
const float front_right = (CVarGetInteger("gPositionFrontRight", 300) - 90) * (M_PI / 180.0f);
const float rear_left = (CVarGetInteger("gPositionRearLeft", 160) - 90) * (M_PI / 180.0f);
const float rear_right = (CVarGetInteger("gPositionRearRight", 20) - 90) * (M_PI / 180.0f);
// Calculate volumes using cosine panning law
panVolumeLeft = fmaxf(0, cosf(pan_angle - front_left)); // Front Left
@@ -164,8 +160,10 @@ void Audio_InitNoteSub(Note* note, NoteAttributes* noteAttr) {
} else { // MUSIC
panVolumeLeft = gStereoPanVolume[pan];
panVolumeRight = gStereoPanVolume[ARRAY_COUNT(gStereoPanVolume) - 1 - pan];
panVolumeRearLeft = gStereoPanVolume[pan];
panVolumeRearRight = gStereoPanVolume[ARRAY_COUNT(gStereoPanVolume) - 1 - pan];
f32 rearMusicVolume = CVarGetFloat("gVolumeRearMusic", 1.0f);
panVolumeRearLeft = gStereoPanVolume[pan] * rearMusicVolume;
panVolumeRearRight = gStereoPanVolume[ARRAY_COUNT(gStereoPanVolume) - 1 - pan] * rearMusicVolume;
}
}
+5 -4
View File
@@ -1368,6 +1368,7 @@ Acmd* AudioSynth_ProcessEnvelope(Acmd* aList, NoteSubEu* noteSub, NoteSynthesisS
synthState->curVolLfe = curVolLfe + (rampLfe * aiBufLenSmall);
synthState->curVolRLeft = curVolRLeft + (rampRLeft * aiBufLenSmall);
synthState->curVolRRight = curVolRRight + (rampRRight * aiBufLenSmall);
uint32_t cutoffFreqLfe = CVarGetInteger("gSubwooferThreshold", 80);
if (noteSub->bitField0.usesHeadsetPanEffects) {
int32_t num_audio_channels = 2;
@@ -1378,24 +1379,24 @@ Acmd* AudioSynth_ProcessEnvelope(Acmd* aList, NoteSubEu* noteSub, NoteSynthesisS
switch (delaySide) {
case HAAS_EFFECT_DELAY_LEFT:
aEnvMixer(aList++, dmemSrc, aiBufLen, ((sourceReverbVol & 0x80) >> 7),
noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, DMEM_HAAS_TEMP << 16, num_audio_channels);
noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, DMEM_HAAS_TEMP << 16, num_audio_channels, cutoffFreqLfe);
break;
case HAAS_EFFECT_DELAY_RIGHT:
aEnvMixer(aList++, dmemSrc, aiBufLen, ((sourceReverbVol & 0x80) >> 7),
noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, DMEM_HAAS_TEMP, num_audio_channels);
noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, DMEM_HAAS_TEMP, num_audio_channels, cutoffFreqLfe);
break;
default: // HAAS_EFFECT_DELAY_NONE
aEnvMixer(aList++, dmemSrc, aiBufLen, ((sourceReverbVol & 0x80) >> 7),
noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, 0, num_audio_channels);
noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, 0, num_audio_channels, cutoffFreqLfe);
break;
}
} else {
aEnvSetup1(aList++, (sourceReverbVol & 0x7F), rampReverb, rampLeft, rampRight, rampCenter, rampLfe, rampRLeft, rampRRight);
aEnvSetup2(aList++, curVolLeft, curVolRight, curVolCenter, curVolLfe, curVolRLeft, curVolRRight);
aEnvMixer(aList++, dmemSrc, aiBufLen, ((sourceReverbVol & 0x80) >> 7),
noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, 0, GetNumAudioChannels());
noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, 0, GetNumAudioChannels(), cutoffFreqLfe);
}
return aList;
+3 -3
View File
@@ -77,7 +77,6 @@ static __m128i m256i_clamp_to_m128i(m256i a) {
#define BUF_S16(a) (int16_t*) BUF_U8(a)
#define SAMPLE_RATE 32000 // Adjusted to match the actual sample rate of 32 kHz
#define CUTOFF_FREQ_LFE 80 // Cutoff frequency of 80 Hz
static struct {
uint16_t in;
@@ -645,7 +644,8 @@ void aEnvSetup2Impl(uint16_t initial_vol_left, uint16_t initial_vol_right, int16
void aEnvMixerImpl(uint16_t in_addr, uint16_t n_samples, bool swap_reverb,
bool neg_left, bool neg_right,
uint32_t wet_dry_addr, uint32_t haas_temp_addr, uint32_t num_channels)
uint32_t wet_dry_addr, uint32_t haas_temp_addr, uint32_t num_channels,
uint32_t cutoff_freq_lfe)
{
// Note: max number of samples is 192 (192 * 2 = 384 bytes = 0x180)
int max_num_samples = 192;
@@ -675,7 +675,7 @@ void aEnvMixerImpl(uint16_t in_addr, uint16_t n_samples, bool swap_reverb,
if (num_channels == 6) {
// Calculate the filter coefficient
float RC = 1.f / (2 * M_PI * CUTOFF_FREQ_LFE);
float RC = 1.f / (2 * M_PI * cutoff_freq_lfe);
float dt = 1.f / SAMPLE_RATE;
float alpha = dt / (RC + dt);
+4 -3
View File
@@ -47,7 +47,8 @@ void aEnvSetup1Impl(uint8_t initial_vol_wet, uint16_t rate_wet, uint16_t rate_le
void aEnvSetup2Impl(uint16_t initial_vol_left, uint16_t initial_vol_right, int16_t initial_vol_center,
int16_t initial_vol_lfe, int16_t initial_vol_rear_left, int16_t initial_vol_rear_right);
void aEnvMixerImpl(uint16_t in_addr, uint16_t n_samples, bool swap_reverb, bool neg_left,
bool neg_right, uint32_t wet_dry_addr, uint32_t haas_temp_addr, uint32_t num_channels);
bool neg_right, uint32_t wet_dry_addr, uint32_t haas_temp_addr, uint32_t num_channels,
uint32_t cutoff_freq_lfe);
void aMixImpl(uint16_t count, int16_t gain, uint16_t in_addr, uint16_t out_addr);
void aS8DecImpl(uint8_t flags, ADPCM_STATE state);
void aAddMixerImpl(uint16_t count, uint16_t in_addr, uint16_t out_addr);
@@ -76,8 +77,8 @@ void aUnkCmd19Impl(uint8_t f, uint16_t count, uint16_t out_addr, uint16_t in_add
aEnvSetup1Impl(initialVolReverb, rampReverb, rampLeft, rampRight, rampCenter, rampLfe, rampRLeft, rampRRight)
#define aEnvSetup2(pkt, initialVolLeft, initialVolRight, initialVolCenter, initialVolLfe, initialVolRLeft, initialVolRRight) \
aEnvSetup2Impl(initialVolLeft, initialVolRight, initialVolCenter, initialVolLfe, initialVolRLeft, initialVolRRight)
#define aEnvMixer(pkt, inAddr, nSamples, swapReverb, negLeft, negRight, wetDryAddr, haasTempAddr, numChannels) \
aEnvMixerImpl(inAddr, nSamples, swapReverb, negLeft, negRight, wetDryAddr, haasTempAddr, numChannels)
#define aEnvMixer(pkt, inAddr, nSamples, swapReverb, negLeft, negRight, wetDryAddr, haasTempAddr, numChannels, cutoffFreqLfe) \
aEnvMixerImpl(inAddr, nSamples, swapReverb, negLeft, negRight, wetDryAddr, haasTempAddr, numChannels, cutoffFreqLfe)
#define aMix(pkt, c, g, i, o) aMixImpl(c, g, i, o)
#define aS8Dec(pkt, f, s) aS8DecImpl(f, s)
#define aAddMixer(pkt, s, d, c) aAddMixerImpl(s, d, c)
+128
View File
@@ -116,6 +116,117 @@ static const char* voiceLangs[] = {
"Original", /*"Japanese",*/ "Lylat"
};
void DrawSpeakerPositionEditor() {
static ImVec2 lastCanvasPos;
ImGui::Text("Speaker Position Editor");
ImVec2 canvasSize = ImVec2(200, 200); // Static canvas size
ImVec2 canvasPos = ImGui::GetCursorScreenPos();
ImVec2 center = ImVec2(canvasPos.x + canvasSize.x / 2, canvasPos.y + canvasSize.y / 2);
// Speaker positions
static ImVec2 speakerPositions[4];
static bool initialized = false;
static float radius = 80.0f;
// Reset positions if canvas position changed (window resized/moved)
if (!initialized || (lastCanvasPos.x != canvasPos.x || lastCanvasPos.y != canvasPos.y)) {
const char* cvarNames[4] = { "gPositionFrontLeft", "gPositionFrontRight", "gPositionRearLeft", "gPositionRearRight" };
float angles[4] = { 240.f, 300.f, 160.f, 20.f }; // Default angles
for (int i = 0; i < 4; i++) {
int savedAngle = CVarGetInteger(cvarNames[i], -1);
if (savedAngle != -1) {
angles[i] = static_cast<float>(savedAngle);
}
float rad = angles[i] * (M_PI / 180.0f);
speakerPositions[i] = ImVec2(center.x + radius * cosf(rad), center.y + radius * sinf(rad));
}
initialized = true;
lastCanvasPos = canvasPos;
}
// Draw canvas
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddRectFilled(canvasPos, ImVec2(canvasPos.x + canvasSize.x, canvasPos.y + canvasSize.y), IM_COL32(26, 26, 26, 255));
drawList->AddCircleFilled(center, 5.0f, IM_COL32(255, 255, 255, 255)); // Central person
// Draw circle line for speaker positions
drawList->AddCircle(center, radius, IM_COL32(163, 163, 163, 255), 100);
// Add markers at 0, 22.5, 45, etc.
for (float angle = 0; angle < 360; angle += 22.5f) {
float rad = angle * (M_PI / 180.0f);
ImVec2 markerStart = ImVec2(center.x + (radius - 5) * cosf(rad), center.y + (radius - 5) * sinf(rad));
ImVec2 markerEnd = ImVec2(center.x + radius * cosf(rad), center.y + radius * sinf(rad));
drawList->AddLine(markerStart, markerEnd, IM_COL32(163, 163, 163, 255));
}
const char* speakerLabels[4] = { "L", "R", "RL", "RR" };
const char* cvarNames[4] = { "gPositionFrontLeft", "gPositionFrontRight", "gPositionRearLeft", "gPositionRearRight" };
const float snapThreshold = 2.5f; // Degrees within which snapping occurs
for (int i = 0; i < 4; i++) {
// Draw speaker as a darker blue circle
drawList->AddCircleFilled(speakerPositions[i], 10.0f, IM_COL32(34, 52, 78, 255)); // Dark blue color
drawList->AddText(ImVec2(speakerPositions[i].x - 6, speakerPositions[i].y - 6), IM_COL32(255, 255, 255, 255), speakerLabels[i]);
// Handle dragging
ImGui::SetCursorScreenPos(ImVec2(speakerPositions[i].x - 10, speakerPositions[i].y - 10));
ImGui::InvisibleButton(speakerLabels[i], ImVec2(20, 20));
if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
ImVec2 mouseDelta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left);
ImVec2 newPos = ImVec2(speakerPositions[i].x + mouseDelta.x, speakerPositions[i].y + mouseDelta.y);
// Constrain position to the circle
ImVec2 direction = ImVec2(newPos.x - center.x, newPos.y - center.y);
float length = sqrtf(direction.x * direction.x + direction.y * direction.y);
ImVec2 constrainedPos = ImVec2(center.x + (direction.x / length) * radius, center.y + (direction.y / length) * radius);
// Calculate angle of the constrained position
float angle = atan2f(constrainedPos.y - center.y, constrainedPos.x - center.x) * (180.0f / M_PI);
if (angle < 0) angle += 360.0f;
// Snap to the nearest 22.5-degree marker if within the snap threshold
float snappedAngle = roundf(angle / 22.5f) * 22.5f;
if (fabsf(snappedAngle - angle) <= snapThreshold) {
float rad = snappedAngle * (M_PI / 180.0f);
constrainedPos = ImVec2(center.x + radius * cosf(rad), center.y + radius * sinf(rad));
}
speakerPositions[i] = constrainedPos;
ImGui::ResetMouseDragDelta();
// Save the updated angle to CVar after dragging
float updatedAngle = atan2f(speakerPositions[i].y - center.y, speakerPositions[i].x - center.x) * (180.0f / M_PI);
if (updatedAngle < 0) updatedAngle += 360.0f;
CVarSetInteger(cvarNames[i], static_cast<int>(updatedAngle));
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); // Mark for saving
}
// Calculate angle and save to CVar
float angle = atan2f(speakerPositions[i].y - center.y, speakerPositions[i].x - center.x) * (180.0f / M_PI);
if (angle < 0) angle += 360.0f;
CVarSetInteger(cvarNames[i], static_cast<int>(angle));
}
// Reset cursor position for button placement
ImGui::SetCursorScreenPos(ImVec2(canvasPos.x, canvasPos.y + canvasSize.y + 10));
if (ImGui::Button("Reset Positions")) {
float defaultAngles[4] = { 240.f, 300.f, 160.f, 20.f };
for (int i = 0; i < 4; i++) {
float rad = defaultAngles[i] * (M_PI / 180.0f);
speakerPositions[i] = ImVec2(center.x + radius * cosf(rad), center.y + radius * sinf(rad));
CVarSetInteger(cvarNames[i], static_cast<int>(defaultAngles[i]));
}
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
}
// Reset cursor position to ensure canvas size remains static
ImGui::SetCursorScreenPos(ImVec2(canvasPos.x, canvasPos.y + canvasSize.y + 10));
}
void DrawSettingsMenu(){
if(UIWidgets::BeginMenu("Settings")){
if (UIWidgets::BeginMenu("Audio")) {
@@ -173,6 +284,23 @@ void DrawSettingsMenu(){
}
UIWidgets::PaddedEnhancementCheckbox("Surround 5.1 (Needs reload)", "gAudioChannelsSetting", 1, 0);
if (CVarGetInteger("gAudioChannelsSetting", 0) == 1) {
// Subwoofer threshold
UIWidgets::CVarSliderInt("Subwoofer threshold (Hz)", "gSubwooferThreshold", 10u, 1000u, 80u, {
.tooltip = "The threshold for the subwoofer to be activated. Any sound under this frequency will be played on the subwoofer.",
.format = "%d",
});
// Rear music volume slider
UIWidgets::CVarSliderFloat("Rear music volume", "gVolumeRearMusic", 0.0f, 1.0f, 1.0f, {
.format = "%.0f%%",
.isPercentage = true,
});
// Configurable positioning of speakers
DrawSpeakerPositionEditor();
}
ImGui::EndMenu();
}