Line data Source code
1 : #pragma once
2 : #include "audio/utils/spsc_queue.h"
3 : #include "midi/i_midi_manager.h"
4 :
5 : namespace Amplitron {
6 :
7 : #ifdef AMPLITRON_NO_MIDI
8 :
9 : // Stub implementation for non-desktop platforms (web, mobile)
10 : class MidiManager : public IMidiManager {
11 : friend class TestAccessor;
12 :
13 : public:
14 : MidiManager() = default;
15 : ~MidiManager() = default;
16 :
17 : bool initialize() override { return false; }
18 : void shutdown() override {}
19 :
20 : std::vector<std::string> get_available_ports() const override { return {}; }
21 : bool open_port(int) override { return false; }
22 : void close_port() override {}
23 : int current_port() const override { return -1; }
24 : std::string current_port_name() const override { return ""; }
25 : bool is_port_open() const override { return false; }
26 :
27 : void add_mapping(const MidiMapping&) override {}
28 : void remove_mapping(int) override {}
29 : void remove_mapping_for_param(const std::string&, const std::string&) override {}
30 : void clear_mappings() override {}
31 : const std::vector<MidiMapping>& mappings() const override {
32 : static std::vector<MidiMapping> empty;
33 : return empty;
34 : }
35 :
36 : void install_default_mappings() override {}
37 :
38 : void start_learn(MidiTargetType, const std::string&, const std::string&) override {}
39 : void cancel_learn() override {}
40 : bool is_learning() const override { return false; }
41 : std::string learn_status() const override { return ""; }
42 : const std::string& learn_effect_name() const override {
43 : static std::string empty;
44 : return empty;
45 : }
46 : const std::string& learn_param_name() const override {
47 : static std::string empty;
48 : return empty;
49 : }
50 :
51 : void poll(IAudioEngine&) override {}
52 : void save_config() const override {}
53 : void load_config() override {}
54 :
55 : void inject_event(const MidiEvent&) override {}
56 : };
57 :
58 : #else
59 :
60 : /**
61 : * @brief MIDI input manager with CC-to-parameter mapping and MIDI learn.
62 : *
63 : * Runs a lock-free SPSC queue between RtMidi's callback thread and the
64 : * GUI thread. The GUI thread calls poll() each frame to drain events and
65 : * route CC values through the existing engine.push_param_change() path.
66 : */
67 : class MidiManager : public IMidiManager {
68 : friend class TestAccessor;
69 :
70 : public:
71 : MidiManager();
72 : ~MidiManager();
73 :
74 : /** @brief Open the first available MIDI input port. @return true on success. */
75 : bool initialize() override;
76 :
77 : /** @brief Close the MIDI port and release resources. */
78 : void shutdown() override;
79 :
80 : // --- Port management ---
81 :
82 : /** @brief List available MIDI input port names. */
83 : std::vector<std::string> get_available_ports() const override;
84 :
85 : /** @brief Open a specific MIDI input port by index. @return true on success. */
86 : bool open_port(int port_index) override;
87 :
88 : /** @brief Close the currently open port. */
89 : void close_port() override;
90 :
91 : /** @brief Return the index of the currently open port, or -1 if none. */
92 0 : int current_port() const override { return current_port_; }
93 :
94 : /** @brief Return the name of the currently open port, or empty string. */
95 6 : std::string current_port_name() const override { return current_port_name_; }
96 :
97 : /** @brief Return true if a MIDI port is currently open. */
98 18 : bool is_port_open() const override { return current_port_ >= 0; }
99 :
100 : // --- Mapping management ---
101 :
102 : void add_mapping(const MidiMapping& mapping) override;
103 : void remove_mapping(int index) override;
104 : void remove_mapping_for_param(const std::string& effect_name,
105 : const std::string& param_name) override;
106 : void clear_mappings() override;
107 783 : const std::vector<MidiMapping>& mappings() const override { return mappings_; }
108 :
109 : /** @brief Install default CC mappings (CC7, CC11, CC64, CC74). */
110 : void install_default_mappings() override;
111 :
112 : // --- MIDI Learn ---
113 :
114 : /**
115 : * @brief Enter learn mode: the next CC event received will be bound to the given target.
116 : */
117 : void start_learn(MidiTargetType type, const std::string& effect_name,
118 : const std::string& param_name) override;
119 :
120 : /** @brief Cancel learn mode without creating a mapping. */
121 : void cancel_learn() override;
122 :
123 : /** @brief Return true if learn mode is active. */
124 1485 : bool is_learning() const override { return learn_active_; }
125 :
126 : /** @brief Human-readable status for the learn indicator, or empty. */
127 : std::string learn_status() const override;
128 :
129 18 : const std::string& learn_effect_name() const override { return learn_effect_name_; }
130 18 : const std::string& learn_param_name() const override { return learn_param_name_; }
131 :
132 : // --- Poll (called from GUI thread each frame) ---
133 :
134 : /**
135 : * @brief Drain the MIDI event queue and apply CC mappings.
136 : *
137 : * For each CC event:
138 : * - If learn mode is active, captures the CC and creates a mapping.
139 : * - Otherwise, resolves the mapping target and pushes the value to the engine.
140 : */
141 : void poll(IAudioEngine& engine) override;
142 :
143 : // --- Persistence ---
144 :
145 : /** @brief Save mappings and port preference to midi_config.json. */
146 : void save_config() const override;
147 :
148 : /** @brief Load mappings and port preference from midi_config.json. */
149 : void load_config() override;
150 :
151 : /**
152 : * @brief Push a MIDI event into the queue from test code.
153 : *
154 : * This is public so unit tests can inject events without hardware.
155 : */
156 : void inject_event(const MidiEvent& event) override;
157 :
158 : private:
159 : static void midi_callback(double timestamp, std::vector<unsigned char>* message,
160 : void* user_data);
161 :
162 : static std::string get_config_path();
163 :
164 : void* midi_in_ = nullptr; // RtMidiIn* (opaque to avoid header dependency)
165 : int current_port_ = -1;
166 : std::string current_port_name_;
167 :
168 : SPSCQueue<MidiEvent, 256> midi_queue_;
169 : std::vector<MidiMapping> mappings_;
170 :
171 : // Learn state
172 : bool learn_active_ = false;
173 : MidiTargetType learn_target_type_ = MidiTargetType::EffectParam;
174 : std::string learn_effect_name_;
175 : std::string learn_param_name_;
176 :
177 : // Helpers
178 : void apply_mapping(const MidiMapping& mapping, int cc_value, IAudioEngine& engine);
179 : std::string mappings_to_json() const;
180 : bool mappings_from_json(const std::string& json);
181 : };
182 :
183 : #endif // AMPLITRON_NO_MIDI
184 :
185 : } // namespace Amplitron
|