From 5645c133623ce4a4cc22c9818f274eefd9a78508 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Sun, 3 Dec 2017 02:35:49 +0100 Subject: [PATCH] build 171202. audio subsystem: volume support, source reassignment, bug fixes --- Driver.cpp | 4 ++-- DynObj.cpp | 44 +++++++++++++++++------------------ DynObj.h | 4 ++-- Globals.cpp | 8 ++++++- Train.cpp | 30 ++++++++++++++---------- TrkFoll.cpp | 3 ++- audiorenderer.cpp | 59 +++++++++++++++++++++++++++++++++++++++++------ audiorenderer.h | 12 ++++++---- simulation.cpp | 4 ++-- sound.cpp | 10 ++++---- version.h | 2 +- 11 files changed, 122 insertions(+), 58 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index cb11fa8f..d66073a7 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -999,7 +999,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN go = cm_Ready; // gotów do odjazdu z W4 (semafor może // zatrzymać) if( ( tsGuardSignal != nullptr ) - && ( false == tsGuardSignal->empty() ) ); { + && ( false == tsGuardSignal->empty() ) ) { // jeśli mamy głos kierownika, to odegrać iDrivigFlags |= moveGuardSignal; } @@ -1447,7 +1447,7 @@ void TController::TableSort() { return; } TSpeedPos sp_temp = TSpeedPos(); // uzywany do przenoszenia - for( std::size_t i = 0; i < ( iLast - 1 ); ++i ) { + for( int i = 0; i < ( iLast - 1 ); ++i ) { // pętla tylko do dwóch pozycji od końca bo ostatniej nie modyfikujemy if (sSpeedTable[i].fDist > sSpeedTable[i + 1].fDist) { // jesli pozycja wcześniejsza jest dalej to źle diff --git a/DynObj.cpp b/DynObj.cpp index b1a0b757..d5be9fe8 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -3705,25 +3705,28 @@ void TDynamicObject::RenderSounds() { } else { - sConverter - .pitch( frequency ) - .gain( volume ) - .play( sound_flags::exclusive | sound_flags::looping ); + if( MoverParameters->ConverterFlag ) { - float fincvol { 0 }; - if( ( MoverParameters->ConverterFlag ) - && ( MoverParameters->enrot * 60 > MoverParameters->DElist[ 0 ].RPM ) ) { - - fincvol = ( MoverParameters->DElist[ MoverParameters->MainCtrlPos ].RPM - ( MoverParameters->enrot * 60 ) ); - fincvol /= ( 0.05 * MoverParameters->DElist[ 0 ].RPM ); - }; - if( fincvol > 0.02 ) { - rsDieselInc - .gain( fincvol ) + sConverter + .pitch( frequency ) + .gain( volume ) .play( sound_flags::exclusive | sound_flags::looping ); - } - else { - rsDieselInc.stop(); + + float fincvol { 0 }; + if( MoverParameters->enrot * 60 > MoverParameters->DElist[ 0 ].RPM ) { + + fincvol = ( MoverParameters->DElist[ MoverParameters->MainCtrlPos ].RPM - ( MoverParameters->enrot * 60 ) ); + fincvol /= ( 0.05 * MoverParameters->DElist[ 0 ].RPM ); + } + + if( fincvol > 0.02 ) { + rsDieselInc + .gain( fincvol ) + .play( sound_flags::exclusive | sound_flags::looping ); + } + else { + rsDieselInc.stop(); + } } } } @@ -4024,10 +4027,7 @@ void TDynamicObject::RenderSounds() { // McZapkie-280302 - pisk mocno zacisnietych hamulcow if( /*( false == MoverParameters->SlippingWheels ) &&*/ ( MoverParameters->UnitBrakeForce > rsPisk.m_amplitudefactor ) - && ( MoverParameters->Vel > ( - true == rsPisk.is_playing() ? - 0.05 : - 0.5 ) ) ) { + && ( MoverParameters->Vel > 2.5 ) ) { rsPisk .gain( MoverParameters->UnitBrakeForce / ( rsPisk.m_amplitudefactor + 1 ) + rsPisk.m_amplitudeoffset ) @@ -4035,7 +4035,7 @@ void TDynamicObject::RenderSounds() { } else { // don't stop the sound too abruptly - volume = std::max( 0.0, rsPisk.gain() - ( rsPisk.gain() * 1.5 * dt ) ); + volume = std::max( 0.0, rsPisk.gain() - ( rsPisk.gain() * 2.5 * dt ) ); rsPisk.gain( volume ); if( volume < 0.05 ) { rsPisk.stop(); diff --git a/DynObj.h b/DynObj.h index 1288ab8a..7dec6ee6 100644 --- a/DynObj.h +++ b/DynObj.h @@ -355,8 +355,8 @@ private: std::vector m_pantographsounds; // typically 2 but can be less (or more?) std::vector m_doorsounds; // can expect symmetrical arrangement, but don't count on it sound_source sDepartureSignal { sound_placement::general }; - sound_source sHorn1 { sound_placement::external }; - sound_source sHorn2 { sound_placement::external }; + sound_source sHorn1 { sound_placement::external, 5 * EU07_SOUND_RUNNINGNOISECUTOFFRANGE }; + sound_source sHorn2 { sound_placement::external, 5 * EU07_SOUND_RUNNINGNOISECUTOFFRANGE }; sound_source rsRunningNoise { sound_placement::external, EU07_SOUND_RUNNINGNOISECUTOFFRANGE }; sound_source rscurve { sound_placement::external, EU07_SOUND_RUNNINGNOISECUTOFFRANGE }; // youBy diff --git a/Globals.cpp b/Globals.cpp index f2bc3ba3..9293298c 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -134,7 +134,7 @@ bool Global::bWireFrame = false; // sound renderer bool Global::bSoundEnabled = true; -float Global::AudioVolume = 1.0f; +float Global::AudioVolume = 2.0f; std::string Global::AudioRenderer; int Global::iWriteLogEnabled = 3; // maska bitowa: 1-zapis do pliku, 2-okienko, 4-nazwy torów @@ -288,6 +288,12 @@ void Global::ConfigParse(cParser &Parser) // selected device for audio renderer Global::AudioRenderer = Parser.getToken( false ); // case-sensitive } + else if( token == "sound.volume" ) { + // selected device for audio renderer + Parser.getTokens(); + Parser >> Global::AudioVolume; + Global::AudioVolume = clamp( Global::AudioVolume, 1.f, 4.f ); + } // else if (str==AnsiString("renderalpha")) //McZapkie-1312302 - dwuprzebiegowe renderowanie // bRenderAlpha=(GetNextSymbol().LowerCase()==AnsiString("yes")); else if (token == "physicslog") diff --git a/Train.cpp b/Train.cpp index 213c9683..2d2ef7b0 100644 --- a/Train.cpp +++ b/Train.cpp @@ -2731,16 +2731,16 @@ void TTrain::OnCommand_departureannounce( TTrain *Train, command_data const &Com if( Command.action == GLFW_PRESS ) { // only reacting to press, so the sound can loop uninterrupted - if( false == Train->mvControlled->DepartureSignal ) { + if( false == Train->mvOccupied->DepartureSignal ) { // turn on - Train->mvControlled->signal_departure( true ); + Train->mvOccupied->signal_departure( true ); // visual feedback Train->ggDepartureSignalButton.UpdateValue( 1.0, Train->dsbSwitch ); } } else if( Command.action == GLFW_RELEASE ) { // turn off - Train->mvControlled->signal_departure( false ); + Train->mvOccupied->signal_departure( false ); // visual feedback Train->ggDepartureSignalButton.UpdateValue( 0.0, Train->dsbSwitch ); } @@ -5494,6 +5494,20 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName) bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) { m_controlmapper.clear(); + // reset sound positions and owner + auto const nullvector { glm::vec3() }; + std::vector sounds = { + &dsbReverserKey, &dsbNastawnikJazdy, &dsbNastawnikBocz, + &dsbSwitch, &dsbPneumaticSwitch, + &rsHiss, &rsHissU, &rsHissE, &rsHissX, &rsHissT, &rsSBHiss, + &rsFadeSound, + &dsbHasler, &dsbBuzzer, &dsbSlipAlarm + }; + for( auto sound : sounds ) { + sound->offset( nullvector ); + sound->owner( DynamicObject ); + } + pyScreens.reset(this); pyScreens.setLookupPath(DynamicObject->asBaseDir); bool parse = false; @@ -5658,7 +5672,6 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) set_cab_controls(); // configure placement of sound emitters which aren't bound with any device model, and weren't placed manually - auto const nullvector { glm::vec3() }; // try first to bind sounds to location of possible devices if( dsbReverserKey.offset() == nullvector ) { dsbReverserKey.offset( ggDirKey.model_offset() ); @@ -5682,14 +5695,7 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) } } // for whatever is left fallback on generic location, centre of the cab - auto const caboffset { glm::dvec3 { ( Cabine[ NewCabNo ].CabPos1 + Cabine[ NewCabNo ].CabPos2 ) * 0.5 } + glm::dvec3 { 0, 1, 0 } }; - std::vector sounds = { - &dsbReverserKey, &dsbNastawnikJazdy, &dsbNastawnikBocz, - &dsbSwitch, &dsbPneumaticSwitch, - &rsHiss, &rsHissU, &rsHissE, &rsHissX, &rsHissT, &rsSBHiss, - &rsFadeSound, - &dsbHasler, &dsbBuzzer, &dsbSlipAlarm - }; + auto const caboffset { glm::dvec3 { ( Cabine[ cabindex ].CabPos1 + Cabine[ cabindex ].CabPos2 ) * 0.5 } + glm::dvec3 { 0, 1, 0 } }; for( auto sound : sounds ) { if( sound->offset() == nullvector ) { sound->offset( caboffset ); diff --git a/TrkFoll.cpp b/TrkFoll.cpp index 38b61d1a..9c9f9db9 100644 --- a/TrkFoll.cpp +++ b/TrkFoll.cpp @@ -103,7 +103,8 @@ bool TTrackFollower::Move(double fDistance, bool bPrimary) // TODO: refactor following block as track method if( pCurrentTrack->iEvents ) { // sumaryczna informacja o eventach // omijamy cały ten blok, gdy tor nie ma on żadnych eventów (większość nie ma) - if( std::abs( fDistance ) < 0.01 ) { + if( ( std::abs( fDistance ) < 0.01 ) + && ( Owner->GetVelocity() < 0.01 ) ) { //McZapkie-140602: wyzwalanie zdarzenia gdy pojazd stoi if( ( Owner->Mechanik != nullptr ) && ( Owner->Mechanik->Primary() ) ) { diff --git a/audiorenderer.cpp b/audiorenderer.cpp index 2cf98df4..104c8203 100644 --- a/audiorenderer.cpp +++ b/audiorenderer.cpp @@ -94,18 +94,21 @@ openal_source::sync_with( sound_properties const &State ) { */ // location properties.location = State.location; - auto sourceoffset { glm::vec3 { properties.location - glm::dvec3 { Global::pCameraPosition } } }; + sound_distance = properties.location - glm::dvec3 { Global::pCameraPosition }; if( sound_range > 0 ) { // range cutoff check - auto const cutoffrange { sound_range * 7.5f }; - if( glm::length2( sourceoffset ) > std::min( ( cutoffrange * cutoffrange ), ( EU07_SOUND_CUTOFFRANGE * EU07_SOUND_CUTOFFRANGE ) ) ) { + 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(); is_synced = false; // flag sync failure for the controller return; } } if( sound_range >= 0 ) { - ::alSourcefv( id, AL_POSITION, glm::value_ptr( sourceoffset ) ); + ::alSourcefv( id, AL_POSITION, glm::value_ptr( sound_distance ) ); } else { // sounds with 'unlimited' range are positioned on top of the listener @@ -233,6 +236,7 @@ openal_renderer::init() { // // ::alDistanceModel( AL_LINEAR_DISTANCE ); ::alDistanceModel( AL_INVERSE_DISTANCE_CLAMPED ); + ::alListenerf( AL_GAIN, clamp( Global::AudioVolume, 1.f, 4.f ) ); // all done m_ready = true; return true; @@ -315,13 +319,54 @@ openal_renderer::update( double const Deltatime ) { audio::openal_source openal_renderer::fetch_source() { - audio::openal_source soundsource; + audio::openal_source newsource; if( false == m_sourcespares.empty() ) { // reuse (a copy of) already allocated source - soundsource.id = m_sourcespares.top(); + newsource.id = m_sourcespares.top(); m_sourcespares.pop(); } - return soundsource; + 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 ) { + // 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::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 > 0 ? + ( source->sound_range * source->sound_range ) / ( glm::length2( source->sound_distance ) + 1 ) : + std::numeric_limits::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(); + leastimportantsource->update( 0 ); // HACK: a roundabout way to notify the controller its emitter has stopped + 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 ); + } + } + + return newsource; } bool diff --git a/audiorenderer.h b/audiorenderer.h index f2196a8e..629ca969 100644 --- a/audiorenderer.h +++ b/audiorenderer.h @@ -29,6 +29,8 @@ namespace audio { // TODO: generic interface base, for implementations other than openAL struct openal_source { + friend class openal_renderer; + // types using buffer_sequence = std::vector; @@ -48,14 +50,14 @@ struct openal_source { bind( sound_source *Controller, Iterator_ First, Iterator_ Last ) { controller = Controller; buffers.insert( std::end( buffers ), First, Last ); - if( id == audio::null_resource ) { - ::alGenSources( 1, &id ); } + is_multipart = ( buffers.size() > 1 ); // look up and queue assigned buffers std::vector bufferids; for( auto const buffer : buffers ) { bufferids.emplace_back( audio::renderer.buffer( buffer ).id ); } - ::alSourceQueueBuffers( id, bufferids.size(), bufferids.data() ); - ::alSourceRewind( id ); + if( id != audio::null_resource ) { + ::alSourceQueueBuffers( id, bufferids.size(), bufferids.data() ); + ::alSourceRewind( id ); } return *this; } // starts playback of queued buffers void @@ -88,6 +90,8 @@ private: double update_deltatime; // 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; // cached distance between sound and the listener + bool is_multipart { false }; // multi-part sounds are kept alive at longer ranges }; diff --git a/simulation.cpp b/simulation.cpp index 0a4a02cd..abef9f31 100644 --- a/simulation.cpp +++ b/simulation.cpp @@ -307,8 +307,8 @@ state_manager::deserialize_node( cParser &Input, scene::scratch_data &Scratchpad } if( ( vehicle->MoverParameters->CategoryFlag == 1 ) // trains only - && ( ( vehicle->LightList( side::front ) & ( light::headlight_left | light::headlight_right | light::headlight_upper ) != 0 ) - || ( vehicle->LightList( side::rear ) & ( light::headlight_left | light::headlight_right | light::headlight_upper ) != 0 ) ) ) { + && ( ( ( vehicle->LightList( side::front ) & ( light::headlight_left | light::headlight_right | light::headlight_upper ) ) != 0 ) + || ( ( vehicle->LightList( side::rear ) & ( light::headlight_left | light::headlight_right | light::headlight_upper ) ) != 0 ) ) ) { simulation::Lights.insert( vehicle ); } } diff --git a/sound.cpp b/sound.cpp index 23f6d187..af001856 100644 --- a/sound.cpp +++ b/sound.cpp @@ -95,7 +95,7 @@ sound_source::play( int const Flags ) { // initialize emitter-specific pitch variation if it wasn't yet set if( m_pitchvariation == 0.f ) { - m_pitchvariation = 0.01f * static_cast( Random( 95, 105 ) ); + m_pitchvariation = 0.01f * static_cast( Random( 97.5, 102.5 ) ); } m_flags = Flags; @@ -130,7 +130,8 @@ sound_source::stop() { 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.buffer != m_soundmain.buffer ) // end == main can happen in malformed legacy cases + && ( m_soundend.playing == 0 ) ) { // spawn potentially defined sound end sample, if the emitter is currently active insert( m_soundend.buffer ); } @@ -142,8 +143,9 @@ sound_source::update( audio::openal_source &Source ) { if( true == Source.is_playing ) { - // kill the sound if requested - if( true == m_stop ) { + if( ( true == m_stop ) + && ( Source.buffers[ Source.buffer_index ] != m_soundend.buffer ) ) { + // kill the sound if stop was requested, unless it's sound bookend sample Source.stop(); update_counter( Source.buffers[ Source.buffer_index ], -1 ); if( false == is_playing() ) { diff --git a/version.h b/version.h index 75135ee3..592d1424 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 17 -#define VERSION_MINOR 1130 +#define VERSION_MINOR 1202 #define VERSION_REVISION 0