diff --git a/CMakeLists.txt b/CMakeLists.txt index 171e542b..35f8d01e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/libultraship b/libultraship index a8cddd2f..dda07c8a 160000 --- a/libultraship +++ b/libultraship @@ -1 +1 @@ -Subproject commit a8cddd2f8991c9d50783db4de855c98a392f4ac9 +Subproject commit dda07c8ac6033ff98d37a05bc28406440c7769e4 diff --git a/src/audio/audio_playback.c b/src/audio/audio_playback.c index be9dfbcd..976d9f81 100644 --- a/src/audio/audio_playback.c +++ b/src/audio/audio_playback.c @@ -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; } } diff --git a/src/audio/audio_synthesis.c b/src/audio/audio_synthesis.c index 84d894c5..17917d53 100644 --- a/src/audio/audio_synthesis.c +++ b/src/audio/audio_synthesis.c @@ -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; diff --git a/src/audio/mixer.c b/src/audio/mixer.c index c2f9f3ea..d3c5a4d7 100644 --- a/src/audio/mixer.c +++ b/src/audio/mixer.c @@ -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); diff --git a/src/audio/mixer.h b/src/audio/mixer.h index 64c2221c..52cafd98 100644 --- a/src/audio/mixer.h +++ b/src/audio/mixer.h @@ -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) diff --git a/src/port/ui/ImguiUI.cpp b/src/port/ui/ImguiUI.cpp index 615a07d0..1987efd7 100644 --- a/src/port/ui/ImguiUI.cpp +++ b/src/port/ui/ImguiUI.cpp @@ -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(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(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(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(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(); }