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