Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include "playbacktoolbarmodel.h"
23 :
24 : #include <vector>
25 :
26 : #include "global/containers.h"
27 : #include "types/translatablestring.h"
28 :
29 : #include "ui/view/musicalsymbolcodes.h"
30 : #include "playback/playbacktypes.h"
31 : #include "playback/playbackcommands.h"
32 :
33 : #include "ui/toolconfig.h"
34 :
35 : using namespace mu::playback;
36 : using namespace mu::engraving;
37 : using namespace muse;
38 : using namespace muse::ui;
39 : using namespace muse::uicomponents;
40 : using namespace muse::audio;
41 :
42 : static const QString PLAY_TOGGLE_ID("play-toggle");
43 :
44 0 : static const ToolConfig& defaultPlaybackToolConfig()
45 : {
46 0 : static ToolConfig config;
47 0 : if (!config.isValid()) {
48 0 : config.items = {
49 : { REWIND_COMMAND, true },
50 : { "play-toggle", true }, // virtual code
51 : { LOOP_TOGGLE_COMMAND, true },
52 : { LOOP_IN_COMMAND, true },
53 : { LOOP_OUT_COMMAND, true },
54 : { METRONOME_TOGGLE_COMMAND, true },
55 0 : };
56 : }
57 0 : return config;
58 0 : }
59 :
60 0 : PlaybackToolBarModel::PlaybackToolBarModel(QObject* parent)
61 0 : : AbstractMenuModel(parent)
62 : {
63 0 : }
64 :
65 0 : void PlaybackToolBarModel::load()
66 : {
67 0 : AbstractMenuModel::load();
68 0 : updateActions();
69 :
70 0 : connect(this, &PlaybackToolBarModel::isToolbarFloatingChanged, this, &PlaybackToolBarModel::updateActions);
71 :
72 0 : setupConnections();
73 0 : }
74 :
75 0 : void PlaybackToolBarModel::setupConnections()
76 : {
77 0 : playbackController()->isPlayAllowedChanged().onReceive(this, [this](bool) {
78 0 : emit isPlayAllowedChanged();
79 0 : });
80 :
81 0 : playbackController()->isPlayingChanged().onReceive(this, [this](bool) {
82 0 : updatePlayItem();
83 0 : });
84 :
85 0 : playbackController()->playbackPositionChanged().onReceive(this, [this](secs_t secs) {
86 0 : updatePlayPosition(secs);
87 0 : });
88 :
89 0 : playbackController()->totalPlayTimeChanged().onNotify(this, [this]() {
90 0 : emit maxPlayTimeChanged();
91 0 : secs_t pos = playbackController()->playbackPosition();
92 0 : updatePlayPosition(pos);
93 0 : });
94 :
95 0 : playbackController()->currentTempoChanged().onNotify(this, [this]() {
96 0 : emit tempoChanged();
97 0 : });
98 0 : }
99 :
100 0 : void PlaybackToolBarModel::updateActions()
101 : {
102 0 : MenuItemList result;
103 : MenuItemList settingsItems = {
104 0 : makeMenuItem(MIDI_TOGGLE_COMMAND),
105 0 : makeInputPitchMenu(),
106 0 : makeSeparator(),
107 0 : makeMenuItem(REPEATS_TOGGLE_COMMAND),
108 0 : makeMenuItem(CHORDSYMBOLS_TOGGLE_COMMAND),
109 0 : makeMenuItem(HEAR_PLAYBACK_WHEN_EDITING_TOGGLE_COMMAND),
110 0 : makeMenuItem(PAN_TOGGLE_COMMAND),
111 0 : makeMenuItem(COUNTIN_TOGGLE_COMMAND),
112 0 : };
113 :
114 0 : if (!m_isToolbarFloating) {
115 0 : settingsItems << makeSeparator();
116 : }
117 :
118 : //! NOTE At the moment no customization ability
119 0 : ToolConfig config = defaultPlaybackToolConfig();
120 0 : for (const ToolConfig::Item& item : config.items) {
121 0 : if (item.intent == PLAY_TOGGLE_ID) {
122 0 : result << makePlayItem();
123 : } else {
124 0 : const rcommand::Command command(item.intent);
125 0 : if (isAdditionalCommand(command) && !m_isToolbarFloating) {
126 : //! NOTE In this case, we want to see the actions' description instead of the title
127 0 : settingsItems << makeMenuItem(command);
128 : } else {
129 0 : result << makeMenuItem(command);
130 : }
131 0 : }
132 : }
133 :
134 0 : MenuItem* settingsItem = makeMenu(TranslatableString("action", "Playback settings"), settingsItems);
135 0 : settingsItem->setIcon(ui::IconCode::Code::SETTINGS_COG);
136 0 : result << settingsItem;
137 :
138 0 : setItems(result);
139 0 : }
140 :
141 0 : MenuItem* PlaybackToolBarModel::makeInputPitchMenu()
142 : {
143 : MenuItemList items = {
144 0 : makeMenuItem(MIDI_INPUT_WRITTEN_PITCH_COMMAND),
145 0 : makeMenuItem(MIDI_INPUT_SOUNDING_PITCH_COMMAND),
146 0 : };
147 :
148 0 : MenuItem* menu = makeMenu(muse::TranslatableString("playback", "MIDI input pitch"), items);
149 0 : menu->setIcon(ui::IconCode::Code::MUSIC_NOTES);
150 0 : return menu;
151 0 : }
152 :
153 0 : bool PlaybackToolBarModel::isAdditionalCommand(const muse::rcommand::Command& command) const
154 : {
155 : static const std::vector<muse::rcommand::Command> additionalCommands = {
156 : LOOP_IN_COMMAND,
157 : LOOP_OUT_COMMAND,
158 0 : };
159 :
160 0 : return muse::contains(additionalCommands, command);
161 0 : }
162 :
163 0 : bool PlaybackToolBarModel::isPlayAllowed() const
164 : {
165 0 : return playbackController()->isPlayAllowed();
166 : }
167 :
168 0 : bool PlaybackToolBarModel::isToolbarFloating() const
169 : {
170 0 : return m_isToolbarFloating;
171 : }
172 :
173 0 : void PlaybackToolBarModel::setIsToolbarFloating(bool floating)
174 : {
175 0 : if (floating == m_isToolbarFloating) {
176 0 : return;
177 : }
178 :
179 0 : m_isToolbarFloating = floating;
180 0 : emit isToolbarFloatingChanged(floating);
181 : }
182 :
183 0 : QTime PlaybackToolBarModel::maxPlayTime() const
184 : {
185 0 : return timeFromSeconds(totalPlayTime());
186 : }
187 :
188 0 : QTime PlaybackToolBarModel::playTime() const
189 : {
190 0 : return timeFromSeconds(m_playbackPositionSecs);
191 : }
192 :
193 0 : void PlaybackToolBarModel::setPlayTime(const QTime& time)
194 : {
195 0 : rewind(timeToSeconds(time));
196 0 : }
197 :
198 0 : qreal PlaybackToolBarModel::playPosition() const
199 : {
200 0 : const secs_t totalSecs = totalPlayTime();
201 0 : if (totalSecs.is_zero()) {
202 0 : return 0.0;
203 : }
204 :
205 0 : const qreal position = static_cast<qreal>(m_playbackPositionSecs) / static_cast<qreal>(totalSecs);
206 0 : return position;
207 : }
208 :
209 0 : void PlaybackToolBarModel::setPlayPosition(qreal position)
210 : {
211 0 : rewind(totalPlayTime() * position);
212 0 : }
213 :
214 0 : secs_t PlaybackToolBarModel::totalPlayTime() const
215 : {
216 0 : return playbackController()->totalPlayTime();
217 : }
218 :
219 0 : MeasureBeat PlaybackToolBarModel::measureBeat() const
220 : {
221 0 : return playbackController()->currentBeat();
222 : }
223 :
224 0 : muse::uicomponents::MenuItem* PlaybackToolBarModel::makePlayItem()
225 : {
226 0 : rcommand::Command command = playbackController()->isPlaying() ? PAUSE_COMMAND : PLAY_COMMAND;
227 0 : MenuItem* item = makeMenuItem(command);
228 0 : item->setId(PLAY_TOGGLE_ID);
229 0 : return item;
230 0 : }
231 :
232 0 : void PlaybackToolBarModel::updatePlayItem()
233 : {
234 0 : MenuItem& item = findItem(PLAY_TOGGLE_ID);
235 0 : if (item.isValid()) {
236 0 : rcommand::Command command = playbackController()->isPlaying() ? PAUSE_COMMAND : PLAY_COMMAND;
237 0 : item.setCommandInfo(commandsRegister()->commandInfo(command));
238 0 : item.setId(PLAY_TOGGLE_ID);
239 0 : }
240 0 : }
241 :
242 0 : void PlaybackToolBarModel::updatePlayPosition(secs_t secs)
243 : {
244 0 : if (m_playbackPositionSecs == secs) {
245 0 : return;
246 : }
247 :
248 0 : m_playbackPositionSecs = secs;
249 0 : emit playPositionChanged();
250 : }
251 :
252 0 : void PlaybackToolBarModel::rewind(secs_t secs)
253 : {
254 0 : if (m_playbackPositionSecs == secs || secs > totalPlayTime()) {
255 0 : return;
256 : }
257 :
258 0 : dispatchCommand(REWIND_COMMAND, { { "position", Val(secs) } });
259 0 : }
260 :
261 0 : void PlaybackToolBarModel::rewindToBeat(const MeasureBeat& beat)
262 : {
263 0 : secs_t secs = playbackController()->beatToSecs(beat.measureIndex, static_cast<int>(beat.beat));
264 0 : rewind(secs);
265 0 : }
266 :
267 0 : int PlaybackToolBarModel::measureNumber() const
268 : {
269 0 : return measureBeat().measureIndex + 1;
270 : }
271 :
272 0 : void PlaybackToolBarModel::setMeasureNumber(int measureNumber)
273 : {
274 0 : int measureIndex = measureNumber - 1;
275 0 : MeasureBeat measureBeat = this->measureBeat();
276 :
277 0 : if (measureIndex == measureBeat.measureIndex) {
278 0 : return;
279 : }
280 :
281 0 : measureBeat.measureIndex = measureIndex;
282 0 : rewindToBeat(measureBeat);
283 : }
284 :
285 0 : int PlaybackToolBarModel::maxMeasureNumber() const
286 : {
287 0 : return measureBeat().maxMeasureIndex + 1;
288 : }
289 :
290 0 : int PlaybackToolBarModel::beatNumber() const
291 : {
292 0 : return static_cast<int>(measureBeat().beat) + 1;
293 : }
294 :
295 0 : void PlaybackToolBarModel::setBeatNumber(int beatNumber)
296 : {
297 0 : int beatIndex = beatNumber - 1;
298 0 : MeasureBeat measureBeat = this->measureBeat();
299 :
300 0 : if (beatIndex == static_cast<int>(measureBeat.beat)) {
301 0 : return;
302 : }
303 :
304 0 : measureBeat.beat = beatIndex;
305 0 : rewindToBeat(measureBeat);
306 : }
307 :
308 0 : void PlaybackToolBarModel::setTempoMultiplier(qreal multiplier)
309 : {
310 0 : if (muse::RealIsEqual(multiplier, tempoMultiplier())) {
311 0 : return;
312 : }
313 :
314 0 : playbackController()->setTempoMultiplier(multiplier);
315 0 : emit tempoChanged();
316 : }
317 :
318 0 : int PlaybackToolBarModel::maxBeatNumber() const
319 : {
320 0 : return measureBeat().maxBeatIndex + 1;
321 : }
322 :
323 0 : QVariant PlaybackToolBarModel::tempo() const
324 : {
325 : static const std::unordered_map<DurationType, MusicalSymbolCodes::Code> DURATION_TO_ICON {
326 : { DurationType::V_WHOLE, MusicalSymbolCodes::Code::SEMIBREVE },
327 : { DurationType::V_HALF, MusicalSymbolCodes::Code::MINIM },
328 : { DurationType::V_QUARTER, MusicalSymbolCodes::Code::CROTCHET },
329 : { DurationType::V_EIGHTH, MusicalSymbolCodes::Code::QUAVER },
330 : { DurationType::V_16TH, MusicalSymbolCodes::Code::SEMIQUAVER },
331 0 : };
332 :
333 0 : const notation::Tempo& tempo = playbackController()->currentTempo();
334 0 : const MusicalSymbolCodes::Code noteIcon = muse::value(DURATION_TO_ICON, tempo.duration,
335 0 : MusicalSymbolCodes::Code::CROTCHET);
336 :
337 0 : QVariantMap obj;
338 0 : obj["noteSymbol"] = musicalSymbolToString(noteIcon, tempo.withDot);
339 0 : obj["value"] = tempo.valueBpm;
340 :
341 0 : return obj;
342 0 : }
343 :
344 0 : qreal PlaybackToolBarModel::tempoMultiplier() const
345 : {
346 0 : return playbackController()->tempoMultiplier();
347 : }
|