mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-03-22 15:05:03 +01:00
additional sound type for local brake, support for combined sounds
This commit is contained in:
@@ -85,7 +85,7 @@ TButton::Load_mapping( cParser &Input ) {
|
||||
|
||||
// token can be a key or block end
|
||||
std::string const key { Input.getToken<std::string>( true, "\n\r\t ,;" ) };
|
||||
if( key == "}" ) { return false; }
|
||||
if( ( true == key.empty() ) || ( key == "}" ) ) { return false; }
|
||||
// if not block end then the key is followed by assigned value or sub-block
|
||||
if( key == "soundinc:" ) { m_soundfxincrease.deserialize( Input, sound_type::single ); }
|
||||
else if( key == "sounddec:" ) { m_soundfxdecrease.deserialize( Input, sound_type::single ); }
|
||||
|
||||
26
Driver.cpp
26
Driver.cpp
@@ -2368,19 +2368,25 @@ bool TController::ReleaseEngine()
|
||||
else
|
||||
OK = true;
|
||||
}
|
||||
else if (mvOccupied->ActiveDir == 0)
|
||||
OK = mvControlling->Mains; // tylko to testujemy dla pojazdu człowieka
|
||||
if (AIControllFlag)
|
||||
if (!mvOccupied->DecBrakeLevel()) // tu moze zmieniać na -2, ale to bez znaczenia
|
||||
if (!mvOccupied->IncLocalBrakeLevel(1))
|
||||
{
|
||||
while (DecSpeed(true))
|
||||
else if( mvOccupied->ActiveDir == 0 ) {
|
||||
// tylko to testujemy dla pojazdu człowieka
|
||||
OK = mvControlling->Mains;
|
||||
}
|
||||
|
||||
if( AIControllFlag ) {
|
||||
mvOccupied->BrakeReleaser( 0 );
|
||||
if( !mvOccupied->DecBrakeLevel() ) {
|
||||
// tu moze zmieniać na -2, ale to bez znaczenia
|
||||
if( !mvOccupied->IncLocalBrakeLevel( 1 ) ) {
|
||||
while( DecSpeed( true ) )
|
||||
; // zerowanie nastawników
|
||||
while (mvOccupied->ActiveDir > 0)
|
||||
while( mvOccupied->ActiveDir > 0 )
|
||||
mvOccupied->DirectionBackward();
|
||||
while (mvOccupied->ActiveDir < 0)
|
||||
while( mvOccupied->ActiveDir < 0 )
|
||||
mvOccupied->DirectionForward();
|
||||
}
|
||||
}
|
||||
}
|
||||
OK = OK && (mvOccupied->Vel < 0.01);
|
||||
if (OK)
|
||||
{ // jeśli się zatrzymał
|
||||
@@ -3108,7 +3114,7 @@ bool TController::PutCommand( std::string NewCommand, double NewValue1, double N
|
||||
iGuardRadio = 0; // nie przez radio
|
||||
}
|
||||
else {
|
||||
NewCommand = NewCommand.insert(NewCommand.find_last_of("."),"radio"); // wstawienie przed kropkč
|
||||
NewCommand = NewCommand.insert(NewCommand.rfind('.'),"radio"); // wstawienie przed kropkč
|
||||
if (FileExists(NewCommand)) {
|
||||
// wczytanie dźwięku odjazdu w wersji radiowej (słychać tylko w kabinie)
|
||||
#ifdef EU07_USE_OLD_SOUNDCODE
|
||||
|
||||
15
DynObj.cpp
15
DynObj.cpp
@@ -4116,7 +4116,6 @@ void TDynamicObject::RenderSounds() {
|
||||
else {
|
||||
sHorn2.stop();
|
||||
}
|
||||
|
||||
// szum w czasie jazdy
|
||||
if( ( GetVelocity() > 0.5 )
|
||||
&& ( // compound test whether the vehicle belongs to user-driven consist (as these don't emit outer noise in cab view)
|
||||
@@ -4183,15 +4182,17 @@ void TDynamicObject::RenderSounds() {
|
||||
interpolate(
|
||||
0.0, 1.0,
|
||||
clamp(
|
||||
MoverParameters->Vel / 60.0,
|
||||
MoverParameters->Vel / 40.0,
|
||||
0.0, 1.0 ) );
|
||||
|
||||
rsOuterNoise
|
||||
.pitch( clamp( frequency, 0.5, 1.15 ) ) // arbitrary limits to prevent the pitch going out of whack
|
||||
.pitch( frequency ) // arbitrary limits to prevent the pitch going out of whack
|
||||
.gain( volume )
|
||||
.play( sound_flags::exclusive | sound_flags::looping );
|
||||
}
|
||||
else {
|
||||
rsOuterNoise.stop();
|
||||
// don't play the optional ending sound if the listener switches views
|
||||
rsOuterNoise.stop( false == FreeFlyModeFlag );
|
||||
}
|
||||
|
||||
// youBy: dzwiek ostrych lukow i ciasnych zwrotek
|
||||
@@ -4312,7 +4313,6 @@ void TDynamicObject::RenderSounds() {
|
||||
|
||||
MoverParameters->EventFlag = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// McZapkie-250202
|
||||
@@ -5062,14 +5062,13 @@ void TDynamicObject::LoadMMediaFile( std::string BaseDir, std::string TypeName,
|
||||
else if( ( token == "transmission:" )
|
||||
&& ( MoverParameters->EngineType == ElectricSeriesMotor ) ) {
|
||||
// plik z dzwiekiem, mnozniki i ofsety amp. i czest.
|
||||
rsPrzekladnia.deserialize( parser, sound_type::single, sound_parameters::range );
|
||||
|
||||
// NOTE, TODO: fixed parameters, put them into simulation-side calculations
|
||||
// NOTE, fixed default parameters, legacy system leftover
|
||||
rsPrzekladnia.m_amplitudefactor = 0.029;
|
||||
rsPrzekladnia.m_amplitudeoffset = 0.1;
|
||||
rsPrzekladnia.m_frequencyfactor = 0.005;
|
||||
rsPrzekladnia.m_frequencyoffset = 1.0;
|
||||
|
||||
rsPrzekladnia.deserialize( parser, sound_type::single, sound_parameters::range );
|
||||
rsPrzekladnia.owner( this );
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ bool Global::bWireFrame = false;
|
||||
|
||||
// sound renderer
|
||||
bool Global::bSoundEnabled = true;
|
||||
float Global::AudioVolume = 2.0f;
|
||||
float Global::AudioVolume = 1.5f;
|
||||
std::string Global::AudioRenderer;
|
||||
|
||||
int Global::iWriteLogEnabled = 3; // maska bitowa: 1-zapis do pliku, 2-okienko, 4-nazwy torów
|
||||
|
||||
49
Train.cpp
49
Train.cpp
@@ -5139,14 +5139,15 @@ TTrain::update_sounds( double const Deltatime ) {
|
||||
// Winger-160404 - syczenie pomocniczego (luzowanie)
|
||||
if( m_lastlocalbrakepressure != -1.f ) {
|
||||
// calculate rate of pressure drop in local brake cylinder, once it's been initialized
|
||||
auto const brakepressuredifference{ m_lastlocalbrakepressure - mvOccupied->LocBrakePress };
|
||||
auto const brakepressuredifference { mvOccupied->LocBrakePress - m_lastlocalbrakepressure };
|
||||
m_localbrakepressurechange = interpolate<float>( m_localbrakepressurechange, 10 * ( brakepressuredifference / Deltatime ), 0.1f );
|
||||
}
|
||||
m_lastlocalbrakepressure = mvOccupied->LocBrakePress;
|
||||
if( ( m_localbrakepressurechange > 0.05f )
|
||||
// local brake, release
|
||||
if( ( m_localbrakepressurechange < -0.05f )
|
||||
&& ( mvOccupied->LocBrakePress > mvOccupied->BrakePress - 0.05 ) ) {
|
||||
rsSBHiss
|
||||
.gain( clamp( 0.05 * m_localbrakepressurechange, 0.0, 1.5 ) )
|
||||
.gain( clamp( rsSBHiss.m_amplitudeoffset + rsSBHiss.m_amplitudefactor * -m_localbrakepressurechange * 0.05, 0.0, 1.5 ) )
|
||||
.play( sound_flags::exclusive | sound_flags::looping );
|
||||
}
|
||||
else {
|
||||
@@ -5157,6 +5158,20 @@ TTrain::update_sounds( double const Deltatime ) {
|
||||
rsSBHiss.stop();
|
||||
}
|
||||
}
|
||||
// local brake, engage
|
||||
if( m_localbrakepressurechange > 0.05f ) {
|
||||
rsSBHissU
|
||||
.gain( clamp( rsSBHissU.m_amplitudeoffset + rsSBHissU.m_amplitudefactor * m_localbrakepressurechange * 0.05, 0.0, 1.5 ) )
|
||||
.play( sound_flags::exclusive | sound_flags::looping );
|
||||
}
|
||||
else {
|
||||
// don't stop the sound too abruptly
|
||||
volume = std::max( 0.0, rsSBHissU.gain() - 0.1 * Deltatime );
|
||||
rsSBHissU.gain( volume );
|
||||
if( volume < 0.05 ) {
|
||||
rsSBHissU.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// McZapkie-280302 - syczenie
|
||||
// TODO: softer volume reduction than plain abrupt stop, perhaps as reusable wrapper?
|
||||
@@ -5290,12 +5305,13 @@ TTrain::update_sounds( double const Deltatime ) {
|
||||
mvOccupied->Vel / 40.0,
|
||||
0.0, 1.0 ) );
|
||||
rsRunningNoise
|
||||
.pitch( clamp( frequency, 0.5, 1.15 ) ) // arbitrary limits to prevent the pitch going out of whack
|
||||
.pitch( frequency )
|
||||
.gain( volume )
|
||||
.play( sound_flags::exclusive | sound_flags::looping );
|
||||
}
|
||||
else {
|
||||
rsRunningNoise.stop();
|
||||
// don't play the optional ending sound if the listener switches views
|
||||
rsRunningNoise.stop( true == FreeFlyModeFlag );
|
||||
}
|
||||
|
||||
// McZapkie-141102: SHP i czuwak, TODO: sygnalizacja kabinowa
|
||||
@@ -5448,6 +5464,10 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName)
|
||||
// syk:
|
||||
rsHissU.deserialize( parser, sound_type::single, sound_parameters::amplitude );
|
||||
rsHissU.owner( DynamicObject );
|
||||
if( true == rsSBHissU.empty() ) {
|
||||
// fallback for vehicles without defined local brake hiss sound
|
||||
rsSBHissU = rsHissU;
|
||||
}
|
||||
}
|
||||
else if (token == "airsound3:")
|
||||
{
|
||||
@@ -5473,6 +5493,11 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName)
|
||||
rsSBHiss.deserialize( parser, sound_type::single, sound_parameters::amplitude );
|
||||
rsSBHiss.owner( DynamicObject );
|
||||
}
|
||||
else if( token == "localbrakesound2:" ) {
|
||||
// syk:
|
||||
rsSBHissU.deserialize( parser, sound_type::single, sound_parameters::amplitude );
|
||||
rsSBHissU.owner( DynamicObject );
|
||||
}
|
||||
else if (token == "fadesound:")
|
||||
{
|
||||
// ambient sound:
|
||||
@@ -5537,7 +5562,7 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName)
|
||||
std::vector<sound_source *> sounds = {
|
||||
&dsbReverserKey, &dsbNastawnikJazdy, &dsbNastawnikBocz,
|
||||
&dsbSwitch, &dsbPneumaticSwitch,
|
||||
&rsHiss, &rsHissU, &rsHissE, &rsHissX, &rsHissT, &rsSBHiss,
|
||||
&rsHiss, &rsHissU, &rsHissE, &rsHissX, &rsHissT, &rsSBHiss, &rsSBHissU,
|
||||
&rsFadeSound, &rsRunningNoise,
|
||||
&dsbHasler, &dsbBuzzer, &dsbSlipAlarm
|
||||
};
|
||||
@@ -5723,9 +5748,19 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName)
|
||||
if( dsbBuzzer.offset() == nullvector ) {
|
||||
dsbBuzzer.offset( btLampkaCzuwaka.model_offset() );
|
||||
}
|
||||
auto const localbrakeoffset { ggLocalBrake.model_offset() };
|
||||
std::vector<sound_source *> localbrakesounds = {
|
||||
&rsSBHiss, &rsSBHissU
|
||||
};
|
||||
for( auto sound : localbrakesounds ) {
|
||||
if( sound->offset() == nullvector ) {
|
||||
sound->offset( localbrakeoffset );
|
||||
}
|
||||
}
|
||||
// NOTE: if the local brake model can't be located the emitter will also be assigned location of main brake
|
||||
auto const brakeoffset { ggBrakeCtrl.model_offset() };
|
||||
std::vector<sound_source *> brakesounds = {
|
||||
&rsHiss, &rsHissU, &rsHissE, &rsHissX, &rsHissT, &rsSBHiss,
|
||||
&rsHiss, &rsHissU, &rsHissE, &rsHissX, &rsHissT, &rsSBHiss, &rsSBHissU,
|
||||
};
|
||||
for( auto sound : brakesounds ) {
|
||||
if( sound->offset() == nullvector ) {
|
||||
|
||||
3
Train.h
3
Train.h
@@ -410,11 +410,12 @@ public: // reszta może by?publiczna
|
||||
sound_source rsHissX { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // fala
|
||||
sound_source rsHissT { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // czasowy
|
||||
sound_source rsSBHiss { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // local
|
||||
sound_source rsSBHissU { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // local, engage brakes
|
||||
float m_lastlocalbrakepressure { -1.f }; // helper, cached level of pressure in local brake cylinder
|
||||
float m_localbrakepressurechange { 0.f }; // recent change of pressure in local brake cylinder
|
||||
|
||||
sound_source rsFadeSound { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE };
|
||||
sound_source rsRunningNoise{ sound_placement::internal, 2.0 * EU07_SOUND_CABCONTROLSCUTOFFRANGE };
|
||||
sound_source rsRunningNoise{ sound_placement::internal, 2 * EU07_SOUND_CABCONTROLSCUTOFFRANGE };
|
||||
|
||||
sound_source dsbHasler { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE };
|
||||
sound_source dsbBuzzer { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE };
|
||||
|
||||
@@ -59,14 +59,14 @@ openal_source::update( double const Deltatime ) {
|
||||
|
||||
if( id != audio::null_resource ) {
|
||||
|
||||
::alGetSourcei( id, AL_BUFFERS_PROCESSED, &buffer_index );
|
||||
::alGetSourcei( id, AL_BUFFERS_PROCESSED, &sound_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 ) ) {
|
||||
while( ( sound_index > 0 )
|
||||
&& ( sounds.size() > 1 ) ) {
|
||||
::alSourceUnqueueBuffers( id, 1, &bufferid );
|
||||
buffers.erase( std::begin( buffers ) );
|
||||
--buffer_index;
|
||||
sounds.erase( std::begin( sounds ) );
|
||||
--sound_index;
|
||||
}
|
||||
|
||||
int state;
|
||||
@@ -84,7 +84,7 @@ 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
|
||||
is_synced = false;
|
||||
sync = sync_state::bad_resource;
|
||||
return;
|
||||
}
|
||||
/*
|
||||
@@ -100,10 +100,10 @@ openal_source::sync_with( sound_properties const &State ) {
|
||||
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 * 5.0f );
|
||||
sound_range * 7.5f );
|
||||
if( glm::length2( sound_distance ) > std::min( ( cutoffrange * cutoffrange ), ( EU07_SOUND_CUTOFFRANGE * EU07_SOUND_CUTOFFRANGE ) ) ) {
|
||||
stop();
|
||||
is_synced = false; // flag sync failure for the controller
|
||||
sync = sync_state::bad_distance; // flag sync failure for the controller
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -147,9 +147,9 @@ openal_source::sync_with( sound_properties const &State ) {
|
||||
// pitch value has changed
|
||||
properties.pitch = State.pitch;
|
||||
|
||||
::alSourcef( id, AL_PITCH, properties.pitch * pitch_variation );
|
||||
::alSourcef( id, AL_PITCH, clamp( properties.pitch * pitch_variation, 0.1f, 10.f ) );
|
||||
}
|
||||
is_synced = true;
|
||||
sync = sync_state::good;
|
||||
}
|
||||
|
||||
// sets max audible distance for sounds emitted by the source
|
||||
@@ -205,7 +205,7 @@ openal_source::clear() {
|
||||
stop();
|
||||
// ...prepare space for returned ids of unqueued buffers (not that we need that info)...
|
||||
std::vector<ALuint> bufferids;
|
||||
bufferids.resize( buffers.size() );
|
||||
bufferids.resize( sounds.size() );
|
||||
// ...release the buffers...
|
||||
::alSourceUnqueueBuffers( id, bufferids.size(), bufferids.data() );
|
||||
}
|
||||
@@ -260,17 +260,6 @@ openal_renderer::init() {
|
||||
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 ) {
|
||||
|
||||
@@ -14,6 +14,8 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
class sound_source;
|
||||
|
||||
using uint32_sequence = std::vector<std::uint32_t>;
|
||||
|
||||
// sound emitter state sync item
|
||||
struct sound_properties {
|
||||
glm::dvec3 location;
|
||||
@@ -23,6 +25,12 @@ struct sound_properties {
|
||||
float pitch { 1.f };
|
||||
};
|
||||
|
||||
enum class sync_state {
|
||||
good,
|
||||
bad_distance,
|
||||
bad_resource
|
||||
};
|
||||
|
||||
namespace audio {
|
||||
|
||||
// implementation part of the sound emitter
|
||||
@@ -37,27 +45,30 @@ struct openal_source {
|
||||
// members
|
||||
ALuint id { audio::null_resource }; // associated AL resource
|
||||
sound_source *controller { nullptr }; // source controller
|
||||
buffer_sequence buffers; // sequence of samples the source will emit
|
||||
int buffer_index { 0 }; // currently queued sample from the buffer sequence
|
||||
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 is_playing { false };
|
||||
bool is_looping { false };
|
||||
bool is_synced { true }; // set to false only if a sync attempt fails
|
||||
sound_properties properties;
|
||||
sync_state sync { sync_state::good };
|
||||
|
||||
// methods
|
||||
template <class Iterator_>
|
||||
openal_source &
|
||||
bind( sound_source *Controller, Iterator_ First, Iterator_ Last ) {
|
||||
bind( sound_source *Controller, uint32_sequence Sounds, Iterator_ First, Iterator_ Last ) {
|
||||
controller = Controller;
|
||||
buffers.insert( std::end( buffers ), First, Last );
|
||||
is_multipart = ( buffers.size() > 1 );
|
||||
sounds = Sounds;
|
||||
// look up and queue assigned buffers
|
||||
std::vector<ALuint> bufferids;
|
||||
for( auto const buffer : buffers ) {
|
||||
bufferids.emplace_back( audio::renderer.buffer( buffer ).id ); }
|
||||
std::vector<ALuint> buffers;
|
||||
std::for_each(
|
||||
First, Last,
|
||||
[&]( audio::buffer_handle const &buffer ) {
|
||||
buffers.emplace_back( audio::renderer.buffer( buffer ).id ); } );
|
||||
if( id != audio::null_resource ) {
|
||||
::alSourceQueueBuffers( id, static_cast<ALsizei>( bufferids.size() ), bufferids.data() );
|
||||
::alSourceQueueBuffers( id, static_cast<ALsizei>( buffers.size() ), buffers.data() );
|
||||
::alSourceRewind( id ); }
|
||||
is_multipart = ( buffers.size() > 1 );
|
||||
return *this; }
|
||||
// starts playback of queued buffers
|
||||
void
|
||||
@@ -119,11 +130,8 @@ public:
|
||||
// schedules playback of provided range of samples, under control of the specified sound emitter
|
||||
template <class Iterator_>
|
||||
void
|
||||
insert( sound_source *Controller, Iterator_ First, Iterator_ Last ) {
|
||||
m_sources.emplace_back( fetch_source().bind( Controller, First, Last ) ); }
|
||||
// schedules playback of specified sample, under control of the specified sound emitter
|
||||
void
|
||||
insert( sound_source *Controller, audio::buffer_handle const Sound );
|
||||
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 );
|
||||
|
||||
17
parser.cpp
17
parser.cpp
@@ -109,9 +109,21 @@ cParser::getToken<bool>( bool const ToLower, const char *Break ) {
|
||||
}
|
||||
|
||||
// methods
|
||||
cParser &
|
||||
cParser::autoclear( bool const Autoclear ) {
|
||||
|
||||
m_autoclear = Autoclear;
|
||||
if( mIncludeParser ) { mIncludeParser->autoclear( Autoclear ); }
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool cParser::getTokens(unsigned int Count, bool ToLower, const char *Break)
|
||||
{
|
||||
tokens.clear(); // emulates old parser behaviour. TODO, TBD: allow manual reset?
|
||||
if( true == m_autoclear ) {
|
||||
// legacy parser behaviour
|
||||
tokens.clear();
|
||||
}
|
||||
/*
|
||||
if (LoadTraction==true)
|
||||
trtest="niemaproblema"; //wczytywać
|
||||
@@ -123,7 +135,7 @@ bool cParser::getTokens(unsigned int Count, bool ToLower, const char *Break)
|
||||
this->str("");
|
||||
this->clear();
|
||||
*/
|
||||
for (unsigned int i = 0; i < Count; ++i)
|
||||
for (unsigned int i = tokens.size(); i < Count; ++i)
|
||||
{
|
||||
std::string token = readToken(ToLower, Break);
|
||||
if( true == token.empty() ) {
|
||||
@@ -215,6 +227,7 @@ std::string cParser::readToken( bool ToLower, const char *Break ) {
|
||||
parameter = readToken( false );
|
||||
}
|
||||
mIncludeParser = std::make_shared<cParser>( includefile, buffer_FILE, mPath, LoadTraction, includeparameters );
|
||||
mIncludeParser->autoclear( m_autoclear );
|
||||
if( mIncludeParser->mSize <= 0 ) {
|
||||
ErrorLog( "Bad include: can't open file \"" + includefile + "\"" );
|
||||
}
|
||||
|
||||
6
parser.h
6
parser.h
@@ -61,6 +61,11 @@ class cParser //: public std::stringstream
|
||||
bool
|
||||
ok() {
|
||||
return !mStream->fail(); };
|
||||
cParser &
|
||||
autoclear( bool const Autoclear );
|
||||
bool
|
||||
autoclear() const {
|
||||
return m_autoclear; }
|
||||
bool
|
||||
getTokens( unsigned int Count = 1, bool ToLower = true, char const *Break = "\n\r\t ;" );
|
||||
// returns next incoming token, if any, without removing it from the set
|
||||
@@ -91,6 +96,7 @@ class cParser //: public std::stringstream
|
||||
bool trimComments( std::string &String );
|
||||
std::size_t count();
|
||||
// members:
|
||||
bool m_autoclear { true }; // not retrieved tokens are discarded when another read command is issued (legacy behaviour)
|
||||
bool LoadTraction; // load traction?
|
||||
std::shared_ptr<std::istream> mStream; // relevant kind of buffer is attached on creation.
|
||||
std::string mFile; // name of the open file, if any
|
||||
|
||||
625
sound.cpp
625
sound.cpp
@@ -37,41 +37,85 @@ sound_source::deserialize( std::string const &Input, sound_type const Legacytype
|
||||
sound_source &
|
||||
sound_source::deserialize( cParser &Input, sound_type const Legacytype, int const Legacyparameters ) {
|
||||
|
||||
// TODO: implement block type config parsing
|
||||
switch( Legacytype ) {
|
||||
case sound_type::single: {
|
||||
// single sample only
|
||||
m_soundmain.buffer = audio::renderer.fetch_buffer( deserialize_filename( Input ) );
|
||||
break;
|
||||
}
|
||||
case sound_type::multipart: {
|
||||
// three samples: start, middle, stop
|
||||
m_soundbegin.buffer = audio::renderer.fetch_buffer( deserialize_filename( Input ) );
|
||||
m_soundmain.buffer = audio::renderer.fetch_buffer( deserialize_filename( Input ) );
|
||||
m_soundend.buffer = audio::renderer.fetch_buffer( deserialize_filename( Input ) );
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// cache parser config, as it may change during deserialization
|
||||
auto const inputautoclear { Input.autoclear() };
|
||||
|
||||
if( Legacyparameters & sound_parameters::range ) {
|
||||
Input.getTokens( 1, false );
|
||||
Input >> m_range;
|
||||
Input.getTokens( 1, true, "\n\r\t ,;" );
|
||||
if( Input.peek() == "{" ) {
|
||||
// block type config
|
||||
while( true == deserialize_mapping( Input ) ) {
|
||||
; // all work done by while()
|
||||
}
|
||||
|
||||
if( false == m_soundchunks.empty() ) {
|
||||
// arrange loaded sound chunks in requested order
|
||||
std::sort(
|
||||
std::begin( m_soundchunks ), std::end( m_soundchunks ),
|
||||
[]( soundchunk_pair const &Left, soundchunk_pair const &Right ) {
|
||||
return ( Left.second.threshold < Right.second.threshold ); } );
|
||||
// calculate and cache full range points for each chunk, including crossfade sections:
|
||||
// on the far end the crossfade section extends to the threshold point of the next chunk...
|
||||
for( std::size_t idx = 0; idx < m_soundchunks.size() - 1; ++idx ) {
|
||||
m_soundchunks[ idx ].second.fadeout = m_soundchunks[ idx + 1 ].second.threshold;
|
||||
}
|
||||
// ...and on the other end from the threshold point back into the range of previous chunk
|
||||
for( std::size_t idx = 1; idx < m_soundchunks.size(); ++idx ) {
|
||||
auto const previouschunkwidth { m_soundchunks[ idx ].second.threshold - m_soundchunks[ idx - 1 ].second.threshold };
|
||||
m_soundchunks[ idx ].second.fadein = m_soundchunks[ idx ].second.threshold - 0.01f * m_crossfaderange * previouschunkwidth;
|
||||
}
|
||||
m_soundchunks.back().second.fadeout = std::max( m_soundchunks.back().second.threshold, 100 );
|
||||
// test if the chunk table contains any actual samples while at it
|
||||
for( auto &soundchunk : m_soundchunks ) {
|
||||
if( soundchunk.first.buffer != null_handle ) {
|
||||
m_soundchunksempty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if( Legacyparameters & sound_parameters::amplitude ) {
|
||||
Input.getTokens( 2, false );
|
||||
Input
|
||||
>> m_amplitudefactor
|
||||
>> m_amplitudeoffset;
|
||||
}
|
||||
if( Legacyparameters & sound_parameters::frequency ) {
|
||||
Input.getTokens( 2, false );
|
||||
Input
|
||||
>> m_frequencyfactor
|
||||
>> m_frequencyoffset;
|
||||
else {
|
||||
// legacy type config
|
||||
|
||||
// set the parser to preserve retrieved tokens, so we don't need to mess with separately passing the initial read
|
||||
Input.autoclear( false );
|
||||
|
||||
switch( Legacytype ) {
|
||||
case sound_type::single: {
|
||||
// single sample only
|
||||
m_sounds[ main ].buffer = audio::renderer.fetch_buffer( deserialize_filename( Input ) );
|
||||
break;
|
||||
}
|
||||
case sound_type::multipart: {
|
||||
// three samples: start, middle, stop
|
||||
for( auto &sound : m_sounds ) {
|
||||
sound.buffer = audio::renderer.fetch_buffer( deserialize_filename( Input ) );
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( Legacyparameters & sound_parameters::range ) {
|
||||
Input.getTokens( 1, false );
|
||||
Input >> m_range;
|
||||
}
|
||||
if( Legacyparameters & sound_parameters::amplitude ) {
|
||||
Input.getTokens( 2, false );
|
||||
Input
|
||||
>> m_amplitudefactor
|
||||
>> m_amplitudeoffset;
|
||||
}
|
||||
if( Legacyparameters & sound_parameters::frequency ) {
|
||||
Input.getTokens( 2, false );
|
||||
Input
|
||||
>> m_frequencyfactor
|
||||
>> m_frequencyoffset;
|
||||
}
|
||||
}
|
||||
// restore parser behaviour
|
||||
Input.autoclear( inputautoclear );
|
||||
|
||||
return *this;
|
||||
}
|
||||
@@ -85,11 +129,11 @@ sound_source::deserialize_filename( cParser &Input ) {
|
||||
// simple case, single file
|
||||
return token;
|
||||
}
|
||||
// if instead of filename we've encountered '[' this marks beginning of random sound set
|
||||
// we retrieve all filenames from the set, then return a random one
|
||||
// if instead of filename we've encountered '[' this marks a beginning of random sounds
|
||||
// we retrieve all entries, then return a random one
|
||||
std::vector<std::string> filenames;
|
||||
while( ( ( token = Input.getToken<std::string>( true, "\n\r\t ,;" ) ) != "" )
|
||||
&& ( token != "]" ) ) {
|
||||
&& ( token != "]" ) ) {
|
||||
filenames.emplace_back( token );
|
||||
}
|
||||
if( false == filenames.empty() ) {
|
||||
@@ -102,6 +146,127 @@ sound_source::deserialize_filename( cParser &Input ) {
|
||||
}
|
||||
}
|
||||
|
||||
// imports member data pair from the config file
|
||||
bool
|
||||
sound_source::deserialize_mapping( cParser &Input ) {
|
||||
// token can be a key or block end
|
||||
std::string const key { Input.getToken<std::string>( true, "\n\r\t ,;[]" ) };
|
||||
|
||||
if( ( true == key.empty() ) || ( key == "}" ) ) { return false; }
|
||||
|
||||
// if not block end then the key is followed by assigned value or sub-block
|
||||
if( key == "soundmain:" ) {
|
||||
sound( sound_id::main ).buffer = audio::renderer.fetch_buffer( deserialize_filename( Input ) );
|
||||
}
|
||||
else if( key == "soundset:" ) {
|
||||
deserialize_soundset( Input );
|
||||
}
|
||||
else if( key == "soundbegin:" ) {
|
||||
sound( sound_id::begin ).buffer = audio::renderer.fetch_buffer( deserialize_filename( Input ) );
|
||||
}
|
||||
else if( key == "soundend:" ) {
|
||||
sound( sound_id::end ).buffer = audio::renderer.fetch_buffer( deserialize_filename( Input ) );
|
||||
}
|
||||
else if( key.compare( 0, std::min<std::size_t>( key.size(), 5 ), "sound" ) == 0 ) {
|
||||
// sound chunks, defined with key soundX where X = activation threshold
|
||||
auto const indexstart { key.find_first_of( "1234567890" ) };
|
||||
auto const indexend { key.find_first_not_of( "1234567890", indexstart ) };
|
||||
if( indexstart != std::string::npos ) {
|
||||
// NOTE: we'll sort the chunks at the end of deserialization
|
||||
m_soundchunks.emplace_back(
|
||||
soundchunk_pair {
|
||||
// sound data
|
||||
{ audio::renderer.fetch_buffer( deserialize_filename( Input ) ), 0 },
|
||||
// chunk data
|
||||
{ std::stoi( key.substr( indexstart, indexend - indexstart ) ), 0, 0, 1.f } } );
|
||||
}
|
||||
}
|
||||
else if( key.compare( 0, std::min<std::size_t>( key.size(), 5 ), "pitch" ) == 0 ) {
|
||||
// sound chunk pitch, defined with key pitchX where X = activation threshold
|
||||
auto const indexstart { key.find_first_of( "1234567890" ) };
|
||||
auto const indexend { key.find_first_not_of( "1234567890", indexstart ) };
|
||||
if( indexstart != std::string::npos ) {
|
||||
auto const index { std::stoi( key.substr( indexstart, indexend - indexstart ) ) };
|
||||
auto const pitch { Input.getToken<float>( false, "\n\r\t ,;" ) };
|
||||
for( auto &chunk : m_soundchunks ) {
|
||||
if( chunk.second.threshold == index ) {
|
||||
chunk.second.pitch = pitch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( key == "crossfade:" ) {
|
||||
// for combined sounds, percentage of assigned range allocated to crossfade sections
|
||||
Input.getTokens( 1, "\n\r\t ,;" );
|
||||
Input >> m_crossfaderange;
|
||||
m_crossfaderange = clamp( m_crossfaderange, 0, 100 );
|
||||
}
|
||||
else if( key == "placement:" ) {
|
||||
auto const value { Input.getToken<std::string>( true, "\n\r\t ,;" ) };
|
||||
std::map<std::string, sound_placement> const placements {
|
||||
{ "internal", sound_placement::internal },
|
||||
{ "engine", sound_placement::engine },
|
||||
{ "external", sound_placement::external },
|
||||
{ "general", sound_placement::general } };
|
||||
auto lookup{ placements.find( value ) };
|
||||
if( lookup != placements.end() ) {
|
||||
m_placement = lookup->second;
|
||||
}
|
||||
}
|
||||
else if( key == "offset:" ) {
|
||||
// point in 3d space, in format [ x, y, z ]
|
||||
Input.getTokens( 3, false, "\n\r\t ,;[]" );
|
||||
Input
|
||||
>> m_offset.x
|
||||
>> m_offset.y
|
||||
>> m_offset.z;
|
||||
}
|
||||
else {
|
||||
// floating point properties
|
||||
std::map<std::string, float &> const properties {
|
||||
{ "frequencyfactor:", m_frequencyfactor },
|
||||
{ "frequencyoffset:", m_frequencyoffset },
|
||||
{ "amplitudefactor:", m_amplitudefactor },
|
||||
{ "amplitudeoffset:", m_amplitudeoffset },
|
||||
{ "range:", m_range } };
|
||||
|
||||
auto lookup { properties.find( key ) };
|
||||
if( lookup != properties.end() ) {
|
||||
Input.getTokens( 1, false, "\n\r\t ,;" );
|
||||
Input >> lookup->second;
|
||||
}
|
||||
}
|
||||
|
||||
return true; // return value marks a [ key: value ] pair was extracted, nothing about whether it's recognized
|
||||
}
|
||||
|
||||
// imports values for initial, main and ending sounds from provided data stream
|
||||
void
|
||||
sound_source::deserialize_soundset( cParser &Input ) {
|
||||
|
||||
auto token { Input.getToken<std::string>( true, "\n\r\t ,;|" ) };
|
||||
if( token != "[" ) {
|
||||
// simple case, basic set of three filenames separated with |
|
||||
// three samples: start, middle, stop
|
||||
sound( sound_id::begin ).buffer = audio::renderer.fetch_buffer( token );
|
||||
sound( sound_id::main ).buffer = audio::renderer.fetch_buffer( Input.getToken<std::string>( true, "\n\r\t ,;|" ) );
|
||||
sound( sound_id::end ).buffer = audio::renderer.fetch_buffer( Input.getToken<std::string>( true, "\n\r\t ,;|" ) );
|
||||
return;
|
||||
}
|
||||
// if instead of filename we've encountered '[' this marks a beginning of random sets
|
||||
// we retrieve all entries, then process a random one
|
||||
std::vector<std::string> soundsets;
|
||||
while( ( ( token = Input.getToken<std::string>( true, "\n\r\t ,;" ) ) != "" )
|
||||
&& ( token != "]" ) ) {
|
||||
soundsets.emplace_back( token );
|
||||
}
|
||||
if( false == soundsets.empty() ) {
|
||||
std::shuffle( std::begin( soundsets ), std::end( soundsets ), Global::random_engine );
|
||||
return deserialize_soundset( cParser( soundsets.front() ) );
|
||||
}
|
||||
}
|
||||
|
||||
// issues contextual play commands for the audio renderer
|
||||
void
|
||||
sound_source::play( int const Flags ) {
|
||||
@@ -112,9 +277,11 @@ sound_source::play( int const Flags ) {
|
||||
return;
|
||||
}
|
||||
if( m_range > 0 ) {
|
||||
auto const cutoffrange{ m_range * 5 };
|
||||
if( glm::length2( location() - glm::dvec3{ Global::pCameraPosition } ) > std::min( 2750.f * 2750.f, cutoffrange * cutoffrange ) ) {
|
||||
// drop sounds from beyond sensible and/or audible range
|
||||
auto const cutoffrange { m_range * 5 };
|
||||
if( glm::length2( location() - glm::dvec3 { Global::pCameraPosition } ) > std::min( 2750.f * 2750.f, cutoffrange * cutoffrange ) ) {
|
||||
// while we drop sounds from beyond sensible and/or audible range
|
||||
// we act as if it was activated normally, meaning no need to include the opening bookend in subsequent calls
|
||||
m_playbeginning = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -126,40 +293,106 @@ sound_source::play( int const Flags ) {
|
||||
|
||||
m_flags = Flags;
|
||||
|
||||
if( sound( sound_id::main ).buffer != null_handle ) {
|
||||
// basic variant: single main sound, with optional bookends
|
||||
play_basic();
|
||||
}
|
||||
else {
|
||||
// combined variant, main sound consists of multiple chunks, with optional bookends
|
||||
play_combined();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sound_source::play_basic() {
|
||||
|
||||
if( false == is_playing() ) {
|
||||
// dispatch appropriate sound
|
||||
// TODO: support for parameter-driven sound table
|
||||
if( m_soundbegin.buffer != null_handle ) {
|
||||
std::vector<audio::buffer_handle> bufferlist { m_soundbegin.buffer, m_soundmain.buffer };
|
||||
insert( std::begin( bufferlist ), std::end( bufferlist ) );
|
||||
if( ( true == m_playbeginning )
|
||||
&& ( sound( sound_id::begin ).buffer != null_handle ) ) {
|
||||
std::vector<sound_id> sounds { sound_id::begin, sound_id::main };
|
||||
insert( std::begin( sounds ), std::end( sounds ) );
|
||||
m_playbeginning = false;
|
||||
}
|
||||
else {
|
||||
insert( m_soundmain.buffer );
|
||||
insert( sound_id::main );
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
if( ( m_soundbegin.buffer == null_handle )
|
||||
// for single part non-looping samples we allow spawning multiple instances, if not prevented by set flags
|
||||
if( ( sound( sound_id::begin ).buffer == null_handle )
|
||||
&& ( ( m_flags & ( sound_flags::exclusive | sound_flags::looping ) ) == 0 ) ) {
|
||||
// for single part non-looping samples we allow spawning multiple instances, if not prevented by set flags
|
||||
insert( m_soundmain.buffer );
|
||||
insert( sound_id::main );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sound_source::play_combined() {
|
||||
// combined sound consists of table od samples, each sample associated with certain range of values of controlling variable
|
||||
// current value of the controlling variable is passed to the source with pitch() call
|
||||
auto const soundpoint { clamp( m_properties.pitch * 100.f, 0.f, 100.f ) };
|
||||
for( std::uint32_t idx = 0; idx < m_soundchunks.size(); ++idx ) {
|
||||
|
||||
auto const &soundchunk { m_soundchunks[ idx ] };
|
||||
// a chunk covers range from fade in point, where it starts rising in volume over crossfade distance,
|
||||
// lasts until fadeout - crossfade distance point, past which it grows quiet until fade out point where it ends
|
||||
if( soundpoint < soundchunk.second.fadein ) { break; }
|
||||
if( soundpoint > soundchunk.second.fadeout ) { continue; }
|
||||
|
||||
if( ( soundchunk.first.playing > 0 )
|
||||
|| ( soundchunk.first.buffer == null_handle ) ) {
|
||||
// combined sounds only play looped, single copy of each activated chunk
|
||||
continue;
|
||||
}
|
||||
|
||||
if( idx > 0 ) {
|
||||
insert( sound_id::chunk | idx );
|
||||
}
|
||||
else {
|
||||
// initial chunk requires some safety checks if the optional bookend is present,
|
||||
// so we don't queue another instance while the bookend is still playing
|
||||
if( sound( sound_id::begin ).buffer == null_handle ) {
|
||||
// no bookend, safe to play the chunk
|
||||
insert( sound_id::chunk | idx );
|
||||
}
|
||||
else {
|
||||
// branches:
|
||||
// beginning requested, not playing; queue beginning and chunk
|
||||
// beginning not requested, not playing; queue chunk
|
||||
// otherwise skip, one instance is already in the audio queue
|
||||
if( sound( sound_id::begin ).playing == 0 ) {
|
||||
if( true == m_playbeginning ) {
|
||||
std::vector<sound_handle> sounds{ sound_id::begin, sound_id::chunk | idx };
|
||||
insert( std::begin( sounds ), std::end( sounds ) );
|
||||
m_playbeginning = false;
|
||||
}
|
||||
else {
|
||||
insert( sound_id::chunk | idx );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stops currently active play commands controlled by this emitter
|
||||
void
|
||||
sound_source::stop() {
|
||||
sound_source::stop( bool const Skipend ) {
|
||||
|
||||
// if the source was stopped on simulation side, we should play the opening bookend next time it's activated
|
||||
m_playbeginning = true;
|
||||
|
||||
if( false == is_playing() ) { return; }
|
||||
|
||||
m_stop = true;
|
||||
|
||||
if( ( m_soundend.buffer != null_handle )
|
||||
&& ( m_soundend.buffer != m_soundmain.buffer ) // end == main can happen in malformed legacy cases
|
||||
&& ( m_soundend.playing == 0 ) ) {
|
||||
if( ( false == Skipend )
|
||||
&& ( sound( sound_id::end ).buffer != null_handle )
|
||||
&& ( sound( sound_id::end ).buffer != sound( sound_id::main ).buffer ) // end == main can happen in malformed legacy cases
|
||||
&& ( sound( sound_id::end ).playing == 0 ) ) {
|
||||
// spawn potentially defined sound end sample, if the emitter is currently active
|
||||
insert( m_soundend.buffer );
|
||||
insert( sound_id::end );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,48 +400,64 @@ sound_source::stop() {
|
||||
void
|
||||
sound_source::update( audio::openal_source &Source ) {
|
||||
|
||||
if( sound( sound_id::main ).buffer != null_handle ) {
|
||||
// basic variant: single main sound, with optional bookends
|
||||
update_basic( Source );
|
||||
return;
|
||||
}
|
||||
if( false == m_soundchunksempty ) {
|
||||
// combined variant, main sound consists of multiple chunks, with optional bookends
|
||||
update_combined( Source );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sound_source::update_basic( audio::openal_source &Source ) {
|
||||
|
||||
if( true == Source.is_playing ) {
|
||||
|
||||
if( ( true == m_stop )
|
||||
&& ( Source.buffers[ Source.buffer_index ] != m_soundend.buffer ) ) {
|
||||
&& ( Source.sounds[ Source.sound_index ] != sound_id::end ) ) {
|
||||
// kill the sound if stop was requested, unless it's sound bookend sample
|
||||
Source.stop();
|
||||
update_counter( Source.buffers[ Source.buffer_index ], -1 );
|
||||
update_counter( Source.sounds[ Source.sound_index ], -1 );
|
||||
if( false == is_playing() ) {
|
||||
m_stop = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if( m_soundbegin.buffer != null_handle ) {
|
||||
if( sound( sound_id::begin ).buffer != null_handle ) {
|
||||
// potentially a multipart sound
|
||||
// detect the moment when the sound moves from startup sample to the main
|
||||
auto const soundhandle { Source.sounds[ Source.sound_index ] };
|
||||
if( ( false == Source.is_looping )
|
||||
&& ( Source.buffers[ Source.buffer_index ] == m_soundmain.buffer ) ) {
|
||||
// when it happens update active sample flags, and activate the looping
|
||||
&& ( soundhandle == sound_id::main ) ) {
|
||||
// when it happens update active sample counters, and activate the looping
|
||||
update_counter( sound_id::begin, -1 );
|
||||
update_counter( soundhandle, 1 );
|
||||
Source.loop( true );
|
||||
--( m_soundbegin.playing );
|
||||
++( m_soundmain.playing );
|
||||
}
|
||||
}
|
||||
|
||||
// check and update if needed current sound properties
|
||||
update_location();
|
||||
update_soundproofing();
|
||||
Source.sync_with( m_properties );
|
||||
if( false == Source.is_synced ) {
|
||||
if( Source.sync != sync_state::good ) {
|
||||
// if the sync went wrong we let the renderer kill its part of the emitter, and update our playcounter(s) to match
|
||||
update_counter( Source.buffers[ Source.buffer_index ], -1 );
|
||||
update_counter( Source.sounds[ Source.sound_index ], -1 );
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
// if the emitter isn't playing it's either done or wasn't yet started
|
||||
// we can determine this from number of processed buffers
|
||||
if( Source.buffer_index != Source.buffers.size() ) {
|
||||
auto const buffer { Source.buffers[ Source.buffer_index ] };
|
||||
if( Source.sound_index != Source.sounds.size() ) {
|
||||
// the emitter wasn't yet started
|
||||
auto const soundhandle { Source.sounds[ Source.sound_index ] };
|
||||
// emitter initialization
|
||||
if( ( buffer == m_soundmain.buffer )
|
||||
if( ( soundhandle == sound_id::main )
|
||||
&& ( true == TestFlag( m_flags, sound_flags::looping ) ) ) {
|
||||
// main sample can be optionally set to loop
|
||||
Source.loop( true );
|
||||
@@ -218,22 +467,208 @@ sound_source::update( audio::openal_source &Source ) {
|
||||
update_location();
|
||||
update_soundproofing();
|
||||
Source.sync_with( m_properties );
|
||||
if( true == Source.is_synced ) {
|
||||
if( Source.sync == sync_state::good ) {
|
||||
// all set, start playback
|
||||
Source.play();
|
||||
if( false == Source.is_playing ) {
|
||||
// if the playback didn't start update the state counter
|
||||
update_counter( buffer, -1 );
|
||||
update_counter( soundhandle, -1 );
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if the initial sync went wrong we skip the activation so the renderer can clean the emitter on its end
|
||||
update_counter( buffer, -1 );
|
||||
update_counter( soundhandle, -1 );
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto const buffer { Source.buffers[ Source.buffer_index - 1 ] };
|
||||
update_counter( buffer, -1 );
|
||||
// the emitter is either all done or was terminated early
|
||||
update_counter( Source.sounds[ Source.sound_index - 1 ], -1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sound_source::update_combined( audio::openal_source &Source ) {
|
||||
|
||||
if( true == Source.is_playing ) {
|
||||
|
||||
auto const soundhandle { Source.sounds[ Source.sound_index ] };
|
||||
|
||||
if( ( true == m_stop )
|
||||
&& ( soundhandle != sound_id::end ) ) {
|
||||
// kill the sound if stop was requested, unless it's sound bookend sample
|
||||
Source.stop();
|
||||
update_counter( soundhandle, -1 );
|
||||
if( false == is_playing() ) {
|
||||
m_stop = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if( sound( sound_id::begin ).buffer != null_handle ) {
|
||||
// potentially a multipart sound
|
||||
// detect the moment when the sound moves from startup sample to the main
|
||||
auto const soundhandle { Source.sounds[ Source.sound_index ] };
|
||||
if( ( false == Source.is_looping )
|
||||
&& ( soundhandle == ( sound_id::chunk | 0 ) ) ) {
|
||||
// when it happens update active sample counters, and activate the looping
|
||||
update_counter( sound_id::begin, -1 );
|
||||
update_counter( soundhandle, 1 );
|
||||
Source.loop( true );
|
||||
}
|
||||
}
|
||||
|
||||
if( ( soundhandle & sound_id::chunk ) != 0 ) {
|
||||
// for sound chunks, test whether the chunk should still be active given current value of the controlling variable
|
||||
auto const soundpoint { clamp( m_properties.pitch * 100.f, 0.f, 100.f ) };
|
||||
auto const &soundchunk { m_soundchunks[ soundhandle ^ sound_id::chunk ] };
|
||||
if( ( soundpoint < soundchunk.second.fadein )
|
||||
|| ( soundpoint > soundchunk.second.fadeout ) ) {
|
||||
Source.stop();
|
||||
update_counter( soundhandle, -1 );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check and update if needed current sound properties
|
||||
update_location();
|
||||
update_soundproofing();
|
||||
// pitch and volume are adjusted on per-chunk basis
|
||||
// since they're relative to base values, backup these...
|
||||
auto const baseproperties = m_properties;
|
||||
// ...adjust per-chunk parameters...
|
||||
update_crossfade( soundhandle );
|
||||
// ... pass the parameters to the audio renderer...
|
||||
Source.sync_with( m_properties );
|
||||
if( Source.sync != sync_state::good ) {
|
||||
// if the sync went wrong we let the renderer kill its part of the emitter, and update our playcounter(s) to match
|
||||
update_counter( Source.sounds[ Source.sound_index ], -1 );
|
||||
}
|
||||
// ...and restore base properties
|
||||
m_properties = baseproperties;
|
||||
}
|
||||
else {
|
||||
// if the emitter isn't playing it's either done or wasn't yet started
|
||||
// we can determine this from number of processed buffers
|
||||
if( Source.sound_index != Source.sounds.size() ) {
|
||||
// the emitter wasn't yet started
|
||||
auto const soundhandle { Source.sounds[ Source.sound_index ] };
|
||||
// emitter initialization
|
||||
if( ( soundhandle != sound_id::begin )
|
||||
&& ( soundhandle != sound_id::end )
|
||||
&& ( true == TestFlag( m_flags, sound_flags::looping ) ) ) {
|
||||
// main sample can be optionally set to loop
|
||||
Source.loop( true );
|
||||
}
|
||||
Source.range( m_range );
|
||||
Source.pitch( m_pitchvariation );
|
||||
update_location();
|
||||
update_soundproofing();
|
||||
// pitch and volume are adjusted on per-chunk basis
|
||||
auto const baseproperties = m_properties;
|
||||
update_crossfade( soundhandle );
|
||||
Source.sync_with( m_properties );
|
||||
if( Source.sync == sync_state::good ) {
|
||||
// all set, start playback
|
||||
Source.play();
|
||||
if( false == Source.is_playing ) {
|
||||
// if the playback didn't start update the state counter
|
||||
update_counter( soundhandle, -1 );
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if the initial sync went wrong we skip the activation so the renderer can clean the emitter on its end
|
||||
update_counter( soundhandle, -1 );
|
||||
}
|
||||
m_properties = baseproperties;
|
||||
}
|
||||
else {
|
||||
// the emitter is either all done or was terminated early
|
||||
update_counter( Source.sounds[ Source.sound_index - 1 ], -1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sound_source::update_crossfade( sound_handle const Chunk ) {
|
||||
|
||||
if( ( Chunk & sound_id::chunk ) == 0 ) {
|
||||
// bookend sounds are played at their base pitch
|
||||
m_properties.pitch = 1.f;
|
||||
return;
|
||||
}
|
||||
|
||||
auto const soundpoint { clamp( m_properties.pitch * 100.f, 0.f, 100.f ) };
|
||||
|
||||
// NOTE: direct access to implementation details ahead, kinda fugly
|
||||
auto const chunkindex { Chunk ^ sound_id::chunk };
|
||||
auto const &chunkdata { m_soundchunks[ chunkindex ].second };
|
||||
|
||||
// relative pitch adjustment
|
||||
// pitch of each chunk is modified based on ratio of the chunk's pitch to that of its neighbour
|
||||
if( soundpoint < chunkdata.threshold ) {
|
||||
// interpolate between the pitch of previous chunk and this chunk's base pitch,
|
||||
// based on how far the current soundpoint is in the range of previous chunk
|
||||
auto const &previouschunkdata{ m_soundchunks[ chunkindex - 1 ].second };
|
||||
m_properties.pitch =
|
||||
interpolate(
|
||||
previouschunkdata.pitch / chunkdata.pitch,
|
||||
1.f,
|
||||
clamp(
|
||||
( soundpoint - previouschunkdata.threshold ) / ( chunkdata.threshold - previouschunkdata.threshold ),
|
||||
0.f, 1.f ) );
|
||||
}
|
||||
else {
|
||||
|
||||
if( chunkindex < ( m_soundchunks.size() - 1 ) ) {
|
||||
// interpolate between this chunk's base pitch and the pitch of next chunk
|
||||
// based on how far the current soundpoint is in the range of this chunk
|
||||
auto const &nextchunkdata { m_soundchunks[ chunkindex + 1 ].second };
|
||||
m_properties.pitch =
|
||||
interpolate(
|
||||
1.f,
|
||||
nextchunkdata.pitch / chunkdata.pitch,
|
||||
clamp(
|
||||
( soundpoint - chunkdata.threshold ) / ( nextchunkdata.threshold - chunkdata.threshold ),
|
||||
0.f, 1.f ) );
|
||||
}
|
||||
else {
|
||||
// pitch of the last (or the only) chunk remains fixed throughout
|
||||
m_properties.pitch = 1.f;
|
||||
}
|
||||
}
|
||||
|
||||
// if there's no crossfade sections, our work is done
|
||||
if( m_crossfaderange == 0 ) { return; }
|
||||
|
||||
if( chunkindex > 0 ) {
|
||||
// chunks other than the first can have fadein
|
||||
auto const fadeinwidth { chunkdata.threshold - chunkdata.fadein };
|
||||
if( soundpoint < chunkdata.threshold ) {
|
||||
m_properties.gain *=
|
||||
interpolate(
|
||||
0.f, 1.f,
|
||||
clamp(
|
||||
( soundpoint - chunkdata.fadein ) / fadeinwidth,
|
||||
0.f, 1.f ) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
if( chunkindex < ( m_soundchunks.size() - 1 ) ) {
|
||||
// chunks other than the last can have fadeout
|
||||
// TODO: cache widths in the chunk data struct?
|
||||
// fadeout point of this chunk and activation threshold of the next are the same
|
||||
// fadein range of the next chunk and the fadeout of the processed one are the same
|
||||
auto const fadeoutwidth { chunkdata.fadeout - m_soundchunks[ chunkindex + 1 ].second.fadein };
|
||||
auto const fadeoutstart { chunkdata.fadeout - fadeoutwidth };
|
||||
if( soundpoint > fadeoutstart ) {
|
||||
m_properties.gain *=
|
||||
interpolate(
|
||||
1.f, 0.f,
|
||||
clamp(
|
||||
( soundpoint - fadeoutstart ) / fadeoutwidth,
|
||||
0.f, 1.f ) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,7 +692,7 @@ sound_source::gain() const {
|
||||
sound_source &
|
||||
sound_source::pitch( float const Pitch ) {
|
||||
|
||||
m_properties.pitch = clamp( Pitch, 0.1f, 10.f );
|
||||
m_properties.pitch = Pitch;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -265,15 +700,25 @@ bool
|
||||
sound_source::empty() const {
|
||||
|
||||
// NOTE: we test only the main sound, won't bother playing potential bookends if this is missing
|
||||
// TODO: take into account presence of sample table, for combined sounds
|
||||
return ( m_soundmain.buffer == null_handle );
|
||||
return ( ( sound( sound_id::main ).buffer == null_handle ) && ( m_soundchunksempty ) );
|
||||
}
|
||||
|
||||
// returns true if the source is emitting any sound
|
||||
bool
|
||||
sound_source::is_playing( bool const Includesoundends ) const {
|
||||
|
||||
return ( ( m_soundbegin.playing + m_soundmain.playing ) > 0 );
|
||||
auto isplaying { ( sound( sound_id::begin ).playing + sound( sound_id::main ).playing ) > 0 };
|
||||
if( ( false == isplaying )
|
||||
&& ( false == m_soundchunks.empty() ) ) {
|
||||
// for emitters with sample tables check also if any of the chunks is active
|
||||
for( auto const &soundchunk : m_soundchunks ) {
|
||||
if( soundchunk.first.playing > 0 ) {
|
||||
isplaying = true;
|
||||
break; // one will do
|
||||
}
|
||||
}
|
||||
}
|
||||
return isplaying;
|
||||
}
|
||||
|
||||
// returns location of the sound source in simulation region space
|
||||
@@ -293,12 +738,10 @@ sound_source::location() const {
|
||||
}
|
||||
|
||||
void
|
||||
sound_source::update_counter( audio::buffer_handle const Buffer, int const Value ) {
|
||||
sound_source::update_counter( sound_handle const Sound, int const Value ) {
|
||||
|
||||
if( Buffer == m_soundbegin.buffer ) { m_soundbegin.playing += Value; }
|
||||
// TODO: take ito accound sample table for combined sounds
|
||||
else if( Buffer == m_soundmain.buffer ) { m_soundmain.playing += Value; }
|
||||
else if( Buffer == m_soundend.buffer ) { m_soundend.playing += Value; }
|
||||
sound( Sound ).playing += Value;
|
||||
assert( sound( Sound ).playing >= 0 );
|
||||
}
|
||||
|
||||
void
|
||||
@@ -380,10 +823,28 @@ sound_source::update_soundproofing() {
|
||||
}
|
||||
|
||||
void
|
||||
sound_source::insert( audio::buffer_handle Buffer ) {
|
||||
sound_source::insert( sound_handle const Sound ) {
|
||||
|
||||
std::vector<audio::buffer_handle> buffers { Buffer };
|
||||
return insert( std::begin( buffers ), std::end( buffers ) );
|
||||
std::vector<sound_handle> sounds { Sound };
|
||||
return insert( std::begin( sounds ), std::end( sounds ) );
|
||||
}
|
||||
|
||||
sound_source::sound_data &
|
||||
sound_source::sound( sound_handle const Sound ) {
|
||||
|
||||
return (
|
||||
( Sound & sound_id::chunk ) == sound_id::chunk ?
|
||||
m_soundchunks[ Sound ^ sound_id::chunk ].first :
|
||||
m_sounds[ Sound ] );
|
||||
}
|
||||
|
||||
sound_source::sound_data const &
|
||||
sound_source::sound( sound_handle const Sound ) const {
|
||||
|
||||
return (
|
||||
( Sound & sound_id::chunk ) == sound_id::chunk ?
|
||||
m_soundchunks[ Sound ^ sound_id::chunk ].first :
|
||||
m_sounds[ Sound ] );
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
71
sound.h
71
sound.h
@@ -63,7 +63,7 @@ public:
|
||||
play( int const Flags = 0 );
|
||||
// stops currently active play commands controlled by this emitter
|
||||
void
|
||||
stop();
|
||||
stop( bool const Skipend = false );
|
||||
// adjusts parameters of provided implementation-side sound source
|
||||
void
|
||||
update( audio::openal_source &Source );
|
||||
@@ -110,30 +110,74 @@ public:
|
||||
private:
|
||||
// types
|
||||
struct sound_data {
|
||||
audio::buffer_handle buffer { null_handle };
|
||||
int playing { 0 }; // number of currently active sample instances
|
||||
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
|
||||
// extracts name of the sound file from provided data stream
|
||||
std::string
|
||||
deserialize_filename( cParser &Input );
|
||||
// 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
|
||||
update_counter( audio::buffer_handle const Buffer, int const Value );
|
||||
deserialize_soundset( cParser &Input );
|
||||
// issues contextual play commands for the audio renderer
|
||||
void
|
||||
play_basic();
|
||||
void
|
||||
play_combined();
|
||||
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( audio::buffer_handle Buffer );
|
||||
insert( sound_handle const Sound );
|
||||
template <class Iterator_>
|
||||
void
|
||||
insert( Iterator_ First, Iterator_ Last ) {
|
||||
|
||||
audio::renderer.insert( this, First, Last );
|
||||
update_counter( *First, 1 );
|
||||
}
|
||||
uint32_sequence sounds;
|
||||
std::vector<audio::buffer_handle> buffers;
|
||||
std::for_each(
|
||||
First, Last,
|
||||
[&]( sound_handle const &soundhandle ) {
|
||||
sounds.emplace_back( soundhandle );
|
||||
buffers.emplace_back( sound( soundhandle ).buffer ); } );
|
||||
audio::renderer.insert( std::begin( buffers ), std::end( buffers ), this, sounds );
|
||||
update_counter( *First, 1 ); }
|
||||
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
|
||||
@@ -145,10 +189,11 @@ private:
|
||||
sound_properties m_properties; // current properties of the emitted sounds
|
||||
float m_pitchvariation { 0.f }; // emitter-specific shift in base pitch
|
||||
bool m_stop { false }; // indicates active sample instances should be terminated
|
||||
sound_data m_soundmain; // main sound emitted by the source
|
||||
sound_data m_soundbegin; // optional, sound emitted before the main sound
|
||||
sound_data m_soundend; // optional, sound emitted after the main sound
|
||||
// TODO: table of samples with associated values, activated when controlling variable matches the value
|
||||
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
|
||||
};
|
||||
|
||||
// owner setter/getter
|
||||
|
||||
Reference in New Issue
Block a user