mirror of
https://github.com/HarbourMasters/Starship
synced 2026-07-11 23:50:02 -04:00
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:
@@ -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
|
||||
|
||||
+1
-1
Submodule libultraship updated: a8cddd2f89...dda07c8ac6
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user