64 * Is passed through the ring buffer |
64 * Is passed through the ring buffer |
65 * from the the jack-writing thread (callback) to the relpipe-writing thread. |
65 * from the the jack-writing thread (callback) to the relpipe-writing thread. |
66 */ |
66 */ |
67 struct MidiMessage { |
67 struct MidiMessage { |
68 uint8_t buffer[4096] = {0}; |
68 uint8_t buffer[4096] = {0}; |
69 uint32_t size; |
69 size_t size; |
70 uint32_t time; |
70 /** |
|
71 * Time in micro seconds; |
|
72 * starts on the first recorded note/message. |
|
73 */ |
|
74 relpipe::common::type::Integer time; |
71 }; |
75 }; |
72 |
76 |
73 /** |
77 /** |
74 * JACK callbacks (called from the real-time thread) |
78 * JACK callbacks (called from the real-time thread) |
75 */ |
79 */ |
76 class RealTimeContext { |
80 class RealTimeContext { |
|
81 private: |
|
82 jack_nframes_t startFrame = 0; |
77 public: |
83 public: |
78 jack_client_t* jackClient = nullptr; |
84 jack_client_t* jackClient = nullptr; |
79 jack_port_t* jackPort = nullptr; |
85 jack_port_t* jackPort = nullptr; |
80 jack_ringbuffer_t* ringBuffer = nullptr; |
86 jack_ringbuffer_t* ringBuffer = nullptr; |
81 |
87 |
86 |
92 |
87 int processCallback(jack_nframes_t frames) { |
93 int processCallback(jack_nframes_t frames) { |
88 void* buffer = jack_port_get_buffer(jackPort, frames); |
94 void* buffer = jack_port_get_buffer(jackPort, frames); |
89 if (buffer == nullptr) throw JackException(L"Unable to get port buffer."); // TODO: exception in RT callback? |
95 if (buffer == nullptr) throw JackException(L"Unable to get port buffer."); // TODO: exception in RT callback? |
90 |
96 |
|
97 jack_nframes_t lastFrame = jack_last_frame_time(jackClient); |
|
98 |
91 for (jack_nframes_t i = 0, eventCount = jack_midi_get_event_count(buffer); i < eventCount; i++) { |
99 for (jack_nframes_t i = 0, eventCount = jack_midi_get_event_count(buffer); i < eventCount; i++) { |
92 jack_midi_event_t event; |
100 jack_midi_event_t event; |
93 int noData = jack_midi_event_get(&event, buffer, i); |
101 int noData = jack_midi_event_get(&event, buffer, i); |
94 if (noData) continue; |
102 if (noData) continue; |
|
103 |
|
104 if (startFrame == 0) startFrame = lastFrame; |
95 |
105 |
96 if (event.size > sizeof (MidiMessage::buffer)) { |
106 if (event.size > sizeof (MidiMessage::buffer)) { |
97 // TODO: should not printf in RT callback: |
107 // TODO: should not printf in RT callback: |
98 fwprintf(stderr, L"Error: MIDI message was too large → skipping event. Maximum allowed size: %lu bytes.\n", sizeof (MidiMessage::buffer)); |
108 fwprintf(stderr, L"Error: MIDI message was too large → skipping event. Maximum allowed size: %lu bytes.\n", sizeof (MidiMessage::buffer)); |
99 } else if (jack_ringbuffer_write_space(ringBuffer) >= sizeof (MidiMessage)) { |
109 } else if (jack_ringbuffer_write_space(ringBuffer) >= sizeof (MidiMessage)) { |
100 MidiMessage m; |
110 MidiMessage m; |
101 m.time = event.time; |
111 m.time = lastFrame - startFrame + event.time; |
|
112 m.time = m.time * 1000 * 1000 / jack_get_sample_rate(jackClient); |
102 m.size = event.size; |
113 m.size = event.size; |
103 memcpy(m.buffer, event.buffer, event.size); |
114 memcpy(m.buffer, event.buffer, event.size); |
104 jack_ringbuffer_write(ringBuffer, (const char *) &m, sizeof (MidiMessage)); |
115 jack_ringbuffer_write(ringBuffer, (const char *) &m, sizeof (MidiMessage)); |
105 } else { |
116 } else { |
106 // TODO: should not printf in RT callback: |
117 // TODO: should not printf in RT callback: |
123 } |
134 } |
124 |
135 |
125 } realTimeContext; |
136 } realTimeContext; |
126 |
137 |
127 static void writeRecord(std::shared_ptr<relpipe::writer::RelationalWriter> writer, |
138 static void writeRecord(std::shared_ptr<relpipe::writer::RelationalWriter> writer, |
128 relpipe::common::type::StringX eventType, relpipe::common::type::Integer channel, |
139 relpipe::common::type::StringX eventType, |
|
140 relpipe::common::type::Integer time, |
|
141 relpipe::common::type::Integer channel, |
129 relpipe::common::type::Boolean noteOn, relpipe::common::type::Integer pitch, relpipe::common::type::Integer velocity, |
142 relpipe::common::type::Boolean noteOn, relpipe::common::type::Integer pitch, relpipe::common::type::Integer velocity, |
130 relpipe::common::type::Integer controllerId, relpipe::common::type::Integer value, |
143 relpipe::common::type::Integer controllerId, relpipe::common::type::Integer value, |
131 relpipe::common::type::StringX raw) { |
144 relpipe::common::type::StringX raw) { |
132 writer->writeAttribute(eventType); |
145 writer->writeAttribute(eventType); |
|
146 writer->writeAttribute(&time, typeid (time)); |
133 writer->writeAttribute(&channel, typeid (channel)); |
147 writer->writeAttribute(&channel, typeid (channel)); |
134 writer->writeAttribute(¬eOn, typeid (noteOn)); |
148 writer->writeAttribute(¬eOn, typeid (noteOn)); |
135 writer->writeAttribute(&pitch, typeid (pitch)); |
149 writer->writeAttribute(&pitch, typeid (pitch)); |
136 writer->writeAttribute(&velocity, typeid (velocity)); |
150 writer->writeAttribute(&velocity, typeid (velocity)); |
137 writer->writeAttribute(&controllerId, typeid (controllerId)); |
151 writer->writeAttribute(&controllerId, typeid (controllerId)); |
143 if (event->size == 0) { |
157 if (event->size == 0) { |
144 return; |
158 return; |
145 } else { |
159 } else { |
146 uint8_t type = event->buffer[0] & 0xF0; |
160 uint8_t type = event->buffer[0] & 0xF0; |
147 uint8_t channel = event->buffer[0] & 0x0F; |
161 uint8_t channel = event->buffer[0] & 0x0F; |
148 |
162 jack_time_t time = event->time; |
149 // TODO: write timestamp, message number |
|
150 |
163 |
151 if ((type == 0x90 || type == 0x80) && event->size == 3) { |
164 if ((type == 0x90 || type == 0x80) && event->size == 3) { |
152 writeRecord(writer, L"note", channel, type == 0x90, event->buffer[1], event->buffer[2], 0, 0, toHex(event)); |
165 writeRecord(writer, L"note", time, channel, type == 0x90, event->buffer[1], event->buffer[2], 0, 0, toHex(event)); |
153 } else if (type == 0xB0 && event->size == 3) { |
166 } else if (type == 0xB0 && event->size == 3) { |
154 writeRecord(writer, L"control", channel, false, 0, 0, event->buffer[1], event->buffer[2], toHex(event)); |
167 writeRecord(writer, L"control", time, channel, false, 0, 0, event->buffer[1], event->buffer[2], toHex(event)); |
155 } else if (event->buffer[0] == 0xF0) { |
168 } else if (event->buffer[0] == 0xF0) { |
156 writeRecord(writer, L"sysex", channel, false, 0, 0, 0, 0, toHex(event)); |
169 writeRecord(writer, L"sysex", time, channel, false, 0, 0, 0, 0, toHex(event)); |
157 } else { |
170 } else { |
158 writeRecord(writer, L"unknown", channel, false, 0, 0, 0, 0, toHex(event)); |
171 writeRecord(writer, L"unknown", time, channel, false, 0, 0, 0, 0, toHex(event)); |
159 } |
172 } |
160 } |
173 } |
161 } |
174 } |
162 |
175 |
163 relpipe::common::type::StringX toHex(MidiMessage* event) { |
176 relpipe::common::type::StringX toHex(MidiMessage* event) { |
365 if (configuration.listConnections) listConnections(writer); |
378 if (configuration.listConnections) listConnections(writer); |
366 if (configuration.listProperties) listProperties(writer); |
379 if (configuration.listProperties) listProperties(writer); |
367 if (!configuration.listMidiMessages) return; |
380 if (!configuration.listMidiMessages) return; |
368 |
381 |
369 metadata.push_back({L"event", TypeId::STRING}); |
382 metadata.push_back({L"event", TypeId::STRING}); |
|
383 metadata.push_back({L"time", TypeId::INTEGER}); |
370 metadata.push_back({L"channel", TypeId::INTEGER}); |
384 metadata.push_back({L"channel", TypeId::INTEGER}); |
371 metadata.push_back({L"note_on", TypeId::BOOLEAN}); |
385 metadata.push_back({L"note_on", TypeId::BOOLEAN}); |
372 metadata.push_back({L"note_pitch", TypeId::INTEGER}); |
386 metadata.push_back({L"note_pitch", TypeId::INTEGER}); |
373 metadata.push_back({L"note_velocity", TypeId::INTEGER}); |
387 metadata.push_back({L"note_velocity", TypeId::INTEGER}); |
374 metadata.push_back({L"controller_id", TypeId::INTEGER}); |
388 metadata.push_back({L"controller_id", TypeId::INTEGER}); |