Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-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 "automationcontrolnode.h"
24 :
25 : #include "../dsp/audiomathutils.h"
26 :
27 : using namespace muse;
28 : using namespace muse::audio;
29 : using namespace muse::audio::engine;
30 :
31 0 : void AutomationControlNode::setPlayheadPosition(const PlayheadPositionPtr& playheadPosition)
32 : {
33 0 : m_playheadPosition = playheadPosition;
34 0 : m_lastGainsPos = std::nullopt;
35 0 : }
36 :
37 0 : void AutomationControlNode::setVolume(const AutomatableValue<volume_db_t>& volume)
38 : {
39 0 : m_volume = volume;
40 0 : updateChannelGains(currentPos());
41 0 : }
42 :
43 0 : void AutomationControlNode::setPan(const AutomatableValue<balance_t>& pan)
44 : {
45 0 : m_pan = pan;
46 0 : updateChannelGains(currentPos());
47 0 : }
48 :
49 0 : void AutomationControlNode::setMuted(bool muted)
50 : {
51 0 : setEnabled(!muted);
52 0 : }
53 :
54 0 : bool AutomationControlNode::muted() const
55 : {
56 0 : return !enabled();
57 : }
58 :
59 0 : AutomatedControlParamsChanges AutomationControlNode::automatedControlParamsChanges() const
60 : {
61 0 : return m_automatedControlParamsChanges;
62 : }
63 :
64 0 : void AutomationControlNode::onOutputSpecChanged(const OutputSpec&)
65 : {
66 0 : updateChannelGains(currentPos());
67 0 : }
68 :
69 0 : secs_t AutomationControlNode::currentPos() const
70 : {
71 0 : return m_playheadPosition ? m_playheadPosition->currentPosition().time() : secs_t(0.0);
72 : }
73 :
74 0 : void AutomationControlNode::updateChannelGains(secs_t pos)
75 : {
76 0 : const volume_db_t volumeDb = m_volume.evaluateAt(pos, VOLUME_DB_MIN, VOLUME_DB_MAX);
77 0 : const balance_t panValue = m_pan.evaluateAt(pos, BALANCE_MIN, BALANCE_MAX);
78 :
79 0 : const float volume = muse::db_to_linear(volumeDb.raw());
80 0 : const float pan = panValue.raw();
81 :
82 0 : const audioch_t channelsCount = m_outputSpec.audioChannelCount;
83 0 : const bool firstUpdate = !m_lastGainsPos.has_value();
84 0 : m_channelGains.resize(channelsCount);
85 :
86 0 : for (audioch_t audioChNum = 0; audioChNum < channelsCount; ++audioChNum) {
87 0 : const float gain = dsp::balanceGain(pan, audioChNum) * volume;
88 0 : if (firstUpdate) {
89 0 : m_channelGains[audioChNum].initWithValue(gain);
90 : } else {
91 0 : m_channelGains[audioChNum].setTargetValue(gain);
92 : }
93 : }
94 :
95 0 : m_lastGainsPos = pos;
96 :
97 0 : if (m_volume.hasAutomation() || m_pan.hasAutomation()) {
98 : //! NOTE Avoid flooding m_automatedControlParamsChanges with updates the UI couldn't display anyway
99 0 : constexpr volume_db_t VOLUME_MINIMAL_VALUABLE_DIFF = volume_db_t::make(0.1f);
100 0 : constexpr balance_t PAN_MINIMAL_VALUABLE_DIFF = balance_t::make(0.01f);
101 :
102 0 : bool volumeChanged = false;
103 0 : bool panChanged = false;
104 :
105 0 : if (m_lastSentAutomatedControlParams.has_value()) {
106 0 : volumeChanged = std::abs(volumeDb - m_lastSentAutomatedControlParams->volume) >= VOLUME_MINIMAL_VALUABLE_DIFF;
107 0 : panChanged = std::abs(panValue - m_lastSentAutomatedControlParams->balance) >= PAN_MINIMAL_VALUABLE_DIFF;
108 : } else {
109 0 : volumeChanged = panChanged = true;
110 : }
111 :
112 0 : if (volumeChanged || panChanged) {
113 0 : m_lastSentAutomatedControlParams = AutomatedControlParams { volumeDb, panValue };
114 0 : m_automatedControlParamsChanges.send(*m_lastSentAutomatedControlParams);
115 : }
116 : }
117 0 : }
118 :
119 0 : void AutomationControlNode::doSelfProcess(float* buffer, samples_t samplesPerChannel)
120 : {
121 0 : if (m_volume.hasAutomation() || m_pan.hasAutomation()) {
122 0 : const secs_t pos = currentPos();
123 0 : if (m_lastGainsPos != pos) {
124 0 : updateChannelGains(pos);
125 : }
126 : }
127 :
128 0 : const audioch_t channelsCount = m_outputSpec.audioChannelCount;
129 :
130 0 : for (size_t s = 0; s < samplesPerChannel; ++s) {
131 0 : const size_t frameOffset = s * channelsCount;
132 0 : for (size_t ch = 0; ch < channelsCount; ++ch) {
133 0 : const size_t idx = frameOffset + ch;
134 0 : dsp::SmoothLinearValue<float>& gain = m_channelGains[ch];
135 0 : buffer[idx] = buffer[idx] * gain.getValue();
136 0 : gain.tick();
137 : }
138 : }
139 0 : }
|