1 //////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2004-2021 musikcube team
4 //
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions are met:
9 //
10 //    * Redistributions of source code must retain the above copyright notice,
11 //      this list of conditions and the following disclaimer.
12 //
13 //    * Redistributions in binary form must reproduce the above copyright
14 //      notice, this list of conditions and the following disclaimer in the
15 //      documentation and/or other materials provided with the distribution.
16 //
17 //    * Neither the name of the author nor the names of other contributors may
18 //      be used to endorse or promote products derived from this software
19 //      without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 //
33 //////////////////////////////////////////////////////////////////////////////
34 
35 #include <stdafx.h>
36 
37 #include "PlaybackOverlays.h"
38 
39 #include <musikcore/audio/Outputs.h>
40 #include <musikcore/support/Preferences.h>
41 #include <musikcore/support/PreferenceKeys.h>
42 
43 #include <cursespp/App.h>
44 #include <cursespp/SimpleScrollAdapter.h>
45 #include <cursespp/ListOverlay.h>
46 #include <cursespp/DialogOverlay.h>
47 
48 #include <vector>
49 
50 using namespace musik::cube;
51 using namespace musik::core;
52 using namespace musik::core::audio;
53 using namespace musik::core::sdk;
54 using namespace cursespp;
55 
56 static std::vector<std::shared_ptr<IOutput> > plugins;
57 static std::set<std::string> invalidCrossfadeOutputs = { "WaveOut" };
58 
59 template <typename T>
60 struct ReleaseDeleter {
operator ()ReleaseDeleter61     void operator()(T* t) {
62         if (t) t->Release();
63     }
64 };
65 
showNoOutputPluginsMessage()66 static void showNoOutputPluginsMessage() {
67     std::shared_ptr<DialogOverlay> dialog(new DialogOverlay());
68 
69     (*dialog)
70         .SetTitle(_TSTR("default_overlay_title"))
71         .SetMessage(_TSTR("playback_overlay_no_output_plugins_mesage"))
72         .AddButton(
73             "KEY_ENTER",
74             "ENTER",
75             _TSTR("button_ok"));
76 
77     App::Overlays().Push(dialog);
78 }
79 
showOutputCannotCrossfadeMessage(const std::string & outputName)80 static void showOutputCannotCrossfadeMessage(const std::string& outputName) {
81     std::shared_ptr<DialogOverlay> dialog(new DialogOverlay());
82 
83     std::string message = u8fmt(
84         _TSTR("playback_overlay_invalid_transport"),
85         outputName.c_str());
86 
87     (*dialog)
88         .SetTitle(_TSTR("default_overlay_title"))
89         .SetMessage(message)
90         .AddButton(
91             "KEY_ENTER",
92             "ENTER",
93             _TSTR("button_ok"));
94 
95     App::Overlays().Push(dialog);
96 }
97 
PlaybackOverlays()98 PlaybackOverlays::PlaybackOverlays() {
99 }
100 
ShowOutputDriverOverlay(MasterTransport::Type transportType,std::function<void ()> callback)101 void PlaybackOverlays::ShowOutputDriverOverlay(
102     MasterTransport::Type transportType,
103     std::function<void()> callback)
104 {
105     plugins = outputs::GetAllOutputs();
106 
107     if (!plugins.size()) {
108         showNoOutputPluginsMessage();
109         return;
110     }
111 
112     using Adapter = cursespp::SimpleScrollAdapter;
113     using ListOverlay = cursespp::ListOverlay;
114 
115     std::string currentOutput = outputs::SelectedOutput()->Name();
116     size_t selectedIndex = 0;
117 
118     std::shared_ptr<Adapter> adapter(new Adapter());
119     for (size_t i = 0; i < plugins.size(); i++) {
120         const std::string name = plugins[i]->Name();
121         adapter->AddEntry(name);
122 
123         if (name == currentOutput) {
124             selectedIndex = i;
125         }
126     }
127 
128     adapter->SetSelectable(true);
129 
130     std::shared_ptr<ListOverlay> dialog(new ListOverlay());
131 
132     dialog->SetAdapter(adapter)
133         .SetTitle(_TSTR("playback_overlay_output_plugins_title"))
134         .SetSelectedIndex(selectedIndex)
135         .SetItemSelectedCallback(
136             [callback, transportType](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
137 
138                 if (transportType == MasterTransport::Type::Crossfade) {
139                     std::string output = outputs::GetAllOutputs().at(index)->Name();
140                     if (invalidCrossfadeOutputs.find(output) != invalidCrossfadeOutputs.end()) {
141                         showOutputCannotCrossfadeMessage(output);
142                         return;
143                     }
144                 }
145 
146                 outputs::SelectOutput(plugins[index]);
147 
148                 if (callback) {
149                     callback();
150                 }
151             });
152 
153     cursespp::App::Overlays().Push(dialog);
154 }
155 
ShowOutputDeviceOverlay(std::function<void ()> callback)156 void PlaybackOverlays::ShowOutputDeviceOverlay(std::function<void()> callback) {
157     auto output = outputs::SelectedOutput();
158     if (!output) {
159         showNoOutputPluginsMessage();
160         return;
161     }
162 
163     std::string currentDeviceName = _TSTR("settings_output_device_default");
164 
165     std::shared_ptr<IDeviceList> deviceList = std::shared_ptr<IDeviceList>(
166         output->GetDeviceList(), ReleaseDeleter<IDeviceList>());
167 
168     std::shared_ptr<IDevice> device = std::shared_ptr<IDevice>(
169         output->GetDefaultDevice(), ReleaseDeleter<IDevice>());
170 
171     if (device) {
172         currentDeviceName = device->Name();
173     }
174 
175     using Adapter = cursespp::SimpleScrollAdapter;
176     using ListOverlay = cursespp::ListOverlay;
177 
178     size_t width = _DIMEN("output_device_overlay_width", 35);
179     size_t selectedIndex = 0;
180 
181     std::shared_ptr<Adapter> adapter(new Adapter());
182     adapter->AddEntry(_TSTR("settings_output_device_default"));
183 
184     if (deviceList) {
185         for (size_t i = 0; i < deviceList->Count(); i++) {
186             const std::string name = deviceList->At(i)->Name();
187             adapter->AddEntry(name);
188 
189             width = std::max(width, u8cols(name));
190 
191             if (name == currentDeviceName) {
192                 selectedIndex = i + 1;
193             }
194         }
195     }
196 
197     adapter->SetSelectable(true);
198 
199     std::shared_ptr<ListOverlay> dialog(new ListOverlay());
200 
201     dialog->SetAdapter(adapter)
202         .SetTitle(_TSTR("playback_overlay_output_device_title"))
203         .SetSelectedIndex(selectedIndex)
204         .SetWidth(narrow_cast<int>(width))
205         .SetItemSelectedCallback(
206             [output, deviceList, callback](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
207                 if (index == 0) {
208                     output->SetDefaultDevice("");
209                 }
210                 else {
211                     output->SetDefaultDevice(deviceList->At(index - 1)->Id());
212                 }
213 
214                 if (callback) {
215                     callback();
216                 }
217             });
218 
219     cursespp::App::Overlays().Push(dialog);
220 }
221 
ShowTransportOverlay(MasterTransport::Type transportType,std::function<void (MasterTransport::Type)> callback)222 void PlaybackOverlays::ShowTransportOverlay(
223     MasterTransport::Type transportType,
224     std::function<void(MasterTransport::Type)> callback)
225 {
226     using Adapter = cursespp::SimpleScrollAdapter;
227     using ListOverlay = cursespp::ListOverlay;
228 
229     std::shared_ptr<Adapter> adapter(new Adapter());
230     adapter->AddEntry(_TSTR("settings_transport_type_gapless"));
231     adapter->AddEntry(_TSTR("settings_transport_type_crossfade"));
232     adapter->SetSelectable(true);
233 
234     size_t selectedIndex = (transportType == MasterTransport::Type::Gapless) ? 0 : 1;
235 
236     std::shared_ptr<ListOverlay> dialog(new ListOverlay());
237 
238     dialog->SetAdapter(adapter)
239         .SetTitle(_TSTR("playback_overlay_transport_title"))
240         .SetSelectedIndex(selectedIndex)
241         .SetItemSelectedCallback(
242             [callback](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
243                 auto result = (index == 0)
244                     ? MasterTransport::Type::Gapless
245                     : MasterTransport::Type::Crossfade;
246 
247                 std::string output = outputs::SelectedOutput()->Name();
248 
249                 if (result == MasterTransport::Type::Crossfade &&
250                     invalidCrossfadeOutputs.find(output) != invalidCrossfadeOutputs.end())
251                 {
252                     showOutputCannotCrossfadeMessage(output);
253                 }
254                 else if (callback) {
255                     callback(result);
256                 }
257             });
258 
259     cursespp::App::Overlays().Push(dialog);
260 }
261 
ShowReplayGainOverlay(std::function<void ()> callback)262 void PlaybackOverlays::ShowReplayGainOverlay(std::function<void()> callback) {
263     using Adapter = cursespp::SimpleScrollAdapter;
264     using ListOverlay = cursespp::ListOverlay;
265 
266     std::shared_ptr<Adapter> adapter(new Adapter());
267     adapter->AddEntry(_TSTR("settings_replay_gain_mode_disabled"));
268     adapter->AddEntry(_TSTR("settings_replay_gain_mode_track"));
269     adapter->AddEntry(_TSTR("settings_replay_gain_mode_album"));
270     adapter->SetSelectable(true);
271 
272     auto prefs = Preferences::ForComponent(core::prefs::components::Playback);
273 
274     auto selectedIndex = prefs->GetInt(
275         core::prefs::keys::ReplayGainMode.c_str(),
276         (int) ReplayGainMode::Disabled);
277 
278     std::shared_ptr<ListOverlay> dialog(new ListOverlay());
279 
280     dialog->SetAdapter(adapter)
281         .SetTitle(_TSTR("settings_replay_gain_title"))
282         .SetSelectedIndex((size_t) selectedIndex)
283         .SetItemSelectedCallback(
284             [callback, selectedIndex, prefs](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
285                 prefs->SetInt(core::prefs::keys::ReplayGainMode.c_str(), (int) index);
286                 prefs->Save();
287                 if (selectedIndex != index && callback) {
288                     callback();
289                 }
290             });
291 
292     cursespp::App::Overlays().Push(dialog);
293 }
294