diff --git a/src/core/audio/CrossfadeTransport.cpp b/src/core/audio/CrossfadeTransport.cpp index 871eef007..1ee6fed92 100644 --- a/src/core/audio/CrossfadeTransport.cpp +++ b/src/core/audio/CrossfadeTransport.cpp @@ -40,6 +40,7 @@ #include #include +#define CROSS_FADE_DURATION_MS 2000 #define END_OF_TRACK_MIXPOINT 1001 using namespace musik::core::audio; @@ -52,17 +53,15 @@ CrossfadeTransport::CrossfadeTransport() , state(PlaybackStopped) , nextCanStart(false) , muted(false) -, crossfader(*this) { +, crossfader(*this) +, active(crossfader) +, next(crossfader) { } CrossfadeTransport::~CrossfadeTransport() { this->disconnect_all(); - - //LockT lock(this->stateMutex); - - //RESET_NEXT_PLAYER(this); - //RESET_ACTIVE_PLAYER(this); + this->Stop(); } PlaybackState CrossfadeTransport::GetPlaybackState() { @@ -71,67 +70,22 @@ PlaybackState CrossfadeTransport::GetPlaybackState() { } void CrossfadeTransport::PrepareNextTrack(const std::string& trackUrl) { - //bool startNext = false; - //{ - // LockT lock(this->stateMutex); - // RESET_NEXT_PLAYER(this); - // this->nextPlayer = Player::Create(trackUrl, this->output, this); - // startNext = this->nextCanStart; - //} - - //if (startNext) { - // this->StartWithPlayer(this->nextPlayer); - //} + //Lock lock(this->stateMutex); + //this->next.Reset(trackUrl, this); } void CrossfadeTransport::Start(const std::string& url) { - { Lock lock(this->stateMutex); - musik::debug::info(TAG, "we were asked to start the track at " + url); - this->nextPlayer.Reset(this->crossfader, Crossfader::SoftCut); - this->activePlayer.Reset(this->crossfader, Crossfader::SoftCut, url, this); - musik::debug::info(TAG, "Player created successfully"); + musik::debug::info(TAG, "trying to play " + url); - this->activePlayer.Play(this->crossfader); + this->next.Stop(); + this->active.Reset(url, this); + this->active.Play(); } - this->RaiseStreamEvent(StreamScheduled, this->activePlayer.player); -} - -void CrossfadeTransport::Restart() { - //if (newPlayer) { - // bool playingNext = false; - - // { - // LockT lock(this->stateMutex); - - // playingNext = (newPlayer == nextPlayer); - // if (nextPlayer != nullptr && newPlayer != nextPlayer) { - // this->nextPlayer->Destroy(); - // } - - // RESET_ACTIVE_PLAYER(this); - - // this->nextPlayer = nullptr; - // this->activePlayer = newPlayer; - // } - - // /* first argument suppresses the "Stop" event from getting triggered, - // the second param is used for gapless playback -- we won't stop the output - // and will allow pending buffers to finish if we're not automatically - // playing the next track. note we do this outside of critical section so - // outputs *can* stop buffers immediately, and not to worry about causing a - // deadlock. */ - // this->StopInternal(true, !playingNext, newPlayer); - // this->SetNextCanStart(false); - // this->output->Resume(); - // newPlayer->Play(); - // musik::debug::info(TAG, "play()"); - - // this->RaiseStreamEvent(StreamScheduled, newPlayer); - //} + this->RaiseStreamEvent(StreamScheduled, this->active.player); } void CrossfadeTransport::ReloadOutput() { @@ -139,74 +93,42 @@ void CrossfadeTransport::ReloadOutput() { } void CrossfadeTransport::Stop() { - //this->StopInternal(false, true); -} + { + Lock lock(this->stateMutex); + this->crossfader.Stop(); + this->active.Stop(); + this->next.Stop(); + } -void CrossfadeTransport::StopInternal( - bool suppressStopEvent, - bool stopOutput, - Player* exclude) -{ - //musik::debug::info(TAG, "stop"); - - ///* if we stop the output, we kill all of the Players immediately. - //otherwise, we let them finish naturally; RemoveActive() will take - //care of disposing of them */ - //if (stopOutput) { - // { - // LockT lock(this->stateMutex); - - // RESET_NEXT_PLAYER(this); - - // if (this->activePlayer && this->activePlayer != exclude) { - // this->activePlayer->Destroy(); - // this->activePlayer = nullptr; - // } - // } - - // /* stopping the transport will stop any buffers that are currently in - // flight. this makes the sound end immediately. */ - // this->output->Stop(); - //} - - //if (!suppressStopEvent) { - // /* if we know we're starting another track immediately, suppress - // the stop event. this functionality is not available to the public - // interface, it's an internal optimization */ - // this->SetPlaybackState(PlaybackStopped); - //} + this->SetPlaybackState(PlaybackStopped); } bool CrossfadeTransport::Pause() { - //musik::debug::info(TAG, "pause"); + { + Lock lock(this->stateMutex); + this->crossfader.Pause(); + this->active.Pause(); + } - //this->output->Pause(); - - //if (this->activePlayer) { - // this->SetPlaybackState(PlaybackPaused); - // return true; - //} + if (this->active.player) { + this->SetPlaybackState(PlaybackPaused); + return true; + } return false; } bool CrossfadeTransport::Resume() { - //musik::debug::info(TAG, "resume"); + { + Lock lock(this->stateMutex); + this->crossfader.Resume(); + this->active.Resume(); + } - //this->output->Resume(); - - //{ - // LockT lock(this->stateMutex); - - // if (this->activePlayer) { - // this->activePlayer->Play(); - // } - //} - - //if (this->activePlayer) { - // this->SetPlaybackState(PlaybackPlaying); - // return true; - //} + if (this->active.player) { + this->SetPlaybackState(PlaybackPlaying); + return true; + } return false; } @@ -214,30 +136,30 @@ bool CrossfadeTransport::Resume() { double CrossfadeTransport::Position() { Lock lock(this->stateMutex); - if (this->activePlayer.player) { - return this->activePlayer.player->GetPosition(); + if (this->active.player) { + return this->active.player->GetPosition(); } return 0; } void CrossfadeTransport::SetPosition(double seconds) { - //{ - // LockT lock(this->stateMutex); + { + Lock lock(this->stateMutex); - // if (this->activePlayer) { - // this->activePlayer->SetPosition(seconds); - // } - //} + if (this->active.player) { + this->active.player->SetPosition(seconds); + } + } - //if (this->activePlayer) { - // this->TimeChanged(seconds); - //} + if (this->active.player) { + this->TimeChanged(seconds); + } } double CrossfadeTransport::GetDuration() { Lock lock(this->stateMutex); - return this->activePlayer.player ? this->activePlayer.player->GetDuration() : -1.0f; + return this->active.player ? this->active.player->GetDuration() : -1.0f; } bool CrossfadeTransport::IsMuted() { @@ -245,11 +167,17 @@ bool CrossfadeTransport::IsMuted() { } void CrossfadeTransport::SetMuted(bool muted) { - //if (this->muted != muted) { - // this->muted = muted; - // this->output->SetVolume(muted ? 0.0f : this->volume); - // this->VolumeChanged(); - //} + if (this->muted != muted) { + this->muted = muted; + this->active.SetVolume(muted ? 0.0f : this->volume); + this->next.SetVolume(muted ? 0.0f : this->volume); + + if (muted) { + this->crossfader.Reset(); + } + + this->VolumeChanged(); + } } double CrossfadeTransport::Volume() { @@ -257,22 +185,20 @@ double CrossfadeTransport::Volume() { } void CrossfadeTransport::SetVolume(double volume) { - //double oldVolume = this->volume; + double oldVolume = this->volume; - //volume = std::max(0.0, std::min(1.0, volume)); + volume = std::max(0.0, std::min(1.0, volume)); - //this->volume = volume; + this->volume = volume; + if (oldVolume != this->volume) { + this->VolumeChanged(); + } - //if (oldVolume != this->volume) { - // this->VolumeChanged(); - //} - - //std::string output = boost::str( - // boost::format("set volume %d%%") % round(volume * 100)); - - //musik::debug::info(TAG, output); - - //this->output->SetVolume(this->volume); + { + Lock lock(this->stateMutex); + active.SetVolume(volume); + next.SetVolume(volume); + } } void CrossfadeTransport::SetNextCanStart(bool nextCanStart) { @@ -289,9 +215,12 @@ void CrossfadeTransport::OnPlayerStarted(Player* player) { { Lock lock(this->stateMutex); - double offset = this->activePlayer.player->GetDuration() - 2.0f; + double offset = + player->GetDuration() - + ((float) CROSS_FADE_DURATION_MS / 1000.0f); + if (offset > 0.0) { - this->activePlayer.player->AddMixPoint(END_OF_TRACK_MIXPOINT, offset); + player->AddMixPoint(END_OF_TRACK_MIXPOINT, offset); } } } @@ -313,50 +242,41 @@ void CrossfadeTransport::OnPlayerAlmostEnded(Player* player) { } void CrossfadeTransport::OnPlayerFinished(Player* player) { - //this->RaiseStreamEvent(StreamFinished, player); + this->RaiseStreamEvent(StreamFinished, player); - //bool stopped = false; - - //{ - // LockT lock(this->stateMutex); - - // bool startedNext = false; - // bool playerIsActive = (player == this->activePlayer); - - // /* only start the next player if the currently active player is the - // one that just finished. */ - // if (playerIsActive && this->nextPlayer) { - // this->StartWithPlayer(this->nextPlayer); - // startedNext = true; - // } - - // if (!startedNext) { - // stopped = playerIsActive; - // } - //} - - //if (stopped) { - // this->Stop(); - //} + /* the next player will have already started due to + hitting the mix point... TODO: case where we couldn't + do a crossfade because the track is too short. */ + if (active.player != nullptr) { + this->Stop(); + } } void CrossfadeTransport::OnPlayerError(Player* player) { this->RaiseStreamEvent(StreamError, player); - this->SetPlaybackState(PlaybackStopped); + this->Stop(); } void CrossfadeTransport::OnPlayerDestroying(Player *player) { - //LockT lock(this->stateMutex); + Lock lock(this->stateMutex); - //if (player == this->activePlayer) { - // RESET_ACTIVE_PLAYER(this); - // return; - //} + if (active.player == player) { + active.Reset(); + } + + if (next.player == player) { + next.Reset(); + } } void CrossfadeTransport::OnPlayerMixPoint(Player* player, int id, double time) { if (id == END_OF_TRACK_MIXPOINT) { /* fade out */ + if (player == active.player) { + active.Reset(); + next.TransferTo(active); + //active.Play(); /* fade the new one in, if it exists */ + } } } @@ -378,15 +298,19 @@ void CrossfadeTransport::RaiseStreamEvent(int type, Player* player) { this->StreamEvent(type, player->GetUrl()); } -CrossfadeTransport::PlayerContext::PlayerContext() { - this->player = nullptr; +CrossfadeTransport::PlayerContext::PlayerContext(Crossfader& crossfader) +: crossfader(crossfader) +, player(nullptr) { + } -void CrossfadeTransport::PlayerContext::Reset( - Crossfader& crossfader, Crossfader::Cut cut) -{ - //if (this->player) { - // crossfader.Remove(this->player, cut); +void CrossfadeTransport::PlayerContext::Reset() { + //if (this->player && this->output) { + // crossfader.Fade( + // this->player, + // this->output, + // Crossfader::FadeOut, + // CROSS_FADE_DURATION_MS); //} this->player = nullptr; @@ -394,32 +318,66 @@ void CrossfadeTransport::PlayerContext::Reset( } void CrossfadeTransport::PlayerContext::Reset( - Crossfader& crossfader, - Crossfader::Cut cut, const std::string& url, Player::PlayerEventListener* listener) { - //if (this->player) { - // if (this->output) { - // crossfader.Remove(this->player, cut); - // } - //} + if (this->player && this->output) { + crossfader.Fade( + this->player, + this->output, + Crossfader::FadeOut, + CROSS_FADE_DURATION_MS); + } this->output = outputs::SelectedOutput(); this->player = Player::Create(url, this->output, listener); } -void CrossfadeTransport::PlayerContext::Play(Crossfader& crossfader) { +void CrossfadeTransport::PlayerContext::TransferTo(PlayerContext& to) { + to.player = player; + to.output = output; + this->player = player; + this->output.reset(); +} + +void CrossfadeTransport::PlayerContext::Play() { if (this->output && this->player) { this->output->SetVolume(0.0f); this->output->Resume(); this->player->Play(); - //crossfader.Fade( - // this->player, - // this->output, - // Crossfader::FadeIn, - // 1.0f, - // 1000); + crossfader.Fade( + this->player, + this->output, + Crossfader::FadeIn, + CROSS_FADE_DURATION_MS); + } +} + +void CrossfadeTransport::PlayerContext::Stop() { + if (this->output && this->player) { + this->output->Stop(); + this->player->Destroy(); + } + + this->player = nullptr; + this->output.reset(); +} + +void CrossfadeTransport::PlayerContext::Pause() { + if (this->player && this->output) { + this->Pause(); + } +} + +void CrossfadeTransport::PlayerContext::Resume() { + if (this->player && this->output) { + this->Resume(); + } +} + +void CrossfadeTransport::PlayerContext::SetVolume(double volume) { + if (this->output) { + this->output->SetVolume(volume); } } \ No newline at end of file diff --git a/src/core/audio/CrossfadeTransport.h b/src/core/audio/CrossfadeTransport.h index 1897150a2..7cd97fdb5 100644 --- a/src/core/audio/CrossfadeTransport.h +++ b/src/core/audio/CrossfadeTransport.h @@ -89,31 +89,27 @@ namespace musik { namespace core { namespace audio { using MessageQueue = musik::core::runtime::MessageQueue; struct PlayerContext { - PlayerContext(); + PlayerContext(Crossfader& crossfader); + + void Reset(); void Reset( - Crossfader& crossfader, - Crossfader::Cut cut); - - void Reset( - Crossfader& crossfader, - Crossfader::Cut cut, const std::string& url, Player::PlayerEventListener* listener); - void Play(Crossfader& crossfader); + void TransferTo(PlayerContext& context); + + void Stop(); + void Play(); + void Pause(); + void Resume(); + void SetVolume(double volume); Output output; Player *player; + Crossfader& crossfader; }; - void Restart(); - - void StopInternal( - bool suppressStopEvent, - bool stopOutput, - Player* exclude = nullptr); - void SetNextCanStart(bool nextCanStart); void RaiseStreamEvent(int type, Player* player); @@ -128,9 +124,9 @@ namespace musik { namespace core { namespace audio { musik::core::sdk::PlaybackState state; std::recursive_mutex stateMutex; - PlayerContext activePlayer; - PlayerContext nextPlayer; Crossfader crossfader; + PlayerContext active; + PlayerContext next; double volume; bool nextCanStart; bool muted; diff --git a/src/core/audio/Crossfader.cpp b/src/core/audio/Crossfader.cpp index bb474e5ab..0b1669029 100644 --- a/src/core/audio/Crossfader.cpp +++ b/src/core/audio/Crossfader.cpp @@ -42,7 +42,8 @@ using namespace musik::core::audio; using namespace musik::core::sdk; using namespace musik::core::runtime; -#define TICK_TIME_MILLIS 100 +#define TICKS_PER_SECOND 10 +#define TICK_TIME_MILLIS (1000 / TICKS_PER_SECOND) #define ENQUEUE_TICK() \ this->messageQueue.Post(Message::Create( \ @@ -57,6 +58,7 @@ using namespace musik::core::runtime; Crossfader::Crossfader(ITransport& transport) : transport(transport) { this->quit = false; + this->paused = false; this->thread = std::make_unique( std::bind(&Crossfader::ThreadLoop, this)); @@ -72,19 +74,16 @@ void Crossfader::Fade( Player* player, std::shared_ptr output, Direction direction, - float targetPercent, long durationMs) { LOCK(this->contextListLock); std::shared_ptr context = std::make_shared(); - context->targetPercent = targetPercent; context->output = output; context->player = player; - context->durationMs = durationMs; context->direction = direction; context->ticksCounted = 0; - context->ticksTotal = context->durationMs / TICK_TIME_MILLIS; + context->ticksTotal = (durationMs / TICK_TIME_MILLIS); contextList.push_back(context); if (contextList.size() == 1) { @@ -92,50 +91,59 @@ void Crossfader::Fade( } } +void Crossfader::Stop() { + LOCK(this->contextListLock); + + auto it = this->contextList.begin(); + while (it != this->contextList.end()) { + if ((*it)->player) { + (*it)->player->Destroy(); + } + + (*it)->output->Stop(); + ++it; + } + + this->contextList.clear(); +} + +void Crossfader::Pause() { + LOCK(this->contextListLock); + + this->paused = true; + + std::for_each( + this->contextList.begin(), + this->contextList.end(), + [](auto it) { + it->output->Pause(); + }); + + this->messageQueue.Remove(this, MESSAGE_TICK); +} + +void Crossfader::Resume() { + LOCK(this->contextListLock); + + this->paused = false; + + std::for_each( + this->contextList.begin(), + this->contextList.end(), + [](auto it) { + it->output->Resume(); + }); + + this->messageQueue.Post( + Message::Create(this, MESSAGE_TICK, 0, 0), 0); +} + void Crossfader::Reset() { LOCK(this->contextListLock); this->contextList.clear(); } -bool Crossfader::Remove(Player* player, Cut cut) { - LOCK(this->contextListLock); - - bool found = false; - - if (cut == SoftCut) { - std::for_each( - this->contextList.begin(), - this->contextList.end(), - [player, &found](auto item) { - /* don't worry about the player! it'll get cleaned up - automatically. just worry about fading the output's volume - to nothing. */ - if (item->player == player) { - item->direction = FadeOut; - item->player = nullptr; - found = true; - } - }); - } - else { - std::remove_if( - this->contextList.begin(), - this->contextList.end(), - [player, &found](auto item) { - /* hard cut stops the output and destroys the player - immediately! */ - if (item->player == player) { - item->player->Destroy(); - item->output->Stop(); - found = true; - } - - return item->player == player; - }); - } - - return found; -} +#include void Crossfader::ProcessMessage(IMessage &message) { switch (message.Type()) { @@ -147,27 +155,37 @@ void Crossfader::ProcessMessage(IMessage &message) { while (it != this->contextList.end()) { auto fade = *it; + ++fade->ticksCounted; - float percent = + double percent = (float) fade->ticksCounted / (float) fade->ticksTotal; - fade->output->SetVolume(globalVolume * percent); - ++fade->ticksCounted; + if (fade->direction == FadeOut) { + percent = (1.0f - percent); + } + + float outputVolume = globalVolume * percent; + +#if 1 + std::string dir = (fade->direction == FadeIn) ? "in" : "out"; + std::string dbg = boost::str(boost::format("%s %f\n") % dir % outputVolume); + OutputDebugStringA(dbg.c_str()); +#endif + + fade->output->SetVolume(outputVolume); if (fade->ticksCounted >= fade->ticksTotal) { - /* contract: if we're fading the player out, we are responsible for - destroying it. if it gets destroyed before we get here, the reference - here will be null. */ - if (fade->direction == FadeOut && fade->player) { - fade->player->Destroy(); + if (fade->direction == FadeOut) { + (*it)->player->Destroy(); + (*it)->output->Stop(); } it = this->contextList.erase(it); - continue; } - - ++it; + else { + ++it; + } } if (this->contextList.size()) { diff --git a/src/core/audio/Crossfader.h b/src/core/audio/Crossfader.h index cf6d978dc..cbe27d723 100644 --- a/src/core/audio/Crossfader.h +++ b/src/core/audio/Crossfader.h @@ -54,7 +54,6 @@ namespace musik { namespace core { namespace audio { { public: enum Direction { FadeIn, FadeOut }; - enum Cut { HardCut, SoftCut }; Crossfader(ITransport& transport); virtual ~Crossfader(); @@ -66,12 +65,12 @@ namespace musik { namespace core { namespace audio { Player* player, std::shared_ptr output, Direction direction, - float targetPercent, long durationMs); void Reset(); - - bool Remove(Player* player, Cut cut = SoftCut); + void Pause(); + void Resume(); + void Stop(); private: void ThreadLoop(); @@ -80,17 +79,15 @@ namespace musik { namespace core { namespace audio { std::shared_ptr output; Player* player; Direction direction; - long durationMs; long ticksCounted; long ticksTotal; - float targetPercent; }; std::mutex contextListLock; std::unique_ptr thread; musik::core::runtime::MessageQueue messageQueue; std::list> contextList; - std::atomic quit; + std::atomic quit, paused; ITransport& transport; };