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) 2026 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 "mixerchannelitem.h"
24 :
25 : #include "defer.h"
26 : #include "translation.h"
27 : #include "log.h"
28 :
29 : #include "notation/imasternotation.h" // IWYU pragma: keep
30 : #include "notation/inotationautomation.h"
31 : #include "notation/inotationplayback.h"
32 :
33 : #include "project/inotationproject.h"
34 :
35 : #include "vst/vstcommands.h"
36 :
37 : using namespace mu::playback;
38 : using namespace muse;
39 : using namespace muse::audio;
40 : using namespace muse::vst;
41 : using namespace mu::project;
42 :
43 : static constexpr volume_dbfs_t MAX_DISPLAYED_DBFS = volume_dbfs_t::make(0.f); // 100%
44 : static constexpr volume_dbfs_t MIN_DISPLAYED_DBFS = volume_dbfs_t::make(-60.f); // 0%
45 :
46 : static constexpr float BALANCE_SCALING_FACTOR = 100.f;
47 :
48 : static constexpr int OUTPUT_RESOURCE_COUNT_LIMIT = 4;
49 :
50 : static const std::string TRACK_ID_KEY("trackId");
51 : static const std::string RESOURCE_ID_KEY("resourceId");
52 : static const std::string CHAIN_ORDER_KEY("chainOrder");
53 :
54 0 : MixerChannelItem::MixerChannelItem(QObject* parent, Type type, bool outputOnly, audio::TrackId trackId)
55 0 : : QObject(parent), muse::Contextable(muse::iocCtxForQmlObject(this)),
56 0 : m_type(type),
57 0 : m_trackId(trackId),
58 0 : m_outputOnly(outputOnly),
59 0 : m_leftChannelPressure(MIN_DISPLAYED_DBFS),
60 0 : m_rightChannelPressure(MIN_DISPLAYED_DBFS)
61 : {
62 0 : if (!m_outputOnly) {
63 0 : m_inputResourceItem = buildInputResourceItem();
64 : }
65 :
66 0 : m_panel = new ui::NavigationPanel(this);
67 0 : m_panel->setDirection(ui::NavigationPanel::Vertical);
68 0 : m_panel->setName("MixerChannelPanel " + QString::number(m_trackId));
69 0 : m_panel->accessible()->setName(muse::qtrc("playback", "Mixer channel panel %1").arg(m_trackId));
70 0 : m_panel->componentComplete();
71 :
72 0 : connect(this, &MixerChannelItem::mutedChanged, this, [this]() {
73 0 : if (muted()) {
74 0 : resetAudioChannelsVolumePressure();
75 : }
76 0 : });
77 0 : }
78 :
79 0 : MixerChannelItem::~MixerChannelItem()
80 : {
81 0 : m_audioSignalChanges.disconnect(this);
82 0 : }
83 :
84 0 : MixerChannelItem::Type MixerChannelItem::type() const
85 : {
86 0 : return m_type;
87 : }
88 :
89 0 : TrackId MixerChannelItem::trackId() const
90 : {
91 0 : return m_trackId;
92 : }
93 :
94 0 : const mu::engraving::InstrumentTrackId& MixerChannelItem::instrumentTrackId() const
95 : {
96 0 : return m_instrumentTrackId;
97 : }
98 :
99 0 : void MixerChannelItem::setInstrumentTrackId(const mu::engraving::InstrumentTrackId& instrumentTrackId)
100 : {
101 0 : m_instrumentTrackId = instrumentTrackId;
102 :
103 0 : updateHasAutomationFlags();
104 0 : }
105 :
106 0 : QString MixerChannelItem::title() const
107 : {
108 0 : return m_title;
109 : }
110 :
111 0 : float MixerChannelItem::leftChannelPressure() const
112 : {
113 0 : return m_leftChannelPressure;
114 : }
115 :
116 0 : float MixerChannelItem::rightChannelPressure() const
117 : {
118 0 : return m_rightChannelPressure;
119 : }
120 :
121 0 : float MixerChannelItem::volumeLevel() const
122 : {
123 0 : return m_volumeLevel;
124 : }
125 :
126 0 : float MixerChannelItem::volumeLevelMin() const
127 : {
128 0 : return audio::VOLUME_DB_MIN.raw();
129 : }
130 :
131 0 : float MixerChannelItem::volumeLevelMax() const
132 : {
133 0 : return audio::VOLUME_DB_MAX.raw();
134 : }
135 :
136 0 : int MixerChannelItem::balance() const
137 : {
138 0 : return m_balance;
139 : }
140 :
141 0 : int MixerChannelItem::balanceMin() const
142 : {
143 0 : return audio::BALANCE_MIN.raw() * BALANCE_SCALING_FACTOR;
144 : }
145 :
146 0 : int MixerChannelItem::balanceMax() const
147 : {
148 0 : return audio::BALANCE_MAX.raw() * BALANCE_SCALING_FACTOR;
149 : }
150 :
151 0 : bool MixerChannelItem::hasVolumeAutomation() const
152 : {
153 0 : return m_hasVolumeAutomation;
154 : }
155 :
156 0 : bool MixerChannelItem::hasBalanceAutomation() const
157 : {
158 0 : return m_hasBalanceAutomation;
159 : }
160 :
161 0 : bool MixerChannelItem::solo() const
162 : {
163 0 : return m_outParams.solo;
164 : }
165 :
166 0 : bool MixerChannelItem::muted() const
167 : {
168 0 : return m_outParams.muted;
169 : }
170 :
171 0 : bool MixerChannelItem::forceMute() const
172 : {
173 0 : return m_outParams.forceMute;
174 : }
175 :
176 0 : muse::ui::NavigationPanel* MixerChannelItem::panel() const
177 : {
178 0 : return m_panel;
179 : }
180 :
181 0 : void MixerChannelItem::setPanelOrder(int panelOrder)
182 : {
183 0 : m_panel->setOrder(panelOrder);
184 0 : }
185 :
186 0 : void MixerChannelItem::setPanelSection(muse::ui::INavigationSection* section)
187 : {
188 0 : m_panel->setSection(section);
189 0 : }
190 :
191 0 : void MixerChannelItem::setOutputResourceItemCount(size_t count)
192 : {
193 0 : IF_ASSERT_FAILED(count >= m_outParams.fxChain.size()) {
194 0 : return;
195 : }
196 :
197 0 : count = std::min(count, static_cast<size_t>(OUTPUT_RESOURCE_COUNT_LIMIT));
198 0 : size_t itemsSize = static_cast<size_t>(m_outputResourceItems.size());
199 :
200 0 : if (itemsSize == count) {
201 0 : return;
202 : }
203 :
204 0 : if (itemsSize < count) {
205 0 : addBlankSlots(count - itemsSize);
206 0 : } else if (itemsSize > count) {
207 0 : removeBlankSlotsFromEnd(itemsSize - count);
208 : }
209 : }
210 :
211 0 : void MixerChannelItem::addBlankSlots(size_t count)
212 : {
213 0 : TRACEFUNC;
214 :
215 0 : if (count == 0) {
216 0 : return;
217 : }
218 :
219 0 : for (size_t i = 0; i < count; ++i) {
220 0 : AudioFxParams params;
221 0 : params.chainOrder = resolveNewBlankOutputResourceItemOrder();
222 0 : m_outputResourceItems.insert(params.chainOrder, buildOutputResourceItem(params));
223 0 : }
224 :
225 0 : emit outputResourceItemListChanged();
226 0 : }
227 :
228 0 : void MixerChannelItem::removeBlankSlotsFromEnd(size_t count)
229 : {
230 0 : TRACEFUNC;
231 :
232 0 : bool itemsRemoved = false;
233 0 : DEFER {
234 0 : if (itemsRemoved) {
235 0 : emit outputResourceItemListChanged();
236 : }
237 0 : };
238 :
239 0 : for (size_t i = 0; i < count; ++i) {
240 0 : if (m_outputResourceItems.empty()) {
241 0 : return;
242 : }
243 :
244 0 : auto lastItemIt = std::prev(m_outputResourceItems.end());
245 0 : OutputResourceItem* item = lastItemIt.value();
246 :
247 0 : if (!item->isBlank()) {
248 0 : return;
249 : }
250 :
251 0 : m_outputResourceItems.erase(lastItemIt);
252 0 : closeEditor(item);
253 0 : item->disconnect();
254 0 : item->deleteLater();
255 0 : itemsRemoved = true;
256 : }
257 0 : }
258 :
259 0 : void MixerChannelItem::loadInputParams(const AudioInputParams& newParams)
260 : {
261 0 : if (m_outputOnly) {
262 0 : return;
263 : }
264 :
265 0 : if (m_inputParams == newParams) {
266 0 : return;
267 : }
268 :
269 0 : m_inputParams = newParams;
270 0 : m_inputResourceItem->setParams(newParams);
271 : }
272 :
273 0 : void MixerChannelItem::loadOutputParams(const AudioOutputParams& newParams)
274 : {
275 0 : if (!muse::RealIsEqual(m_outParams.volume, newParams.volume)) {
276 0 : m_outParams.volume = newParams.volume;
277 0 : if (!m_hasVolumeAutomation) {
278 0 : setDisplayedVolumeLevel(m_outParams.volume);
279 : }
280 : }
281 :
282 0 : if (!muse::RealIsEqual(m_outParams.balance, newParams.balance)) {
283 0 : m_outParams.balance = newParams.balance;
284 0 : if (!m_hasBalanceAutomation) {
285 0 : setDisplayedBalance(m_outParams.balance.raw() * BALANCE_SCALING_FACTOR);
286 : }
287 : }
288 :
289 0 : if (m_outParams.solo != newParams.solo) {
290 0 : m_outParams.solo = newParams.solo;
291 0 : emit soloChanged();
292 : }
293 :
294 0 : if (m_outParams.muted != newParams.muted) {
295 0 : m_outParams.muted = newParams.muted;
296 0 : emit mutedChanged();
297 : }
298 :
299 0 : if (m_outParams.forceMute != newParams.forceMute) {
300 0 : m_outParams.forceMute = newParams.forceMute;
301 0 : emit forceMuteChanged();
302 : }
303 :
304 0 : loadOutputResourceItems(newParams.fxChain);
305 0 : loadAuxSendItems(newParams.auxSends);
306 0 : }
307 :
308 0 : void MixerChannelItem::loadOutputResourceItems(const AudioFxChain& fxChain)
309 : {
310 0 : m_outParams.fxChain = fxChain;
311 :
312 0 : m_outputResourceItemsLoading = true;
313 0 : DEFER {
314 0 : m_outputResourceItemsLoading = false;
315 0 : };
316 :
317 0 : QMap<AudioFxChainOrder, OutputResourceItem*> newItems = m_outputResourceItems;
318 :
319 0 : for (AudioFxChainOrder chainOrder : m_outputResourceItems.keys()) {
320 0 : OutputResourceItem* item = m_outputResourceItems.value(chainOrder);
321 :
322 0 : if (item->isBlank()) {
323 0 : continue;
324 : }
325 :
326 0 : if (fxChain.find(chainOrder) == fxChain.cend()) {
327 0 : item->disconnect();
328 0 : item->deleteLater();
329 0 : newItems.remove(chainOrder);
330 : }
331 0 : }
332 :
333 0 : for (const auto& pair : fxChain) {
334 0 : OutputResourceItem* item = newItems.value(pair.first, nullptr);
335 :
336 0 : if (item) {
337 0 : item->setParams(pair.second);
338 : } else {
339 0 : newItems.insert(pair.first, buildOutputResourceItem(pair.second));
340 : }
341 : }
342 :
343 0 : if (m_outputResourceItems != newItems) {
344 0 : m_outputResourceItems = std::move(newItems);
345 0 : emit outputResourceItemListChanged();
346 : }
347 0 : }
348 :
349 0 : void MixerChannelItem::loadAuxSendItems(const AuxSendsParams& auxSends)
350 : {
351 0 : if (m_outParams.auxSends == auxSends) {
352 0 : return;
353 : }
354 :
355 0 : m_outParams.auxSends = auxSends;
356 :
357 0 : configuration()->isAuxSendVisibleChanged().onReceive(this, [this](aux_channel_idx_t index, bool visible) {
358 0 : if (visible) {
359 0 : IF_ASSERT_FAILED(index < m_outParams.auxSends.size()) {
360 0 : return;
361 : };
362 :
363 0 : m_auxSendItems.insert(index, buildAuxSendItem(index, m_outParams.auxSends[index]));
364 : } else {
365 0 : m_auxSendItems.remove(index);
366 : }
367 :
368 0 : emit auxSendItemListChanged();
369 : }, async::Asyncable::Mode::SetReplace);
370 :
371 0 : if (m_auxSendItems.size() == static_cast<int>(auxSends.size())) {
372 0 : return;
373 : }
374 :
375 0 : QMap<aux_channel_idx_t, AuxSendItem*> newItems;
376 :
377 0 : for (aux_channel_idx_t i = 0; i < auxSends.size(); ++i) {
378 0 : if (!configuration()->isAuxSendVisible(i)) {
379 0 : continue;
380 : }
381 :
382 0 : auto it = m_auxSendItems.find(i);
383 :
384 0 : if (it != m_auxSendItems.end()) {
385 0 : newItems.insert(it.key(), it.value());
386 : } else {
387 0 : newItems.insert(i, buildAuxSendItem(i, auxSends[i]));
388 : }
389 : }
390 :
391 0 : if (m_auxSendItems != newItems) {
392 0 : m_auxSendItems = std::move(newItems);
393 0 : emit auxSendItemListChanged();
394 : }
395 0 : }
396 :
397 0 : void MixerChannelItem::loadSoloMuteState(const notation::INotationSoloMuteState::SoloMuteState& newState)
398 : {
399 0 : if (m_outParams.muted != newState.mute) {
400 0 : m_outParams.muted = newState.mute;
401 0 : emit mutedChanged();
402 : }
403 :
404 0 : if (m_outParams.solo != newState.solo) {
405 0 : m_outParams.solo = newState.solo;
406 0 : emit soloChanged();
407 : }
408 0 : }
409 :
410 0 : void MixerChannelItem::subscribeOnAudioSignalChanges(AudioSignalChanges& audioSignalChanges)
411 : {
412 0 : m_audioSignalChanges = audioSignalChanges;
413 :
414 0 : m_audioSignalChanges.onReceive(this, [this](const AudioSignalValuesMap& signalValues) {
415 : //!Note There should be no signal changes when the mixer channel is muted.
416 : //! But some audio signal changes still might be "on the way" from the times when the mixer channel wasn't muted
417 : //! So that we have to just ignore them
418 0 : if (muted()) {
419 0 : return;
420 : }
421 :
422 0 : for (const auto& pair : signalValues) {
423 0 : audioch_t audioChNum = pair.first;
424 0 : volume_dbfs_t newPressure = pair.second.pressure;
425 :
426 0 : if (newPressure < MIN_DISPLAYED_DBFS) {
427 0 : setAudioChannelVolumePressure(audioChNum, MIN_DISPLAYED_DBFS);
428 0 : } else if (newPressure > MAX_DISPLAYED_DBFS) {
429 0 : setAudioChannelVolumePressure(audioChNum, MAX_DISPLAYED_DBFS);
430 : } else {
431 0 : setAudioChannelVolumePressure(audioChNum, newPressure);
432 : }
433 : }
434 : });
435 0 : }
436 :
437 0 : void MixerChannelItem::subscribeOnAutomatedControlParamsChanges(AutomatedControlParamsChanges& changes)
438 : {
439 0 : m_automatedControlParamsChanges = changes;
440 :
441 0 : m_automatedControlParamsChanges.onReceive(this, [this](const AutomatedControlParams& params) {
442 0 : if (m_hasVolumeAutomation) {
443 0 : setDisplayedVolumeLevel(params.volume.raw());
444 : }
445 :
446 0 : if (m_hasBalanceAutomation) {
447 0 : setDisplayedBalance(params.balance.raw() * BALANCE_SCALING_FACTOR);
448 : }
449 0 : });
450 0 : }
451 :
452 0 : void MixerChannelItem::setTitle(QString title)
453 : {
454 0 : if (m_title == title) {
455 0 : return;
456 : }
457 :
458 0 : m_title = title;
459 0 : emit titleChanged(m_title);
460 : }
461 :
462 0 : void MixerChannelItem::setLeftChannelPressure(float leftChannelPressure)
463 : {
464 0 : if (qFuzzyCompare(m_leftChannelPressure, leftChannelPressure)) {
465 0 : return;
466 : }
467 :
468 0 : m_leftChannelPressure = leftChannelPressure;
469 0 : emit leftChannelPressureChanged(m_leftChannelPressure);
470 : }
471 :
472 0 : void MixerChannelItem::setRightChannelPressure(float rightChannelPressure)
473 : {
474 0 : if (qFuzzyCompare(m_rightChannelPressure, rightChannelPressure)) {
475 0 : return;
476 : }
477 :
478 0 : m_rightChannelPressure = rightChannelPressure;
479 0 : emit rightChannelPressureChanged(m_rightChannelPressure);
480 : }
481 :
482 0 : void MixerChannelItem::setVolumeLevel(float volumeLevel)
483 : {
484 0 : if (qFuzzyCompare(m_outParams.volume, volumeLevel)) {
485 0 : return;
486 : }
487 :
488 0 : m_outParams.volume = volumeLevel;
489 0 : setDisplayedVolumeLevel(volumeLevel);
490 0 : emit controlParamsChanged(m_outParams);
491 : }
492 :
493 0 : void MixerChannelItem::setBalance(int balance)
494 : {
495 0 : if (m_outParams.balance.raw() * BALANCE_SCALING_FACTOR == balance) {
496 0 : return;
497 : }
498 :
499 0 : m_outParams.balance = balance / BALANCE_SCALING_FACTOR;
500 0 : setDisplayedBalance(balance);
501 0 : emit controlParamsChanged(m_outParams);
502 : }
503 :
504 0 : void MixerChannelItem::setSolo(bool solo)
505 : {
506 0 : if (m_outParams.solo == solo) {
507 0 : return;
508 : }
509 :
510 0 : m_outParams.solo = solo;
511 :
512 0 : notation::INotationSoloMuteState::SoloMuteState soloMuteState;
513 0 : soloMuteState.mute = m_outParams.muted;
514 0 : soloMuteState.solo = m_outParams.solo;
515 :
516 0 : emit soloMuteStateChanged(soloMuteState);
517 0 : emit soloChanged();
518 :
519 0 : if (solo && m_outParams.muted) {
520 0 : setMuted(false);
521 : }
522 : }
523 :
524 0 : void MixerChannelItem::setMuted(bool mute)
525 : {
526 0 : if (m_outParams.muted == mute) {
527 0 : return;
528 : }
529 :
530 0 : m_outParams.muted = mute;
531 :
532 0 : notation::INotationSoloMuteState::SoloMuteState soloMuteState;
533 0 : soloMuteState.mute = m_outParams.muted;
534 0 : soloMuteState.solo = m_outParams.solo;
535 :
536 0 : emit soloMuteStateChanged(soloMuteState);
537 0 : emit mutedChanged();
538 :
539 0 : if (mute && m_outParams.solo) {
540 0 : setSolo(false);
541 : }
542 : }
543 :
544 0 : mu::notation::INotationPlaybackPtr MixerChannelItem::notationPlayback() const
545 : {
546 0 : project::INotationProjectPtr project = context()->project();
547 0 : return project ? project->masterNotation()->playback() : nullptr;
548 0 : }
549 :
550 0 : void MixerChannelItem::updateHasAutomationFlags()
551 : {
552 0 : bool hasVolumeAutomation = false;
553 0 : bool hasBalanceAutomation = false;
554 :
555 0 : INotationProjectPtr project = context()->project();
556 : const notation::AutomationDataConstPtr automationData
557 0 : = (project && m_instrumentTrackId.isValid()) ? project->masterNotation()->automation()->automationData() : nullptr;
558 :
559 0 : if (automationData) {
560 0 : hasVolumeAutomation = !automationData->curve(
561 0 : notation::AutomationCurveKey::instrument(notation::AutomationType::Volume, m_instrumentTrackId)).empty();
562 0 : hasBalanceAutomation = !automationData->curve(
563 0 : notation::AutomationCurveKey::instrument(notation::AutomationType::Pan, m_instrumentTrackId)).empty();
564 : }
565 :
566 0 : if (m_hasVolumeAutomation != hasVolumeAutomation) {
567 0 : m_hasVolumeAutomation = hasVolumeAutomation;
568 0 : emit hasVolumeAutomationChanged();
569 0 : if (!m_hasVolumeAutomation) {
570 0 : setDisplayedVolumeLevel(m_outParams.volume);
571 : }
572 : }
573 :
574 0 : if (m_hasBalanceAutomation != hasBalanceAutomation) {
575 0 : m_hasBalanceAutomation = hasBalanceAutomation;
576 0 : emit hasBalanceAutomationChanged();
577 0 : if (!m_hasBalanceAutomation) {
578 0 : setDisplayedBalance(m_outParams.balance.raw() * BALANCE_SCALING_FACTOR);
579 : }
580 : }
581 0 : }
582 :
583 0 : void MixerChannelItem::setDisplayedVolumeLevel(float volumeLevel)
584 : {
585 0 : if (muse::RealIsEqual(m_volumeLevel, volumeLevel)) {
586 0 : return;
587 : }
588 :
589 0 : m_volumeLevel = volumeLevel;
590 0 : emit volumeLevelChanged(m_volumeLevel);
591 : }
592 :
593 0 : void MixerChannelItem::setDisplayedBalance(int balance)
594 : {
595 0 : if (m_balance == balance) {
596 0 : return;
597 : }
598 :
599 0 : m_balance = balance;
600 0 : emit balanceChanged(m_balance);
601 : }
602 :
603 0 : void MixerChannelItem::setAudioChannelVolumePressure(const audio::audioch_t chNum, const float newValue)
604 : {
605 0 : if (chNum == 0) {
606 0 : setLeftChannelPressure(newValue);
607 : } else {
608 0 : setRightChannelPressure(newValue);
609 : }
610 0 : }
611 :
612 0 : void MixerChannelItem::resetAudioChannelsVolumePressure()
613 : {
614 0 : setLeftChannelPressure(MIN_DISPLAYED_DBFS);
615 0 : setRightChannelPressure(MIN_DISPLAYED_DBFS);
616 0 : }
617 :
618 0 : InputResourceItem* MixerChannelItem::buildInputResourceItem()
619 : {
620 0 : InputResourceItem* newItem = new InputResourceItem(this);
621 :
622 0 : connect(newItem, &InputResourceItem::inputParamsChangeRequested, this, [this, newItem](const AudioResourceMeta& newMeta) {
623 0 : if (askAboutChangingSound()) {
624 0 : newItem->setParamsRecourceMeta(newMeta);
625 : }
626 0 : });
627 :
628 0 : connect(newItem, &InputResourceItem::inputParamsChanged, this, [this, newItem]() {
629 0 : bool audioSourceChanged = m_inputParams.type() != newItem->params().type();
630 :
631 0 : m_inputParams = newItem->params();
632 0 : emit inputParamsChanged(m_inputParams);
633 :
634 0 : if (!audioSourceChanged) {
635 0 : return;
636 : }
637 :
638 0 : bool auxParamsChanged = false;
639 0 : for (aux_channel_idx_t idx = 0; idx < static_cast<size_t>(m_outParams.auxSends.size()); ++idx) {
640 0 : const muse::String& soundId = m_inputParams.resourceMeta.attributeVal(PLAYBACK_SETUP_DATA_ATTRIBUTE);
641 0 : gain_t newAudioSignalAmount = configuration()->defaultAuxSendValue(idx, m_inputParams.type(), soundId);
642 :
643 0 : auto it = m_auxSendItems.find(idx);
644 0 : if (it == m_auxSendItems.end()) {
645 0 : if (!muse::RealIsEqual(m_outParams.auxSends.at(idx).signalAmount, newAudioSignalAmount)) {
646 0 : m_outParams.auxSends.at(idx).signalAmount = newAudioSignalAmount;
647 0 : auxParamsChanged = true;
648 : }
649 : } else {
650 0 : it.value()->setAudioSignalPercentage(newAudioSignalAmount * 100.f);
651 : }
652 : }
653 :
654 0 : if (auxParamsChanged) {
655 0 : emit auxSendsParamsChanged(m_outParams);
656 : }
657 : });
658 :
659 0 : connect(newItem, &InputResourceItem::isBlankChanged, this, &MixerChannelItem::inputResourceItemChanged);
660 :
661 0 : connect(newItem, &InputResourceItem::nativeEditorViewLaunchRequested, this, [this, newItem]() {
662 0 : if (newItem->params().type() != AudioSourceType::Vsti) {
663 0 : return;
664 : }
665 :
666 0 : rcommand::CommandQuery aq(VST_OPEN_INSTRUMENT_EDITOR_COMMAND);
667 0 : aq.addParam(TRACK_ID_KEY, Val(m_trackId));
668 0 : aq.addParam(RESOURCE_ID_KEY, Val(newItem->params().resourceMeta.id));
669 :
670 0 : openEditor(newItem, aq);
671 0 : });
672 :
673 0 : connect(newItem, &InputResourceItem::nativeEditorViewCloseRequested, this, [this, newItem]() {
674 0 : closeEditor(newItem);
675 0 : });
676 :
677 0 : return newItem;
678 : }
679 :
680 0 : OutputResourceItem* MixerChannelItem::buildOutputResourceItem(const audio::AudioFxParams& fxParams)
681 : {
682 0 : OutputResourceItem* newItem = new OutputResourceItem(this, fxParams);
683 :
684 0 : connect(newItem, &OutputResourceItem::fxParamsChanged, this, [this]() {
685 0 : if (m_outputResourceItemsLoading) {
686 0 : return;
687 : }
688 :
689 0 : m_outParams.fxChain.clear();
690 :
691 0 : for (const OutputResourceItem* item : std::as_const(m_outputResourceItems)) {
692 0 : m_outParams.fxChain.insert({ item->params().chainOrder, item->params() });
693 : }
694 :
695 0 : emit fxChainParamsChanged(m_outParams);
696 : });
697 :
698 0 : connect(newItem, &OutputResourceItem::nativeEditorViewLaunchRequested, this, [this, newItem]() {
699 0 : if (newItem->params().type() != AudioFxType::VstFx) {
700 0 : return;
701 : }
702 :
703 0 : rcommand::CommandQuery aq(VST_OPEN_FX_EDITOR_COMMAND);
704 :
705 0 : aq.addParam(TRACK_ID_KEY, Val(m_trackId));
706 0 : aq.addParam(RESOURCE_ID_KEY, Val(newItem->params().resourceMeta.id));
707 0 : aq.addParam(CHAIN_ORDER_KEY, Val(newItem->params().chainOrder));
708 :
709 0 : openEditor(newItem, aq);
710 0 : });
711 :
712 0 : connect(newItem, &OutputResourceItem::nativeEditorViewCloseRequested, this, [this, newItem]() {
713 0 : closeEditor(newItem);
714 0 : });
715 :
716 0 : return newItem;
717 : }
718 :
719 0 : AuxSendItem* MixerChannelItem::buildAuxSendItem(aux_channel_idx_t index, const AuxSendParams& params)
720 : {
721 0 : AuxSendItem* newItem = new AuxSendItem(this);
722 0 : newItem->blockSignals(true);
723 0 : newItem->setIsActive(params.active);
724 0 : newItem->setAudioSignalPercentage(params.signalAmount * 100.f);
725 0 : newItem->setTitle(muse::qtrc("playback", "Aux %1").arg(index + 1));
726 0 : newItem->blockSignals(false);
727 :
728 0 : connect(newItem, &AuxSendItem::isActiveChanged, this, [this, index](bool active) {
729 0 : IF_ASSERT_FAILED(index < m_outParams.auxSends.size()) {
730 0 : return;
731 : }
732 :
733 0 : m_outParams.auxSends[index].active = active;
734 0 : emit auxSendsParamsChanged(m_outParams);
735 : });
736 :
737 0 : connect(newItem, &AuxSendItem::audioSignalPercentageChanged, this, [this, index](int percentage) {
738 0 : IF_ASSERT_FAILED(index < m_outParams.auxSends.size()) {
739 0 : return;
740 : }
741 :
742 0 : m_outParams.auxSends[index].signalAmount = static_cast<float>(percentage) / 100.f;
743 0 : emit auxSendsParamsChanged(m_outParams);
744 : });
745 :
746 0 : return newItem;
747 : }
748 :
749 0 : void MixerChannelItem::openEditor(AbstractAudioResourceItem* item, const rcommand::CommandQuery& command)
750 : {
751 0 : if (item->editorCommand() != command) {
752 0 : if (item->editorCommand().isValid()) {
753 : // make and send close
754 0 : rcommand::CommandQuery closeCommand = item->editorCommand();
755 0 : closeCommand.addParam("operation", Val("close"));
756 0 : closeCommand.addParam("sync", Val(true));
757 0 : commandDispatcher()->dispatch(closeCommand);
758 0 : }
759 : // set new action
760 0 : item->setEditorCommand(command);
761 : }
762 :
763 0 : commandDispatcher()->dispatch(command);
764 0 : }
765 :
766 0 : void MixerChannelItem::closeEditor(AbstractAudioResourceItem* item)
767 : {
768 : // make and send close
769 0 : rcommand::CommandQuery closeCommand = item->editorCommand();
770 0 : closeCommand.addParam("operation", Val("close"));
771 0 : closeCommand.addParam("sync", Val(true));
772 0 : commandDispatcher()->dispatch(closeCommand);
773 :
774 0 : item->setEditorCommand({});
775 0 : }
776 :
777 0 : bool MixerChannelItem::askAboutChangingSound()
778 : {
779 0 : if (!configuration()->needToShowResetSoundFlagsWhenChangeSoundWarning()) {
780 0 : return true;
781 : }
782 :
783 0 : if (!notationPlayback()->hasSoundFlags({ m_instrumentTrackId })) {
784 0 : return true;
785 : }
786 :
787 0 : int changeBtn = int(IInteractive::Button::Apply);
788 0 : IInteractive::Options options = IInteractive::Option::WithIcon | IInteractive::Option::WithDontShowAgainCheckBox;
789 : IInteractive::ButtonDatas buttons = {
790 0 : interactive()->buttonData(IInteractive::Button::Cancel),
791 0 : IInteractive::ButtonData(changeBtn, muse::trc("playback", "Change sound"), true /*accent*/)
792 0 : };
793 :
794 0 : IInteractive::Result result = interactive()->warningSync(muse::trc("playback", "Are you sure you want to change this sound?"),
795 0 : muse::trc("playback",
796 : "Sound flags on this instrument may be reset, but staff text will remain. This action can’t be undone."),
797 : buttons, changeBtn, options);
798 :
799 0 : if (result.button() == changeBtn) {
800 0 : if (!result.showAgain()) {
801 0 : configuration()->setNeedToShowResetSoundFlagsWhenChangeSoundWarning(false);
802 : }
803 :
804 0 : return true;
805 : } else {
806 0 : return false;
807 : }
808 0 : }
809 :
810 0 : AudioFxChainOrder MixerChannelItem::resolveNewBlankOutputResourceItemOrder() const
811 : {
812 0 : if (m_outputResourceItems.empty()) {
813 0 : return 0;
814 : }
815 :
816 0 : AudioFxChainOrder lastChainOrder = m_outputResourceItems.lastKey();
817 0 : if (lastChainOrder < OUTPUT_RESOURCE_COUNT_LIMIT - 1) {
818 0 : return lastChainOrder + 1;
819 : }
820 :
821 0 : for (AudioFxChainOrder order = 0; order < OUTPUT_RESOURCE_COUNT_LIMIT; ++order) {
822 0 : if (!m_outputResourceItems.contains(order)) {
823 0 : return order;
824 : }
825 : }
826 :
827 0 : return OUTPUT_RESOURCE_COUNT_LIMIT - 1;
828 : }
829 :
830 0 : bool MixerChannelItem::outputOnly() const
831 : {
832 0 : return m_outputOnly;
833 : }
834 :
835 0 : const AudioInputParams& MixerChannelItem::inputParams() const
836 : {
837 0 : return m_inputParams;
838 : }
839 :
840 0 : const AudioOutputParams& MixerChannelItem::outputParams() const
841 : {
842 0 : return m_outParams;
843 : }
844 :
845 0 : InputResourceItem* MixerChannelItem::inputResourceItem() const
846 : {
847 0 : return m_inputResourceItem;
848 : }
849 :
850 0 : QList<OutputResourceItem*> MixerChannelItem::outputResourceItemList() const
851 : {
852 0 : return m_outputResourceItems.values();
853 : }
854 :
855 0 : QList<AuxSendItem*> MixerChannelItem::auxSendItemList() const
856 : {
857 0 : return m_auxSendItems.values();
858 : }
859 :
860 0 : const QMap<aux_channel_idx_t, AuxSendItem*>& MixerChannelItem::auxSendItems() const
861 : {
862 0 : return m_auxSendItems;
863 : }
|