Line data Source code
1 : #pragma once
2 :
3 : #include <atomic>
4 : #include <cmath>
5 : #include <cstdint>
6 : #include <ostream>
7 : #include <vector>
8 :
9 : #include "audio/effects/core/effect.h"
10 :
11 : namespace Amplitron {
12 :
13 : /**
14 : * A simple in-chain looper (record / play / overdub / clear).
15 : *
16 : * Design goals:
17 : * - No allocations inside process/process_stereo (buffer is preallocated).
18 : * - Thread-safe UI visibility for state and loop position via atomics.
19 : * - Minimal, predictable state machine for practice workflows.
20 : */
21 : class Looper : public Effect {
22 : public:
23 : enum class State : uint32_t {
24 : Empty = 0, // no loop in memory
25 : Idle, // loop exists but not playing
26 : Recording,
27 : Playing,
28 : Overdubbing,
29 : };
30 :
31 : Looper();
32 :
33 : void process(float* buffer, int num_samples) override;
34 : void process_stereo(float* left, float* right, int num_samples) override;
35 : void set_sample_rate(int sample_rate) override;
36 : void reset() override;
37 27 : const char* name() const override { return "Looper"; }
38 6 : const char* type_id() const override { return "Looper"; }
39 156 : std::vector<EffectParam>& params() override { return params_; }
40 0 : const std::vector<EffectParam>& params() const override { return params_; }
41 :
42 : // --- UI control (thread-safe) ---
43 : void request_record_toggle();
44 : void request_play_toggle();
45 : void request_overdub_toggle();
46 : void request_clear();
47 :
48 : // --- UI status snapshot (thread-safe) ---
49 208 : State state() const { return static_cast<State>(ui_state_.load(std::memory_order_relaxed)); }
50 156 : bool has_loop() const { return ui_has_loop_.load(std::memory_order_relaxed) != 0; }
51 93 : int loop_length_samples() const {
52 124 : return ui_loop_length_samples_.load(std::memory_order_relaxed);
53 : }
54 260 : int playhead_samples() const { return ui_playhead_samples_.load(std::memory_order_relaxed); }
55 :
56 : private:
57 : static constexpr int kMaxSeconds = 60;
58 : static constexpr float kMinLoopSeconds = 0.10f;
59 : static constexpr float kLoopLevelSmoothingSeconds = 0.02f;
60 :
61 : enum CommandBits : uint32_t {
62 : CmdRecordToggle = 1u << 0,
63 : CmdPlayToggle = 1u << 1,
64 : CmdOverdubToggle = 1u << 2,
65 : CmdClear = 1u << 3,
66 : };
67 :
68 : // Params (saved in presets): loop playback level + crossfade length.
69 : std::vector<EffectParam> params_;
70 :
71 : // Preallocated buffers (full capacity, mono or stereo).
72 : std::vector<float> buffer_l_;
73 : std::vector<float> buffer_r_;
74 : int max_samples_ = 0;
75 :
76 : // Audio-thread state (not atomic; only touched in process/process_stereo).
77 : State state_rt_ = State::Empty;
78 : bool has_loop_rt_ = false;
79 : int record_pos_ = 0;
80 : int playhead_ = 0;
81 : int loop_length_ = 0;
82 :
83 : float loop_level_smoothed_ = 0.80f;
84 : float loop_level_alpha_ = 0.0f;
85 : float crossfade_ms_smoothed_ = 5.0f;
86 : float crossfade_alpha_ = 0.0f;
87 : // UI-visible atomics (written from audio thread, read by GUI thread).
88 : std::atomic<uint32_t> ui_state_{static_cast<uint32_t>(State::Empty)};
89 : std::atomic<int> ui_has_loop_{0};
90 : std::atomic<int> ui_loop_length_samples_{0};
91 : std::atomic<int> ui_playhead_samples_{0};
92 :
93 : // UI -> audio thread command mailbox (bitmask).
94 : std::atomic<uint32_t> pending_commands_{0};
95 :
96 : // Helpers
97 : void ensure_capacity();
98 : void apply_pending_commands();
99 : void publish_ui_snapshot();
100 : void clear_loop_rt();
101 : void start_recording_rt();
102 : void stop_recording_rt_and_play_rt();
103 : void toggle_play_rt();
104 : void toggle_overdub_rt();
105 :
106 151572 : static inline float soft_clip(float x) {
107 131092 : const float ax = std::fabs(x);
108 131092 : return x / (1.0f + ax);
109 : }
110 :
111 : inline int crossfade_samples_rt(float ms) const;
112 : inline void process_core(float* left, float* right, int num_samples, bool stereo);
113 : };
114 :
115 : std::ostream& operator<<(std::ostream& os, Looper::State s);
116 : } // namespace Amplitron
|