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