Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
mock_midi_system.hpp
Go to the documentation of this file.
1#pragma once
2
11
12#include <pulp/midi/device.hpp>
13
14#include <algorithm>
15#include <memory>
16#include <mutex>
17#include <string>
18#include <utility>
19#include <vector>
20
21namespace pulp::midi {
22
23class MockMidiSystem : public MidiSystem {
24 public:
26 void add_port(const std::string& id, const std::string& name, bool is_input = true) {
27 {
28 std::lock_guard<std::mutex> lk(mu_);
29 remove_locked(id, is_input);
30 ports_.push_back({id, name, is_input, !is_input});
31 }
32 notify();
33 }
34
36 void remove_port(const std::string& id, bool is_input = true) {
37 {
38 std::lock_guard<std::mutex> lk(mu_);
39 remove_locked(id, is_input);
40 }
41 notify();
42 }
43
46 void set_ports_silently(std::vector<MidiPortInfo> ports) {
47 std::lock_guard<std::mutex> lk(mu_);
48 ports_ = std::move(ports);
49 }
50
51 void notify() {
53 {
54 std::lock_guard<std::mutex> lk(mu_);
55 cb = port_change_cb_;
56 }
57 if (cb)
58 cb();
59 }
60
62 int inject(const std::string& id, const MidiEvent& event) {
63 std::vector<std::shared_ptr<InputState>> targets;
64 {
65 std::lock_guard<std::mutex> lk(mu_);
66 for (auto& s : inputs_) {
67 if (s->open && s->port_id == id)
68 targets.push_back(s);
69 }
70 }
71 int delivered = 0;
72 for (auto& s : targets) {
73 if (s->callback) {
74 s->callback(event);
75 ++delivered;
76 }
77 }
78 return delivered;
79 }
80
85 void drop_connections(const std::string& id) {
86 std::lock_guard<std::mutex> lk(mu_);
87 for (auto& s : inputs_) {
88 if (s->port_id == id)
89 s->open = false;
90 }
91 inputs_.erase(std::remove_if(inputs_.begin(), inputs_.end(),
92 [](const std::shared_ptr<InputState>& s) { return !s->open; }),
93 inputs_.end());
94 }
95
97 int open_input_count(const std::string& id) const {
98 std::lock_guard<std::mutex> lk(mu_);
99 int n = 0;
100 for (auto& s : inputs_)
101 if (s->open && s->port_id == id)
102 ++n;
103 return n;
104 }
105
107 int total_opens() const {
108 std::lock_guard<std::mutex> lk(mu_);
109 return total_opens_;
110 }
111
113 void fail_next_opens(int count) {
114 std::lock_guard<std::mutex> lk(mu_);
115 failing_opens_ = count;
116 }
117
118 std::vector<MidiPortInfo> enumerate_inputs() override {
119 return list(true);
120 }
121 std::vector<MidiPortInfo> enumerate_outputs() override {
122 return list(false);
123 }
124
125 std::unique_ptr<MidiInput> create_input() override {
126 return std::make_unique<Input>(this);
127 }
128
129 std::unique_ptr<MidiOutput> create_output() override {
130 return std::make_unique<Output>();
131 }
132
134 std::lock_guard<std::mutex> lk(mu_);
135 port_change_cb_ = std::move(cb);
136 }
137
138 private:
139 struct InputState {
140 std::string port_id;
141 MidiInputCallback callback;
142 bool open = false;
143 };
144
145 class Input : public MidiInput {
146 public:
147 explicit Input(MockMidiSystem* owner)
148 : owner_(owner), state_(std::make_shared<InputState>()) {}
149 ~Input() override {
150 close();
151 }
152
153 bool open(const std::string& port_id, MidiInputCallback callback) override {
154 std::lock_guard<std::mutex> lk(owner_->mu_);
155 if (owner_->failing_opens_ > 0) {
156 --owner_->failing_opens_;
157 return false;
158 }
159 const bool exists =
160 std::any_of(owner_->ports_.begin(), owner_->ports_.end(),
161 [&](const MidiPortInfo& p) { return p.is_input && p.id == port_id; });
162 if (!exists)
163 return false;
164 state_->port_id = port_id;
165 state_->callback = std::move(callback);
166 state_->open = true;
167 owner_->inputs_.push_back(state_);
168 ++owner_->total_opens_;
169 return true;
170 }
171
172 void close() override {
173 std::lock_guard<std::mutex> lk(owner_->mu_);
174 if (!state_->open)
175 return;
176 state_->open = false;
177 auto& v = owner_->inputs_;
178 v.erase(std::remove(v.begin(), v.end(), state_), v.end());
179 }
180
181 bool is_open() const override {
182 std::lock_guard<std::mutex> lk(owner_->mu_);
183 return state_->open;
184 }
185
186 private:
187 MockMidiSystem* owner_;
188 std::shared_ptr<InputState> state_;
189 };
190
191 class Output : public MidiOutput {
192 public:
193 bool open(const std::string&) override {
194 open_ = true;
195 return true;
196 }
197 void close() override {
198 open_ = false;
199 }
200 bool is_open() const override {
201 return open_;
202 }
203 void send(const MidiEvent&) override {}
204
205 private:
206 bool open_ = false;
207 };
208
209 std::vector<MidiPortInfo> list(bool inputs) const {
210 std::lock_guard<std::mutex> lk(mu_);
211 std::vector<MidiPortInfo> out;
212 for (auto& p : ports_)
213 if (p.is_input == inputs)
214 out.push_back(p);
215 return out;
216 }
217
218 void remove_locked(const std::string& id, bool is_input) {
219 ports_.erase(std::remove_if(ports_.begin(), ports_.end(),
220 [&](const MidiPortInfo& p) {
221 return p.id == id && p.is_input == is_input;
222 }),
223 ports_.end());
224 }
225
226 mutable std::mutex mu_;
227 std::vector<MidiPortInfo> ports_;
228 std::vector<std::shared_ptr<InputState>> inputs_;
229 PortChangeCallback port_change_cb_;
230 int total_opens_ = 0;
231 int failing_opens_ = 0;
232};
233
234} // namespace pulp::midi
virtual void close()=0
virtual bool is_open() const =0
virtual void close()=0
virtual bool is_open() const =0
Definition device.hpp:52
std::function< void()> PortChangeCallback
Register a callback for MIDI port changes (device plug/unplug).
Definition device.hpp:62
Definition mock_midi_system.hpp:23
void add_port(const std::string &id, const std::string &name, bool is_input=true)
Add (or replace) a port and notify the port-change callback.
Definition mock_midi_system.hpp:26
std::vector< MidiPortInfo > enumerate_outputs() override
Definition mock_midi_system.hpp:121
std::vector< MidiPortInfo > enumerate_inputs() override
Definition mock_midi_system.hpp:118
std::unique_ptr< MidiOutput > create_output() override
Definition mock_midi_system.hpp:129
std::unique_ptr< MidiInput > create_input() override
Definition mock_midi_system.hpp:125
int total_opens() const
Total successful input opens since construction.
Definition mock_midi_system.hpp:107
void remove_port(const std::string &id, bool is_input=true)
Remove a port and notify the port-change callback.
Definition mock_midi_system.hpp:36
void notify()
Definition mock_midi_system.hpp:51
void fail_next_opens(int count)
Make the next count open attempts fail, to model a busy device.
Definition mock_midi_system.hpp:113
int open_input_count(const std::string &id) const
Number of currently open inputs on id.
Definition mock_midi_system.hpp:97
void set_ports_silently(std::vector< MidiPortInfo > ports)
Change the port list without notifying, to model a notification that never arrived.
Definition mock_midi_system.hpp:46
void drop_connections(const std::string &id)
Sever the connection of every open input on id while the port stays listed, as when an OS replaces an...
Definition mock_midi_system.hpp:85
void set_port_change_callback(PortChangeCallback cb) override
Definition mock_midi_system.hpp:133
int inject(const std::string &id, const MidiEvent &event)
Deliver event to every open input on id. Returns how many received it.
Definition mock_midi_system.hpp:62
@ MidiOutput
Definition live_dsp_telemetry.hpp:42
Definition custom_node_events.hpp:26
std::function< void(const MidiEvent &event)> MidiInputCallback
Definition device.hpp:21
@ p
Definition input_events.hpp:40
@ v
Definition input_events.hpp:41
STL namespace.
Timestamped MIDI event combining a choc::midi::ShortMessage with timing info.
Definition message.hpp:32