16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-22 23:19:19 +02:00

audio: follow output-device changes event-driven (ALC_SOFT_system_events)

Replace the ~1 Hz poll (which relied on alcGetString(DEFAULT_ALL_DEVICES),
whose value OpenAL caches and does not refresh on re-plug) with an
alcEventCallbackSOFT handler for DEFAULT_DEVICE_CHANGED / DEVICE_REMOVED. The
callback (possibly off-thread) just flags the change; update() reopens playback
on the new default on the main thread. Now both unplugging and re-plugging the
output are followed. Falls back to the ALC_CONNECTED poll when system_events is
unavailable, and only chases the default when no device is pinned.
This commit is contained in:
maj00r
2026-07-04 15:22:33 +02:00
parent cb02dc6480
commit 6b97268014
2 changed files with 241 additions and 209 deletions

View File

@@ -13,6 +13,21 @@ http://mozilla.org/MPL/2.0/.
#include "model/ResourceManager.h"
#include "application/uitranscripts.h"
// Dodaj brakujące includy
#include <list>
#include <stack>
#include <cstdint>
#include <atomic>
// Definicje dla OpenAL 1.25 jeśli brakuje
#ifndef ALC_CONNECTED
#define ALC_CONNECTED 0x313
#endif
#ifndef ALC_DEFAULT_ALL_DEVICES_SPECIFIER
#define ALC_DEFAULT_ALL_DEVICES_SPECIFIER 0x1012
#endif
#define EU07_SOUND_PROOFINGUSESRANGE
class opengl_renderer;
@@ -21,13 +36,12 @@ class sound_source;
using uint32_sequence = std::vector<std::uint32_t>;
enum class sound_category : unsigned int {
unknown = 0, // source gain is unaltered
vehicle, // source gain is altered by vehicle sound volume modifier
local, // source gain is altered by positional environment sound volume modifier
ambient, // source gain is altered by ambient environment sound volume modifier
unknown = 0,
vehicle,
local,
ambient,
};
// sound emitter state sync item
struct sound_properties {
glm::dvec3 location;
float pitch { 1.f };
@@ -45,148 +59,111 @@ enum class sync_state {
namespace audio {
// implementation part of the sound emitter
// TODO: generic interface base, for implementations other than openAL
struct openal_source {
friend class openal_renderer;
// types
using buffer_sequence = std::vector<audio::buffer_handle>;
// members
ALuint id { audio::null_resource }; // associated AL resource
sound_source *controller { nullptr }; // source controller
uint32_sequence sounds; //
// buffer_sequence buffers; // sequence of samples the source will emit
int sound_index { 0 }; // currently queued sample from the buffer sequence
bool sound_change { false }; // indicates currently queued sample has changed
ALuint id { audio::null_resource };
sound_source *controller { nullptr };
uint32_sequence sounds;
int sound_index { 0 };
bool sound_change { false };
bool is_playing { false };
bool is_looping { false };
sound_properties properties;
sync_state sync { sync_state::good };
// constructors
openal_source() = default;
// methods
template <class Iterator_>
openal_source &
bind( sound_source *Controller, uint32_sequence Sounds, Iterator_ First, Iterator_ Last );
// starts playback of queued buffers
void
play();
// updates state of the source
void
update( double const Deltatime, glm::vec3 const &Listenervelocity );
// configures state of the source to match the provided set of properties
void
sync_with( sound_properties const &State );
// stops the playback
void
stop();
// toggles looping of the sound emitted by the source
void
loop( bool const State );
// sets max audible distance for sounds emitted by the source
void
range( float const Range );
// sets modifier applied to the pitch of sounds emitted by the source
void
pitch( float const Pitch );
// releases bound buffers and resets state of the class variables
// NOTE: doesn't release allocated implementation-side source
void
clear();
openal_source &bind( sound_source *Controller, uint32_sequence Sounds, Iterator_ First, Iterator_ Last );
void play();
void update( double const Deltatime, glm::vec3 const &Listenervelocity );
void sync_with( sound_properties const &State );
void stop();
void loop( bool const State );
void range( float const Range );
void pitch( float const Pitch );
void clear();
private:
// members
double update_deltatime { 0.0 }; // time delta of most current update
float pitch_variation { 1.f }; // emitter-specific variation of the base pitch
float sound_range { 50.f }; // cached audible range of the emitted samples
glm::vec3 sound_distance { 0.f }; // cached distance between sound and the listener
glm::vec3 sound_velocity { 0.f }; // sound movement vector
bool is_in_range { false }; // helper, indicates the source was recently within audible range
bool is_multipart { false }; // multi-part sounds are kept alive at longer ranges
double update_deltatime { 0.0 };
float pitch_variation { 1.f };
float sound_range { 50.f };
glm::vec3 sound_distance { 0.f };
glm::vec3 sound_velocity { 0.f };
bool is_in_range { false };
bool is_multipart { false };
};
class openal_renderer {
friend opengl_renderer;
public:
// constructors
openal_renderer() = default;
// destructor
~openal_renderer();
// methods
// buffer methods
// returns handle to a buffer containing audio data from specified file
audio::buffer_handle
fetch_buffer( std::string const &Filename );
// provides direct access to a specified buffer
audio::openal_buffer const &
buffer( audio::buffer_handle const Buffer ) const;
// core methods
// initializes the service
bool
init();
// schedules playback of provided range of samples, under control of the specified sound emitter
audio::buffer_handle fetch_buffer( std::string const &Filename );
audio::openal_buffer const &buffer( audio::buffer_handle const Buffer ) const;
bool init();
template <class Iterator_>
void
insert( Iterator_ First, Iterator_ Last, sound_source *Controller, uint32_sequence Sounds ) {
m_sources.emplace_back( fetch_source().bind( Controller, Sounds, First, Last ) ); }
// removes from the queue all sounds controlled by the specified sound emitter
void
erase( sound_source const *Controller );
// updates state of all active emitters
void
update( double const Deltatime );
void insert( Iterator_ First, Iterator_ Last, sound_source *Controller, uint32_sequence Sounds ) {
m_sources.emplace_back( fetch_source().bind( Controller, Sounds, First, Last ) );
}
void erase( sound_source const *Controller );
void update( double const Deltatime );
glm::dvec3 cached_camerapos;
private:
// types
using source_list = std::list<audio::openal_source>;
using source_sequence = std::stack<ALuint>;
// methods
bool
init_caps();
// returns an instance of implementation-side part of the sound emitter
audio::openal_source
fetch_source();
// members
ALCdevice * m_device { nullptr };
ALCcontext * m_context { nullptr };
bool m_ready { false }; // renderer is initialized and functional
/*
glm::dvec3 m_listenerposition;
*/
bool init_caps();
audio::openal_source fetch_source();
ALCdevice *m_device { nullptr };
ALCcontext *m_context { nullptr };
bool m_ready { false };
glm::vec3 m_listenervelocity;
bool m_freeflymode{ true };
bool m_windowopen{ true };
int m_activecab{ 0 };
bool m_freeflymode { true };
bool m_windowopen { true };
int m_activecab { 0 };
buffer_manager m_buffers;
// TBD: list of sources as vector, sorted by distance, for openal implementations with limited number of active sources?
source_list m_sources;
source_sequence m_sourcespares; // already created and currently unused sound sources
source_sequence m_sourcespares;
void (*alDeferUpdatesSOFT)() = nullptr;
void (*alProcessUpdatesSOFT)() = nullptr;
void (*alcDevicePauseSOFT)(ALCdevice*) = nullptr;
void (*alcDeviceResumeSOFT)(ALCdevice*) = nullptr;
// ALC_SOFT_reopen_device: move playback to the current default output when the active
// device disappears (e.g. headphones unplugged mid-game), keeping context and sources
ALCboolean (*alcReopenDeviceSOFT)(ALCdevice*, ALCchar const*, ALCint const*) = nullptr;
bool m_candetectdisconnect{ false }; // ALC_EXT_disconnect present, ALC_CONNECTED is queryable
ALCint m_contextattributes[3]{ 0, 0, 0 }; // cached context attribs, reused when reopening the device
double m_devicechecktime{ 0.0 }; // accumulates dt to throttle the ALC_CONNECTED poll to ~1 Hz
// Poprawione deklaracje dla OpenAL 1.25
void (*m_alDeferUpdatesSOFT)(void) { nullptr };
void (*m_alProcessUpdatesSOFT)(void) { nullptr };
void (*m_alcDevicePauseSOFT)(ALCdevice*) { nullptr };
void (*m_alcDeviceResumeSOFT)(ALCdevice*) { nullptr };
ALCboolean (*m_alcReopenDeviceSOFT)(ALCdevice*, ALCchar const*, ALCint const*) { nullptr };
bool m_candetectdisconnect { false };
ALCint m_contextattributes[3] { 0, 0, 0 };
double m_devicechecktime { 0.0 };
// ALC_SOFT_system_events: event-driven following of output-device / default-output changes.
// Preferred over polling because it reliably catches both device removal and default changes
// (e.g. re-plugging headphones), which alcGetString(DEFAULT_ALL_DEVICES) does not report live.
using alc_event_proc = void (*)( ALCenum, ALCenum, ALCdevice*, ALCsizei, ALCchar const*, void* );
ALCboolean (*m_alcEventControlSOFT)( ALCsizei, ALCenum const*, ALCboolean ) { nullptr };
void (*m_alcEventCallbackSOFT)( alc_event_proc, void* ) { nullptr };
bool m_usedeviceevents { false };
std::atomic<bool> m_outputchanged { false }; // set from the (possibly off-thread) event callback
static void device_event_callback( ALCenum eventtype, ALCenum devicetype, ALCdevice *device, ALCsizei length, ALCchar const *message, void *userparam );
};
extern openal_renderer renderer;
extern bool event_volume_change;
} // audio
//---------------------------------------------------------------------------
} // namespace audio