50 class JackHandler : public relpipe::reader::handlers::RelationalReaderStringHandler { |
50 class JackHandler : public relpipe::reader::handlers::RelationalReaderStringHandler { |
51 private: |
51 private: |
52 Configuration& configuration; |
52 Configuration& configuration; |
53 std::wstring_convert<std::codecvt_utf8<wchar_t>> convertor; // TODO: local system encoding |
53 std::wstring_convert<std::codecvt_utf8<wchar_t>> convertor; // TODO: local system encoding |
54 |
54 |
55 jack_client_t* jackClient = nullptr; |
|
56 jack_port_t* jackPort = nullptr; |
|
57 jack_ringbuffer_t* ringBuffer = nullptr; |
|
58 |
|
59 std::atomic<bool> continueProcessing{true}; |
55 std::atomic<bool> continueProcessing{true}; |
60 |
|
61 const int RING_BUFFER_SIZE = 100; |
|
62 |
56 |
63 /** |
57 /** |
64 * Is passed through the ring buffer |
58 * Is passed through the ring buffer |
65 * from the relpipe-reading thread to the jack-writing thread (callback). |
59 * from the relpipe-reading thread to the jack-writing thread (callback). |
66 */ |
60 */ |
67 struct MidiMessage { |
61 struct MidiMessage { |
68 uint8_t buffer[4096]; |
62 uint8_t buffer[4096]; |
69 uint32_t size; |
63 uint32_t size; |
70 uint32_t time; |
64 uint32_t time; |
71 }; |
65 }; |
|
66 |
|
67 /** |
|
68 * JACK callbacks (called from the real-time thread) |
|
69 */ |
|
70 class RealTimeContext { |
|
71 public: |
|
72 jack_client_t* jackClient = nullptr; |
|
73 jack_port_t* jackPort = nullptr; |
|
74 jack_ringbuffer_t* ringBuffer = nullptr; |
|
75 |
|
76 const int RING_BUFFER_SIZE = 100; |
|
77 |
|
78 int processCallback(jack_nframes_t frames) { |
|
79 const size_t queuedMessages = jack_ringbuffer_read_space(ringBuffer) / sizeof (MidiMessage); |
|
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 |
|
81 jack_midi_clear_buffer(jackPortBuffer); // TODO: clean buffer? |
|
82 for (size_t i = 0; i < queuedMessages && i < frames; i++) { |
|
83 // TODO: correct timing? |
|
84 MidiMessage m; |
|
85 jack_ringbuffer_read(ringBuffer, (char*) &m, sizeof (MidiMessage)); |
|
86 jack_midi_data_t* midiData = jack_midi_event_reserve(jackPortBuffer, m.time, m.size); |
|
87 memcpy(midiData, m.buffer, m.size); |
|
88 } |
|
89 return 0; |
|
90 } |
|
91 |
|
92 static int processCallback(jack_nframes_t frames, void* arg) { |
|
93 return static_cast<RealTimeContext*> (arg)->processCallback(frames); |
|
94 } |
|
95 |
|
96 } realTimeContext; |
72 |
97 |
73 /** |
98 /** |
74 * Temporary storage of values read from relational input. |
99 * Temporary storage of values read from relational input. |
75 */ |
100 */ |
76 class RelationContext { |
101 class RelationContext { |
121 public: |
146 public: |
122 |
147 |
123 JackHandler(Configuration& configuration) : configuration(configuration) { |
148 JackHandler(Configuration& configuration) : configuration(configuration) { |
124 // Initialize JACK connection: |
149 // Initialize JACK connection: |
125 std::string clientName = convertor.to_bytes(configuration.jackClientName); |
150 std::string clientName = convertor.to_bytes(configuration.jackClientName); |
126 jackClient = jack_client_open(clientName.c_str(), JackNullOption, nullptr); |
151 realTimeContext.jackClient = jack_client_open(clientName.c_str(), JackNullOption, nullptr); |
127 if (jackClient == nullptr) throw JackException(L"Could not create JACK client."); |
152 if (realTimeContext.jackClient == nullptr) throw JackException(L"Could not create JACK client."); |
128 |
153 |
129 ringBuffer = jack_ringbuffer_create(RING_BUFFER_SIZE * sizeof (MidiMessage)); |
154 realTimeContext.ringBuffer = jack_ringbuffer_create(realTimeContext.RING_BUFFER_SIZE * sizeof (MidiMessage)); |
130 |
155 |
131 jack_set_process_callback(jackClient, relpipe::out::jack::dequeueMessages, this); |
156 jack_set_process_callback(realTimeContext.jackClient, RealTimeContext::processCallback, &realTimeContext); |
132 // TODO: report also other events (connections etc.) |
157 // TODO: report also other events (connections etc.) |
133 |
158 |
134 jackPort = jack_port_register(jackClient, "output", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); |
159 realTimeContext.jackPort = jack_port_register(realTimeContext.jackClient, "output", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); |
135 if (jackPort == nullptr) throw JackException(L"Could not register port."); |
160 if (realTimeContext.jackPort == nullptr) throw JackException(L"Could not register port."); |
136 |
161 |
137 if (mlockall(MCL_CURRENT | MCL_FUTURE)) fwprintf(stderr, L"Warning: Can not lock memory.\n"); |
162 if (mlockall(MCL_CURRENT | MCL_FUTURE)) fwprintf(stderr, L"Warning: Can not lock memory.\n"); |
138 |
163 |
139 int jackError = jack_activate(jackClient); |
164 int jackError = jack_activate(realTimeContext.jackClient); |
140 if (jackError) throw JackException(L"Could not activate client."); |
165 if (jackError) throw JackException(L"Could not activate client."); |
141 |
166 |
142 // Wait for a port connection, because it does not make much sense to send MIDI events nowhere: |
167 // Wait for a port connection, because it does not make much sense to send MIDI events nowhere: |
143 while (jack_port_connected(jackPort) < configuration.requiredJackConnections) usleep(10000); |
168 while (jack_port_connected(realTimeContext.jackPort) < configuration.requiredJackConnections) usleep(10000); |
144 |
169 |
145 // TODO: configurable auto-connection to another client/port |
170 // TODO: configurable auto-connection to another client/port |
146 } |
171 } |
147 |
172 |
148 void startRelation(const relpipe::common::type::StringX name, std::vector<relpipe::reader::handlers::AttributeMetadata> attributes) override { |
173 void startRelation(const relpipe::common::type::StringX name, std::vector<relpipe::reader::handlers::AttributeMetadata> attributes) override { |
160 else if (attributeName == L"note_on") relationContext.indexOfNoteOn = i; |
185 else if (attributeName == L"note_on") relationContext.indexOfNoteOn = i; |
161 else if (attributeName == L"note_pitch") relationContext.indexOfNotePitch = i; |
186 else if (attributeName == L"note_pitch") relationContext.indexOfNotePitch = i; |
162 else if (attributeName == L"note_velocity") relationContext.indexOfNoteVelocity = i; |
187 else if (attributeName == L"note_velocity") relationContext.indexOfNoteVelocity = i; |
163 else if (attributeName == L"raw") relationContext.indexOfRaw = i; |
188 else if (attributeName == L"raw") relationContext.indexOfRaw = i; |
164 } |
189 } |
165 |
190 |
166 // TODO: check also data types and skipt relation if important attributes are missing |
191 // TODO: check also data types and skipt relation if important attributes are missing |
167 // TODO: configurable relation name |
192 // TODO: configurable relation name |
168 } |
193 } |
169 |
194 |
170 void attribute(const relpipe::common::type::StringX& value) override { |
195 void attribute(const relpipe::common::type::StringX& value) override { |
194 } |
219 } |
195 |
220 |
196 relationContext.recordContext.attributeIndex++; |
221 relationContext.recordContext.attributeIndex++; |
197 |
222 |
198 if (relationContext.recordContext.attributeIndex == relationContext.attributeCount) { |
223 if (relationContext.recordContext.attributeIndex == relationContext.attributeCount) { |
199 if (jack_ringbuffer_write_space(ringBuffer) >= sizeof (MidiMessage)) { |
224 if (jack_ringbuffer_write_space(realTimeContext.ringBuffer) >= sizeof (MidiMessage)) { |
200 MidiMessage m; |
225 MidiMessage m; |
201 |
226 |
202 // TODO: convert relationContext.recordContext to m |
227 // TODO: convert relationContext.recordContext to m |
203 m.time = 0; |
228 m.time = 0; |
204 m.size = 3; |
229 m.size = 3; |
205 m.buffer[0] = 0x90; |
230 m.buffer[0] = 0x90; |
206 m.buffer[1] = 0x24; |
231 m.buffer[1] = 0x24; |
207 m.buffer[2] = 0x40; |
232 m.buffer[2] = 0x40; |
208 |
233 |
209 jack_ringbuffer_write(ringBuffer, (const char *) &m, sizeof (m)); |
234 jack_ringbuffer_write(realTimeContext.ringBuffer, (const char *) &m, sizeof (m)); |
210 } else { |
235 } else { |
211 fwprintf(stderr, L"Error: ring buffer is full → skipping event.\n"); |
236 fwprintf(stderr, L"Error: ring buffer is full → skipping event.\n"); |
212 } |
237 } |
213 |
238 |
214 relationContext.recordContext = RelationContext::RecordContext(); |
239 relationContext.recordContext = RelationContext::RecordContext(); |
218 |
243 |
219 void endOfPipe() { |
244 void endOfPipe() { |
220 // TODO: send optional (configurable) MIDI events |
245 // TODO: send optional (configurable) MIDI events |
221 |
246 |
222 // Wait until the ring buffer is empty |
247 // Wait until the ring buffer is empty |
223 while (continueProcessing && jack_ringbuffer_read_space(ringBuffer)) usleep(1000); |
248 while (continueProcessing && jack_ringbuffer_read_space(realTimeContext.ringBuffer)) usleep(1000); |
224 usleep(1000000); |
249 usleep(1000000); |
225 // TODO: better waiting (ringBuffer might be empty, but events have not been sent to JACK yet) |
250 // TODO: better waiting (ringBuffer might be empty, but events have not been sent to JACK yet) |
226 // TODO: optionally mute all; probably enabled by default |
251 // TODO: optionally mute all; probably enabled by default |
227 } |
252 } |
228 |
253 |
229 /** |
|
230 * TODO: use separate class/instance for JACK callbacks to minimize scope and prevent mistakes. |
|
231 */ |
|
232 int dequeueMessages(jack_nframes_t frames) { |
|
233 const size_t queuedMessages = jack_ringbuffer_read_space(ringBuffer) / sizeof (MidiMessage); |
|
234 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 |
|
235 jack_midi_clear_buffer(jackPortBuffer); // TODO: clean buffer? |
|
236 for (size_t i = 0; i < queuedMessages && i < frames; i++) { |
|
237 // TODO: correct timing? |
|
238 MidiMessage m; |
|
239 jack_ringbuffer_read(ringBuffer, (char*) &m, sizeof (MidiMessage)); |
|
240 jack_midi_data_t* midiData = jack_midi_event_reserve(jackPortBuffer, m.time, m.size); |
|
241 memcpy(midiData, m.buffer, m.size); |
|
242 } |
|
243 return 0; |
|
244 } |
|
245 |
|
246 void finish(int sig) { |
254 void finish(int sig) { |
247 continueProcessing = false; |
255 continueProcessing = false; |
248 } |
256 } |
249 |
257 |
250 virtual ~JackHandler() { |
258 virtual ~JackHandler() { |
251 // Close JACK connection: |
259 // Close JACK connection: |
252 jack_deactivate(jackClient); |
260 jack_deactivate(realTimeContext.jackClient); |
253 jack_client_close(jackClient); |
261 jack_client_close(realTimeContext.jackClient); |
254 jack_ringbuffer_free(ringBuffer); |
262 jack_ringbuffer_free(realTimeContext.ringBuffer); |
255 } |
263 } |
256 |
264 |
257 }; |
265 }; |
258 |
|
259 int dequeueMessages(jack_nframes_t frames, void* arg) { |
|
260 JackHandler* instance = (JackHandler*) arg; |
|
261 return instance->dequeueMessages(frames); |
|
262 } |
|
263 |
266 |
264 } |
267 } |
265 } |
268 } |
266 } |
269 } |