56 * from the relpipe-reading thread to the jack-writing thread (callback). |
56 * from the relpipe-reading thread to the jack-writing thread (callback). |
57 */ |
57 */ |
58 struct MidiMessage { |
58 struct MidiMessage { |
59 uint8_t buffer[4096] = {0}; |
59 uint8_t buffer[4096] = {0}; |
60 size_t size; |
60 size_t size; |
61 jack_nframes_t time; |
61 /** |
|
62 * Time in micro seconds; |
|
63 * starts at the beginning of the playback. |
|
64 */ |
|
65 relpipe::common::type::Integer time; |
62 }; |
66 }; |
63 |
67 |
64 /** |
68 /** |
65 * JACK callbacks (called from the real-time thread) |
69 * JACK callbacks (called from the real-time thread) |
66 */ |
70 */ |
67 class RealTimeContext { |
71 class RealTimeContext { |
|
72 private: |
|
73 jack_nframes_t startFrame = 0; |
68 public: |
74 public: |
69 jack_client_t* jackClient = nullptr; |
75 jack_client_t* jackClient = nullptr; |
70 jack_port_t* jackPort = nullptr; |
76 jack_port_t* jackPort = nullptr; |
71 jack_ringbuffer_t* ringBuffer = nullptr; |
77 jack_ringbuffer_t* ringBuffer = nullptr; |
72 |
78 |
74 pthread_cond_t processingDone = PTHREAD_COND_INITIALIZER; |
80 pthread_cond_t processingDone = PTHREAD_COND_INITIALIZER; |
75 |
81 |
76 const int RING_BUFFER_SIZE = 100; |
82 const int RING_BUFFER_SIZE = 100; |
77 |
83 |
78 int processCallback(jack_nframes_t frames) { |
84 int processCallback(jack_nframes_t frames) { |
79 const size_t queuedMessages = jack_ringbuffer_read_space(ringBuffer) / sizeof (MidiMessage); |
85 jack_nframes_t lastFrame = jack_last_frame_time(jackClient); |
|
86 |
80 void* jackPortBuffer = jack_port_get_buffer(jackPort, frames); // jack_port_get_buffer() must be called outside the loop, otherwise it will multiply the MIDI events |
87 void* jackPortBuffer = jack_port_get_buffer(jackPort, frames); // jack_port_get_buffer() must be called outside the loop, otherwise it will multiply the MIDI events |
81 jack_midi_clear_buffer(jackPortBuffer); // TODO: clean buffer? |
88 jack_midi_clear_buffer(jackPortBuffer); |
82 for (size_t i = 0; i < queuedMessages && i < frames; i++) { |
89 MidiMessage m; |
83 // TODO: correct timing? |
90 while (jack_ringbuffer_peek(ringBuffer, (char*) &m, sizeof (m)) == sizeof (m)) { |
84 MidiMessage m; |
91 if (startFrame == 0) startFrame = lastFrame; |
85 jack_ringbuffer_read(ringBuffer, (char*) &m, sizeof (MidiMessage)); |
92 |
86 jack_midi_data_t* midiData = jack_midi_event_reserve(jackPortBuffer, m.time, m.size); |
93 jack_nframes_t eventFrame = std::max(0L, (m.time * jack_get_sample_rate(jackClient) / 1000 / 1000) - (lastFrame - startFrame)); |
87 if (midiData) memcpy(midiData, m.buffer, m.size); |
94 // If std::max() does its job, the message comes from the past and missed its cycle → we will send it now rather than lose it completely. |
88 else /* error: not enough space TODO: store and send later */; |
95 |
|
96 if (eventFrame < frames) { |
|
97 jack_midi_data_t* midiData = jack_midi_event_reserve(jackPortBuffer, eventFrame, m.size); |
|
98 if (midiData) { |
|
99 memcpy(midiData, m.buffer, m.size); |
|
100 jack_ringbuffer_read_advance(ringBuffer, sizeof (m)); |
|
101 } // else = error: not enough space; will be kept in the ring buffer and sent in the next cycle |
|
102 } else { |
|
103 /** |
|
104 * This message does not belong to this cycle. |
|
105 * Its time will come later. |
|
106 * For now, it stays in the ring buffer. |
|
107 */ |
|
108 break; |
|
109 } |
89 } |
110 } |
90 |
111 |
91 if (pthread_mutex_trylock(&processingLock) == 0) { |
112 if (pthread_mutex_trylock(&processingLock) == 0) { |
92 pthread_cond_signal(&processingDone); |
113 pthread_cond_signal(&processingDone); |
93 pthread_mutex_unlock(&processingLock); |
114 pthread_mutex_unlock(&processingLock); |
144 else if (name == L"disconnect") return Event::DISCONNECT; |
165 else if (name == L"disconnect") return Event::DISCONNECT; |
145 else return Event::UNKNOWN; |
166 else return Event::UNKNOWN; |
146 } |
167 } |
147 |
168 |
148 Event event = Event::UNKNOWN; |
169 Event event = Event::UNKNOWN; |
|
170 relpipe::common::type::Integer time; |
149 relpipe::common::type::Integer channel; |
171 relpipe::common::type::Integer channel; |
150 relpipe::common::type::Boolean noteOn; |
172 relpipe::common::type::Boolean noteOn; |
151 relpipe::common::type::Integer notePitch; |
173 relpipe::common::type::Integer notePitch; |
152 relpipe::common::type::Integer noteVelocity; |
174 relpipe::common::type::Integer noteVelocity; |
153 relpipe::common::type::Integer controllerId; |
175 relpipe::common::type::Integer controllerId; |
247 RelationContext::RecordContext& rec = rel.recordContext; |
269 RelationContext::RecordContext& rec = rel.recordContext; |
248 |
270 |
249 const auto attributeName = rel.attributes[rec.attributeIndex].getAttributeName(); |
271 const auto attributeName = rel.attributes[rec.attributeIndex].getAttributeName(); |
250 |
272 |
251 if (attributeName == L"event") rec.event = rec.parseEventType(value); |
273 if (attributeName == L"event") rec.event = rec.parseEventType(value); |
|
274 else if (attributeName == L"time") rec.time = std::stoll(value); |
252 else if (attributeName == L"channel") rec.channel = std::stoi(value); |
275 else if (attributeName == L"channel") rec.channel = std::stoi(value); |
253 else if (attributeName == L"controller_id") rec.controllerId = std::stoi(value); |
276 else if (attributeName == L"controller_id") rec.controllerId = std::stoi(value); |
254 else if (attributeName == L"controller_value") rec.controllerValue = std::stoi(value); |
277 else if (attributeName == L"controller_value") rec.controllerValue = std::stoi(value); |
255 else if (attributeName == L"note_on") rec.noteOn = value == L"true"; |
278 else if (attributeName == L"note_on") rec.noteOn = value == L"true"; |
256 else if (attributeName == L"note_pitch") rec.notePitch = std::stoi(value); |
279 else if (attributeName == L"note_pitch") rec.notePitch = std::stoi(value); |
261 |
284 |
262 rec.attributeIndex++; |
285 rec.attributeIndex++; |
263 |
286 |
264 if (rec.attributeIndex == rel.attributes.size()) { |
287 if (rec.attributeIndex == rel.attributes.size()) { |
265 |
288 |
266 while (continueProcessing && jack_ringbuffer_write_space(realTimeContext.ringBuffer) < sizeof (MidiMessage)) waitForRTCycle(); // should not happen, the real-time thread should be faster; see also note in endOfPipe() |
289 while (continueProcessing && jack_ringbuffer_write_space(realTimeContext.ringBuffer) < sizeof (MidiMessage)) waitForRTCycle(); // wait, if we are faster than the real-time thread |
267 if (!continueProcessing) return; |
290 if (!continueProcessing) return; |
268 |
291 |
269 MidiMessage m; |
292 MidiMessage m; |
270 |
293 |
271 // TODO: correct timing? |
294 m.time = rec.time; |
272 m.time = 0; |
|
273 m.size = 0; |
295 m.size = 0; |
274 |
296 |
275 if (rec.event == RelationContext::RecordContext::Event::NOTE) { |
297 if (rec.event == RelationContext::RecordContext::Event::NOTE) { |
276 m.size = 3; |
298 m.size = 3; |
277 m.buffer[0] = (rec.noteOn ? 0x90 : 0x80) | rec.channel; |
299 m.buffer[0] = (rec.noteOn ? 0x90 : 0x80) | rec.channel; |