1 /*
2 JackEngine.cpp
3
4 Copyright 2009-2011, Alan Calvert
5 Copyright 2014-2019, Will Godfrey & others
6
7 This file is part of yoshimi, which is free software: you can
8 redistribute it and/or modify it under the terms of the GNU General
9 Public License as published by the Free Software Foundation, either
10 version 2 of the License, or (at your option) any later version.
11
12 yoshimi is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with yoshimi. If not, see <http://www.gnu.org/licenses/>.
19
20 */
21
22 #include <errno.h>
23 #include <iostream>
24
25 #include <jack/midiport.h>
26 #include <jack/thread.h>
27 #include <unistd.h> // for usleep()
28
29 #include "Misc/Config.h"
30 #include "Misc/FormatFuncs.h"
31 #include "MusicIO/JackEngine.h"
32
33 using func::asString;
34 using func::asHexString;
35
36
JackEngine(SynthEngine * _synth,BeatTracker * _beatTracker)37 JackEngine::JackEngine(SynthEngine *_synth, BeatTracker *_beatTracker) :
38 MusicIO(_synth, _beatTracker),
39 jackClient(NULL),
40 internalbuff(0)
41 {
42 audio.jackSamplerate = 0;
43 audio.jackNframes = 0;
44 for (int i = 0; i < (2 * NUM_MIDI_PARTS + 2); ++i)
45 {
46 audio.ports[i] = NULL;
47 audio.portBuffs[i] = NULL;
48 }
49 midiPort = NULL;
50 }
51
52
connectServer(std::string server)53 bool JackEngine::connectServer(std::string server)
54 {
55 for (int tries = 0; tries < 3 && !jackClient; ++tries)
56 {
57 if (!openJackClient(server) && tries < 2)
58 {
59 synth->getRuntime().Log("Failed to open jack client, trying again", _SYS_::LogError);
60 usleep(3333);
61 }
62 }
63 if (jackClient)
64 {
65 synth->getRuntime().setRtprio(jack_client_max_real_time_priority(jackClient));
66 audio.jackSamplerate = jack_get_sample_rate(jackClient);
67 audio.jackNframes = jack_get_buffer_size(jackClient);
68 return true;
69 }
70 else
71 {
72 synth->getRuntime().Log("Failed to open jack client on server " + server);
73 }
74 return false;
75 }
76
77
openJackClient(std::string server)78 bool JackEngine::openJackClient(std::string server)
79 {
80 int jopts = JackNullOption;
81 jack_status_t jstatus;
82 std::string clientname = "yoshimi";
83 if (synth->getRuntime().nameTag.size())
84 clientname += ("-" + synth->getRuntime().nameTag);
85
86 //Andrew Deryabin: for multi-instance support add unique id to
87 //instances other then default (0)
88 unsigned int synthUniqueId = synth->getUniqueId();
89 if (synthUniqueId > 0)
90 {
91 char sUniqueId [256];
92 memset(sUniqueId, 0, sizeof(sUniqueId));
93 snprintf(sUniqueId, sizeof(sUniqueId), "%d", synthUniqueId);
94 clientname += ("-" + std::string(sUniqueId));
95 }
96 //std::cout << " C name " << clientname << std::endl;
97 bool named_server = server.size() > 0 && server.compare("default") != 0;
98 if (named_server)
99 jopts |= JackServerName;
100 if (!synth->getRuntime().startJack)
101 jopts |= JackNoStartServer;
102 #if defined(JACK_SESSION)
103 if (synth->getRuntime().restoreJackSession && synth->getRuntime().jackSessionUuid.size())
104 {
105 jopts |= JackSessionID;
106 if (named_server)
107 jackClient = jack_client_open(clientname.c_str(), (jack_options_t)jopts,
108 &jstatus, synth->getRuntime().jackServer.c_str(),
109 synth->getRuntime().jackSessionUuid.c_str());
110 else
111 jackClient = jack_client_open(clientname.c_str(), (jack_options_t)jopts,
112 &jstatus, synth->getRuntime().jackSessionUuid.c_str());
113 }
114 else
115 {
116 if (named_server)
117 jackClient = jack_client_open(clientname.c_str(), (jack_options_t)jopts,
118 &jstatus, synth->getRuntime().jackServer.c_str());
119 else
120 jackClient = jack_client_open(clientname.c_str(), (jack_options_t)jopts, &jstatus);
121 }
122 #else
123 if (named_server)
124 jackClient = jack_client_open(clientname.c_str(), (jack_options_t)jopts,
125 &jstatus, synth->getRuntime().jackServer.c_str());
126 else
127 jackClient = jack_client_open(clientname.c_str(), (jack_options_t)jopts, &jstatus);
128 #endif
129 if (jackClient)
130 return true;
131 else
132 synth->getRuntime().Log("Failed jack_client_open(), status: " + asHexString((int)jstatus), 1);
133 return false;
134 }
135
136
Start(void)137 bool JackEngine::Start(void)
138 {
139 bool jackPortsRegistered = true;
140 internalbuff = synth->getRuntime().Buffersize;
141 jack_set_xrun_callback(jackClient, _xrunCallback, this);
142 #if defined(JACK_SESSION)
143 if (jack_set_session_callback
144 && jack_set_session_callback(jackClient, _jsessionCallback, this))
145 synth->getRuntime().Log("Set jack session callback failed");
146 #endif
147
148 if (jack_set_process_callback(jackClient, _processCallback, this))
149 {
150 synth->getRuntime().Log("JackEngine failed to set process callback");
151 goto bail_out;
152 }
153
154 if (!latencyPrep())
155 {
156 synth->getRuntime().Log("Jack latency prep failed ");
157 goto bail_out;
158 }
159
160 if (!jack_activate(jackClient) && jackPortsRegistered)
161 {
162 if (!synth->getRuntime().restoreJackSession && synth->getRuntime().connectJackaudio && !connectJackPorts())
163 {
164 synth->getRuntime().Log("Failed to connect jack audio ports");
165 goto bail_out;
166 }
167 }
168 else
169 {
170 synth->getRuntime().Log("Failed to activate jack client");
171 goto bail_out;
172 }
173 /*
174 * TODO fix this - now moved to where it should be.
175 * Shows identical results but doesn't connect.
176 * Original 1.4.1 version also fails - it used to work.
177 */
178 /* pre V 1.3.0 was this:
179 if (Runtime.midiEngine == jack_midi and jack_connect(jackClient,Runtime.midiDevice.c_str(),jack_port_name(midi.port)))
180 Runtime.Log("Didn't find jack MIDI source '" + Runtime.midiDevice + "'");
181 */
182
183 // style-wise I think the next bit is the wrong place
184 /*if (synth->getRuntime().midiEngine == jack_midi
185 && !synth->getRuntime().midiDevice.empty()
186 && synth->getRuntime().midiDevice != "default")
187 {
188 if (jack_connect(jackClient, synth->getRuntime().midiDevice.c_str(), jack_port_name(midiPort)))
189 {
190 synth->getRuntime().Log("Didn't find jack MIDI source '"
191 + synth->getRuntime().midiDevice + "'", _SYS_::LogError);
192 synth->getRuntime().midiDevice = "";
193 }
194 }*/
195 return true;
196
197 bail_out:
198 Close();
199 return false;
200 }
201
202
Close(void)203 void JackEngine::Close(void)
204 {
205 if (synth->getRuntime().runSynth)
206 {
207 synth->getRuntime().runSynth = false;
208 }
209
210 if (NULL != jackClient)
211 {
212 int chk;
213 for (int chan = 0; chan < (2*NUM_MIDI_PARTS+2); ++chan)
214 {
215 if (NULL != audio.ports[chan])
216 jack_port_unregister(jackClient, audio.ports[chan]);
217 audio.ports[chan] = NULL;
218 }
219 if (NULL != midiPort)
220 {
221 if ((chk = jack_port_unregister(jackClient, midiPort)))
222 synth->getRuntime().Log("Failed to close jack client, status: " + asString(chk));
223 midiPort = NULL;
224 }
225 chk = jack_deactivate(jackClient);
226 if (chk)
227 synth->getRuntime().Log("Failed to close jack client, status: " + asString(chk));
228
229 jackClient = NULL;
230 }
231 }
232
233
registerAudioPort(int partnum)234 void JackEngine::registerAudioPort(int partnum)
235 {
236 int portnum = partnum * 2;
237 if (partnum >=0 && partnum < NUM_MIDI_PARTS)
238 {
239 if (audio.ports [portnum] != NULL)
240 {
241 synth->getRuntime().Log("Jack port " + asString(partnum) + " already registered!", 2);
242 return;
243 }
244 /* This has a hack to stop all enabled parts from resistering
245 * individual ports (at startup) if part is not configured for
246 * direct O/P.
247 */
248 std::string portName;
249 if (synth->part [partnum] && synth->partonoffRead(partnum) && (synth->part [partnum]->Paudiodest > 1))
250 {
251 portName = "track_" + asString(partnum + 1) + "_l";
252 audio.ports[portnum] = jack_port_register(jackClient, portName.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
253 portName = "track_" + asString(partnum + 1) + "_r";
254 audio.ports[portnum + 1] = jack_port_register(jackClient, portName.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
255
256 if (audio.ports [portnum])
257 {
258 synth->getRuntime().Log("Registered jack port " + asString(partnum + 1));
259 }
260 else
261 {
262 synth->getRuntime().Log("Error registering jack port " + asString(partnum + 1));
263 }
264 }
265 }
266 }
267
268
openAudio(void)269 bool JackEngine::openAudio(void)
270 {
271 if (jackClient == 0)
272 {
273 if (!connectServer(synth->getRuntime().audioDevice))
274 {
275 return false;
276 }
277 }
278 // Register mixed outputs
279 audio.ports[2 * NUM_MIDI_PARTS] = jack_port_register(jackClient, "left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
280 audio.ports[2 * NUM_MIDI_PARTS + 1] = jack_port_register(jackClient, "right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
281
282 bool jackPortsRegistered = true;
283 if (!audio.ports[2 * NUM_MIDI_PARTS] || !audio.ports[2 * NUM_MIDI_PARTS + 1])
284 jackPortsRegistered = false;
285
286 if (jackPortsRegistered)
287 return prepBuffers() && latencyPrep();
288 else
289 synth->getRuntime().Log("Failed to register jack audio ports");
290 Close();
291 return false;
292 }
293
294
openMidi(void)295 bool JackEngine::openMidi(void)
296 {
297 synth->setBPMAccurate(true);
298
299 if (jackClient == 0)
300 {
301 if (!connectServer(synth->getRuntime().midiDevice))
302 {
303 return false;
304 }
305 }
306
307 const char *port_name = "midi in";
308 midiPort = jack_port_register(jackClient, port_name,
309 JACK_DEFAULT_MIDI_TYPE,
310 JackPortIsInput, 0);
311 if (!midiPort)
312 {
313 synth->getRuntime().Log("Failed to register jack midi port");
314 return false;
315 }
316
317 std::cout << "client " << jackClient<< " device " << synth->getRuntime().midiDevice << " port " << jack_port_name(midiPort) << std::endl;
318 if (jack_connect(jackClient, synth->getRuntime().midiDevice.c_str(), jack_port_name(midiPort)))
319 {
320 synth->getRuntime().Log("Didn't find jack MIDI source '"
321 + synth->getRuntime().midiDevice + "'");
322 //synth->getRuntime().midiDevice = "";
323 }
324
325 return true;
326 }
327
328
connectJackPorts(void)329 bool JackEngine::connectJackPorts(void)
330 {
331 const char** playback_ports = jack_get_ports(jackClient, NULL, NULL,
332 JackPortIsPhysical|JackPortIsInput);
333 if (!playback_ports)
334 {
335 synth->getRuntime().Log("No physical jack playback ports found.");
336 return false;
337 }
338 int ret;
339 for (int port = 0; port < 2 && (NULL != audio.ports[port + NUM_MIDI_PARTS*2]); ++port)
340 {
341 const char *port_name = jack_port_name(audio.ports[port + NUM_MIDI_PARTS * 2]);
342 if ((ret = jack_connect(jackClient, port_name, playback_ports[port])))
343 {
344 if (ret == EEXIST)
345 {
346 synth->getRuntime().Log(std::string(port_name)
347 + " is already connected to jack port " + std::string(playback_ports[port])
348 + ", status " + asString(ret));
349 }
350 else
351 {
352 synth->getRuntime().Log("Cannot connect " + std::string(port_name)
353 + " to jack port " + std::string(playback_ports[port])
354 + ", status " + asString(ret));
355 return false;
356 }
357 }
358 }
359 return true;
360 }
361
362
clientId(void)363 int JackEngine::clientId(void)
364 {
365 if (jackClient)
366 return long(jack_client_thread_id(jackClient));
367 else
368 return -1;
369 }
370
371
clientName(void)372 std::string JackEngine::clientName(void)
373 {
374 if (jackClient)
375 return std::string(jack_get_client_name(jackClient));
376 else
377 synth->getRuntime().Log("clientName() with null jackClient");
378 return std::string("Oh, yoshimi :-(");
379 }
380
381
_processCallback(jack_nframes_t nframes,void * arg)382 int JackEngine::_processCallback(jack_nframes_t nframes, void *arg)
383 {
384 return static_cast<JackEngine*>(arg)->processCallback(nframes);
385 }
386
387
processCallback(jack_nframes_t nframes)388 int JackEngine::processCallback(jack_nframes_t nframes)
389 {
390 bool okaudio = true;
391 bool okmidi = true;
392
393 if (midiPort) {
394 // input exists, using jack midi
395 handleBeatValues(nframes);
396 okmidi = processMidi(nframes);
397 }
398 if (audio.ports[NUM_MIDI_PARTS * 2] && audio.ports[NUM_MIDI_PARTS * 2 + 1])
399 // (at least) main outputs exist, using jack audio
400 okaudio = processAudio(nframes);
401 return (okaudio && okmidi) ? 0 : -1;
402 }
403
404
processAudio(jack_nframes_t nframes)405 bool JackEngine::processAudio(jack_nframes_t nframes)
406 {
407 // Part buffers
408 for (int port = 0; port < 2 * NUM_MIDI_PARTS; ++port)
409 {
410 if (audio.ports [port])
411 {
412 audio.portBuffs[port] =
413 (float*)jack_port_get_buffer(audio.ports[port], nframes);
414 if (!audio.portBuffs[port])
415 {
416 synth->getRuntime().Log("Failed to get jack audio port buffer: " + asString(port));
417 return false;
418 }
419 }
420 }
421 // And mixed outputs
422 audio.portBuffs[2 * NUM_MIDI_PARTS] = (float*)jack_port_get_buffer(audio.ports[2 * NUM_MIDI_PARTS], nframes);
423 if (!audio.portBuffs[2 * NUM_MIDI_PARTS])
424 {
425 synth->getRuntime().Log("Failed to get jack audio port buffer: " + asString(2 * NUM_MIDI_PARTS));
426 return false;
427 }
428 audio.portBuffs[2 * NUM_MIDI_PARTS + 1] = (float*)jack_port_get_buffer(audio.ports[2 * NUM_MIDI_PARTS + 1], nframes);
429 if (!audio.portBuffs[2 * NUM_MIDI_PARTS + 1])
430 {
431 synth->getRuntime().Log("Failed to get jack audio port buffer: " + asString(2 * NUM_MIDI_PARTS + 1));
432 return false;
433 }
434
435 BeatTracker::BeatValues beats(beatTracker->getBeatValues());
436 int framesize;
437 if (nframes <= internalbuff)
438 {
439 synth->setBeatValues(beats.songBeat, beats.monotonicBeat, beats.bpm);
440 framesize = sizeof(float) * nframes;
441 synth->MasterAudio(zynLeft, zynRight, nframes);
442 sendAudio(framesize, 0);
443 }
444 else
445 {
446 framesize = sizeof(float) * internalbuff;
447 for (unsigned int pos = 0; pos < nframes; pos += internalbuff)
448 {
449 float bpmInc = (float)pos * beats.bpm / (audio.jackSamplerate * 60.0f);
450 synth->setBeatValues(beats.songBeat + bpmInc, beats.monotonicBeat + bpmInc, beats.bpm);
451 synth->MasterAudio(zynLeft, zynRight, internalbuff);
452 sendAudio(framesize, pos);
453 }
454 }
455 return true;
456 }
457
458
sendAudio(int framesize,unsigned int offset)459 void JackEngine::sendAudio(int framesize, unsigned int offset)
460 {
461 // Part outputs
462 int currentmax = synth->getRuntime().NumAvailableParts;
463 for (int port = 0, idx = 0; idx < 2 * NUM_MIDI_PARTS; port++ , idx += 2)
464 {
465 if (audio.ports [idx])
466 {
467 if (jack_port_connected(audio.ports[idx])) // just a few % improvement.
468 {
469 float *lpoint = audio.portBuffs[idx] + offset;
470 float *rpoint = audio.portBuffs[idx + 1] + offset;
471 if ((synth->part[port]->Paudiodest & 2) && port < currentmax)
472 {
473 memcpy(lpoint, zynLeft[port], framesize);
474 memcpy(rpoint, zynRight[port], framesize);
475 }
476 else
477 {
478 memset(lpoint, 0, framesize);
479 memset(rpoint, 0, framesize);
480 }
481 }
482 }
483 }
484 // And mixed outputs
485 float *Lpoint = audio.portBuffs[2 * NUM_MIDI_PARTS] + offset;
486 float *Rpoint = audio.portBuffs[2 * NUM_MIDI_PARTS + 1] + offset;
487 memcpy(Lpoint, zynLeft[NUM_MIDI_PARTS], framesize);
488 memcpy(Rpoint, zynRight[NUM_MIDI_PARTS], framesize);
489 }
490
491
processMidi(jack_nframes_t nframes)492 bool JackEngine::processMidi(jack_nframes_t nframes)
493 {
494 void *portBuf = jack_port_get_buffer(midiPort, nframes);
495 if (!portBuf)
496 {
497 synth->getRuntime().Log("Bad midi jack_port_get_buffer");
498 return false;
499 }
500
501 unsigned int idx;
502 jack_midi_event_t jEvent;
503 jack_nframes_t eventCount = jack_midi_get_event_count(portBuf);
504
505 for (idx = 0; idx < eventCount; ++idx)
506 {
507 if (!jack_midi_event_get(&jEvent, portBuf, idx))
508 {
509 if (jEvent.size >= 1 && jEvent.size <= 4) // no interest in zero sized or long events
510 {
511 //std::cout << "Offset " << int(jEvent.time) << std::endl;
512 setMidi(jEvent.buffer[0], jEvent.buffer[1], jEvent.buffer[2]);
513 }
514 }
515 }
516 return true;
517 }
518
handleBeatValues(jack_nframes_t nframes)519 void JackEngine::handleBeatValues(jack_nframes_t nframes)
520 {
521 jack_position_t pos;
522 jack_transport_state_t state = jack_transport_query(jackClient, &pos);
523
524 BeatTracker::BeatValues beats(beatTracker->getBeatValues());
525
526 if (pos.valid & JackPositionBBT)
527 beats.bpm = pos.beats_per_minute;
528
529 float bpmInc = (float)nframes * beats.bpm
530 / ((float)audio.jackSamplerate * 60.0f);
531
532 beats.monotonicBeat += bpmInc;
533
534 if (!(pos.valid & JackPositionBBT) || state == JackTransportStopped)
535 // If stopped, keep oscillating.
536 beats.songBeat += bpmInc;
537 else
538 {
539 // If rolling, sync to exact beat.
540 beats.songBeat = (float)pos.tick / (float)pos.ticks_per_beat;
541 beats.songBeat += pos.beat - 1;
542 beats.songBeat += (pos.bar - 1) * pos.beats_per_bar;
543 }
544
545 beatTracker->setBeatValues(beats);
546 }
547
548
_xrunCallback(void * arg)549 int JackEngine::_xrunCallback(void *arg)
550 {
551 ((JackEngine *)arg)->synth->getRuntime().Log("xrun reported", _SYS_::LogNotSerious);
552 return 0;
553 }
554
555
latencyPrep(void)556 bool JackEngine::latencyPrep(void)
557 {
558 #if defined(JACK_LATENCY) // >= 0.120.1 API
559
560 if (jack_set_latency_callback(jackClient, _latencyCallback, this))
561 {
562 synth->getRuntime().Log("Set latency callback failed");
563 return false;
564 }
565 return true;
566
567 #else // < 0.120.1 API
568
569 for (int i = 0; i < 2 * NUM_MIDI_PARTS + 2; ++i)
570 {
571 if (jack_port_set_latency && audio.ports[i])
572 jack_port_set_latency(audio.ports[i], jack_get_buffer_size(jackClient));
573 }
574 if (jack_recompute_total_latencies)
575 jack_recompute_total_latencies(jackClient);
576 return true;
577
578 #endif
579 }
580
581
582 #if defined(JACK_SESSION)
583
_jsessionCallback(jack_session_event_t * event,void * arg)584 void JackEngine::_jsessionCallback(jack_session_event_t *event, void *arg)
585 {
586 return static_cast<JackEngine*>(arg)->jsessionCallback(event);
587 }
588
jsessionCallback(jack_session_event_t * event)589 void JackEngine::jsessionCallback(jack_session_event_t *event)
590 {
591 std::string uuid = std::string(event->client_uuid);
592 std::string filename = std::string("yoshimi-") + uuid + std::string(".xml");
593 std::string filepath = std::string(event->session_dir) + filename;
594 synth->getRuntime().setJackSessionSave((int)event->type, filepath);
595 std::string cmd = synth->getRuntime().programCmd() + std::string(" -U ") + uuid
596 + std::string(" -u ${SESSION_DIR}") + filename;
597 event->command_line = strdup(cmd.c_str());
598 if (jack_session_reply(jackClient, event))
599 synth->getRuntime().Log("Jack session reply failed");
600 jack_session_event_free(event);
601 }
602
603 #endif
604
605
606 #if defined(JACK_LATENCY)
607
_latencyCallback(jack_latency_callback_mode_t mode,void * arg)608 void JackEngine::_latencyCallback(jack_latency_callback_mode_t mode, void *arg)
609 {
610 return static_cast<JackEngine*>(arg)->latencyCallback(mode);
611 }
612
613
latencyCallback(jack_latency_callback_mode_t mode)614 void JackEngine::latencyCallback(jack_latency_callback_mode_t mode)
615 {
616 if (mode == JackCaptureLatency)
617 {
618 for (int i = 0; i < 2 * NUM_MIDI_PARTS + 2; ++i)
619 {
620 jack_latency_range_t range;
621 if (audio.ports[i])
622 {
623 jack_port_get_latency_range(audio.ports[i], mode, &range);
624 range.min++;
625 range.max += audio.jackNframes;
626 jack_port_set_latency_range(audio.ports[i], JackPlaybackLatency, &range);
627 }
628 }
629 }
630 }
631
632 #endif
633