diff --git a/port/linux/src/dsound_sdl.c b/port/linux/src/dsound_sdl.c index e3a3f563..9da37dad 100644 --- a/port/linux/src/dsound_sdl.c +++ b/port/linux/src/dsound_sdl.c @@ -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, diff --git a/source/sound/sound_dsound_xbox.c b/source/sound/sound_dsound_xbox.c index 0ec8f4c3..b9f77764 100644 --- a/source/sound/sound_dsound_xbox.c +++ b/source/sound/sound_dsound_xbox.c @@ -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, diff --git a/source/sound/sound_manager.c b/source/sound/sound_manager.c index 0a57f3e7..67b05d80 100644 --- a/source/sound/sound_manager.c +++ b/source/sound/sound_manager.c @@ -244,6 +244,10 @@ symbols in this file: #include #include +/* 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;