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) 2023 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 : #ifndef MUSE_AUDIO_SMOOTHLINEARVALUE_H
24 : #define MUSE_AUDIO_SMOOTHLINEARVALUE_H
25 :
26 : #include <algorithm>
27 : #include <type_traits>
28 :
29 : namespace muse::audio::dsp {
30 : template<typename ValueT, int _initialSteps = 1024, typename StepT = double>
31 : class SmoothLinearValue
32 : {
33 : public:
34 0 : SmoothLinearValue(const ValueT& startValue = ValueT(0))
35 : {
36 : static_assert(std::is_floating_point<StepT>::value, "Internal step type needs to be floating point.");
37 0 : m_targetValue = m_currentValue = startValue;
38 0 : m_currentSteps = (StepT)_initialSteps;
39 0 : m_targetSteps = 0;
40 0 : m_deltaValue = 0;
41 0 : }
42 :
43 0 : void setSteps(int steps)
44 : {
45 0 : m_currentSteps = (StepT)std::max(1, steps);
46 0 : }
47 :
48 : /** Advance one tick by just adding the delta. Can lead to error accumulation.
49 : * @return true if target was reached
50 : */
51 0 : bool tick()
52 : {
53 0 : if (m_targetSteps <= 0) {
54 0 : m_currentValue = m_targetValue; // get rid of error accumulation
55 0 : return true;
56 : }
57 :
58 0 : m_targetSteps--;
59 0 : m_currentValue += m_deltaValue;
60 0 : return false;
61 : }
62 :
63 : /** Advance one tick and use multiplication to avoid error accumulation
64 : * @return true if target was reached
65 : */
66 : bool preciseTick()
67 : {
68 : if (m_targetSteps <= 0) {
69 : return true;
70 : }
71 :
72 : m_targetSteps--;
73 : m_currentValue = m_targetValue - m_targetSteps * m_deltaValue; // if n reaches 0, _value will be exactly targetvalue
74 : return false;
75 : }
76 :
77 : /** Advance one tick without checking whether target was reached
78 : * can lead to error accumulation for large n and small delta
79 : * warning! will overshoot target if called too many times
80 : */
81 : void tickUnchecked()
82 : {
83 : m_targetSteps--;
84 : m_currentValue += m_deltaValue;
85 : }
86 :
87 : /** Advance one tick without checking whether target was reached
88 : * uses an extra multiplication to avoid error accumulation
89 : * warning! will overshoot target if called too many times
90 : */
91 : void preciseTickUnchecked()
92 : {
93 : m_targetSteps--;
94 : m_currentValue = m_targetValue - m_targetSteps * m_deltaValue;
95 : }
96 :
97 : /** Advance multiple ticks
98 : * @param n num ticks to advance
99 : */
100 : void advanceTicks(int n)
101 : {
102 : m_targetSteps -= (double)n;
103 : if (m_targetSteps < 0) {
104 : m_targetSteps = 0;
105 : }
106 : m_currentValue = m_targetValue - m_targetSteps * m_deltaValue;
107 : }
108 :
109 : /// Set the value instantly (for initialization)
110 0 : void initWithValue(const ValueT& val)
111 : {
112 0 : m_targetValue = m_currentValue = val;
113 0 : m_targetSteps = 0;
114 0 : }
115 :
116 : /// Set value and target instantly
117 : void initWithValueAndTarget(const ValueT& val, const ValueT& target)
118 : {
119 : initWithValue(val);
120 : setTargetValue(target);
121 : }
122 :
123 : /** Sets the target value which is reached after "steps" calls to smoothTick */
124 0 : void setTargetValue(const ValueT& val)
125 : {
126 0 : if (m_targetValue != val) {
127 0 : m_targetValue = val;
128 0 : m_deltaValue = ValueT((1.0 / m_currentSteps) * (m_targetValue - m_currentValue));
129 0 : m_targetSteps = m_currentSteps;
130 : }
131 0 : }
132 :
133 : /** @returns the value the smoothed value will reach eventually */
134 0 : const ValueT& getTargetValue() const
135 : {
136 0 : return m_targetValue;
137 : }
138 :
139 : /** @return the current (smoothed) value */
140 0 : inline const ValueT& getValue() const
141 : {
142 0 : return m_currentValue;
143 : }
144 :
145 : /** Go to target immediately.
146 : * @returns value
147 : */
148 0 : inline const ValueT& setToTarget()
149 : {
150 0 : m_currentValue = m_targetValue;
151 0 : m_targetSteps = 0.f;
152 0 : return m_currentValue;
153 : }
154 :
155 : /// check if the smoothed value isn't moving and has a specific (target)value
156 0 : bool isStaticAtValue(const ValueT& static_value) const
157 : {
158 0 : return isAtTargetValue() && (getTargetValue() == static_value);
159 : }
160 :
161 0 : inline bool isAtTargetValue() const
162 : {
163 0 : return m_targetSteps <= (StepT)0;
164 : }
165 :
166 : int getRemainingSteps() const
167 : {
168 : return (int)m_targetSteps;
169 : }
170 :
171 : int smoothingSteps() const
172 : {
173 : return (int)m_currentSteps;
174 : }
175 :
176 : protected:
177 : ValueT m_currentValue;
178 : ValueT m_targetValue;
179 : ValueT m_deltaValue;
180 : StepT m_targetSteps;
181 : StepT m_currentSteps;
182 : };
183 : } // namespace muse::audio::dsp
184 :
185 : #endif // MUSE_AUDIO_SMOOTHLINEARVALUE_H
|