Line data Source code
1 : #include "midi/midi_manager.h"
2 : #ifndef __EMSCRIPTEN__
3 : #include <rtmidi/RtMidi.h>
4 : #endif
5 :
6 : #include <iostream>
7 :
8 : namespace Amplitron {
9 :
10 : // ---------------------------------------------------------------------------
11 : // Construction / destruction
12 : // ---------------------------------------------------------------------------
13 :
14 372 : MidiManager::MidiManager() = default;
15 :
16 372 : MidiManager::~MidiManager() {
17 279 : shutdown();
18 372 : }
19 :
20 : // ---------------------------------------------------------------------------
21 : // RtMidi callback — runs on RtMidi's internal thread, must be lock-free
22 : // ---------------------------------------------------------------------------
23 :
24 : #ifdef __EMSCRIPTEN__
25 : void MidiManager::midi_callback(double /*timestamp*/,
26 : std::vector<unsigned char>* /*message*/,
27 : void* /*user_data*/) {
28 : }
29 :
30 : bool MidiManager::initialize() { return true; }
31 : void MidiManager::shutdown() { close_port(); }
32 : std::vector<std::string> MidiManager::get_available_ports() const { return {}; }
33 : bool MidiManager::open_port(int /*port_index*/) { return false; }
34 : void MidiManager::close_port() { current_port_ = -1; current_port_name_.clear(); }
35 : #else
36 0 : void MidiManager::midi_callback(double /*timestamp*/,
37 : std::vector<unsigned char>* message,
38 : void* user_data) {
39 0 : if (!message || message->size() < 3) return;
40 :
41 0 : auto* self = static_cast<MidiManager*>(user_data);
42 0 : uint8_t status = (*message)[0];
43 :
44 : // Only handle Control Change messages (0xB0 .. 0xBF)
45 0 : if ((status & 0xF0) != 0xB0) return;
46 :
47 0 : MidiEvent event{};
48 0 : event.status = status;
49 0 : event.data1 = (*message)[1]; // CC number
50 0 : event.data2 = (*message)[2]; // CC value
51 0 : self->midi_queue_.try_push(event); // Drop if full — acceptable for CC
52 : }
53 :
54 : // ---------------------------------------------------------------------------
55 : // Port management
56 : // ---------------------------------------------------------------------------
57 :
58 15 : bool MidiManager::initialize() {
59 15 : if (midi_in_) return true; // Already initialized
60 :
61 5 : try {
62 25 : auto* rt = new RtMidiIn(RtMidi::UNSPECIFIED, "Amplitron MIDI");
63 10 : rt->ignoreTypes(true, true, true); // Ignore SysEx, timing, active sensing
64 10 : midi_in_ = rt;
65 10 : } catch (const RtMidiError& e) {
66 5 : std::cerr << "[MidiManager] RtMidi init failed: " << e.getMessage() << "\n";
67 5 : return false;
68 5 : }
69 :
70 : // Auto-open the first available port (if any)
71 10 : auto ports = get_available_ports();
72 10 : if (!ports.empty()) {
73 0 : open_port(0);
74 0 : }
75 10 : return true;
76 10 : }
77 :
78 297 : void MidiManager::shutdown() {
79 297 : close_port();
80 297 : if (midi_in_) {
81 10 : delete static_cast<RtMidiIn*>(midi_in_);
82 10 : midi_in_ = nullptr;
83 5 : }
84 297 : }
85 :
86 22 : std::vector<std::string> MidiManager::get_available_ports() const {
87 22 : std::vector<std::string> result;
88 22 : if (!midi_in_) return result;
89 :
90 12 : auto* rt = static_cast<RtMidiIn*>(midi_in_);
91 6 : try {
92 12 : unsigned int count = rt->getPortCount();
93 12 : result.reserve(count);
94 12 : for (unsigned int i = 0; i < count; ++i) {
95 0 : result.push_back(rt->getPortName(i));
96 0 : }
97 6 : } catch (const RtMidiError& e) {
98 0 : std::cerr << "[MidiManager] Failed to enumerate MIDI ports: "
99 0 : << e.getMessage() << "\n";
100 0 : }
101 6 : return result;
102 9 : }
103 :
104 12 : bool MidiManager::open_port(int port_index) {
105 12 : if (!midi_in_) return false;
106 :
107 6 : close_port();
108 :
109 6 : auto* rt = static_cast<RtMidiIn*>(midi_in_);
110 :
111 3 : try {
112 6 : unsigned int count = rt->getPortCount();
113 6 : if (port_index < 0 || static_cast<unsigned int>(port_index) >= count) return false;
114 :
115 0 : rt->setCallback(&MidiManager::midi_callback, this);
116 0 : rt->openPort(static_cast<unsigned int>(port_index), "Amplitron In");
117 0 : current_port_ = port_index;
118 0 : current_port_name_ = rt->getPortName(static_cast<unsigned int>(port_index));
119 0 : return true;
120 0 : } catch (const RtMidiError& e) {
121 0 : std::cerr << "[MidiManager] Failed to open port " << port_index
122 0 : << ": " << e.getMessage() << "\n";
123 : // Ensure port is closed on error
124 0 : try {
125 0 : rt->closePort();
126 0 : } catch (...) {}
127 0 : current_port_ = -1;
128 0 : current_port_name_.clear();
129 0 : return false;
130 0 : }
131 4 : }
132 :
133 309 : void MidiManager::close_port() {
134 309 : if (!midi_in_ || current_port_ < 0) return;
135 :
136 0 : auto* rt = static_cast<RtMidiIn*>(midi_in_);
137 0 : try {
138 0 : rt->cancelCallback();
139 0 : rt->closePort();
140 0 : } catch (...) {}
141 0 : current_port_ = -1;
142 0 : current_port_name_.clear();
143 104 : }
144 : #endif
145 :
146 : } // namespace Amplitron
|