Line data Source code
1 : #include "audio/effects/looper.h"
2 : #include "audio/effects/effect_factory.h"
3 :
4 : #include <ostream>
5 : #include <algorithm>
6 : #include <cmath>
7 :
8 : namespace Amplitron {
9 :
10 2 : static EffectRegistrar<Looper> reg("Looper");
11 :
12 128 : Looper::Looper() {
13 224 : params_ = {
14 96 : {"Loop Level", 0.80f, 0.0f, 1.0f, 0.80f, "", "Playback volume of the recorded loop mixed with live input."},
15 32 : {"Crossfade", 5.0f, 0.0f, 20.0f, 5.0f, "ms", "Crossfade length at the loop boundary to reduce clicks/pops."},
16 192 : };
17 96 : ensure_capacity();
18 96 : const float sr = static_cast<float>(std::max(sample_rate_, 1));
19 96 : loop_level_alpha_ = 1.0f - std::exp(-1.0f / (sr * kLoopLevelSmoothingSeconds));
20 96 : crossfade_alpha_ = 1.0f - std::exp(-1.0f / (sr * kLoopLevelSmoothingSeconds));
21 96 : reset();
22 224 : }
23 :
24 57 : void Looper::set_sample_rate(int sample_rate) {
25 57 : Effect::set_sample_rate(sample_rate);
26 57 : const float sr = static_cast<float>(std::max(sample_rate_, 1));
27 57 : loop_level_alpha_ = 1.0f - std::exp(-1.0f / (sr * kLoopLevelSmoothingSeconds));
28 57 : crossfade_alpha_ = 1.0f - std::exp(-1.0f / (sr * kLoopLevelSmoothingSeconds));
29 57 : ensure_capacity();
30 57 : reset();
31 57 : }
32 :
33 153 : void Looper::ensure_capacity() {
34 153 : const int sr = std::max(sample_rate_, 1);
35 153 : const int cap = std::max(sr * kMaxSeconds, 1);
36 153 : if (cap == max_samples_) return;
37 111 : max_samples_ = cap;
38 111 : buffer_l_.assign(static_cast<size_t>(max_samples_), 0.0f);
39 111 : buffer_r_.assign(static_cast<size_t>(max_samples_), 0.0f);
40 51 : }
41 :
42 210 : void Looper::reset() {
43 210 : state_rt_ = State::Empty;
44 210 : has_loop_rt_ = false;
45 210 : record_pos_ = 0;
46 210 : playhead_ = 0;
47 210 : loop_length_ = 0;
48 210 : loop_level_smoothed_ = clamp(params_[0].value, 0.0f, 1.0f);
49 210 : crossfade_ms_smoothed_ = clamp(params_[1].value, 0.0f, 20.0f);
50 210 : pending_commands_.store(0, std::memory_order_relaxed);
51 210 : publish_ui_snapshot();
52 210 : }
53 :
54 87 : void Looper::request_record_toggle() {
55 87 : pending_commands_.fetch_or(CmdRecordToggle, std::memory_order_relaxed);
56 87 : }
57 :
58 18 : void Looper::request_play_toggle() {
59 18 : pending_commands_.fetch_or(CmdPlayToggle, std::memory_order_relaxed);
60 18 : }
61 :
62 21 : void Looper::request_overdub_toggle() {
63 21 : pending_commands_.fetch_or(CmdOverdubToggle, std::memory_order_relaxed);
64 21 : }
65 :
66 6 : void Looper::request_clear() {
67 6 : pending_commands_.fetch_or(CmdClear, std::memory_order_relaxed);
68 6 : }
69 :
70 285 : int Looper::crossfade_samples_rt(float ms) const {
71 285 : const int xf = static_cast<int>(std::round((ms / 1000.0f) * static_cast<float>(sample_rate_)));
72 285 : return std::clamp(xf, 0, std::max(loop_length_ / 2, 0));
73 : }
74 :
75 564 : void Looper::publish_ui_snapshot() {
76 564 : ui_state_.store(static_cast<uint32_t>(state_rt_), std::memory_order_relaxed);
77 564 : ui_has_loop_.store(has_loop_rt_ ? 1 : 0, std::memory_order_relaxed);
78 564 : ui_loop_length_samples_.store(loop_length_, std::memory_order_relaxed);
79 564 : ui_playhead_samples_.store(playhead_, std::memory_order_relaxed);
80 564 : }
81 :
82 9 : void Looper::clear_loop_rt() {
83 9 : has_loop_rt_ = false;
84 9 : loop_length_ = 0;
85 9 : record_pos_ = 0;
86 9 : playhead_ = 0;
87 9 : state_rt_ = State::Empty;
88 8 : }
89 :
90 48 : void Looper::start_recording_rt() {
91 48 : has_loop_rt_ = false;
92 48 : loop_length_ = 0;
93 48 : record_pos_ = 0;
94 48 : playhead_ = 0;
95 48 : state_rt_ = State::Recording;
96 48 : }
97 :
98 45 : void Looper::stop_recording_rt_and_play_rt() {
99 45 : loop_length_ = std::clamp(record_pos_, 0, max_samples_);
100 45 : const int min_len = static_cast<int>(std::round(kMinLoopSeconds * static_cast<float>(sample_rate_)));
101 45 : if (loop_length_ < min_len) {
102 3 : clear_loop_rt();
103 3 : return;
104 : }
105 42 : has_loop_rt_ = true;
106 42 : playhead_ = 0;
107 42 : state_rt_ = State::Playing;
108 15 : }
109 :
110 15 : void Looper::toggle_play_rt() {
111 15 : if (!has_loop_rt_) return;
112 15 : if (state_rt_ == State::Playing || state_rt_ == State::Overdubbing) {
113 9 : state_rt_ = State::Idle;
114 9 : } else if (state_rt_ == State::Idle || state_rt_ == State::Empty) {
115 6 : state_rt_ = State::Playing;
116 2 : }
117 5 : }
118 :
119 18 : void Looper::toggle_overdub_rt() {
120 18 : if (!has_loop_rt_) return;
121 18 : if (state_rt_ == State::Overdubbing) {
122 6 : state_rt_ = State::Playing;
123 14 : } else if (state_rt_ == State::Playing) {
124 9 : state_rt_ = State::Overdubbing;
125 6 : } else if (state_rt_ == State::Idle) {
126 3 : state_rt_ = State::Overdubbing;
127 1 : }
128 6 : }
129 :
130 354 : void Looper::apply_pending_commands() {
131 354 : const uint32_t cmds = pending_commands_.exchange(0, std::memory_order_relaxed);
132 354 : if (cmds == 0) return;
133 :
134 132 : if (cmds & CmdClear) {
135 6 : clear_loop_rt();
136 2 : }
137 :
138 132 : if (cmds & CmdRecordToggle) {
139 87 : if (state_rt_ == State::Recording) {
140 39 : stop_recording_rt_and_play_rt();
141 13 : } else {
142 48 : start_recording_rt();
143 : }
144 29 : }
145 :
146 132 : if (cmds & CmdPlayToggle) {
147 18 : if (state_rt_ == State::Recording) {
148 3 : stop_recording_rt_and_play_rt();
149 16 : } else if (state_rt_ == State::Empty || state_rt_ == State::Idle ||
150 6 : state_rt_ == State::Playing || state_rt_ == State::Overdubbing) {
151 15 : toggle_play_rt();
152 5 : }
153 6 : }
154 :
155 132 : if (cmds & CmdOverdubToggle) {
156 21 : if (state_rt_ != State::Recording) {
157 18 : toggle_overdub_rt();
158 6 : }
159 7 : }
160 118 : }
161 :
162 264 : void Looper::process(float* buffer, int num_samples) {
163 264 : if (!enabled_) {
164 3 : apply_pending_commands();
165 3 : publish_ui_snapshot();
166 3 : return;
167 : }
168 261 : process_core(buffer, nullptr, num_samples, false);
169 88 : }
170 :
171 90 : void Looper::process_stereo(float* left, float* right, int num_samples) {
172 90 : if (!enabled_) {
173 0 : apply_pending_commands();
174 0 : publish_ui_snapshot();
175 0 : return;
176 : }
177 90 : process_core(left, right, num_samples, true);
178 30 : }
179 :
180 351 : void Looper::process_core(float* left, float* right, int num_samples, bool stereo) {
181 351 : apply_pending_commands();
182 :
183 351 : const float loop_level_target = clamp(params_[0].value, 0.0f, 1.0f);
184 351 : const float crossfade_target_ms = clamp(params_[1].value, 0.0f, 20.0f);
185 351 : crossfade_ms_smoothed_ += crossfade_alpha_ * (crossfade_target_ms - crossfade_ms_smoothed_);
186 351 : const int cap = max_samples_;
187 351 : if (cap <= 0) {
188 0 : publish_ui_snapshot();
189 0 : return;
190 : }
191 :
192 351 : if (state_rt_ == State::Recording) {
193 282903 : for (int i = 0; i < num_samples; ++i) {
194 282855 : if (record_pos_ >= cap) {
195 3 : stop_recording_rt_and_play_rt();
196 3 : break;
197 : }
198 282852 : buffer_l_[record_pos_] = left[i];
199 282852 : if (stereo && right) buffer_r_[record_pos_] = right[i];
200 282852 : ++record_pos_;
201 94284 : }
202 17 : }
203 :
204 386 : if (has_loop_rt_ && loop_length_ > 0 &&
205 294 : (state_rt_ == State::Playing || state_rt_ == State::Overdubbing)) {
206 285 : const int xf = crossfade_samples_rt(crossfade_ms_smoothed_);
207 90417 : for (int i = 0; i < num_samples; ++i) {
208 90132 : loop_level_smoothed_ += loop_level_alpha_ * (loop_level_target - loop_level_smoothed_);
209 90132 : const float loop_level = loop_level_smoothed_;
210 90132 : const int pos = playhead_;
211 90132 : float loop_l = buffer_l_[pos];
212 90132 : float loop_r = (stereo && right) ? buffer_r_[pos] : loop_l;
213 :
214 90132 : if (xf > 0 && pos >= loop_length_ - xf) {
215 2163 : const int t = pos - (loop_length_ - xf); // 0..xf-1
216 2163 : const float w_end = static_cast<float>(xf - t) / static_cast<float>(xf);
217 2163 : const float w_start = 1.0f - w_end;
218 2163 : const int start_pos = t;
219 2163 : loop_l = buffer_l_[pos] * w_end + buffer_l_[start_pos] * w_start;
220 2163 : if (stereo && right) {
221 720 : loop_r = buffer_r_[pos] * w_end + buffer_r_[start_pos] * w_start;
222 240 : } else {
223 962 : loop_r = loop_l;
224 : }
225 721 : }
226 :
227 90132 : const float in_l = left[i];
228 90132 : const float in_r = (stereo && right) ? right[i] : in_l;
229 :
230 90132 : float out_l = in_l + loop_l * loop_level;
231 90132 : float out_r = in_r + loop_r * loop_level;
232 :
233 90132 : if (state_rt_ == State::Overdubbing) {
234 24576 : buffer_l_[pos] = soft_clip(buffer_l_[pos] + in_l);
235 24576 : if (stereo && right) {
236 15360 : buffer_r_[pos] = soft_clip(buffer_r_[pos] + in_r);
237 5120 : }
238 8192 : }
239 :
240 90132 : left[i] = soft_clip(out_l);
241 90132 : if (stereo && right) right[i] = soft_clip(out_r);
242 :
243 90132 : ++playhead_;
244 90132 : if (playhead_ >= loop_length_) playhead_ = 0;
245 30044 : }
246 190 : } else {
247 : // Keep smoothing responsive even when not actively mixing the loop.
248 66 : loop_level_smoothed_ += loop_level_alpha_ * (loop_level_target - loop_level_smoothed_);
249 : }
250 :
251 351 : publish_ui_snapshot();
252 117 : }
253 :
254 18 : std::ostream& operator<<(std::ostream& os, Looper::State s)
255 : {
256 18 : switch (s)
257 : {
258 2 : case Looper::State::Empty:
259 3 : return os << "Empty";
260 :
261 2 : case Looper::State::Idle:
262 3 : return os << "Idle";
263 :
264 2 : case Looper::State::Recording:
265 3 : return os << "Recording";
266 :
267 2 : case Looper::State::Playing:
268 3 : return os << "Playing";
269 :
270 2 : case Looper::State::Overdubbing:
271 3 : return os << "Overdubbing";
272 :
273 2 : default:
274 3 : return os << "Unknown";
275 : }
276 6 : }
277 :
278 :
279 : } // namespace Amplitron
|