diff --git a/src/core/audio/CrossfadeTransport.cpp b/src/core/audio/CrossfadeTransport.cpp index 7902fb154..6fff0ed73 100644 --- a/src/core/audio/CrossfadeTransport.cpp +++ b/src/core/audio/CrossfadeTransport.cpp @@ -362,7 +362,7 @@ void CrossfadeTransport::PlayerContext::Reset( this->canFade = this->started = false; this->output = url.size() ? outputs::SelectedOutput() : nullptr; - this->player = url.size() ? Player::Create(url, this->output, listener) : nullptr; + this->player = url.size() ? Player::Create(url, this->output, Player::Drain, listener) : nullptr; } void CrossfadeTransport::PlayerContext::TransferTo(PlayerContext& to) { diff --git a/src/core/audio/GaplessTransport.cpp b/src/core/audio/GaplessTransport.cpp index b90d7394d..3e1a5f5c0 100644 --- a/src/core/audio/GaplessTransport.cpp +++ b/src/core/audio/GaplessTransport.cpp @@ -86,7 +86,7 @@ void GaplessTransport::PrepareNextTrack(const std::string& trackUrl) { { LockT lock(this->stateMutex); RESET_NEXT_PLAYER(this); - this->nextPlayer = Player::Create(trackUrl, this->output, this); + this->nextPlayer = Player::Create(trackUrl, this->output, Player::NoDrain, this); startNext = this->nextCanStart; } @@ -98,7 +98,7 @@ void GaplessTransport::PrepareNextTrack(const std::string& trackUrl) { void GaplessTransport::Start(const std::string& url) { musik::debug::info(TAG, "we were asked to start the track at " + url); - Player* newPlayer = Player::Create(url, this->output, this); + Player* newPlayer = Player::Create(url, this->output, Player::NoDrain, this); musik::debug::info(TAG, "Player created successfully"); this->StartWithPlayer(newPlayer); diff --git a/src/core/audio/Player.cpp b/src/core/audio/Player.cpp index c46304835..2c7a61516 100644 --- a/src/core/audio/Player.cpp +++ b/src/core/audio/Player.cpp @@ -109,14 +109,16 @@ namespace musik { Player* Player::Create( const std::string &url, std::shared_ptr output, + FinishMode finishMode, EventListener *listener) { - return new Player(url, output, listener); + return new Player(url, output, finishMode, listener); } Player::Player( const std::string &url, std::shared_ptr output, + FinishMode finishMode, EventListener *listener) : state(Player::Precache) , url(url) @@ -124,6 +126,7 @@ Player::Player( , output(output) , notifiedStarted(false) , setPosition(-1) +, finishMode(finishMode) , fftContext(nullptr) { musik::debug::info(TAG, "new instance created"); @@ -370,7 +373,9 @@ void musik::core::audio::playerThreadLoop(Player* player) { } /* buffers have been written, wait for the output to play them all */ - player->output->Drain(); + if (player->finishMode == Player::Drain) { + player->output->Drain(); + } if (!player->Exited()) { for (Listener* l : player->Listeners()) { diff --git a/src/core/audio/Player.h b/src/core/audio/Player.h index 1cce98db0..de52ec973 100644 --- a/src/core/audio/Player.h +++ b/src/core/audio/Player.h @@ -52,6 +52,8 @@ namespace musik { namespace core { namespace audio { class Player : public musik::core::sdk::IBufferProvider { public: + enum FinishMode { Drain, NoDrain }; + struct EventListener { virtual void OnPlayerPrepared(Player *player) { } virtual void OnPlayerStarted(Player *player) { } @@ -65,6 +67,7 @@ namespace musik { namespace core { namespace audio { static Player* Create( const std::string &url, std::shared_ptr output, + FinishMode finishMode, EventListener *listener); virtual void OnBufferProcessed(musik::core::sdk::IBuffer *buffer); @@ -89,6 +92,7 @@ namespace musik { namespace core { namespace audio { Player( const std::string &url, std::shared_ptr output, + FinishMode finishMode, EventListener *listener); virtual ~Player(); @@ -144,6 +148,7 @@ namespace musik { namespace core { namespace audio { bool notifiedStarted; float* spectrum; uint64 samplesWritten; + FinishMode finishMode; FftContext* fftContext; }; diff --git a/src/core/support/PreferenceKeys.cpp b/src/core/support/PreferenceKeys.cpp index 55e42268b..074d05190 100644 --- a/src/core/support/PreferenceKeys.cpp +++ b/src/core/support/PreferenceKeys.cpp @@ -48,6 +48,7 @@ namespace musik { namespace core { namespace prefs { const std::string keys::Volume = "Volume"; const std::string keys::RepeatMode = "RepeatMode"; const std::string keys::OutputPlugin = "OutputPlugin"; + const std::string keys::Transport = "Transport"; } } } diff --git a/src/core/support/PreferenceKeys.h b/src/core/support/PreferenceKeys.h index 235608c54..0bd46c1e5 100644 --- a/src/core/support/PreferenceKeys.h +++ b/src/core/support/PreferenceKeys.h @@ -52,6 +52,7 @@ namespace musik { namespace core { namespace prefs { extern const std::string Volume; extern const std::string RepeatMode; extern const std::string OutputPlugin; + extern const std::string Transport; } } } } diff --git a/src/musikbox/Main.cpp b/src/musikbox/Main.cpp index deef7e948..3f056b6e0 100644 --- a/src/musikbox/Main.cpp +++ b/src/musikbox/Main.cpp @@ -47,10 +47,9 @@ #include #include #include +#include #include -#include -#include #include #include @@ -65,6 +64,7 @@ #undef MOUSE_MOVED #endif +using namespace musik::box::audio; using namespace musik::core; using namespace musik::core::audio; using namespace musik::box; @@ -110,8 +110,7 @@ int main(int argc, char* argv[]) LibraryPtr library = LibraryFactory::Libraries().at(0); - //GaplessTransport transport; - CrossfadeTransport transport; + MasterTransport transport; PlaybackService playback(library, transport); GlobalHotkeys globalHotkeys(playback, library); @@ -129,7 +128,7 @@ int main(int argc, char* argv[]) Layout libraryLayout(new LibraryLayout(playback, library)); Layout consoleLayout(new ConsoleLayout(transport, library)); - Layout settingsLayout(new SettingsLayout(library, playback.GetTransport())); + Layout settingsLayout(new SettingsLayout(library, transport)); Main mainLayout(new MainLayout()); diff --git a/src/musikbox/app/audio/MasterTransport.cpp b/src/musikbox/app/audio/MasterTransport.cpp new file mode 100644 index 000000000..028c76895 --- /dev/null +++ b/src/musikbox/app/audio/MasterTransport.cpp @@ -0,0 +1,163 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2007-2016 musikcube team +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the author nor the names of other contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////////// + +#include + +#include "MasterTransport.h" + +#include +#include +#include +#include + +using namespace musik::box::audio; +using namespace musik::core::audio; +using namespace musik::core; +using namespace musik::core::prefs; +using namespace musik::core::sdk; + +MasterTransport::MasterTransport() +: prefs(Preferences::ForComponent(components::Playback)) { + this->type = (Type) this->prefs->GetInt(keys::Transport, Gapless); + this->SwitchTo(this->type); +} + +MasterTransport::~MasterTransport() { + +} + +void MasterTransport::SwitchTo(Type type) { + if (!this->transport || this->type != type) { + this->type = type; + this->prefs->SetInt(keys::Transport, (int) this->type); + + switch (this->type) { + case Gapless: + this->transport.reset(new GaplessTransport()); + break; + + case Crossfade: + this->transport.reset(new CrossfadeTransport()); + break; + } + + this->transport->PlaybackEvent.connect( + this, &MasterTransport::OnPlaybackEvent); + + this->transport->StreamEvent.connect( + this, &MasterTransport::OnStreamEvent); + + this->transport->TimeChanged.connect( + this, &MasterTransport::OnTimeChanged); + + this->transport->VolumeChanged.connect( + this, &MasterTransport::OnVolumeChanged); + } +} + +MasterTransport::Type MasterTransport::GetType() { + return this->type; +} + +void MasterTransport::PrepareNextTrack(const std::string& trackUrl) { + this->transport->PrepareNextTrack(trackUrl); +} + +void MasterTransport::Start(const std::string& trackUrl) { + this->transport->Start(trackUrl); +} + +void MasterTransport::Stop() { + this->transport->Stop(); +} + +bool MasterTransport::Pause() { + return this->transport->Pause(); +} + +bool MasterTransport::Resume() { + return this->transport->Resume(); +} + +double MasterTransport::Position() { + return this->transport->Position(); +} + +void MasterTransport::SetPosition(double seconds) { + this->transport->SetPosition(seconds); +} + +double MasterTransport::Volume() { + return this->transport->Volume(); +} + +void MasterTransport::SetVolume(double volume) { + this->transport->SetVolume(volume); +} + +double MasterTransport::GetDuration() { + return this->transport->GetDuration(); +} + +bool MasterTransport::IsMuted() { + return this->transport->IsMuted(); +} + +void MasterTransport::SetMuted(bool muted) { + this->transport->SetMuted(muted); +} + +void MasterTransport::ReloadOutput() { + this->transport->ReloadOutput(); +} + +PlaybackState MasterTransport::GetPlaybackState() { + return this->transport->GetPlaybackState(); +} + +void MasterTransport::OnStreamEvent(int type, std::string url) { + this->StreamEvent(type, url); +} + +void MasterTransport::OnPlaybackEvent(int type) { + this->PlaybackEvent(type); +} + +void MasterTransport::OnVolumeChanged() { + this->VolumeChanged(); +} + +void MasterTransport::OnTimeChanged(double time) { + this->TimeChanged(time); +} \ No newline at end of file diff --git a/src/musikbox/app/audio/MasterTransport.h b/src/musikbox/app/audio/MasterTransport.h new file mode 100644 index 000000000..55a773f76 --- /dev/null +++ b/src/musikbox/app/audio/MasterTransport.h @@ -0,0 +1,86 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2007-2016 musikcube team +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the author nor the names of other contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include + +namespace musik { namespace box { namespace audio { + class MasterTransport : + public sigslot::has_slots<>, + public musik::core::audio::ITransport + { + public: + enum Type { Gapless, Crossfade }; + + MasterTransport(); + virtual ~MasterTransport(); + + virtual void PrepareNextTrack(const std::string& trackUrl); + + virtual void Start(const std::string& trackUrl); + virtual void Stop(); + virtual bool Pause(); + virtual bool Resume(); + + virtual double Position(); + virtual void SetPosition(double seconds); + + virtual double Volume(); + virtual void SetVolume(double volume); + + virtual double GetDuration(); + + virtual bool IsMuted(); + virtual void SetMuted(bool muted); + + virtual void ReloadOutput(); + + virtual musik::core::sdk::PlaybackState GetPlaybackState(); + + void SwitchTo(Type type); + Type GetType(); + + private: + void OnStreamEvent(int type, std::string url); + void OnPlaybackEvent(int type); + void OnVolumeChanged(); + void OnTimeChanged(double time); + + std::shared_ptr transport; + std::shared_ptr prefs; + Type type; + }; +} } } diff --git a/src/musikbox/app/layout/SettingsLayout.cpp b/src/musikbox/app/layout/SettingsLayout.cpp index 6ec93fd38..adfdf0161 100755 --- a/src/musikbox/app/layout/SettingsLayout.cpp +++ b/src/musikbox/app/layout/SettingsLayout.cpp @@ -60,6 +60,7 @@ using namespace musik::core::audio; using namespace musik::core::library::constants; using namespace musik::core::sdk; using namespace musik::box; +using namespace musik::box::audio; using namespace cursespp; using namespace std::placeholders; @@ -84,7 +85,7 @@ static bool showDotfiles = false; SettingsLayout::SettingsLayout( musik::core::LibraryPtr library, - musik::core::audio::ITransport& transport) + musik::box::audio::MasterTransport& transport) : LayoutBase() , library(library) , indexer(library->Indexer()) @@ -134,6 +135,17 @@ void SettingsLayout::OnOutputDropdownActivated(cursespp::TextLabel* label) { }); } +void SettingsLayout::OnTransportDropdownActivate(cursespp::TextLabel* label) { + if (this->transport.GetType() == MasterTransport::Crossfade) { + this->transport.SwitchTo(MasterTransport::Gapless); + } + else { + this->transport.SwitchTo(MasterTransport::Crossfade); + } + + this->LoadPreferences(); +} + void SettingsLayout::OnLayout() { int x = this->GetX(), y = this->GetY(); int cx = this->GetWidth(), cy = this->GetHeight(); @@ -154,7 +166,8 @@ void SettingsLayout::OnLayout() { this->addedPathsList->MoveAndResize(rightX, pathListsY, rightWidth, pathsHeight); this->outputDropdown->MoveAndResize(1, BOTTOM(this->browseList), cx - 1, LABEL_HEIGHT); - this->dotfileCheckbox->MoveAndResize(1, BOTTOM(this->outputDropdown), cx - 1, LABEL_HEIGHT); + this->transportDropdown->MoveAndResize(1, BOTTOM(this->outputDropdown), cx - 1, LABEL_HEIGHT); + this->dotfileCheckbox->MoveAndResize(1, BOTTOM(this->transportDropdown), cx - 1, LABEL_HEIGHT); this->removeCheckbox->MoveAndResize(1, BOTTOM(this->dotfileCheckbox), cx - 1, LABEL_HEIGHT); this->focusShortcutsCheckbox->MoveAndResize(1, BOTTOM(this->removeCheckbox), cx - 1, LABEL_HEIGHT); this->customColorsCheckbox->MoveAndResize(1, BOTTOM(this->focusShortcutsCheckbox), cx - 1, LABEL_HEIGHT); @@ -231,6 +244,9 @@ void SettingsLayout::InitializeWindows() { this->outputDropdown.reset(new TextLabel()); this->outputDropdown->Activated.connect(this, &SettingsLayout::OnOutputDropdownActivated); + this->transportDropdown.reset(new TextLabel()); + this->transportDropdown->Activated.connect(this, &SettingsLayout::OnTransportDropdownActivate); + CREATE_CHECKBOX(this->dotfileCheckbox, "show dotfiles in directory browser"); CREATE_CHECKBOX(this->removeCheckbox, "remove missing files from library"); CREATE_CHECKBOX(this->focusShortcutsCheckbox, "esc key focuses shortcuts bar"); @@ -243,17 +259,19 @@ void SettingsLayout::InitializeWindows() { this->browseList->SetFocusOrder(0); this->addedPathsList->SetFocusOrder(1); this->outputDropdown->SetFocusOrder(2); - this->dotfileCheckbox->SetFocusOrder(3); - this->removeCheckbox->SetFocusOrder(4); - this->focusShortcutsCheckbox->SetFocusOrder(5); - this->customColorsCheckbox->SetFocusOrder(6); - this->hotkeyInput->SetFocusOrder(7); + this->transportDropdown->SetFocusOrder(4); + this->dotfileCheckbox->SetFocusOrder(5); + this->removeCheckbox->SetFocusOrder(6); + this->focusShortcutsCheckbox->SetFocusOrder(7); + this->customColorsCheckbox->SetFocusOrder(8); + this->hotkeyInput->SetFocusOrder(9); this->AddWindow(this->browseLabel); this->AddWindow(this->addedPathsLabel); this->AddWindow(this->browseList); this->AddWindow(this->addedPathsList); this->AddWindow(this->outputDropdown); + this->AddWindow(this->transportDropdown); this->AddWindow(this->dotfileCheckbox); this->AddWindow(this->removeCheckbox); this->AddWindow(this->focusShortcutsCheckbox); @@ -319,6 +337,13 @@ void SettingsLayout::LoadPreferences() { if (output) { this->outputDropdown->SetText(arrow + " output device: " + output->Name()); } + + std::string transportName = + this->transport.GetType() == MasterTransport::Gapless + ? "gapless" + : "crossfade"; + + this->transportDropdown->SetText(arrow + " playback transport: " + transportName); } void SettingsLayout::AddSelectedDirectory() { diff --git a/src/musikbox/app/layout/SettingsLayout.h b/src/musikbox/app/layout/SettingsLayout.h index 202f6e3fb..1b71b4e74 100755 --- a/src/musikbox/app/layout/SettingsLayout.h +++ b/src/musikbox/app/layout/SettingsLayout.h @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -66,7 +67,7 @@ namespace musik { public: SettingsLayout( musik::core::LibraryPtr library, - musik::core::audio::ITransport& transport); + musik::box::audio::MasterTransport& transport); virtual ~SettingsLayout(); @@ -92,6 +93,7 @@ namespace musik { cursespp::Checkbox* checkbox, bool checked); void OnOutputDropdownActivated(cursespp::TextLabel* label); + void OnTransportDropdownActivate(cursespp::TextLabel* label); int64 ListItemDecorator( cursespp::ScrollableWindow* w, @@ -101,12 +103,13 @@ namespace musik { musik::core::LibraryPtr library; musik::core::IIndexer* indexer; - musik::core::audio::ITransport& transport; + musik::box::audio::MasterTransport& transport; std::shared_ptr libraryPrefs; std::shared_ptr playbackPrefs; std::shared_ptr outputDropdown; + std::shared_ptr transportDropdown; std::shared_ptr removeCheckbox; std::shared_ptr dotfileCheckbox; diff --git a/src/musikbox/musikbox.vcxproj b/src/musikbox/musikbox.vcxproj index 08682f30c..9bb059813 100755 --- a/src/musikbox/musikbox.vcxproj +++ b/src/musikbox/musikbox.vcxproj @@ -119,6 +119,7 @@ + @@ -174,6 +175,7 @@ + diff --git a/src/musikbox/musikbox.vcxproj.filters b/src/musikbox/musikbox.vcxproj.filters index 0abdeac88..ab584337b 100755 --- a/src/musikbox/musikbox.vcxproj.filters +++ b/src/musikbox/musikbox.vcxproj.filters @@ -147,6 +147,9 @@ app\util + + app\audio + @@ -342,6 +345,9 @@ app\util + + app\audio + @@ -371,5 +377,8 @@ {a84f242d-d70b-49e9-975e-63fc73954a2b} + + {b1b3ddc5-b30f-4668-8267-c32cdf3e4da0} + \ No newline at end of file