mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-18 00:49:19 +02:00
Reorganize source files into logical subdirectories
Co-authored-by: Hirek193 <23196899+Hirek193@users.noreply.github.com>
This commit is contained in:
197
audio/audio.cpp
Normal file
197
audio/audio.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
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 <sndfile.h>
|
||||
|
||||
#include "audio.h"
|
||||
#include "Globals.h"
|
||||
#include "Logs.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "utilities.h"
|
||||
|
||||
namespace audio {
|
||||
|
||||
openal_buffer::openal_buffer( std::string const &Filename ) :
|
||||
name( Filename ) {
|
||||
|
||||
SF_INFO si;
|
||||
si.format = 0;
|
||||
|
||||
std::string file = Filename;
|
||||
|
||||
WriteLog("sound: loading file: " + file);
|
||||
|
||||
SNDFILE *sf = sf_open(file.c_str(), SFM_READ, &si);
|
||||
|
||||
if (sf == nullptr)
|
||||
throw std::runtime_error("sound: sf_open failed");
|
||||
|
||||
sf_command(sf, SFC_SET_NORM_FLOAT, NULL, SF_TRUE);
|
||||
|
||||
float *fbuf = new float[si.frames * si.channels];
|
||||
if (sf_readf_float(sf, fbuf, si.frames) != si.frames)
|
||||
throw std::runtime_error("sound: incomplete file");
|
||||
|
||||
sf_close(sf);
|
||||
|
||||
rate = si.samplerate;
|
||||
|
||||
if (si.channels != 1)
|
||||
WriteLog("sound: warning: mixing multichannel file to mono");
|
||||
|
||||
int16_t *buf = new int16_t[si.frames];
|
||||
for (size_t i = 0; i < si.frames; i++)
|
||||
{
|
||||
float accum = 0;
|
||||
for (size_t j = 0; j < si.channels; j++)
|
||||
accum += fbuf[i * si.channels + j];
|
||||
|
||||
long val = lrintf(accum / si.channels * 32767.0f);
|
||||
if (val > 32767)
|
||||
val = 32767;
|
||||
if (val < -32767)
|
||||
val = -32767;
|
||||
buf[i] = val;
|
||||
}
|
||||
|
||||
alGenBuffers(1, &id);
|
||||
if (id != null_resource && alIsBuffer(id)) {
|
||||
alGetError();
|
||||
alBufferData(id, AL_FORMAT_MONO16, buf, si.frames * 2, rate);
|
||||
}
|
||||
else {
|
||||
id = null_resource;
|
||||
const char *str = alGetString(alGetError());
|
||||
ErrorLog("sound: failed to create AL buffer: " + (str != nullptr ? std::string(str) : ""));
|
||||
}
|
||||
|
||||
delete[] buf;
|
||||
delete[] fbuf;
|
||||
|
||||
fetch_caption();
|
||||
}
|
||||
|
||||
// retrieves sound caption in currently set language
|
||||
void
|
||||
openal_buffer::fetch_caption() {
|
||||
|
||||
std::string captionfilename { name };
|
||||
captionfilename.erase( captionfilename.rfind( '.' ) ); // obcięcie rozszerzenia
|
||||
captionfilename += "-" + Global.asLang + ".txt"; // już może być w różnych językach
|
||||
if( true == FileExists( captionfilename ) ) {
|
||||
// wczytanie
|
||||
std::ifstream inputfile( captionfilename );
|
||||
caption.assign( std::istreambuf_iterator<char>( inputfile ), std::istreambuf_iterator<char>() );
|
||||
}
|
||||
}
|
||||
|
||||
buffer_manager::~buffer_manager() {
|
||||
|
||||
for( auto &buffer : m_buffers ) {
|
||||
if( buffer.id != null_resource ) {
|
||||
::alDeleteBuffers( 1, &( buffer.id ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// creates buffer object out of data stored in specified file. returns: handle to the buffer or null_handle if creation failed
|
||||
audio::buffer_handle
|
||||
buffer_manager::create( std::string const &Filename ) {
|
||||
|
||||
auto filename { ToLower( Filename ) };
|
||||
|
||||
erase_extension( filename );
|
||||
|
||||
audio::buffer_handle lookup { null_handle };
|
||||
std::string filelookup;
|
||||
if( false == Global.asCurrentDynamicPath.empty() ) {
|
||||
// try dynamic-specific sounds first
|
||||
lookup = find_buffer( Global.asCurrentDynamicPath + filename );
|
||||
if( lookup != null_handle ) {
|
||||
return lookup;
|
||||
}
|
||||
filelookup = find_file( Global.asCurrentDynamicPath + filename );
|
||||
if( false == filelookup.empty() ) {
|
||||
return emplace( filelookup );
|
||||
}
|
||||
}
|
||||
if( filename.find( '/' ) != std::string::npos ) {
|
||||
// if the filename includes path, try to use it directly
|
||||
lookup = find_buffer( filename );
|
||||
if( lookup != null_handle ) {
|
||||
return lookup;
|
||||
}
|
||||
filelookup = find_file( filename );
|
||||
if( false == filelookup.empty() ) {
|
||||
return emplace( filelookup );
|
||||
}
|
||||
}
|
||||
// if dynamic-specific and/or direct lookups find nothing, try the default sound folder
|
||||
lookup = find_buffer( szSoundPath + filename );
|
||||
if( lookup != null_handle ) {
|
||||
return lookup;
|
||||
}
|
||||
filelookup = find_file( szSoundPath + filename );
|
||||
if( false == filelookup.empty() ) {
|
||||
return emplace( filelookup );
|
||||
}
|
||||
// if we still didn't find anything, give up
|
||||
ErrorLog( "Bad file: failed to locate audio file \"" + Filename + "\"", logtype::file );
|
||||
return null_handle;
|
||||
}
|
||||
|
||||
// provides direct access to a specified buffer
|
||||
audio::openal_buffer const &
|
||||
buffer_manager::buffer( audio::buffer_handle const Buffer ) const {
|
||||
|
||||
return m_buffers[ Buffer ];
|
||||
}
|
||||
|
||||
// places in the bank a buffer containing data stored in specified file. returns: handle to the buffer
|
||||
audio::buffer_handle
|
||||
buffer_manager::emplace( std::string Filename ) {
|
||||
|
||||
buffer_handle const handle { m_buffers.size() };
|
||||
m_buffers.emplace_back( Filename );
|
||||
|
||||
// NOTE: we store mapping without file type extension, to simplify lookups
|
||||
erase_extension( Filename );
|
||||
m_buffermappings.emplace(
|
||||
Filename,
|
||||
handle );
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
audio::buffer_handle
|
||||
buffer_manager::find_buffer( std::string const &Buffername ) const {
|
||||
|
||||
auto const lookup = m_buffermappings.find( Buffername );
|
||||
if( lookup != std::end( m_buffermappings ) )
|
||||
return lookup->second;
|
||||
else
|
||||
return null_handle;
|
||||
}
|
||||
|
||||
std::string
|
||||
buffer_manager::find_file( std::string const &Filename ) const {
|
||||
|
||||
auto const lookup {
|
||||
FileExists(
|
||||
{ Filename },
|
||||
{ ".ogg", ".flac", ".wav" } ) };
|
||||
|
||||
return lookup.first + lookup.second;
|
||||
}
|
||||
|
||||
} // audio
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
81
audio/audio.h
Normal file
81
audio/audio.h
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenAL/al.h>
|
||||
#include <OpenAL/alc.h>
|
||||
#else
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#endif
|
||||
|
||||
namespace audio {
|
||||
|
||||
ALuint const null_resource{ ~( ALuint { 0 } ) };
|
||||
|
||||
// wrapper for audio sample
|
||||
struct openal_buffer {
|
||||
// members
|
||||
ALuint id { null_resource }; // associated AL resource
|
||||
unsigned int rate {}; // sample rate of the data
|
||||
std::string name;
|
||||
std::string caption;
|
||||
// constructors
|
||||
openal_buffer() = default;
|
||||
explicit openal_buffer( std::string const &Filename );
|
||||
// methods
|
||||
// retrieves sound caption in currently set language
|
||||
void
|
||||
fetch_caption();
|
||||
|
||||
};
|
||||
|
||||
using buffer_handle = std::size_t;
|
||||
|
||||
|
||||
//
|
||||
class buffer_manager {
|
||||
|
||||
public:
|
||||
// constructors
|
||||
buffer_manager() { m_buffers.emplace_back( openal_buffer() ); } // empty bindings for null buffer
|
||||
// destructor
|
||||
~buffer_manager();
|
||||
// methods
|
||||
// creates buffer object out of data stored in specified file. returns: handle to the buffer or null_handle if creation failed
|
||||
buffer_handle
|
||||
create( std::string const &Filename );
|
||||
// provides direct access to a specified buffer
|
||||
audio::openal_buffer const &
|
||||
buffer( audio::buffer_handle const Buffer ) const;
|
||||
|
||||
private:
|
||||
// types
|
||||
using buffer_sequence = std::vector<openal_buffer>;
|
||||
using index_map = std::unordered_map<std::string, std::size_t>;
|
||||
// methods
|
||||
// places in the bank a buffer containing data stored in specified file. returns: handle to the buffer
|
||||
buffer_handle
|
||||
emplace( std::string Filename );
|
||||
// checks whether specified buffer is in the buffer bank. returns: buffer handle, or null_handle.
|
||||
buffer_handle
|
||||
find_buffer( std::string const &Buffername ) const;
|
||||
// checks whether specified file exists. returns: name of the located file, or empty string.
|
||||
std::string
|
||||
find_file( std::string const &Filename ) const;
|
||||
// members
|
||||
buffer_sequence m_buffers;
|
||||
index_map m_buffermappings;
|
||||
};
|
||||
|
||||
} // audio
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
584
audio/audiorenderer.cpp
Normal file
584
audio/audiorenderer.cpp
Normal file
@@ -0,0 +1,584 @@
|
||||
/*
|
||||
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 "Camera.h"
|
||||
#include "Logs.h"
|
||||
#include "utilities.h"
|
||||
#include "simulation.h"
|
||||
#include "Train.h"
|
||||
|
||||
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( ( cutoffrange * cutoffrange ), ( EU07_SOUND_CUTOFFRANGE * 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 {
|
||||
interpolate(
|
||||
1.f, 0.f,
|
||||
clamp<float>(
|
||||
( 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, 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);
|
||||
|
||||
// update listener
|
||||
// gain
|
||||
::alListenerf( AL_GAIN, Global.AudioVolume );
|
||||
// orientation
|
||||
glm::dmat4 cameramatrix;
|
||||
Global.pCamera.SetMatrix( cameramatrix );
|
||||
auto cameraposition = Global.pCamera.Pos + (Global.viewport_move * glm::mat3(cameramatrix));
|
||||
cameramatrix = glm::dmat4(glm::inverse(Global.viewport_rotate)) * 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 ) );
|
||||
// velocity
|
||||
if( Deltatime > 0 ) {
|
||||
auto cameramove { glm::dvec3{ 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::length( cameramove ) > 100.0 ) {
|
||||
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
|
||||
|
||||
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");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // audio
|
||||
186
audio/audiorenderer.h
Normal file
186
audio/audiorenderer.h
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "uitranscripts.h"
|
||||
|
||||
#define EU07_SOUND_PROOFINGUSESRANGE
|
||||
|
||||
class opengl_renderer;
|
||||
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
|
||||
};
|
||||
|
||||
// sound emitter state sync item
|
||||
struct sound_properties {
|
||||
glm::dvec3 location;
|
||||
float pitch { 1.f };
|
||||
sound_category category { sound_category::unknown };
|
||||
float gain { 1.f };
|
||||
float soundproofing { 1.f };
|
||||
std::uintptr_t soundproofing_stamp { ~( std::uintptr_t{ 0 } ) };
|
||||
};
|
||||
|
||||
enum class sync_state {
|
||||
good,
|
||||
bad_distance,
|
||||
bad_resource
|
||||
};
|
||||
|
||||
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
|
||||
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();
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
|
||||
|
||||
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
|
||||
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 );
|
||||
|
||||
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;
|
||||
*/
|
||||
glm::vec3 m_listenervelocity;
|
||||
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
|
||||
|
||||
void (*alDeferUpdatesSOFT)() = nullptr;
|
||||
void (*alProcessUpdatesSOFT)() = nullptr;
|
||||
void (*alcDevicePauseSOFT)(ALCdevice*) = nullptr;
|
||||
void (*alcDeviceResumeSOFT)(ALCdevice*) = nullptr;
|
||||
};
|
||||
|
||||
extern openal_renderer renderer;
|
||||
extern bool event_volume_change;
|
||||
|
||||
} // audio
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
57
audio/audiorenderer_extra.h
Normal file
57
audio/audiorenderer_extra.h
Normal file
@@ -0,0 +1,57 @@
|
||||
// separate file because of include dependecies mess
|
||||
|
||||
namespace audio {
|
||||
template <class Iterator_>
|
||||
openal_source &
|
||||
openal_source::bind( sound_source *Controller, uint32_sequence Sounds, Iterator_ First, Iterator_ Last ) {
|
||||
|
||||
controller = Controller;
|
||||
sounds = Sounds;
|
||||
// look up and queue assigned buffers
|
||||
std::vector<ALuint> buffers;
|
||||
std::for_each(
|
||||
First, Last,
|
||||
[&]( audio::buffer_handle const &bufferhandle ) {
|
||||
auto const &buffer { audio::renderer.buffer( bufferhandle ) };
|
||||
if (buffer.id != null_resource) buffers.emplace_back( buffer.id ); } );
|
||||
|
||||
is_multipart = ( buffers.size() > 1 );
|
||||
|
||||
if( id != audio::null_resource && !buffers.empty()) {
|
||||
::alSourceQueueBuffers( id, static_cast<ALsizei>( buffers.size() ), buffers.data() );
|
||||
::alSourceRewind( id );
|
||||
// sound controller can potentially request playback to start from certain buffer point
|
||||
// for multipart sounds the offset is applied only to last piece during playback
|
||||
// for single sound we also make sure not to apply the offset to optional bookends
|
||||
if( controller->start() == 0.f || is_multipart || controller->is_bookend( buffers.front() ) ) {
|
||||
// regular case with no offset, reset bound source just in case
|
||||
::alSourcei( id, AL_SAMPLE_OFFSET, 0 );
|
||||
}
|
||||
else {
|
||||
// move playback start to specified point in 0-1 range
|
||||
ALint buffersize;
|
||||
::alGetBufferi( buffers.front(), AL_SIZE, &buffersize );
|
||||
::alSourcei(
|
||||
id,
|
||||
AL_SAMPLE_OFFSET,
|
||||
static_cast<ALint>( controller->start() * ( buffersize / sizeof( std::int16_t ) ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline
|
||||
float
|
||||
amplitude_to_db( float const Amplitude ) {
|
||||
|
||||
return 20.f * std::log10( Amplitude );
|
||||
}
|
||||
|
||||
inline
|
||||
float
|
||||
db_to_amplitude( float const Decibels ) {
|
||||
|
||||
return std::pow( 10.f, Decibels / 20.f );
|
||||
}
|
||||
}
|
||||
1053
audio/sound.cpp
Normal file
1053
audio/sound.cpp
Normal file
File diff suppressed because it is too large
Load Diff
268
audio/sound.h
Normal file
268
audio/sound.h
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audiorenderer.h"
|
||||
#include "Classes.h"
|
||||
#include "Names.h"
|
||||
|
||||
float const EU07_SOUND_GLOBALRANGE { -1.f };
|
||||
float const EU07_SOUND_CABCONTROLSCUTOFFRANGE { 7.5f };
|
||||
float const EU07_SOUND_CABANNOUNCEMENTCUTOFFRANGE{ -10.f };
|
||||
float const EU07_SOUND_BRAKINGCUTOFFRANGE { 100.f };
|
||||
float const EU07_SOUND_RUNNINGNOISECUTOFFRANGE { 200.f };
|
||||
float const EU07_SOUND_HANDHELDRADIORANGE { 3500.f };
|
||||
|
||||
enum class sound_type {
|
||||
single,
|
||||
multipart
|
||||
};
|
||||
|
||||
enum sound_parameters {
|
||||
range = 0x1,
|
||||
amplitude = 0x2,
|
||||
frequency = 0x4,
|
||||
};
|
||||
|
||||
enum sound_flags {
|
||||
looping = 0x1, // the main sample will be looping
|
||||
exclusive = 0x2, // the source won't dispatch more than one active instance of the sound
|
||||
event = 0x80 // sound was activated by an event; we should keep note of the activation state for the update() calls it may receive
|
||||
};
|
||||
|
||||
enum class sound_placement {
|
||||
general, // source is equally audible in potential carrier and outside of it
|
||||
internal, // source is located inside of the carrier, and less audible when the listener is outside
|
||||
engine, // source is located in the engine compartment, less audible when the listener is outside and even less in the cabs
|
||||
external, // source is located on the outside of the carrier, and less audible when the listener is inside
|
||||
external_ambient, // source is located on the outside of the carrier, with fixed volume
|
||||
custom, // source doesn't fit in any standard location or requires custom soundproofing
|
||||
};
|
||||
|
||||
auto const EU07_SOUNDPROOFING_NONE{ 1.f };
|
||||
auto const EU07_SOUNDPROOFING_SOME{ std::sqrt( 0.65f ) };
|
||||
auto const EU07_SOUNDPROOFING_STRONG{ std::sqrt( 0.20f ) };
|
||||
auto const EU07_SOUNDPROOFING_VERYSTRONG{ std::sqrt( 0.01f ) };
|
||||
|
||||
// mini controller and audio dispatcher; issues play commands for the audio renderer,
|
||||
// updates parameters of created audio emitters for the playback duration
|
||||
// TODO: move to simulation namespace after clean up of owner classes
|
||||
class sound_source {
|
||||
|
||||
public:
|
||||
// constructors
|
||||
sound_source( sound_placement const Placement = sound_placement::general, float const Range = 50.f );
|
||||
|
||||
// destructor
|
||||
~sound_source();
|
||||
|
||||
// methods
|
||||
// restores state of the class from provided data stream
|
||||
sound_source &
|
||||
deserialize( cParser &Input, sound_type const Legacytype, int const Legacyparameters = 0, int const Chunkrange = 100 );
|
||||
sound_source &
|
||||
deserialize( std::string const &Input, sound_type const Legacytype, int const Legacyparameters = 0 );
|
||||
// sends content of the class in legacy (text) format to provided stream
|
||||
void
|
||||
export_as_text( std::ostream &Output ) const;
|
||||
// copies list of sounds from provided source
|
||||
sound_source &
|
||||
copy_sounds( sound_source const &Source );
|
||||
// issues contextual play commands for the audio renderer
|
||||
void
|
||||
play( int const Flags = 0 );
|
||||
// maintains playback of sounds started by event
|
||||
void
|
||||
play_event();
|
||||
// stops currently active play commands controlled by this emitter
|
||||
void
|
||||
stop( bool const Skipend = false );
|
||||
// adjusts parameters of provided implementation-side sound source
|
||||
void
|
||||
update( audio::openal_source &Source );
|
||||
// sets base volume of the emiter to specified value
|
||||
sound_source &
|
||||
gain( float const Gain );
|
||||
// returns current base volume of the emitter
|
||||
float
|
||||
gain() const;
|
||||
// sets base pitch of the emitter to specified value
|
||||
sound_source &
|
||||
pitch( float const Pitch );
|
||||
// owner setter/getter
|
||||
void
|
||||
owner( TDynamicObject const *Owner );
|
||||
TDynamicObject const *
|
||||
owner() const;
|
||||
// sound source offset setter/getter
|
||||
void
|
||||
offset( glm::vec3 const Offset );
|
||||
glm::vec3 const &
|
||||
offset() const;
|
||||
// sound source name setter/getter
|
||||
void
|
||||
name( std::string Name );
|
||||
std::string const &
|
||||
name() const;
|
||||
// playback starting point shift setter/getter
|
||||
void
|
||||
start( float const Offset );
|
||||
float const &
|
||||
start() const;
|
||||
// custom soundproofing setter/getter
|
||||
auto &
|
||||
soundproofing();
|
||||
auto const &
|
||||
soundproofing() const;
|
||||
// returns true if there isn't any sound buffer associated with the object, false otherwise
|
||||
bool
|
||||
empty() const;
|
||||
// returns true if the source is emitting any sound; by default doesn't take into account optional ending soudnds
|
||||
bool
|
||||
is_playing( bool const Includesoundends = false ) const;
|
||||
// returns true if the source uses sample table
|
||||
bool
|
||||
is_combined() const;
|
||||
// returns true if specified buffer is one of the optional bookends
|
||||
bool
|
||||
is_bookend( audio::buffer_handle const Buffer ) const;
|
||||
// returns true if the source has optional bookends
|
||||
bool
|
||||
has_bookends() const;
|
||||
// returns location of the sound source in simulation region space
|
||||
glm::dvec3 const
|
||||
location() const;
|
||||
// returns defined range of the sound
|
||||
void
|
||||
range( float const Range );
|
||||
float const
|
||||
range() const;
|
||||
|
||||
// members
|
||||
float m_amplitudefactor { 1.f }; // helper, value potentially used by gain calculation
|
||||
float m_amplitudeoffset { 0.f }; // helper, value potentially used by gain calculation
|
||||
float m_frequencyfactor { 1.f }; // helper, value potentially used by pitch calculation
|
||||
float m_frequencyoffset { 0.f }; // helper, value potentially used by pitch calculation
|
||||
|
||||
private:
|
||||
// types
|
||||
struct sound_data {
|
||||
audio::buffer_handle buffer;
|
||||
int playing; // number of currently active sample instances
|
||||
};
|
||||
|
||||
struct chunk_data {
|
||||
int threshold; // nominal point of activation for the given chunk
|
||||
float fadein; // actual activation point for the given chunk
|
||||
float fadeout; // actual end point of activation range for the given chunk
|
||||
float pitch; // base pitch of the chunk
|
||||
};
|
||||
|
||||
using soundchunk_pair = std::pair<sound_data, chunk_data>;
|
||||
|
||||
using sound_handle = std::uint32_t;
|
||||
enum sound_id : std::uint32_t {
|
||||
begin,
|
||||
main,
|
||||
end,
|
||||
// 31 bits for index into relevant array, msb selects between the sample table and the basic array
|
||||
chunk = ( 1u << 31 )
|
||||
};
|
||||
|
||||
// methods
|
||||
// imports member data pair from the provided data stream
|
||||
bool
|
||||
deserialize_mapping( cParser &Input );
|
||||
// imports values for initial, main and ending sounds from provided data stream
|
||||
void
|
||||
deserialize_soundset( cParser &Input );
|
||||
// issues contextual play commands for the audio renderer
|
||||
void
|
||||
play_basic();
|
||||
void
|
||||
play_combined();
|
||||
// calculates requested sound point, used to select specific sample from the sample table
|
||||
float
|
||||
compute_combined_point() const;
|
||||
void
|
||||
update_basic( audio::openal_source &Source );
|
||||
void
|
||||
update_combined( audio::openal_source &Source );
|
||||
void
|
||||
update_crossfade( sound_handle const Chunk );
|
||||
void
|
||||
update_counter( sound_handle const Sound, int const Value );
|
||||
void
|
||||
update_location();
|
||||
// potentially updates area-based gain factor of the source. returns: true if location has changed
|
||||
bool
|
||||
update_soundproofing();
|
||||
void
|
||||
insert( sound_handle const Sound );
|
||||
template <class Iterator_>
|
||||
void
|
||||
insert( Iterator_ First, Iterator_ Last ) {
|
||||
update_counter( *First, 1 );
|
||||
std::vector<audio::buffer_handle> buffers;
|
||||
uint32_sequence sounds;
|
||||
std::for_each(
|
||||
First, Last,
|
||||
[&]( sound_handle const &soundhandle ) {
|
||||
buffers.emplace_back( sound( soundhandle ).buffer );
|
||||
sounds.emplace_back( soundhandle ); } );
|
||||
audio::renderer.insert( std::begin( buffers ), std::end( buffers ), this, sounds ); }
|
||||
sound_data &
|
||||
sound( sound_handle const Sound );
|
||||
sound_data const &
|
||||
sound( sound_handle const Sound ) const;
|
||||
|
||||
// members
|
||||
TDynamicObject const * m_owner { nullptr }; // optional, the vehicle carrying this sound source
|
||||
glm::vec3 m_offset; // relative position of the source, either from the owner or the region centre
|
||||
sound_placement m_placement;
|
||||
float m_range { 50.f }; // audible range of the emitted sounds
|
||||
std::string m_name;
|
||||
int m_flags {}; // requested playback parameters
|
||||
sound_properties m_properties; // current properties of the emitted sounds
|
||||
float m_pitchvariation {}; // emitter-specific shift for base pitch
|
||||
float m_startoffset {}; // emitter-specific shift for playback starting point
|
||||
bool m_stop { false }; // indicates active sample instances should be terminated
|
||||
/*
|
||||
bool m_stopend { false }; // indicates active instances of optional ending sound should be terminated
|
||||
*/
|
||||
bool m_playbeginning { true }; // indicates started sounds should be preceeded by opening bookend if there's one
|
||||
std::array<sound_data, 3> m_sounds { {} }; // basic sounds emitted by the source, main and optional bookends
|
||||
std::vector<soundchunk_pair> m_soundchunks; // table of samples activated when associated variable is within certain range
|
||||
bool m_soundchunksempty { true }; // helper, cached check whether sample table is linked with any actual samples
|
||||
int m_crossfaderange {}; // range of transition from one chunk to another
|
||||
std::optional< std::array<float, 6> > m_soundproofing; // custom soundproofing parameters
|
||||
};
|
||||
|
||||
// owner setter/getter
|
||||
inline void sound_source::owner( TDynamicObject const *Owner ) { m_owner = Owner; }
|
||||
inline TDynamicObject const * sound_source::owner() const { return m_owner; }
|
||||
// sound source offset setter/getter
|
||||
inline void sound_source::offset( glm::vec3 const Offset ) { m_offset = Offset; }
|
||||
inline glm::vec3 const & sound_source::offset() const { return m_offset; }
|
||||
// sound source name setter/getter
|
||||
inline void sound_source::name( std::string Name ) { m_name = Name; }
|
||||
inline std::string const & sound_source::name() const { return m_name; }
|
||||
// playback starting point shift setter/getter
|
||||
inline void sound_source::start( float const Offset ) { m_startoffset = Offset; }
|
||||
inline float const & sound_source::start() const { return m_startoffset; }
|
||||
// custom soundproofing setter/getter
|
||||
inline auto & sound_source::soundproofing() { return m_soundproofing; }
|
||||
inline auto const & sound_source::soundproofing() const { return m_soundproofing; }
|
||||
|
||||
|
||||
// collection of sound sources present in the scene
|
||||
class sound_table : public basic_table<sound_source> {
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user