Line data Source code
1 : #pragma once
2 :
3 : #include "audio/effects/effect.h"
4 :
5 : #include<ostream>
6 : #include <atomic>
7 : #include <cmath>
8 : #include <cstdint>
9 : #include <vector>
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 :
41 : // --- UI control (thread-safe) ---
42 : void request_record_toggle();
43 : void request_play_toggle();
44 : void request_overdub_toggle();
45 : void request_clear();
46 :
47 : // --- UI status snapshot (thread-safe) ---
48 208 : State state() const { return static_cast<State>(ui_state_.load(std::memory_order_relaxed)); }
49 156 : bool has_loop() const { return ui_has_loop_.load(std::memory_order_relaxed) != 0; }
50 124 : int loop_length_samples() const { return ui_loop_length_samples_.load(std::memory_order_relaxed); }
51 260 : int playhead_samples() const { return ui_playhead_samples_.load(std::memory_order_relaxed); }
52 :
53 : private:
54 : static constexpr int kMaxSeconds = 60;
55 : static constexpr float kMinLoopSeconds = 0.10f;
56 : static constexpr float kLoopLevelSmoothingSeconds = 0.02f;
57 :
58 : enum CommandBits : uint32_t {
59 : CmdRecordToggle = 1u << 0,
60 : CmdPlayToggle = 1u << 1,
61 : CmdOverdubToggle = 1u << 2,
62 : CmdClear = 1u << 3,
63 : };
64 :
65 : // Params (saved in presets): loop playback level + crossfade length.
66 : std::vector<EffectParam> params_;
67 :
68 : // Preallocated buffers (full capacity, mono or stereo).
69 : std::vector<float> buffer_l_;
70 : std::vector<float> buffer_r_;
71 : int max_samples_ = 0;
72 :
73 : // Audio-thread state (not atomic; only touched in process/process_stereo).
74 : State state_rt_ = State::Empty;
75 : bool has_loop_rt_ = false;
76 : int record_pos_ = 0;
77 : int playhead_ = 0;
78 : int loop_length_ = 0;
79 :
80 : float loop_level_smoothed_ = 0.80f;
81 : float loop_level_alpha_ = 0.0f;
82 : float crossfade_ms_smoothed_ = 5.0f;
83 : float crossfade_alpha_ = 0.0f;
84 : // UI-visible atomics (written from audio thread, read by GUI thread).
85 : std::atomic<uint32_t> ui_state_{static_cast<uint32_t>(State::Empty)};
86 : std::atomic<int> ui_has_loop_{0};
87 : std::atomic<int> ui_loop_length_samples_{0};
88 : std::atomic<int> ui_playhead_samples_{0};
89 :
90 : // UI -> audio thread command mailbox (bitmask).
91 : std::atomic<uint32_t> pending_commands_{0};
92 :
93 : // Helpers
94 : void ensure_capacity();
95 : void apply_pending_commands();
96 : void publish_ui_snapshot();
97 : void clear_loop_rt();
98 : void start_recording_rt();
99 : void stop_recording_rt_and_play_rt();
100 : void toggle_play_rt();
101 : void toggle_overdub_rt();
102 :
103 151572 : static inline float soft_clip(float x) {
104 131092 : const float ax = std::fabs(x);
105 131092 : return x / (1.0f + ax);
106 : }
107 :
108 : inline int crossfade_samples_rt(float ms) const;
109 : inline void process_core(float* left, float* right, int num_samples, bool stereo);
110 : };
111 :
112 : std::ostream& operator<<(std::ostream& os, Looper::State s);
113 : } // namespace Amplitron
114 :
|