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 :
23 : #include "abstractaudiowriter.h"
24 :
25 : #include <QCoreApplication>
26 : #include <QFile>
27 : #include <QFileInfo>
28 : #include <QThread>
29 :
30 : #include "global/containers.h"
31 : #include "log.h"
32 :
33 : #include "notation/inotation.h"
34 :
35 : using namespace muse;
36 : using namespace muse::audio;
37 : using namespace mu::iex::audioexport;
38 : using namespace mu::project;
39 : using namespace mu::notation;
40 :
41 0 : std::vector<INotationWriter::UnitType> AbstractAudioWriter::supportedUnitTypes() const
42 : {
43 0 : return { UnitType::PER_PART };
44 : }
45 :
46 0 : bool AbstractAudioWriter::supportsUnitType(UnitType unitType) const
47 : {
48 0 : std::vector<UnitType> unitTypes = supportedUnitTypes();
49 0 : return std::find(unitTypes.cbegin(), unitTypes.cend(), unitType) != unitTypes.cend();
50 0 : }
51 :
52 0 : Ret AbstractAudioWriter::write(INotationPtr, io::IODevice&, const Options& options)
53 : {
54 0 : IF_ASSERT_FAILED(unitTypeFromOptions(options) != UnitType::MULTI_PART) {
55 0 : return Ret(Ret::Code::NotSupported);
56 : }
57 :
58 0 : if (supportsUnitType(muse::value(options, OptionKey::UNIT_TYPE, Val(UnitType::PER_PAGE)).toEnum<UnitType>())) {
59 0 : NOT_IMPLEMENTED;
60 0 : return Ret(Ret::Code::NotImplemented);
61 : }
62 :
63 0 : NOT_SUPPORTED;
64 0 : return Ret(Ret::Code::NotSupported);
65 : }
66 :
67 0 : Ret AbstractAudioWriter::writeList(const INotationPtrList&, io::IODevice&, const Options& options)
68 : {
69 0 : IF_ASSERT_FAILED(unitTypeFromOptions(options) == UnitType::MULTI_PART) {
70 0 : return Ret(Ret::Code::NotSupported);
71 : }
72 :
73 0 : if (supportsUnitType(muse::value(options, OptionKey::UNIT_TYPE, Val(UnitType::PER_PAGE)).toEnum<UnitType>())) {
74 0 : NOT_IMPLEMENTED;
75 0 : return Ret(Ret::Code::NotImplemented);
76 : }
77 :
78 0 : NOT_SUPPORTED;
79 0 : return Ret(Ret::Code::NotSupported);
80 : }
81 :
82 0 : void AbstractAudioWriter::abort()
83 : {
84 0 : muse::ContextInject<muse::audio::IPlayback> playback = { m_iocContext };
85 0 : playback()->abortSavingAllSoundTracks();
86 0 : m_writeRet = make_ret(Ret::Code::Cancel);
87 0 : m_isCompleted = true;
88 0 : }
89 :
90 0 : muse::Progress* AbstractAudioWriter::progress()
91 : {
92 0 : return &m_progress;
93 : }
94 :
95 0 : Ret AbstractAudioWriter::doWriteAndWait(INotationPtr notation,
96 : io::IODevice& dstDevice,
97 : const SoundTrackFormat& format,
98 : const Options& options)
99 : {
100 : //! NOTE Temporary fix for the context injection
101 0 : m_iocContext = notation->iocContext();
102 :
103 0 : muse::ContextInject<playback::IPlaybackController> playbackController = { m_iocContext };
104 :
105 : //! NOTE Waiting for the audio system to start if it is not already running
106 0 : while (!startAudioController()->isAudioStarted()) {
107 0 : application()->processEvents();
108 0 : QThread::yieldCurrentThread();
109 : }
110 :
111 : //! NOTE Playback (tracks and duration) loads asynchronously; rendering before it is
112 : //! ready yields 0 tracks and 0 duration ("No audio to export"). Wait for it first.
113 0 : while (!playbackController()->isPlaybackInited()) {
114 0 : application()->processEvents();
115 0 : QThread::yieldCurrentThread();
116 : }
117 :
118 0 : m_isCompleted = false;
119 0 : m_writeRet = muse::Ret();
120 :
121 0 : playbackController()->setNotation(notation);
122 0 : playbackController()->setIsExportingAudio(true);
123 :
124 0 : SoundTrackFormat actualFormat = format;
125 :
126 0 : double leadingSilenceSec = muse::value(options, OptionKey::LEADING_SILENCE_SEC, Val(0.0)).toDouble();
127 0 : actualFormat.leadingSilenceDuration = std::isfinite(leadingSilenceSec)
128 0 : ? static_cast<msecs_t>(leadingSilenceSec) : msecs_t(0);
129 :
130 0 : double trailingSilenceSec = muse::value(options, OptionKey::TRAILING_SILENCE_SEC, Val(0.0)).toDouble();
131 0 : actualFormat.trailingSilenceDuration = std::isfinite(trailingSilenceSec)
132 0 : ? static_cast<msecs_t>(trailingSilenceSec) : msecs_t(0);
133 :
134 0 : doWrite(dstDevice, actualFormat);
135 :
136 0 : const bool waitForCompletion = muse::value(options, OptionKey::WAIT_FOR_COMPLETION, Val(true)).toBool();
137 0 : if (waitForCompletion) {
138 0 : while (!m_isCompleted) {
139 0 : application()->processEvents();
140 0 : QThread::yieldCurrentThread();
141 : }
142 : }
143 :
144 0 : return m_writeRet;
145 0 : }
146 :
147 0 : void AbstractAudioWriter::doWrite(io::IODevice& dstDevice, const SoundTrackFormat& format)
148 : {
149 0 : muse::ContextInject<muse::audio::IPlayback> playbackInj = { m_iocContext };
150 :
151 0 : const std::string processingOnlineSoundsMsg = trc("iex_audio", "Processing online sounds…");
152 :
153 0 : muse::ContextInject<project::IProjectSession> projectSession = { m_iocContext };
154 0 : m_notationForRestore = projectSession()->currentNotation();
155 :
156 0 : auto restorePlaybackState = [this]() {
157 0 : muse::ContextInject<playback::IPlaybackController> playbackController = { m_iocContext };
158 0 : playbackController()->setIsExportingAudio(false);
159 0 : playbackController()->setNotation(m_notationForRestore);
160 0 : };
161 :
162 0 : auto sendProgress = [this, processingOnlineSoundsMsg](int64_t current, int64_t total, SaveSoundTrackStage stage) {
163 0 : switch (stage) {
164 0 : case SaveSoundTrackStage::ProcessingOnlineSounds:
165 0 : m_progress.progress(current, total, processingOnlineSoundsMsg);
166 0 : break;
167 0 : case SaveSoundTrackStage::WritingSoundTrack:
168 : case SaveSoundTrackStage::Unknown:
169 0 : m_progress.progress(current, total);
170 0 : break;
171 : }
172 0 : };
173 :
174 0 : m_progress.start();
175 :
176 0 : auto playback = playbackInj();
177 :
178 0 : playback->saveSoundTrackProgressChanged()
179 0 : .onReceive(this, [sendProgress](int64_t current, int64_t total, SaveSoundTrackStage stage) {
180 0 : sendProgress(current, total, stage);
181 0 : });
182 :
183 0 : playback->saveSoundTrack(std::move(format), dstDevice)
184 0 : .onResolve(this, [this, playback, restorePlaybackState](const bool /*result*/) {
185 0 : LOGI() << "Successfully saved sound track";
186 :
187 0 : restorePlaybackState();
188 :
189 0 : m_writeRet = muse::make_ok();
190 0 : m_isCompleted = true;
191 0 : m_progress.finish(muse::make_ok());
192 0 : playback->saveSoundTrackProgressChanged().disconnect(this);
193 0 : })
194 0 : .onReject(this, [this, playback, restorePlaybackState](int errorCode, const std::string& msg) {
195 0 : restorePlaybackState();
196 :
197 0 : m_writeRet = Ret(errorCode, msg);
198 0 : m_isCompleted = true;
199 0 : m_progress.finish(make_ret(errorCode, msg));
200 0 : playback->saveSoundTrackProgressChanged().disconnect(this);
201 0 : });
202 0 : }
203 :
204 0 : INotationWriter::UnitType AbstractAudioWriter::unitTypeFromOptions(const Options& options) const
205 : {
206 0 : std::vector<UnitType> supported = supportedUnitTypes();
207 0 : IF_ASSERT_FAILED(!supported.empty()) {
208 0 : return UnitType::PER_PART;
209 : }
210 :
211 0 : UnitType defaultUnitType = supported.front();
212 0 : UnitType unitType = muse::value(options, OptionKey::UNIT_TYPE, Val(defaultUnitType)).toEnum<UnitType>();
213 0 : if (!supportsUnitType(unitType)) {
214 0 : return defaultUnitType;
215 : }
216 :
217 0 : return unitType;
218 0 : }
|