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) 2025 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 :
23 : #include "onlinesoundsstatusmodel.h"
24 :
25 : #include "global/async/async.h"
26 : #include "global/translation.h"
27 :
28 : #include "audio/common/audioerrors.h"
29 : #include "notation/imasternotation.h"
30 : #include "notation/inotationplayback.h" // IWYU pragma: keep
31 : #include "playback/playbackcommands.h"
32 :
33 : using namespace mu::notation;
34 : using namespace mu::playback;
35 : using namespace muse::audio;
36 :
37 0 : static QString formatLimitReachedError(const muse::Ret& ret)
38 : {
39 0 : if (ret.code() != (int)Err::OnlineSoundsLimitReached) {
40 0 : return QString();
41 : }
42 :
43 0 : const QString libName = QString::fromStdString(ret.data<std::string>("libraryName", std::string()));
44 0 : const QString date = QString::fromStdString(ret.data<std::string>("date", std::string()));
45 :
46 0 : if (libName.isEmpty() || date.isEmpty()) {
47 0 : return QString();
48 : }
49 :
50 0 : const QString text = muse::qtrc("playback", "You’ve reached your current render limit for %1. "
51 : "You will be able to process online sounds again after your quota resets on %2.")
52 0 : .arg(libName)
53 0 : .arg(date);
54 0 : return text;
55 0 : }
56 :
57 0 : OnlineSoundsStatusModel::OnlineSoundsStatusModel(QObject* parent)
58 0 : : QObject(parent), muse::Contextable(muse::iocCtxForQmlObject(this)), m_ret(muse::make_ok())
59 : {
60 0 : }
61 :
62 0 : void OnlineSoundsStatusModel::load()
63 : {
64 0 : onOnlineSoundsChanged();
65 0 : playbackController()->onlineSoundsChanged().onNotify(this, [this]() {
66 0 : onOnlineSoundsChanged();
67 0 : });
68 :
69 0 : projectSession()->masterNotationChanged().onNotify(this, [this]() {
70 0 : updateManualProcessingAllowed();
71 0 : });
72 :
73 0 : audioConfiguration()->autoProcessOnlineSoundsInBackgroundChanged().onReceive(this, [this](bool) {
74 0 : updateManualProcessingAllowed(false /*enableByDefault*/);
75 0 : });
76 :
77 0 : muse::Progress progress = playbackController()->onlineSoundsProcessingProgress();
78 0 : setStatus(progress.isStarted() ? Status::Processing : Status::Success);
79 :
80 0 : progress.started().onNotify(this, [this]() {
81 0 : setStatus(Status::Processing);
82 0 : });
83 :
84 0 : progress.finished().onReceive(this, [this](const muse::ProgressResult& result) {
85 0 : m_ret = result.ret;
86 0 : setStatus(result.ret ? Status::Success : Status::Error);
87 :
88 0 : if (result.ret && m_manualProcessingAllowed) {
89 0 : setManualProcessingAllowed(false);
90 : }
91 0 : });
92 0 : }
93 :
94 0 : void OnlineSoundsStatusModel::processOnlineSounds()
95 : {
96 0 : if (m_manualProcessingAllowed) {
97 0 : commandsDispatcher()->dispatch(PROCESS_ONLINESOUNDS_COMMAND);
98 : }
99 0 : }
100 :
101 0 : bool OnlineSoundsStatusModel::hasOnlineSounds() const
102 : {
103 0 : return !m_onlineTrackIdSet.empty();
104 : }
105 :
106 0 : bool OnlineSoundsStatusModel::manualProcessingAllowed() const
107 : {
108 0 : return m_manualProcessingAllowed;
109 : }
110 :
111 0 : int OnlineSoundsStatusModel::status() const
112 : {
113 0 : return static_cast<int>(m_status);
114 : }
115 :
116 0 : QString OnlineSoundsStatusModel::errorTitle() const
117 : {
118 0 : if (!m_ret) {
119 0 : if (m_ret.code() == (int)Err::OnlineSoundsProcessingError) {
120 0 : return muse::qtrc("playback", "Some online sounds aren’t ready yet");
121 0 : } else if (m_ret.code() != (int)muse::Ret::Code::Cancel) {
122 0 : return muse::qtrc("playback", "Unable to process online sounds");
123 : }
124 : }
125 :
126 0 : return QString();
127 : }
128 :
129 0 : QString OnlineSoundsStatusModel::errorDescription() const
130 : {
131 0 : if (!m_ret) {
132 0 : if (m_ret.code() == (int)Err::OnlineSoundsLimitReached) {
133 0 : return formatLimitReachedError(m_ret);
134 0 : } else if (m_ret.code() != (int)muse::Ret::Code::Cancel) {
135 0 : return muse::qtrc("playback", "Please check your connection, and make sure MuseHub is running and you are logged in.");
136 : }
137 : }
138 :
139 0 : return QString();
140 : }
141 :
142 0 : void OnlineSoundsStatusModel::onOnlineSoundsChanged()
143 : {
144 0 : const std::map<TrackId, AudioResourceMeta>& onlineSounds = playbackController()->onlineSounds();
145 0 : const IPlaybackController::InstrumentTrackIdMap& instrumentTrackIdMap = playbackController()->instrumentTrackIdMap();
146 :
147 0 : const bool wasEmpty = m_onlineTrackIdSet.empty();
148 0 : m_onlineTrackIdSet.clear();
149 0 : m_onlineTrackIdSet.reserve(onlineSounds.size());
150 :
151 0 : for (const auto& pair : instrumentTrackIdMap) {
152 0 : if (muse::contains(onlineSounds, pair.second)) {
153 0 : m_onlineTrackIdSet.insert(pair.first);
154 : }
155 : }
156 :
157 0 : if (m_onlineTrackIdSet.empty() != wasEmpty) {
158 0 : emit hasOnlineSoundsChanged();
159 : }
160 :
161 0 : updateManualProcessingAllowed();
162 0 : }
163 :
164 0 : void OnlineSoundsStatusModel::updateManualProcessingAllowed(bool enableByDefault)
165 : {
166 0 : if (m_onlineTrackIdSet.empty() || audioConfiguration()->autoProcessOnlineSoundsInBackground()) {
167 0 : m_tracksDataChanged.disconnect(this);
168 0 : setManualProcessingAllowed(false);
169 0 : return;
170 : }
171 :
172 0 : IMasterNotationPtr master = projectSession()->masterNotation();
173 0 : if (!master) {
174 0 : m_tracksDataChanged.disconnect(this);
175 0 : setManualProcessingAllowed(false);
176 0 : return;
177 : }
178 :
179 0 : setManualProcessingAllowed(enableByDefault);
180 :
181 0 : m_tracksDataChanged = master->playback()->tracksDataChanged();
182 0 : m_tracksDataChanged.onReceive(this, [this](const engraving::InstrumentTrackIdSet& changedTrackIdSet) {
183 0 : for (const engraving::InstrumentTrackId& trackId : changedTrackIdSet) {
184 0 : if (muse::contains(m_onlineTrackIdSet, trackId)) {
185 0 : setManualProcessingAllowed(true);
186 0 : return;
187 : }
188 : }
189 : });
190 0 : }
191 :
192 0 : void OnlineSoundsStatusModel::setManualProcessingAllowed(bool allowed)
193 : {
194 0 : if (m_manualProcessingAllowed == allowed) {
195 0 : return;
196 : }
197 :
198 0 : m_manualProcessingAllowed = allowed;
199 0 : emit manualProcessingAllowedChanged();
200 :
201 0 : if (allowed && m_shouldNotifyToursThatManualProcessingAllowed) {
202 0 : muse::async::Async::call(this, [this]() {
203 0 : tours()->onEvent(u"online_sounds_manual_processing_allowed");
204 0 : });
205 :
206 0 : m_shouldNotifyToursThatManualProcessingAllowed = false;
207 : }
208 : }
209 :
210 0 : void OnlineSoundsStatusModel::setStatus(Status status)
211 : {
212 0 : if (m_status == status) {
213 0 : return;
214 : }
215 :
216 0 : m_status = status;
217 0 : emit statusChanged();
218 : }
|