mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-18 00:49: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:
@@ -22,6 +22,17 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#ifndef ALC_CONNECTED
|
#ifndef ALC_CONNECTED
|
||||||
#define ALC_CONNECTED 0x313
|
#define ALC_CONNECTED 0x313
|
||||||
#endif
|
#endif
|
||||||
|
// ALC_ENUMERATE_ALL_EXT token; identifies the system's current default output device
|
||||||
|
#ifndef ALC_DEFAULT_ALL_DEVICES_SPECIFIER
|
||||||
|
#define ALC_DEFAULT_ALL_DEVICES_SPECIFIER 0x1012
|
||||||
|
#endif
|
||||||
|
// ALC_SOFT_system_events tokens (bundled headers may predate them); functions resolved at runtime
|
||||||
|
#ifndef ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT
|
||||||
|
#define ALC_PLAYBACK_DEVICE_SOFT 0x19D4
|
||||||
|
#define ALC_CAPTURE_DEVICE_SOFT 0x19D5
|
||||||
|
#define ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT 0x19D6
|
||||||
|
#define ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT 0x19D8
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace audio {
|
namespace audio {
|
||||||
|
|
||||||
@@ -157,11 +168,14 @@ openal_source::sync_with( sound_properties const &State ) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if( sound_range >= 0 ) {
|
if( sound_range >= 0 ) {
|
||||||
::alSourcefv( id, AL_POSITION, glm::value_ptr( sound_distance ) );
|
// Convert dvec3 to vec3 for OpenAL
|
||||||
|
glm::vec3 sound_distance_float = glm::vec3(sound_distance);
|
||||||
|
::alSourcefv( id, AL_POSITION, glm::value_ptr( sound_distance_float ) );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// sounds with 'unlimited' or negative range are positioned on top of the listener
|
// sounds with 'unlimited' or negative range are positioned on top of the listener
|
||||||
::alSourcefv( id, AL_POSITION, glm::value_ptr( glm::vec3() ) );
|
glm::vec3 zero_pos{ 0.f, 0.f, 0.f };
|
||||||
|
::alSourcefv( id, AL_POSITION, glm::value_ptr( zero_pos ) );
|
||||||
}
|
}
|
||||||
// gain
|
// gain
|
||||||
auto const gain {
|
auto const gain {
|
||||||
@@ -184,7 +198,7 @@ openal_source::sync_with( sound_properties const &State ) {
|
|||||||
}
|
}
|
||||||
if( sound_range != -1 ) {
|
if( sound_range != -1 ) {
|
||||||
auto const rangesquared { sound_range * sound_range };
|
auto const rangesquared { sound_range * sound_range };
|
||||||
auto const distancesquared { glm::length2( sound_distance ) };
|
auto const distancesquared { static_cast<float>( glm::length2( sound_distance ) ) };
|
||||||
if( distancesquared > rangesquared
|
if( distancesquared > rangesquared
|
||||||
|| false == is_in_range ) {
|
|| false == is_in_range ) {
|
||||||
// if the emitter is outside of its nominal hearing range or was outside of it during last check
|
// if the emitter is outside of its nominal hearing range or was outside of it during last check
|
||||||
@@ -275,12 +289,26 @@ openal_source::clear() {
|
|||||||
|
|
||||||
openal_renderer::~openal_renderer() {
|
openal_renderer::~openal_renderer() {
|
||||||
|
|
||||||
|
if( m_alcEventCallbackSOFT != nullptr ) { m_alcEventCallbackSOFT( nullptr, nullptr ); } // stop callbacks before teardown
|
||||||
|
|
||||||
::alcMakeContextCurrent( nullptr );
|
::alcMakeContextCurrent( nullptr );
|
||||||
|
|
||||||
if( m_context != nullptr ) { ::alcDestroyContext( m_context ); }
|
if( m_context != nullptr ) { ::alcDestroyContext( m_context ); }
|
||||||
if( m_device != nullptr ) { ::alcCloseDevice( m_device ); }
|
if( m_device != nullptr ) { ::alcCloseDevice( m_device ); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// invoked by OpenAL (possibly on an internal thread) on device events; only flags the change,
|
||||||
|
// the actual reopen is done on the main thread in update()
|
||||||
|
void
|
||||||
|
openal_renderer::device_event_callback( ALCenum eventtype, ALCenum devicetype, ALCdevice */*device*/, ALCsizei /*length*/, ALCchar const */*message*/, void *userparam ) {
|
||||||
|
|
||||||
|
if( userparam == nullptr ) { return; }
|
||||||
|
if( devicetype == ALC_CAPTURE_DEVICE_SOFT ) { return; } // only care about playback output
|
||||||
|
if( eventtype != ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT
|
||||||
|
&& eventtype != ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT ) { return; }
|
||||||
|
static_cast<openal_renderer*>( userparam )->m_outputchanged.store( true );
|
||||||
|
}
|
||||||
|
|
||||||
audio::buffer_handle
|
audio::buffer_handle
|
||||||
openal_renderer::fetch_buffer( std::string const &Filename ) {
|
openal_renderer::fetch_buffer( std::string const &Filename ) {
|
||||||
|
|
||||||
@@ -339,61 +367,66 @@ openal_renderer::erase( sound_source const *Controller ) {
|
|||||||
void
|
void
|
||||||
openal_renderer::update( double const Deltatime ) {
|
openal_renderer::update( double const Deltatime ) {
|
||||||
|
|
||||||
ALenum err = alGetError();
|
ALenum err = alGetError();
|
||||||
if (err != AL_NO_ERROR)
|
if (err != AL_NO_ERROR)
|
||||||
{
|
{
|
||||||
std::string errname;
|
std::string errname;
|
||||||
if (err == AL_INVALID_NAME)
|
if (err == AL_INVALID_NAME)
|
||||||
errname = "AL_INVALID_NAME";
|
errname = "AL_INVALID_NAME";
|
||||||
else if (err == AL_INVALID_ENUM)
|
else if (err == AL_INVALID_ENUM)
|
||||||
errname = "AL_INVALID_ENUM";
|
errname = "AL_INVALID_ENUM";
|
||||||
else if (err == AL_INVALID_VALUE)
|
else if (err == AL_INVALID_VALUE)
|
||||||
errname = "AL_INVALID_VALUE";
|
errname = "AL_INVALID_VALUE";
|
||||||
else if (err == AL_INVALID_OPERATION)
|
else if (err == AL_INVALID_OPERATION)
|
||||||
errname = "AL_INVALID_OPERATION";
|
errname = "AL_INVALID_OPERATION";
|
||||||
else if (err == AL_OUT_OF_MEMORY)
|
else if (err == AL_OUT_OF_MEMORY)
|
||||||
errname = "AL_OUT_OF_MEMORY";
|
errname = "AL_OUT_OF_MEMORY";
|
||||||
else
|
else
|
||||||
errname = "unknown";
|
errname = "unknown";
|
||||||
|
|
||||||
ErrorLog("sound: al error: " + errname);
|
ErrorLog("sound: al error: " + errname);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Deltatime == 0.0)
|
if (Deltatime == 0.0)
|
||||||
{
|
{
|
||||||
if (alcDevicePauseSOFT)
|
if (m_alcDevicePauseSOFT)
|
||||||
alcDevicePauseSOFT(m_device);
|
m_alcDevicePauseSOFT(m_device);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alcDeviceResumeSOFT)
|
if (m_alcDeviceResumeSOFT)
|
||||||
alcDeviceResumeSOFT(m_device);
|
m_alcDeviceResumeSOFT(m_device);
|
||||||
|
|
||||||
// follow audio output device changes (OpenAL won't on its own): if the active device is
|
// keep audio on the correct output (OpenAL won't re-route on its own). Reopen playback on the
|
||||||
// gone (e.g. headphones unplugged) reopen playback on the current default output. Polled at
|
// current default output when the active device is lost (headphones unplugged) or the system
|
||||||
// ~1 Hz to avoid per-frame ALC round-trips.
|
// default output changes (headphones plugged back in, or default switched in Windows). NULL
|
||||||
if( m_candetectdisconnect ) {
|
// device name selects the current default; context and sources are preserved.
|
||||||
m_devicechecktime += Deltatime;
|
if( m_alcReopenDeviceSOFT != nullptr && Global.AudioRenderer.empty() ) {
|
||||||
if( m_devicechecktime >= 1.0 ) {
|
bool needsreopen{ false };
|
||||||
m_devicechecktime = 0.0;
|
if( m_usedeviceevents ) {
|
||||||
ALCint connected{ ALC_TRUE };
|
// event-driven: the callback (any thread) flags default-output / device-removal changes
|
||||||
::alcGetIntegerv( m_device, ALC_CONNECTED, 1, &connected );
|
needsreopen = m_outputchanged.exchange( false );
|
||||||
if( connected == ALC_FALSE ) {
|
}
|
||||||
if( alcReopenDeviceSOFT != nullptr ) {
|
else if( m_candetectdisconnect ) {
|
||||||
// NULL device name selects the current default output; context and sources are preserved
|
// fallback without ALC_SOFT_system_events: poll for a hard disconnect at ~1 Hz
|
||||||
if( alcReopenDeviceSOFT( m_device, nullptr, m_contextattributes ) == ALC_TRUE ) {
|
m_devicechecktime += Deltatime;
|
||||||
WriteLog( "sound: active audio device lost, reopened on the current default output" );
|
if( m_devicechecktime >= 1.0 ) {
|
||||||
}
|
m_devicechecktime = 0.0;
|
||||||
else {
|
ALCint connected{ ALC_TRUE };
|
||||||
ErrorLog( "sound: active audio device lost, reopening on the default output failed (retrying)" );
|
::alcGetIntegerv( m_device, ALC_CONNECTED, 1, &connected );
|
||||||
}
|
needsreopen = ( connected == ALC_FALSE );
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
ErrorLog( "sound: active audio device lost and ALC_SOFT_reopen_device is unavailable; cannot recover" );
|
if( needsreopen ) {
|
||||||
}
|
if( m_alcReopenDeviceSOFT( m_device, nullptr, m_contextattributes ) == ALC_TRUE ) {
|
||||||
}
|
auto const *nowon { (char const *)::alcGetString( nullptr, ALC_DEFAULT_ALL_DEVICES_SPECIFIER ) };
|
||||||
}
|
WriteLog( "sound: audio output changed, reopened playback on \"" + std::string{ nowon ? nowon : "?" } + "\"" );
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
ErrorLog( "sound: audio output changed but reopening on the default device failed (will retry)" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// update listener
|
// update listener
|
||||||
// gain
|
// gain
|
||||||
@@ -464,11 +497,11 @@ openal_renderer::update( double const Deltatime ) {
|
|||||||
// reset potentially used volume change flag
|
// reset potentially used volume change flag
|
||||||
audio::event_volume_change = false;
|
audio::event_volume_change = false;
|
||||||
|
|
||||||
if (alProcessUpdatesSOFT)
|
if (m_alProcessUpdatesSOFT && m_alDeferUpdatesSOFT)
|
||||||
{
|
{
|
||||||
alProcessUpdatesSOFT();
|
m_alProcessUpdatesSOFT();
|
||||||
alDeferUpdatesSOFT();
|
m_alDeferUpdatesSOFT();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns an instance of implementation-side part of the sound emitter
|
// returns an instance of implementation-side part of the sound emitter
|
||||||
@@ -484,9 +517,14 @@ openal_renderer::fetch_source() {
|
|||||||
if( newsource.id == audio::null_resource ) {
|
if( newsource.id == audio::null_resource ) {
|
||||||
// if there's no source to reuse, try to generate a new one
|
// if there's no source to reuse, try to generate a new one
|
||||||
::alGenSources( 1, &newsource.id );
|
::alGenSources( 1, &newsource.id );
|
||||||
|
// Check for errors
|
||||||
|
ALenum err = alGetError();
|
||||||
|
if (err != AL_NO_ERROR) {
|
||||||
|
ErrorLog("sound: failed to generate source, error: " + std::to_string(err));
|
||||||
|
newsource.id = audio::null_resource;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if( newsource.id == audio::null_resource ) {
|
if( newsource.id == audio::null_resource ) {
|
||||||
alGetError();
|
|
||||||
// if we still don't have a working source, see if we can sacrifice an already active one
|
// if we still don't have a working source, see if we can sacrifice an already active one
|
||||||
// under presumption it's more important to play new sounds than keep the old ones going
|
// under presumption it's more important to play new sounds than keep the old ones going
|
||||||
// TBD, TODO: for better results we could use range and/or position for the new sound
|
// TBD, TODO: for better results we could use range and/or position for the new sound
|
||||||
@@ -504,7 +542,7 @@ openal_renderer::fetch_source() {
|
|||||||
}
|
}
|
||||||
auto const sourceweight { (
|
auto const sourceweight { (
|
||||||
source->sound_range != -1 ?
|
source->sound_range != -1 ?
|
||||||
source->sound_range * source->sound_range / ( glm::length2( source->sound_distance ) + 1 ) :
|
source->sound_range * source->sound_range / ( static_cast<float>( glm::length2( source->sound_distance ) ) + 1 ) :
|
||||||
std::numeric_limits<float>::max() ) };
|
std::numeric_limits<float>::max() ) };
|
||||||
if( sourceweight < leastimportantweight ) {
|
if( sourceweight < leastimportantweight ) {
|
||||||
leastimportantsource = source;
|
leastimportantsource = source;
|
||||||
@@ -524,12 +562,13 @@ openal_renderer::fetch_source() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( newsource.id == audio::null_resource ) {
|
if( newsource.id != audio::null_resource ) {
|
||||||
// for sources with functional emitter reset emitter parameters from potential last use
|
// for sources with functional emitter reset emitter parameters from potential last use
|
||||||
::alSourcef( newsource.id, AL_PITCH, 1.f );
|
::alSourcef( newsource.id, AL_PITCH, 1.f );
|
||||||
::alSourcef( newsource.id, AL_GAIN, 1.f );
|
::alSourcef( newsource.id, AL_GAIN, 1.f );
|
||||||
::alSourcefv( newsource.id, AL_POSITION, glm::value_ptr( glm::vec3{ 0.f } ) );
|
glm::vec3 zero_pos{ 0.f, 0.f, 0.f };
|
||||||
::alSourcefv( newsource.id, AL_VELOCITY, glm::value_ptr( glm::vec3{ 0.f } ) );
|
::alSourcefv( newsource.id, AL_POSITION, glm::value_ptr( zero_pos ) );
|
||||||
|
::alSourcefv( newsource.id, AL_VELOCITY, glm::value_ptr( zero_pos ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
return newsource;
|
return newsource;
|
||||||
@@ -554,7 +593,7 @@ openal_renderer::init_caps() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: default value of audio renderer variable is empty string, meaning argument of NULL i.e. 'preferred' device
|
// NOTE: default value of audio renderer variable is empty string, meaning argument of NULL i.e. 'preferred' device
|
||||||
m_device = ::alcOpenDevice( Global.AudioRenderer.c_str() );
|
m_device = ::alcOpenDevice( Global.AudioRenderer.empty() ? nullptr : Global.AudioRenderer.c_str() );
|
||||||
if( m_device == nullptr ) {
|
if( m_device == nullptr ) {
|
||||||
ErrorLog( "Failed to obtain audio device" );
|
ErrorLog( "Failed to obtain audio device" );
|
||||||
return false;
|
return false;
|
||||||
@@ -575,8 +614,8 @@ openal_renderer::init_caps() {
|
|||||||
|
|
||||||
WriteLog( "Supported extensions: " + std::string{ (char *)::alcGetString( m_device, ALC_EXTENSIONS ) } );
|
WriteLog( "Supported extensions: " + std::string{ (char *)::alcGetString( m_device, ALC_EXTENSIONS ) } );
|
||||||
|
|
||||||
ALCint attr[3] = { ALC_MONO_SOURCES, Global.audio_max_sources, 0 }; // request more sounds
|
ALCint attr[3] = { ALC_MONO_SOURCES, Global.audio_max_sources, 0 }; // request more sounds
|
||||||
std::copy( std::begin( attr ), std::end( attr ), std::begin( m_contextattributes ) ); // cached for device reopen
|
std::copy( std::begin( attr ), std::end( attr ), std::begin( m_contextattributes ) ); // cached for device reopen
|
||||||
|
|
||||||
m_context = ::alcCreateContext( m_device, attr );
|
m_context = ::alcCreateContext( m_device, attr );
|
||||||
if( m_context == nullptr ) {
|
if( m_context == nullptr ) {
|
||||||
@@ -584,40 +623,56 @@ openal_renderer::init_caps() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!alcMakeContextCurrent(m_context))
|
if (!alcMakeContextCurrent(m_context))
|
||||||
{
|
{
|
||||||
ErrorLog("sound: cannot select context");
|
ErrorLog("sound: cannot select context");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the version reported above is the OpenAL API spec level (always 1.1); the real implementation
|
// the version reported above is the OpenAL API spec level (always 1.1); the real implementation
|
||||||
// version string (e.g. "1.1 ALSOFT 1.24.2") is only queryable once a context is current
|
// version string (e.g. "1.1 ALSOFT 1.24.2") is only queryable once a context is current
|
||||||
if( auto const *libversion { (char const *)::alGetString( AL_VERSION ) } ) {
|
if( auto const *libversion { (char const *)::alGetString( AL_VERSION ) } ) {
|
||||||
crashreport_add_info( "openal_lib_version", libversion );
|
crashreport_add_info( "openal_lib_version", libversion );
|
||||||
WriteLog( "sound: library version: " + std::string{ libversion } );
|
WriteLog( "sound: library version: " + std::string{ libversion } );
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alIsExtensionPresent("AL_SOFT_deferred_updates"))
|
// Initialize all extension function pointers
|
||||||
{
|
if (alIsExtensionPresent("AL_SOFT_deferred_updates"))
|
||||||
alDeferUpdatesSOFT = (void(*)())alGetProcAddress("alDeferUpdatesSOFT");
|
{
|
||||||
alProcessUpdatesSOFT = (void(*)())alGetProcAddress("alProcessUpdatesSOFT");
|
m_alDeferUpdatesSOFT = (void(*)())alGetProcAddress("alDeferUpdatesSOFT");
|
||||||
}
|
m_alProcessUpdatesSOFT = (void(*)())alGetProcAddress("alProcessUpdatesSOFT");
|
||||||
if (!alDeferUpdatesSOFT || !alProcessUpdatesSOFT)
|
}
|
||||||
WriteLog("sound: warning: extension AL_SOFT_deferred_updates not found");
|
if (!m_alDeferUpdatesSOFT || !m_alProcessUpdatesSOFT)
|
||||||
|
WriteLog("sound: warning: extension AL_SOFT_deferred_updates not found");
|
||||||
|
|
||||||
if (alcIsExtensionPresent(m_device, "ALC_SOFT_pause_device"))
|
if (alcIsExtensionPresent(m_device, "ALC_SOFT_pause_device"))
|
||||||
{
|
{
|
||||||
alcDevicePauseSOFT = (void(*)(ALCdevice*))alcGetProcAddress(m_device, "alcDevicePauseSOFT");
|
m_alcDevicePauseSOFT = (void(*)(ALCdevice*))alcGetProcAddress(m_device, "alcDevicePauseSOFT");
|
||||||
alcDeviceResumeSOFT = (void(*)(ALCdevice*))alcGetProcAddress(m_device, "alcDeviceResumeSOFT");
|
m_alcDeviceResumeSOFT = (void(*)(ALCdevice*))alcGetProcAddress(m_device, "alcDeviceResumeSOFT");
|
||||||
}
|
}
|
||||||
if (!alcDevicePauseSOFT || !alcDeviceResumeSOFT)
|
if (!m_alcDevicePauseSOFT || !m_alcDeviceResumeSOFT)
|
||||||
WriteLog("sound: warning: extension ALC_SOFT_pause_device not found");
|
WriteLog("sound: warning: extension ALC_SOFT_pause_device not found");
|
||||||
|
|
||||||
m_candetectdisconnect = ( alcIsExtensionPresent( m_device, "ALC_EXT_disconnect" ) == ALC_TRUE );
|
m_candetectdisconnect = ( alcIsExtensionPresent( m_device, "ALC_EXT_disconnect" ) == ALC_TRUE );
|
||||||
if( alcIsExtensionPresent( m_device, "ALC_SOFT_reopen_device" ) == ALC_TRUE )
|
if( alcIsExtensionPresent( m_device, "ALC_SOFT_reopen_device" ) == ALC_TRUE )
|
||||||
alcReopenDeviceSOFT = (ALCboolean(*)(ALCdevice*, ALCchar const*, ALCint const*))alcGetProcAddress( m_device, "alcReopenDeviceSOFT" );
|
m_alcReopenDeviceSOFT = (ALCboolean(*)(ALCdevice*, ALCchar const*, ALCint const*))alcGetProcAddress( m_device, "alcReopenDeviceSOFT" );
|
||||||
if( !alcReopenDeviceSOFT )
|
if( !m_alcReopenDeviceSOFT )
|
||||||
WriteLog( "sound: warning: extension ALC_SOFT_reopen_device not found; audio output device changes won't be followed" );
|
WriteLog( "sound: warning: extension ALC_SOFT_reopen_device not found; audio output device changes won't be followed" );
|
||||||
|
|
||||||
|
// prefer event-driven output following (ALC_SOFT_system_events) over polling: it reliably
|
||||||
|
// catches both device removal and default-output changes, incl. re-plugging headphones
|
||||||
|
if( m_alcReopenDeviceSOFT != nullptr
|
||||||
|
&& alcIsExtensionPresent( m_device, "ALC_SOFT_system_events" ) == ALC_TRUE ) {
|
||||||
|
m_alcEventControlSOFT = (ALCboolean(*)(ALCsizei, ALCenum const*, ALCboolean))alcGetProcAddress( m_device, "alcEventControlSOFT" );
|
||||||
|
m_alcEventCallbackSOFT = (void(*)(alc_event_proc, void*))alcGetProcAddress( m_device, "alcEventCallbackSOFT" );
|
||||||
|
if( m_alcEventControlSOFT != nullptr && m_alcEventCallbackSOFT != nullptr ) {
|
||||||
|
m_alcEventCallbackSOFT( &openal_renderer::device_event_callback, this );
|
||||||
|
ALCenum const events[]{ ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT, ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT };
|
||||||
|
m_alcEventControlSOFT( 2, events, ALC_TRUE );
|
||||||
|
m_usedeviceevents = true;
|
||||||
|
WriteLog( "sound: following audio output device changes via ALC_SOFT_system_events" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,21 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "model/ResourceManager.h"
|
#include "model/ResourceManager.h"
|
||||||
#include "application/uitranscripts.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
|
#define EU07_SOUND_PROOFINGUSESRANGE
|
||||||
|
|
||||||
class opengl_renderer;
|
class opengl_renderer;
|
||||||
@@ -21,13 +36,12 @@ class sound_source;
|
|||||||
using uint32_sequence = std::vector<std::uint32_t>;
|
using uint32_sequence = std::vector<std::uint32_t>;
|
||||||
|
|
||||||
enum class sound_category : unsigned int {
|
enum class sound_category : unsigned int {
|
||||||
unknown = 0, // source gain is unaltered
|
unknown = 0,
|
||||||
vehicle, // source gain is altered by vehicle sound volume modifier
|
vehicle,
|
||||||
local, // source gain is altered by positional environment sound volume modifier
|
local,
|
||||||
ambient, // source gain is altered by ambient environment sound volume modifier
|
ambient,
|
||||||
};
|
};
|
||||||
|
|
||||||
// sound emitter state sync item
|
|
||||||
struct sound_properties {
|
struct sound_properties {
|
||||||
glm::dvec3 location;
|
glm::dvec3 location;
|
||||||
float pitch { 1.f };
|
float pitch { 1.f };
|
||||||
@@ -45,148 +59,111 @@ enum class sync_state {
|
|||||||
|
|
||||||
namespace audio {
|
namespace audio {
|
||||||
|
|
||||||
// implementation part of the sound emitter
|
|
||||||
// TODO: generic interface base, for implementations other than openAL
|
|
||||||
struct openal_source {
|
struct openal_source {
|
||||||
|
|
||||||
friend class openal_renderer;
|
friend class openal_renderer;
|
||||||
|
|
||||||
// types
|
|
||||||
using buffer_sequence = std::vector<audio::buffer_handle>;
|
using buffer_sequence = std::vector<audio::buffer_handle>;
|
||||||
|
|
||||||
// members
|
ALuint id { audio::null_resource };
|
||||||
ALuint id { audio::null_resource }; // associated AL resource
|
sound_source *controller { nullptr };
|
||||||
sound_source *controller { nullptr }; // source controller
|
uint32_sequence sounds;
|
||||||
uint32_sequence sounds; //
|
int sound_index { 0 };
|
||||||
// buffer_sequence buffers; // sequence of samples the source will emit
|
bool sound_change { false };
|
||||||
int sound_index { 0 }; // currently queued sample from the buffer sequence
|
|
||||||
bool sound_change { false }; // indicates currently queued sample has changed
|
|
||||||
bool is_playing { false };
|
bool is_playing { false };
|
||||||
bool is_looping { false };
|
bool is_looping { false };
|
||||||
sound_properties properties;
|
sound_properties properties;
|
||||||
sync_state sync { sync_state::good };
|
sync_state sync { sync_state::good };
|
||||||
// constructors
|
|
||||||
openal_source() = default;
|
openal_source() = default;
|
||||||
// methods
|
|
||||||
template <class Iterator_>
|
template <class Iterator_>
|
||||||
openal_source &
|
openal_source &bind( sound_source *Controller, uint32_sequence Sounds, Iterator_ First, Iterator_ Last );
|
||||||
bind( sound_source *Controller, uint32_sequence Sounds, Iterator_ First, Iterator_ Last );
|
|
||||||
// starts playback of queued buffers
|
void play();
|
||||||
void
|
void update( double const Deltatime, glm::vec3 const &Listenervelocity );
|
||||||
play();
|
void sync_with( sound_properties const &State );
|
||||||
// updates state of the source
|
void stop();
|
||||||
void
|
void loop( bool const State );
|
||||||
update( double const Deltatime, glm::vec3 const &Listenervelocity );
|
void range( float const Range );
|
||||||
// configures state of the source to match the provided set of properties
|
void pitch( float const Pitch );
|
||||||
void
|
void clear();
|
||||||
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();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// members
|
double update_deltatime { 0.0 };
|
||||||
double update_deltatime { 0.0 }; // time delta of most current update
|
float pitch_variation { 1.f };
|
||||||
float pitch_variation { 1.f }; // emitter-specific variation of the base pitch
|
float sound_range { 50.f };
|
||||||
float sound_range { 50.f }; // cached audible range of the emitted samples
|
glm::vec3 sound_distance { 0.f };
|
||||||
glm::vec3 sound_distance { 0.f }; // cached distance between sound and the listener
|
glm::vec3 sound_velocity { 0.f };
|
||||||
glm::vec3 sound_velocity { 0.f }; // sound movement vector
|
bool is_in_range { false };
|
||||||
bool is_in_range { false }; // helper, indicates the source was recently within audible range
|
bool is_multipart { false };
|
||||||
bool is_multipart { false }; // multi-part sounds are kept alive at longer ranges
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class openal_renderer {
|
class openal_renderer {
|
||||||
|
|
||||||
friend opengl_renderer;
|
friend opengl_renderer;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// constructors
|
|
||||||
openal_renderer() = default;
|
openal_renderer() = default;
|
||||||
// destructor
|
|
||||||
~openal_renderer();
|
~openal_renderer();
|
||||||
// methods
|
|
||||||
// buffer methods
|
audio::buffer_handle fetch_buffer( std::string const &Filename );
|
||||||
// returns handle to a buffer containing audio data from specified file
|
audio::openal_buffer const &buffer( audio::buffer_handle const Buffer ) const;
|
||||||
audio::buffer_handle
|
|
||||||
fetch_buffer( std::string const &Filename );
|
bool init();
|
||||||
// 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
|
|
||||||
template <class Iterator_>
|
template <class Iterator_>
|
||||||
void
|
void insert( Iterator_ First, Iterator_ Last, sound_source *Controller, uint32_sequence Sounds ) {
|
||||||
insert( Iterator_ First, Iterator_ Last, sound_source *Controller, uint32_sequence Sounds ) {
|
m_sources.emplace_back( fetch_source().bind( Controller, Sounds, First, Last ) );
|
||||||
m_sources.emplace_back( fetch_source().bind( Controller, Sounds, First, Last ) ); }
|
}
|
||||||
// removes from the queue all sounds controlled by the specified sound emitter
|
|
||||||
void
|
void erase( sound_source const *Controller );
|
||||||
erase( sound_source const *Controller );
|
void update( double const Deltatime );
|
||||||
// updates state of all active emitters
|
|
||||||
void
|
|
||||||
update( double const Deltatime );
|
|
||||||
|
|
||||||
glm::dvec3 cached_camerapos;
|
glm::dvec3 cached_camerapos;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// types
|
|
||||||
using source_list = std::list<audio::openal_source>;
|
using source_list = std::list<audio::openal_source>;
|
||||||
using source_sequence = std::stack<ALuint>;
|
using source_sequence = std::stack<ALuint>;
|
||||||
// methods
|
|
||||||
bool
|
bool init_caps();
|
||||||
init_caps();
|
audio::openal_source fetch_source();
|
||||||
// returns an instance of implementation-side part of the sound emitter
|
|
||||||
audio::openal_source
|
ALCdevice *m_device { nullptr };
|
||||||
fetch_source();
|
ALCcontext *m_context { nullptr };
|
||||||
// members
|
bool m_ready { false };
|
||||||
ALCdevice * m_device { nullptr };
|
|
||||||
ALCcontext * m_context { nullptr };
|
|
||||||
bool m_ready { false }; // renderer is initialized and functional
|
|
||||||
/*
|
|
||||||
glm::dvec3 m_listenerposition;
|
|
||||||
*/
|
|
||||||
glm::vec3 m_listenervelocity;
|
glm::vec3 m_listenervelocity;
|
||||||
bool m_freeflymode{ true };
|
bool m_freeflymode { true };
|
||||||
bool m_windowopen{ true };
|
bool m_windowopen { true };
|
||||||
int m_activecab{ 0 };
|
int m_activecab { 0 };
|
||||||
|
|
||||||
buffer_manager m_buffers;
|
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_list m_sources;
|
||||||
source_sequence m_sourcespares; // already created and currently unused sound sources
|
source_sequence m_sourcespares;
|
||||||
|
|
||||||
void (*alDeferUpdatesSOFT)() = nullptr;
|
// Poprawione deklaracje dla OpenAL 1.25
|
||||||
void (*alProcessUpdatesSOFT)() = nullptr;
|
void (*m_alDeferUpdatesSOFT)(void) { nullptr };
|
||||||
void (*alcDevicePauseSOFT)(ALCdevice*) = nullptr;
|
void (*m_alProcessUpdatesSOFT)(void) { nullptr };
|
||||||
void (*alcDeviceResumeSOFT)(ALCdevice*) = nullptr;
|
void (*m_alcDevicePauseSOFT)(ALCdevice*) { nullptr };
|
||||||
// ALC_SOFT_reopen_device: move playback to the current default output when the active
|
void (*m_alcDeviceResumeSOFT)(ALCdevice*) { nullptr };
|
||||||
// device disappears (e.g. headphones unplugged mid-game), keeping context and sources
|
ALCboolean (*m_alcReopenDeviceSOFT)(ALCdevice*, ALCchar const*, ALCint const*) { nullptr };
|
||||||
ALCboolean (*alcReopenDeviceSOFT)(ALCdevice*, ALCchar const*, ALCint const*) = nullptr;
|
|
||||||
bool m_candetectdisconnect{ false }; // ALC_EXT_disconnect present, ALC_CONNECTED is queryable
|
bool m_candetectdisconnect { false };
|
||||||
ALCint m_contextattributes[3]{ 0, 0, 0 }; // cached context attribs, reused when reopening the device
|
ALCint m_contextattributes[3] { 0, 0, 0 };
|
||||||
double m_devicechecktime{ 0.0 }; // accumulates dt to throttle the ALC_CONNECTED poll to ~1 Hz
|
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 openal_renderer renderer;
|
||||||
extern bool event_volume_change;
|
extern bool event_volume_change;
|
||||||
|
|
||||||
} // audio
|
} // namespace audio
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
|
||||||
Reference in New Issue
Block a user