mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-18 05:29:18 +02:00
321 lines
9.5 KiB
C++
321 lines
9.5 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 "audiorenderer.h"
|
|
#include "sound.h"
|
|
#include "globals.h"
|
|
#include "logs.h"
|
|
|
|
namespace audio {
|
|
|
|
openal_renderer renderer;
|
|
|
|
float const EU07_SOUND_CUTOFFRANGE { 3000.f }; // 2750 m = max expected emitter spawn range, plus safety margin
|
|
|
|
// starts playback of queued buffers
|
|
void
|
|
openal_source::play() {
|
|
|
|
::alSourcePlay( id );
|
|
is_playing = true;
|
|
}
|
|
|
|
// stops the playback
|
|
void
|
|
openal_source::stop() {
|
|
|
|
loop( false );
|
|
::alSourceStop( id );
|
|
is_playing = false;
|
|
}
|
|
|
|
// updates state of the source
|
|
void
|
|
openal_source::update( double const Deltatime ) {
|
|
|
|
update_deltatime = Deltatime; // cached for time-based processing of data from the controller
|
|
|
|
// TODO: test whether the emitter was within range during the last tick, potentially update the counter and flag it for timeout
|
|
::alGetSourcei( id, AL_BUFFERS_PROCESSED, &buffer_index );
|
|
// for multipart sounds trim away processed sources until only one remains, the last one may be set to looping by the controller
|
|
ALuint bufferid;
|
|
while( ( buffer_index > 0 )
|
|
&& ( buffers.size() > 1 ) ) {
|
|
::alSourceUnqueueBuffers( id, 1, &bufferid );
|
|
buffers.erase( std::begin( buffers ) );
|
|
--buffer_index;
|
|
}
|
|
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 ) {
|
|
|
|
/*
|
|
// velocity
|
|
// not used yet
|
|
glm::vec3 const velocity { ( State.location - properties.location ) / update_deltatime };
|
|
*/
|
|
// location
|
|
properties.location = State.location;
|
|
auto sourceoffset { glm::vec3 { properties.location - glm::dvec3 { Global::pCameraPosition } } };
|
|
if( glm::length2( sourceoffset ) > std::max( ( sound_range * sound_range ), ( EU07_SOUND_CUTOFFRANGE * EU07_SOUND_CUTOFFRANGE ) ) ) {
|
|
// range cutoff check
|
|
stop();
|
|
return;
|
|
}
|
|
if( sound_range >= 0 ) {
|
|
::alSourcefv( id, AL_POSITION, glm::value_ptr( sourceoffset ) );
|
|
}
|
|
else {
|
|
// sounds with 'unlimited' range are positioned on top of the listener
|
|
::alSourcefv( id, AL_POSITION, glm::value_ptr( glm::vec3() ) );
|
|
}
|
|
// gain
|
|
if( ( State.placement_stamp != properties.placement_stamp )
|
|
|| ( State.base_gain != properties.base_gain ) ) {
|
|
// gain value has changed
|
|
properties.base_gain = State.base_gain;
|
|
properties.placement_gain = State.placement_gain;
|
|
properties.placement_stamp = State.placement_stamp;
|
|
|
|
::alSourcef( id, AL_GAIN, properties.base_gain * properties.placement_gain * Global::AudioVolume );
|
|
}
|
|
// pitch
|
|
if( State.base_pitch != properties.base_pitch ) {
|
|
// pitch value has changed
|
|
properties.base_pitch = State.base_pitch;
|
|
|
|
::alSourcef( id, AL_PITCH, properties.base_pitch * pitch_variation );
|
|
}
|
|
}
|
|
|
|
// sets max audible distance for sounds emitted by the source
|
|
void
|
|
openal_source::range( float const Range ) {
|
|
|
|
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.5f );
|
|
// NOTE: we cache actual specified range, as we'll be giving 'unlimited' range special treatment
|
|
sound_range = Range;
|
|
}
|
|
|
|
// 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.base_pitch = -1.f;
|
|
}
|
|
|
|
// toggles looping of the sound emitted by the source
|
|
void
|
|
openal_source::loop( bool const State ) {
|
|
|
|
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() {
|
|
|
|
controller = nullptr;
|
|
// 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( buffers.size() );
|
|
// ...release the buffers and update source data to match
|
|
::alSourceUnqueueBuffers( id, bufferids.size(), bufferids.data() );
|
|
buffers.clear();
|
|
buffer_index = 0;
|
|
// reset properties
|
|
properties = sound_properties();
|
|
}
|
|
|
|
|
|
|
|
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_LINEAR_DISTANCE );
|
|
::alDistanceModel( AL_INVERSE_DISTANCE_CLAMPED );
|
|
// all done
|
|
m_ready = true;
|
|
return true;
|
|
}
|
|
|
|
// schedules playback of specified sample, under control of the specified emitter
|
|
void
|
|
openal_renderer::insert( sound_source *Controller, audio::buffer_handle const Sound ) {
|
|
|
|
audio::openal_source::buffer_sequence buffers { Sound };
|
|
return
|
|
insert(
|
|
Controller,
|
|
std::begin( buffers ), std::end( buffers ) );
|
|
}
|
|
|
|
// 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();
|
|
m_sourcespares.push( *source );
|
|
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 ) {
|
|
|
|
// update listener
|
|
glm::dmat4 cameramatrix;
|
|
Global::pCamera->SetMatrix( cameramatrix );
|
|
auto rotationmatrix { glm::mat3{ cameramatrix } };
|
|
glm::vec3 const orientation[] = {
|
|
glm::vec3{ 0, 0,-1 } * rotationmatrix ,
|
|
glm::vec3{ 0, 1, 0 } * rotationmatrix };
|
|
::alListenerfv( AL_ORIENTATION, reinterpret_cast<ALfloat const *>( orientation ) );
|
|
/*
|
|
glm::dvec3 const listenerposition { Global::pCameraPosition };
|
|
// not used yet
|
|
glm::vec3 const velocity { ( listenerposition - m_listenerposition ) / Deltatime };
|
|
m_listenerposition = listenerposition;
|
|
*/
|
|
|
|
// update active emitters
|
|
auto source { std::begin( m_sources ) };
|
|
while( source != std::end( m_sources ) ) {
|
|
// update each source
|
|
source->update( Deltatime );
|
|
// 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();
|
|
m_sourcespares.push( *source );
|
|
source = m_sources.erase( source );
|
|
}
|
|
else {
|
|
// otherwise proceed through the list normally
|
|
++source;
|
|
}
|
|
}
|
|
}
|
|
|
|
// returns an instance of implementation-side part of the sound emitter
|
|
audio::openal_source
|
|
openal_renderer::fetch_source() {
|
|
|
|
audio::openal_source soundsource;
|
|
if( false == m_sourcespares.empty() ) {
|
|
// reuse (a copy of) already allocated source
|
|
soundsource = m_sourcespares.top();
|
|
m_sourcespares.pop();
|
|
}
|
|
return soundsource;
|
|
}
|
|
|
|
bool
|
|
openal_renderer::init_caps() {
|
|
|
|
// select the "preferred device"
|
|
// TODO: support for opening config-specified device
|
|
m_device = ::alcOpenDevice( nullptr );
|
|
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 ) };
|
|
|
|
WriteLog(
|
|
"Audio Renderer: " + std::string { (char *)::alcGetString( m_device, ALC_DEVICE_SPECIFIER ) }
|
|
+ " OpenAL Version: " + oalversion );
|
|
|
|
WriteLog( "Supported extensions: " + std::string{ (char *)::alcGetString( m_device, ALC_EXTENSIONS ) } );
|
|
|
|
m_context = ::alcCreateContext( m_device, nullptr );
|
|
if( m_context == nullptr ) {
|
|
ErrorLog( "Failed to create audio context" );
|
|
return false;
|
|
}
|
|
|
|
return ( ::alcMakeContextCurrent( m_context ) == AL_TRUE );
|
|
}
|
|
|
|
} // audio
|
|
|
|
//---------------------------------------------------------------------------
|