Pan stereo sounds towards where they are

A sound with a position plays on a 3D channel, but those are mono: a stereo one (a Custom Edition map's gunfire, a bullet's impact) fell back to a stereo channel, which the Xbox never panned, so it was only faded with distance and came from nowhere. update_channels now gives a stereo channel's sound in the world a pan towards it, as the mixer pans a 3D voice; the mixer pans the channels' middle and keeps their difference as wide as the far ear's gain. Unpositioned stereo plays as before, and no channel pools change.
This commit is contained in:
MrBruh committed 2026-10-08 21:35:40 +13:00
1 parent 71bf206bd0
commit a14401ac7d
3 files changed
+94

No files matched your search

+43
View File
@@ -91,6 +91,10 @@ struct sdl_stream
/* 2D gains */
float volume; /* SetVolume */
float mix_left, mix_right;
/* a stereo voice of a sound in the world: panned towards it, -1 left
to 1 right (dsound_sdl_stream_set_stereo_pan) */
BOOL stereo_positioned;
float stereo_pan;
float headroom;
/* 3D */
@@ -399,6 +403,15 @@ static void voice_gains(const struct sdl_stream *stream, float *left, float *rig
*room_lowpass = lowpass_coefficient(gain_from_millibels(high_level - level), cosine);
}
}
else if (stream->channels == 2 && stream->stereo_positioned)
{
/* the equal power pan of a 3D voice, at the gains of a 2D one when
centred (the game fades it with distance) */
float angle = (stream->stereo_pan + 1.0f) * 0.25f * 3.14159265f;
*left = cosf(angle) * 1.41421356f * stream->mix_left;
*right = sinf(angle) * 1.41421356f * stream->mix_right;
}
else
{
*left = stream->mix_left;
@@ -554,6 +567,8 @@ static void mix_voice(struct sdl_stream *stream, float *output, float *send, uns
float ramp_direct_lowpass, ramp_room_lowpass;
long width;
unsigned long frame;
/* (a stereo voice panned towards its sound: voice_gains) */
BOOL positioned = stream->channels == 2 && stream->stereo_positioned;
if (stream->paused || !stream->packet_count || !stream->sample_rate)
return;
@@ -665,6 +680,18 @@ static void mix_voice(struct sdl_stream *stream, float *output, float *send, uns
output[frame * 2] += sample_left * left;
output[frame * 2 + 1] += sample_left * right;
}
else if (positioned)
{
/* the channels' middle panned, and their difference kept as
wide as the far ear's gain: the sound comes from where it is,
still stereo */
float middle = 0.5f * (sample_left + sample_right);
float side = 0.5f * (sample_left - sample_right);
float far_gain = left < right ? left : right;
output[frame * 2] += middle * left + side * far_gain;
output[frame * 2 + 1] += middle * right - side * far_gain;
}
else
{
output[frame * 2] += sample_left * left;
@@ -1362,10 +1389,26 @@ static HRESULT STDMETHODCALLTYPE stream_flush(IDirectSoundStream *object)
}
stream->cursor = 0;
resampler_reset(stream);
/* (the next sound on the channel says where it is) */
stream->stereo_positioned = FALSE;
pthread_mutex_unlock(&mixer_lock);
return S_OK;
}
/* A stereo voice of a sound in the world (`positioned`) is panned towards
it, `pan` from -1 (left) to 1 (right); not positioned, it plays as the Xbox
played every stereo sound, unpanned. The game fades it with distance itself
(sound_manager.c, update_channels: sound_dsound_xbox.c calls this). */
void dsound_sdl_stream_set_stereo_pan(IDirectSoundStream *object, BOOL positioned, float pan)
{
struct sdl_stream *stream = stream_from_interface(object);
pthread_mutex_lock(&mixer_lock);
stream->stereo_positioned = positioned && stream->channels == 2;
stream->stereo_pan = pan < -1.0f ? -1.0f : (pan > 1.0f ? 1.0f : pan);
pthread_mutex_unlock(&mixer_lock);
}
static IDirectSoundStreamVtbl stream_vtable =
{
stream_add_reference,
+24
View File
@@ -1929,6 +1929,30 @@ static void dsound_virtual_set_location(
return;
}
/* port: a stereo channel's sound in the world is panned towards it, which
the Xbox's stereo channels were not (sound_manager.c, update_channels;
port/linux/src/dsound_sdl.c) */
void dsound_port_set_channel_stereo_pan(
short virtual_channel_index,
boolean positioned,
real pan)
{
extern void dsound_sdl_stream_set_stereo_pan(IDirectSoundStream *stream, BOOL positioned, float pan);
short channel_index= dsound_virtual_touch(virtual_channel_index);
if (channel_index!=NONE)
{
struct sound_channel *channel= channel_get(channel_index);
if (channel->stream && TEST_FLAG(channel->type_flags, _sound_channel_stereo_bit))
{
dsound_sdl_stream_set_stereo_pan(channel->stream, positioned, pan);
}
}
return;
}
static void dsound_virtual_set_properties(
short virtual_channel_index,
struct platform_sound_channel_properties const *properties,
+27
View File
@@ -244,6 +244,10 @@ symbols in this file:
#include <math.h>
#include <stdio.h>
/* port: a stereo channel's sound in the world panned towards it
(sound_dsound_xbox.c; update_channels) */
void dsound_port_set_channel_stereo_pan(short virtual_channel_index, boolean positioned, real pan);
/* ---------- constants */
enum
@@ -3495,6 +3499,11 @@ static void update_channels(
switch (sound->source.spatialization_mode)
{
case _sound_spatialization_mode_none:
/* port: (unpanned: dsound_port_set_channel_stereo_pan) */
if (TEST_FLAG(channel->type_flags, _sound_channel_stereo_bit))
{
dsound_port_set_channel_stereo_pan(channel_index, FALSE, 0.f);
}
break;
case _sound_spatialization_mode_absolute:
@@ -3530,6 +3539,24 @@ static void update_channels(
(maximum_distance - minimum_distance);
fade *= PIN(attenuation, 0.f, 1.f);
/* port: and a stereo sound is panned towards where it
is, as the mixer pans a 3D one (port/linux/src/dsound_sdl.c,
spatialize: ahead is x, right -y; centred when close),
which the Xbox's stereo channels never were: a Custom
Edition map's stereo gunfire came from nowhere */
if (TEST_FLAG(channel->type_flags, _sound_channel_stereo_bit))
{
real horizontal = square_root(
relative_position.x * relative_position.x +
relative_position.y * relative_position.y);
real pan = horizontal > 1.0e-4f ? -relative_position.y / horizontal : 0.f;
if (distance < minimum_distance && minimum_distance > 0.f)
{
pan *= distance / minimum_distance;
}
dsound_port_set_channel_stereo_pan(channel_index, TRUE, 0.75f * pan);
}
}
break;