mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-17 23:39:18 +02:00
OpenAL does not re-route on its own, so unplugging the active output device (e.g. headphones) mid-game left the sim silent until restart. Poll ALC_CONNECTED (ALC_EXT_disconnect) at ~1 Hz and, when the device is lost, reopen playback on the current default output via alcReopenDeviceSOFT, preserving the context and all sources. Tokens are declared locally since the bundled AL headers ship no alext.h; the entry point is resolved at runtime, so this is a no-op (with a log warning) on OpenAL Soft builds too old to provide ALC_SOFT_reopen_device.
619 lines
23 KiB
C++
619 lines
23 KiB
C++
/*
|
|
This Source Code Form is subject to the
|
|
terms of the Mozilla Public License, v.
|
|
2.0. If a copy of the MPL was not
|
|
distributed with this file, You can
|
|
obtain one at
|
|
http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "audio/audiorenderer.h"
|
|
|
|
#include "audio/sound.h"
|
|
#include "utilities/Globals.h"
|
|
#include "vehicle/Camera.h"
|
|
#include "utilities/Logs.h"
|
|
#include "simulation/simulation.h"
|
|
#include "vehicle/Train.h"
|
|
|
|
// ALC_EXT_disconnect / ALC_SOFT_reopen_device tokens; the bundled AL headers ship no alext.h,
|
|
// but OpenAL Soft provides these at runtime (resolved via alcGetProcAddress).
|
|
#ifndef ALC_CONNECTED
|
|
#define ALC_CONNECTED 0x313
|
|
#endif
|
|
|
|
namespace audio {
|
|
|
|
openal_renderer renderer;
|
|
bool event_volume_change { false };
|
|
|
|
float const EU07_SOUND_CUTOFFRANGE { 3000.f }; // 2750 m = max expected emitter spawn range, plus safety margin
|
|
float const EU07_SOUND_VELOCITYLIMIT { 250 / 3.6f }; // 343 m/sec ~= speed of sound; arbitrary limit of 250 km/h
|
|
|
|
// potentially clamps length of provided vector to 343 meters
|
|
// TBD: make a generic method for utilities out of this
|
|
glm::vec3
|
|
limit_velocity( glm::vec3 const &Velocity ) {
|
|
|
|
auto const ratio { glm::length( Velocity ) / EU07_SOUND_VELOCITYLIMIT };
|
|
|
|
return ratio > 1.f ? Velocity / ratio : Velocity;
|
|
}
|
|
|
|
// starts playback of queued buffers
|
|
void
|
|
openal_source::play() {
|
|
|
|
if( id == audio::null_resource ) { return; } // no implementation-side source to match, no point
|
|
|
|
::alSourcePlay( id );
|
|
|
|
ALint state;
|
|
::alGetSourcei( id, AL_SOURCE_STATE, &state );
|
|
is_playing = state == AL_PLAYING;
|
|
}
|
|
|
|
// stops the playback
|
|
void
|
|
openal_source::stop() {
|
|
|
|
if( id == audio::null_resource ) { return; } // no implementation-side source to match, no point
|
|
|
|
loop( false );
|
|
// NOTE: workaround for potential edge cases where ::alSourceStop() doesn't set source which wasn't yet started to AL_STOPPED
|
|
int state;
|
|
::alGetSourcei( id, AL_SOURCE_STATE, &state );
|
|
if( state == AL_INITIAL ) {
|
|
play();
|
|
}
|
|
::alSourceStop( id );
|
|
is_playing = false;
|
|
}
|
|
|
|
// updates state of the source
|
|
void
|
|
openal_source::update( double const Deltatime, glm::vec3 const &Listenervelocity ) {
|
|
|
|
update_deltatime = Deltatime; // cached for time-based processing of data from the controller
|
|
if( sound_range < 0.0 ) {
|
|
sound_velocity = Listenervelocity; // cached for doppler shift calculation
|
|
}
|
|
/*
|
|
// HACK: if the application gets stuck for long time loading assets the audio can gone awry.
|
|
// terminate all sources when it happens to stay on the safe side
|
|
if( Deltatime > 1.0 ) {
|
|
stop();
|
|
}
|
|
*/
|
|
if( id != audio::null_resource ) {
|
|
|
|
sound_change = false;
|
|
::alGetSourcei( id, AL_BUFFERS_PROCESSED, &sound_index );
|
|
// for multipart sounds trim away processed buffers until only one remains, the last one may be set to looping by the controller
|
|
// TBD, TODO: instead of change flag move processed buffer ids to separate queue, for accurate tracking of longer buffer sequences
|
|
ALuint discard;
|
|
while( sound_index > 0
|
|
&& sounds.size() > 1 ) {
|
|
::alSourceUnqueueBuffers( id, 1, &discard );
|
|
sounds.erase( std::begin( sounds ) );
|
|
--sound_index;
|
|
sound_change = true;
|
|
// potentially adjust starting point of the last buffer (to reduce chance of reverb effect with multiple, looping copies playing)
|
|
if( controller->start() > 0.f && sounds.size() == 1 ) {
|
|
ALint bufferid;
|
|
::alGetSourcei(
|
|
id,
|
|
AL_BUFFER,
|
|
&bufferid );
|
|
ALint buffersize;
|
|
::alGetBufferi( bufferid, AL_SIZE, &buffersize );
|
|
::alSourcei(
|
|
id,
|
|
AL_SAMPLE_OFFSET,
|
|
static_cast<ALint>( controller->start() * ( buffersize / sizeof( std::int16_t ) ) ) );
|
|
}
|
|
}
|
|
|
|
int state;
|
|
::alGetSourcei( id, AL_SOURCE_STATE, &state );
|
|
is_playing = state == AL_PLAYING;
|
|
}
|
|
|
|
// request instructions from the controller
|
|
controller->update( *this );
|
|
}
|
|
|
|
// configures state of the source to match the provided set of properties
|
|
void
|
|
openal_source::sync_with( sound_properties const &State ) {
|
|
|
|
if( id == audio::null_resource ) {
|
|
// no implementation-side source to match, return sync error so the controller can clean up on its end
|
|
sync = sync_state::bad_resource;
|
|
return;
|
|
}
|
|
// velocity
|
|
if( update_deltatime > 0.0
|
|
&& sound_range >= 0
|
|
&& properties.location != glm::dvec3() ) {
|
|
// after sound position was initialized we can start velocity calculations
|
|
sound_velocity = limit_velocity( ( State.location - properties.location ) / update_deltatime );
|
|
}
|
|
// NOTE: velocity at this point can be either listener velocity for global sounds, actual sound velocity, or 0 if sound position is yet unknown
|
|
::alSourcefv( id, AL_VELOCITY, glm::value_ptr( sound_velocity ) );
|
|
|
|
// location
|
|
sound_distance = State.location - renderer.cached_camerapos;
|
|
if( sound_range != -1 ) {
|
|
// range cutoff check for songs other than 'unlimited'
|
|
// NOTE: since we're comparing squared distances we can ignore that sound range can be negative
|
|
auto const cutoffrange = is_multipart ? EU07_SOUND_CUTOFFRANGE : // we keep multi-part sounds around longer, to minimize restarts as the sounds get out and back in range
|
|
sound_range * 7.5f;
|
|
if( glm::length2( sound_distance ) > std::min( sq(cutoffrange), sq(EU07_SOUND_CUTOFFRANGE) ) ) {
|
|
stop();
|
|
sync = sync_state::bad_distance; // flag sync failure for the controller
|
|
return;
|
|
}
|
|
}
|
|
if( sound_range >= 0 ) {
|
|
::alSourcefv( id, AL_POSITION, glm::value_ptr( sound_distance ) );
|
|
}
|
|
else {
|
|
// sounds with 'unlimited' or negative range are positioned on top of the listener
|
|
::alSourcefv( id, AL_POSITION, glm::value_ptr( glm::vec3() ) );
|
|
}
|
|
// gain
|
|
auto const gain {
|
|
State.gain
|
|
* State.soundproofing
|
|
* ( State.category == sound_category::vehicle ? Global.VehicleVolume :
|
|
State.category == sound_category::local ? Global.EnvironmentPositionalVolume :
|
|
State.category == sound_category::ambient ? Global.EnvironmentAmbientVolume :
|
|
1.f ) };
|
|
if( State.gain != properties.gain
|
|
|| State.soundproofing_stamp != properties.soundproofing_stamp
|
|
|| audio::event_volume_change ) {
|
|
// gain value has changed
|
|
::alSourcef( id, AL_GAIN, gain );
|
|
auto const range { (
|
|
sound_range >= 0 ?
|
|
sound_range :
|
|
5 ) }; // range of -1 means sound of unlimited range, positioned at the listener
|
|
::alSourcef( id, AL_REFERENCE_DISTANCE, range * ( 1.f / 16.f ) * State.soundproofing );
|
|
}
|
|
if( sound_range != -1 ) {
|
|
auto const rangesquared { sound_range * sound_range };
|
|
auto const distancesquared { glm::length2( sound_distance ) };
|
|
if( distancesquared > rangesquared
|
|
|| false == is_in_range ) {
|
|
// if the emitter is outside of its nominal hearing range or was outside of it during last check
|
|
// adjust the volume to a suitable fraction of nominal value
|
|
auto const fadedistance { sound_range * 0.75f };
|
|
auto const rangefactor {
|
|
std::lerp(
|
|
1.f, 0.f, std::clamp(
|
|
( distancesquared - rangesquared ) / ( fadedistance * fadedistance ),
|
|
0.f, 1.f ) ) };
|
|
::alSourcef( id, AL_GAIN, gain * rangefactor );
|
|
}
|
|
is_in_range = distancesquared <= rangesquared;
|
|
}
|
|
// pitch
|
|
if( State.pitch != properties.pitch ) {
|
|
// pitch value has changed
|
|
::alSourcef( id, AL_PITCH, std::clamp( State.pitch * pitch_variation, 0.1f, 10.f ) );
|
|
}
|
|
// all synced up
|
|
properties = State;
|
|
sync = sync_state::good;
|
|
}
|
|
|
|
// sets max audible distance for sounds emitted by the source
|
|
void
|
|
openal_source::range( float const Range ) {
|
|
|
|
// NOTE: we cache actual specified range, as we'll be giving 'unlimited' range special treatment
|
|
sound_range = Range;
|
|
|
|
if( id == audio::null_resource ) { return; } // no implementation-side source to match, no point
|
|
|
|
auto const range { (
|
|
Range >= 0 ?
|
|
Range :
|
|
5 ) }; // range of -1 means sound of unlimited range, positioned at the listener
|
|
::alSourcef( id, AL_REFERENCE_DISTANCE, range * ( 1.f / 16.f ) );
|
|
::alSourcef( id, AL_ROLLOFF_FACTOR, 1.75f );
|
|
}
|
|
|
|
// sets modifier applied to the pitch of sounds emitted by the source
|
|
void
|
|
openal_source::pitch( float const Pitch ) {
|
|
|
|
pitch_variation = Pitch;
|
|
// invalidate current pitch value to enforce change of next syns
|
|
properties.pitch = -1.f;
|
|
}
|
|
|
|
// toggles looping of the sound emitted by the source
|
|
void
|
|
openal_source::loop( bool const State ) {
|
|
|
|
if( id == audio::null_resource ) { return; } // no implementation-side source to match, no point
|
|
if( is_looping == State ) { return; }
|
|
|
|
is_looping = State;
|
|
::alSourcei(
|
|
id,
|
|
AL_LOOPING,
|
|
State ? AL_TRUE : AL_FALSE);
|
|
}
|
|
|
|
// releases bound buffers and resets state of the class variables
|
|
// NOTE: doesn't release allocated implementation-side source
|
|
void
|
|
openal_source::clear() {
|
|
|
|
if( id != audio::null_resource ) {
|
|
// unqueue bound buffers:
|
|
// ensure no buffer is in use...
|
|
stop();
|
|
// ...prepare space for returned ids of unqueued buffers (not that we need that info)...
|
|
std::vector<ALuint> bufferids;
|
|
bufferids.resize( sounds.size() );
|
|
// ...release the buffers...
|
|
::alSourceUnqueueBuffers( id, bufferids.size(), bufferids.data() );
|
|
}
|
|
// ...and reset reset the properties, except for the id of the allocated source
|
|
// NOTE: not strictly necessary since except for the id the source data typically get discarded in next step
|
|
auto const sourceid { id };
|
|
*this = openal_source();
|
|
id = sourceid;
|
|
}
|
|
|
|
|
|
|
|
openal_renderer::~openal_renderer() {
|
|
|
|
::alcMakeContextCurrent( nullptr );
|
|
|
|
if( m_context != nullptr ) { ::alcDestroyContext( m_context ); }
|
|
if( m_device != nullptr ) { ::alcCloseDevice( m_device ); }
|
|
}
|
|
|
|
audio::buffer_handle
|
|
openal_renderer::fetch_buffer( std::string const &Filename ) {
|
|
|
|
return m_buffers.create( Filename );
|
|
}
|
|
|
|
// provides direct access to a specified buffer
|
|
audio::openal_buffer const &
|
|
openal_renderer::buffer( audio::buffer_handle const Buffer ) const {
|
|
|
|
return m_buffers.buffer( Buffer );
|
|
}
|
|
|
|
// initializes the service
|
|
bool
|
|
openal_renderer::init() {
|
|
|
|
if( true == m_ready ) {
|
|
// already initialized and enabled
|
|
return true;
|
|
}
|
|
if( false == init_caps() ) {
|
|
// basic initialization failed
|
|
return false;
|
|
}
|
|
::alDistanceModel( AL_INVERSE_DISTANCE_CLAMPED );
|
|
::alDopplerFactor( 0.25f );
|
|
// all done
|
|
m_ready = true;
|
|
return true;
|
|
}
|
|
|
|
// removes from the queue all sounds controlled by the specified sound emitter
|
|
void
|
|
openal_renderer::erase( sound_source const *Controller ) {
|
|
|
|
auto source { std::begin( m_sources ) };
|
|
while( source != std::end( m_sources ) ) {
|
|
if( source->controller == Controller ) {
|
|
// if the controller is the one specified, kill it
|
|
source->clear();
|
|
if( source->id != audio::null_resource ) {
|
|
// keep around functional sources, but no point in doing it with the above-the-limit ones
|
|
m_sourcespares.push( source->id );
|
|
}
|
|
source = m_sources.erase( source );
|
|
}
|
|
else {
|
|
// otherwise proceed through the list normally
|
|
++source;
|
|
}
|
|
}
|
|
}
|
|
|
|
// updates state of all active emitters
|
|
void
|
|
openal_renderer::update( double const Deltatime ) {
|
|
|
|
ALenum err = alGetError();
|
|
if (err != AL_NO_ERROR)
|
|
{
|
|
std::string errname;
|
|
if (err == AL_INVALID_NAME)
|
|
errname = "AL_INVALID_NAME";
|
|
else if (err == AL_INVALID_ENUM)
|
|
errname = "AL_INVALID_ENUM";
|
|
else if (err == AL_INVALID_VALUE)
|
|
errname = "AL_INVALID_VALUE";
|
|
else if (err == AL_INVALID_OPERATION)
|
|
errname = "AL_INVALID_OPERATION";
|
|
else if (err == AL_OUT_OF_MEMORY)
|
|
errname = "AL_OUT_OF_MEMORY";
|
|
else
|
|
errname = "unknown";
|
|
|
|
ErrorLog("sound: al error: " + errname);
|
|
}
|
|
|
|
if (Deltatime == 0.0)
|
|
{
|
|
if (alcDevicePauseSOFT)
|
|
alcDevicePauseSOFT(m_device);
|
|
return;
|
|
}
|
|
|
|
if (alcDeviceResumeSOFT)
|
|
alcDeviceResumeSOFT(m_device);
|
|
|
|
// follow audio output device changes (OpenAL won't on its own): if the active device is
|
|
// gone (e.g. headphones unplugged) reopen playback on the current default output. Polled at
|
|
// ~1 Hz to avoid per-frame ALC round-trips.
|
|
if( m_candetectdisconnect ) {
|
|
m_devicechecktime += Deltatime;
|
|
if( m_devicechecktime >= 1.0 ) {
|
|
m_devicechecktime = 0.0;
|
|
ALCint connected{ ALC_TRUE };
|
|
::alcGetIntegerv( m_device, ALC_CONNECTED, 1, &connected );
|
|
if( connected == ALC_FALSE ) {
|
|
if( alcReopenDeviceSOFT != nullptr ) {
|
|
// NULL device name selects the current default output; context and sources are preserved
|
|
if( alcReopenDeviceSOFT( m_device, nullptr, m_contextattributes ) == ALC_TRUE ) {
|
|
WriteLog( "sound: active audio device lost, reopened on the current default output" );
|
|
}
|
|
else {
|
|
ErrorLog( "sound: active audio device lost, reopening on the default output failed (retrying)" );
|
|
}
|
|
}
|
|
else {
|
|
ErrorLog( "sound: active audio device lost and ALC_SOFT_reopen_device is unavailable; cannot recover" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// update listener
|
|
// gain
|
|
::alListenerf( AL_GAIN, Global.AudioVolume );
|
|
// orientation
|
|
glm::dmat4 cameramatrix;
|
|
Global.pCamera.SetMatrix( cameramatrix );
|
|
auto cameraposition = Global.pCamera.Pos + glm::dvec3(Global.viewport_move * glm::mat3(cameramatrix));
|
|
cameramatrix = glm::dmat4(glm::inverse(Global.viewport_rotate)) * cameramatrix;
|
|
auto rotationmatrix { glm::mat3{ cameramatrix } };
|
|
// AL_ORIENTATION expects 6 tightly-packed floats (at, then up). Do NOT reinterpret a
|
|
// glm::vec3[2] here: with GLM_FORCE_DEFAULT_ALIGNED_GENTYPES a glm::vec3 is 16 bytes
|
|
// (padded), so the array is not 6 contiguous floats and the 'up' vector gets read from
|
|
// padding as garbage, corrupting the listener basis (left/right swapped).
|
|
auto const at { glm::vec3{ 0, 0,-1 } * rotationmatrix };
|
|
auto const up { glm::vec3{ 0, 1, 0 } * rotationmatrix };
|
|
ALfloat const orientation[ 6 ] = { at.x, at.y, at.z, up.x, up.y, up.z };
|
|
::alListenerfv( AL_ORIENTATION, orientation );
|
|
// velocity
|
|
if( Deltatime > 0 ) {
|
|
auto cameramove { cameraposition - cached_camerapos };
|
|
cached_camerapos = cameraposition;
|
|
// intercept sudden user-induced camera jumps...
|
|
// ...from free fly mode change
|
|
if( m_freeflymode != FreeFlyModeFlag ) {
|
|
m_freeflymode = FreeFlyModeFlag;
|
|
cameramove = glm::dvec3{ 0.0 };
|
|
}
|
|
// ...from jump between cab and window/mirror view
|
|
if( m_windowopen != Global.CabWindowOpen ) {
|
|
m_windowopen = Global.CabWindowOpen;
|
|
cameramove = glm::dvec3{ 0.0 };
|
|
}
|
|
// ... from cab change
|
|
if( simulation::Train != nullptr && simulation::Train->iCabn != m_activecab ) {
|
|
m_activecab = simulation::Train->iCabn;
|
|
cameramove = glm::dvec3{ 0.0 };
|
|
}
|
|
// ... from camera jump to another location
|
|
if( glm::length2( cameramove ) > sq(100.0)) { // length2 is better than length for comparing because it does not require sqrt function
|
|
cameramove = glm::dvec3{ 0.0 };
|
|
}
|
|
m_listenervelocity = limit_velocity( cameramove / Deltatime );
|
|
|
|
::alListenerfv( AL_VELOCITY, reinterpret_cast<ALfloat const *>( glm::value_ptr( m_listenervelocity ) ) );
|
|
}
|
|
|
|
// update active emitters
|
|
auto source { std::begin( m_sources ) };
|
|
while( source != std::end( m_sources ) ) {
|
|
// update each source
|
|
source->update( Deltatime, m_listenervelocity );
|
|
// if after the update the source isn't playing, put it away on the spare stack, it's done
|
|
if( false == source->is_playing ) {
|
|
source->clear();
|
|
if( source->id != audio::null_resource ) {
|
|
// keep around functional sources, but no point in doing it with the above-the-limit ones
|
|
m_sourcespares.push( source->id );
|
|
}
|
|
source = m_sources.erase( source );
|
|
}
|
|
else {
|
|
// otherwise proceed through the list normally
|
|
++source;
|
|
}
|
|
}
|
|
|
|
// reset potentially used volume change flag
|
|
audio::event_volume_change = false;
|
|
|
|
if (alProcessUpdatesSOFT)
|
|
{
|
|
alProcessUpdatesSOFT();
|
|
alDeferUpdatesSOFT();
|
|
}
|
|
}
|
|
|
|
// returns an instance of implementation-side part of the sound emitter
|
|
audio::openal_source
|
|
openal_renderer::fetch_source() {
|
|
|
|
audio::openal_source newsource;
|
|
if( false == m_sourcespares.empty() ) {
|
|
// reuse (a copy of) already allocated source
|
|
newsource.id = m_sourcespares.top();
|
|
m_sourcespares.pop();
|
|
}
|
|
if( newsource.id == audio::null_resource ) {
|
|
// if there's no source to reuse, try to generate a new one
|
|
::alGenSources( 1, &newsource.id );
|
|
}
|
|
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
|
|
// 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
|
|
// to better weight whether the new sound is really more important
|
|
auto leastimportantsource { std::end( m_sources ) };
|
|
auto leastimportantweight { std::numeric_limits<float>::max() };
|
|
|
|
for( auto source { std::begin( m_sources ) }; source != std::cend( m_sources ); ++source ) {
|
|
|
|
if( source->id == audio::null_resource
|
|
|| true == source->is_multipart
|
|
|| false == source->is_playing ) {
|
|
|
|
continue;
|
|
}
|
|
auto const sourceweight { (
|
|
source->sound_range != -1 ?
|
|
source->sound_range * source->sound_range / ( glm::length2( source->sound_distance ) + 1 ) :
|
|
std::numeric_limits<float>::max() ) };
|
|
if( sourceweight < leastimportantweight ) {
|
|
leastimportantsource = source;
|
|
leastimportantweight = sourceweight;
|
|
}
|
|
}
|
|
if( leastimportantsource != std::end(m_sources)
|
|
&& leastimportantweight < 1.f ) {
|
|
// only accept the candidate if it's outside of its nominal hearing range
|
|
leastimportantsource->stop();
|
|
// HACK: dt of 0 is a roundabout way to notify the controller its emitter has stopped
|
|
leastimportantsource->update( 0, m_listenervelocity );
|
|
leastimportantsource->clear();
|
|
// we should be now free to grab the id and get rid of the remains
|
|
newsource.id = leastimportantsource->id;
|
|
m_sources.erase( leastimportantsource );
|
|
}
|
|
}
|
|
|
|
if( newsource.id == audio::null_resource ) {
|
|
// for sources with functional emitter reset emitter parameters from potential last use
|
|
::alSourcef( newsource.id, AL_PITCH, 1.f );
|
|
::alSourcef( newsource.id, AL_GAIN, 1.f );
|
|
::alSourcefv( newsource.id, AL_POSITION, glm::value_ptr( glm::vec3{ 0.f } ) );
|
|
::alSourcefv( newsource.id, AL_VELOCITY, glm::value_ptr( glm::vec3{ 0.f } ) );
|
|
}
|
|
|
|
return newsource;
|
|
}
|
|
|
|
bool
|
|
openal_renderer::init_caps() {
|
|
|
|
if( ::alcIsExtensionPresent( nullptr, "ALC_ENUMERATION_EXT" ) == AL_TRUE ) {
|
|
// enumeration supported
|
|
WriteLog( "available audio devices:" );
|
|
auto const *devices { ::alcGetString( nullptr, ALC_DEVICE_SPECIFIER ) };
|
|
auto const
|
|
*device { devices },
|
|
*next { devices + 1 };
|
|
while( device && *device != '\0' && next && *next != '\0' ) {
|
|
WriteLog( { device } );
|
|
auto const len { std::strlen( device ) };
|
|
device += len + 1;
|
|
next += len + 2;
|
|
}
|
|
}
|
|
|
|
// 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() );
|
|
if( m_device == nullptr ) {
|
|
ErrorLog( "Failed to obtain audio device" );
|
|
return false;
|
|
}
|
|
|
|
ALCint versionmajor, versionminor;
|
|
::alcGetIntegerv( m_device, ALC_MAJOR_VERSION, 1, &versionmajor );
|
|
::alcGetIntegerv( m_device, ALC_MINOR_VERSION, 1, &versionminor );
|
|
auto const oalversion { std::to_string( versionmajor ) + "." + std::to_string( versionminor ) };
|
|
|
|
std::string al_renderer((char *)::alcGetString( m_device, ALC_DEVICE_SPECIFIER ));
|
|
crashreport_add_info("openal_renderer", al_renderer);
|
|
crashreport_add_info("openal_version", oalversion);
|
|
|
|
WriteLog(
|
|
"Audio Renderer: " + al_renderer
|
|
+ " OpenAL Version: " + oalversion );
|
|
|
|
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
|
|
std::copy( std::begin( attr ), std::end( attr ), std::begin( m_contextattributes ) ); // cached for device reopen
|
|
|
|
m_context = ::alcCreateContext( m_device, attr );
|
|
if( m_context == nullptr ) {
|
|
ErrorLog( "Failed to create audio context" );
|
|
return false;
|
|
}
|
|
|
|
if (!alcMakeContextCurrent(m_context))
|
|
{
|
|
ErrorLog("sound: cannot select context");
|
|
return false;
|
|
}
|
|
|
|
if (alIsExtensionPresent("AL_SOFT_deferred_updates"))
|
|
{
|
|
alDeferUpdatesSOFT = (void(*)())alGetProcAddress("alDeferUpdatesSOFT");
|
|
alProcessUpdatesSOFT = (void(*)())alGetProcAddress("alProcessUpdatesSOFT");
|
|
}
|
|
if (!alDeferUpdatesSOFT || !alProcessUpdatesSOFT)
|
|
WriteLog("sound: warning: extension AL_SOFT_deferred_updates not found");
|
|
|
|
if (alcIsExtensionPresent(m_device, "ALC_SOFT_pause_device"))
|
|
{
|
|
alcDevicePauseSOFT = (void(*)(ALCdevice*))alcGetProcAddress(m_device, "alcDevicePauseSOFT");
|
|
alcDeviceResumeSOFT = (void(*)(ALCdevice*))alcGetProcAddress(m_device, "alcDeviceResumeSOFT");
|
|
}
|
|
if (!alcDevicePauseSOFT || !alcDeviceResumeSOFT)
|
|
WriteLog("sound: warning: extension ALC_SOFT_pause_device not found");
|
|
|
|
m_candetectdisconnect = ( alcIsExtensionPresent( m_device, "ALC_EXT_disconnect" ) == ALC_TRUE );
|
|
if( alcIsExtensionPresent( m_device, "ALC_SOFT_reopen_device" ) == ALC_TRUE )
|
|
alcReopenDeviceSOFT = (ALCboolean(*)(ALCdevice*, ALCchar const*, ALCint const*))alcGetProcAddress( m_device, "alcReopenDeviceSOFT" );
|
|
if( !alcReopenDeviceSOFT )
|
|
WriteLog( "sound: warning: extension ALC_SOFT_reopen_device not found; audio output device changes won't be followed" );
|
|
|
|
return true;
|
|
}
|
|
|
|
} // audio
|