/*
JackEngine.cpp
Copyright 2009-2011, Alan Calvert
Copyright 2014-2019, Will Godfrey & others
This file is part of yoshimi, which is free software: you can
redistribute it and/or modify it under the terms of the GNU General
Public License as published by the Free Software Foundation, either
version 2 of the License, or (at your option) any later version.
yoshimi is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with yoshimi. If not, see .
*/
#include
#include
#include
#include
#include // for usleep()
#include "Misc/Config.h"
#include "Misc/FormatFuncs.h"
#include "MusicIO/JackEngine.h"
using func::asString;
using func::asHexString;
JackEngine::JackEngine(SynthEngine *_synth, BeatTracker *_beatTracker) :
MusicIO(_synth, _beatTracker),
jackClient(NULL),
internalbuff(0)
{
audio.jackSamplerate = 0;
audio.jackNframes = 0;
for (int i = 0; i < (2 * NUM_MIDI_PARTS + 2); ++i)
{
audio.ports[i] = NULL;
audio.portBuffs[i] = NULL;
}
midiPort = NULL;
}
bool JackEngine::connectServer(std::string server)
{
for (int tries = 0; tries < 3 && !jackClient; ++tries)
{
if (!openJackClient(server) && tries < 2)
{
synth->getRuntime().Log("Failed to open jack client, trying again", _SYS_::LogError);
usleep(3333);
}
}
if (jackClient)
{
synth->getRuntime().setRtprio(jack_client_max_real_time_priority(jackClient));
audio.jackSamplerate = jack_get_sample_rate(jackClient);
audio.jackNframes = jack_get_buffer_size(jackClient);
return true;
}
else
{
synth->getRuntime().Log("Failed to open jack client on server " + server);
}
return false;
}
bool JackEngine::openJackClient(std::string server)
{
int jopts = JackNullOption;
jack_status_t jstatus;
std::string clientname = "yoshimi";
if (synth->getRuntime().nameTag.size())
clientname += ("-" + synth->getRuntime().nameTag);
//Andrew Deryabin: for multi-instance support add unique id to
//instances other then default (0)
unsigned int synthUniqueId = synth->getUniqueId();
if (synthUniqueId > 0)
{
char sUniqueId [256];
memset(sUniqueId, 0, sizeof(sUniqueId));
snprintf(sUniqueId, sizeof(sUniqueId), "%d", synthUniqueId);
clientname += ("-" + std::string(sUniqueId));
}
//std::cout << " C name " << clientname << std::endl;
bool named_server = server.size() > 0 && server.compare("default") != 0;
if (named_server)
jopts |= JackServerName;
if (!synth->getRuntime().startJack)
jopts |= JackNoStartServer;
#if defined(JACK_SESSION)
if (synth->getRuntime().restoreJackSession && synth->getRuntime().jackSessionUuid.size())
{
jopts |= JackSessionID;
if (named_server)
jackClient = jack_client_open(clientname.c_str(), (jack_options_t)jopts,
&jstatus, synth->getRuntime().jackServer.c_str(),
synth->getRuntime().jackSessionUuid.c_str());
else
jackClient = jack_client_open(clientname.c_str(), (jack_options_t)jopts,
&jstatus, synth->getRuntime().jackSessionUuid.c_str());
}
else
{
if (named_server)
jackClient = jack_client_open(clientname.c_str(), (jack_options_t)jopts,
&jstatus, synth->getRuntime().jackServer.c_str());
else
jackClient = jack_client_open(clientname.c_str(), (jack_options_t)jopts, &jstatus);
}
#else
if (named_server)
jackClient = jack_client_open(clientname.c_str(), (jack_options_t)jopts,
&jstatus, synth->getRuntime().jackServer.c_str());
else
jackClient = jack_client_open(clientname.c_str(), (jack_options_t)jopts, &jstatus);
#endif
if (jackClient)
return true;
else
synth->getRuntime().Log("Failed jack_client_open(), status: " + asHexString((int)jstatus), 1);
return false;
}
bool JackEngine::Start(void)
{
bool jackPortsRegistered = true;
internalbuff = synth->getRuntime().Buffersize;
jack_set_xrun_callback(jackClient, _xrunCallback, this);
#if defined(JACK_SESSION)
if (jack_set_session_callback
&& jack_set_session_callback(jackClient, _jsessionCallback, this))
synth->getRuntime().Log("Set jack session callback failed");
#endif
if (jack_set_process_callback(jackClient, _processCallback, this))
{
synth->getRuntime().Log("JackEngine failed to set process callback");
goto bail_out;
}
if (!latencyPrep())
{
synth->getRuntime().Log("Jack latency prep failed ");
goto bail_out;
}
if (!jack_activate(jackClient) && jackPortsRegistered)
{
if (!synth->getRuntime().restoreJackSession && synth->getRuntime().connectJackaudio && !connectJackPorts())
{
synth->getRuntime().Log("Failed to connect jack audio ports");
goto bail_out;
}
}
else
{
synth->getRuntime().Log("Failed to activate jack client");
goto bail_out;
}
/*
* TODO fix this - now moved to where it should be.
* Shows identical results but doesn't connect.
* Original 1.4.1 version also fails - it used to work.
*/
/* pre V 1.3.0 was this:
if (Runtime.midiEngine == jack_midi and jack_connect(jackClient,Runtime.midiDevice.c_str(),jack_port_name(midi.port)))
Runtime.Log("Didn't find jack MIDI source '" + Runtime.midiDevice + "'");
*/
// style-wise I think the next bit is the wrong place
/*if (synth->getRuntime().midiEngine == jack_midi
&& !synth->getRuntime().midiDevice.empty()
&& synth->getRuntime().midiDevice != "default")
{
if (jack_connect(jackClient, synth->getRuntime().midiDevice.c_str(), jack_port_name(midiPort)))
{
synth->getRuntime().Log("Didn't find jack MIDI source '"
+ synth->getRuntime().midiDevice + "'", _SYS_::LogError);
synth->getRuntime().midiDevice = "";
}
}*/
return true;
bail_out:
Close();
return false;
}
void JackEngine::Close(void)
{
if (synth->getRuntime().runSynth)
{
synth->getRuntime().runSynth = false;
}
if (NULL != jackClient)
{
int chk;
for (int chan = 0; chan < (2*NUM_MIDI_PARTS+2); ++chan)
{
if (NULL != audio.ports[chan])
jack_port_unregister(jackClient, audio.ports[chan]);
audio.ports[chan] = NULL;
}
if (NULL != midiPort)
{
if ((chk = jack_port_unregister(jackClient, midiPort)))
synth->getRuntime().Log("Failed to close jack client, status: " + asString(chk));
midiPort = NULL;
}
chk = jack_deactivate(jackClient);
if (chk)
synth->getRuntime().Log("Failed to close jack client, status: " + asString(chk));
jackClient = NULL;
}
}
void JackEngine::registerAudioPort(int partnum)
{
int portnum = partnum * 2;
if (partnum >=0 && partnum < NUM_MIDI_PARTS)
{
if (audio.ports [portnum] != NULL)
{
synth->getRuntime().Log("Jack port " + asString(partnum) + " already registered!", 2);
return;
}
/* This has a hack to stop all enabled parts from resistering
* individual ports (at startup) if part is not configured for
* direct O/P.
*/
std::string portName;
if (synth->part [partnum] && synth->partonoffRead(partnum) && (synth->part [partnum]->Paudiodest > 1))
{
portName = "track_" + asString(partnum + 1) + "_l";
audio.ports[portnum] = jack_port_register(jackClient, portName.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
portName = "track_" + asString(partnum + 1) + "_r";
audio.ports[portnum + 1] = jack_port_register(jackClient, portName.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
if (audio.ports [portnum])
{
synth->getRuntime().Log("Registered jack port " + asString(partnum + 1));
}
else
{
synth->getRuntime().Log("Error registering jack port " + asString(partnum + 1));
}
}
}
}
bool JackEngine::openAudio(void)
{
if (jackClient == 0)
{
if (!connectServer(synth->getRuntime().audioDevice))
{
return false;
}
}
// Register mixed outputs
audio.ports[2 * NUM_MIDI_PARTS] = jack_port_register(jackClient, "left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
audio.ports[2 * NUM_MIDI_PARTS + 1] = jack_port_register(jackClient, "right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
bool jackPortsRegistered = true;
if (!audio.ports[2 * NUM_MIDI_PARTS] || !audio.ports[2 * NUM_MIDI_PARTS + 1])
jackPortsRegistered = false;
if (jackPortsRegistered)
return prepBuffers() && latencyPrep();
else
synth->getRuntime().Log("Failed to register jack audio ports");
Close();
return false;
}
bool JackEngine::openMidi(void)
{
synth->setBPMAccurate(true);
if (jackClient == 0)
{
if (!connectServer(synth->getRuntime().midiDevice))
{
return false;
}
}
const char *port_name = "midi in";
midiPort = jack_port_register(jackClient, port_name,
JACK_DEFAULT_MIDI_TYPE,
JackPortIsInput, 0);
if (!midiPort)
{
synth->getRuntime().Log("Failed to register jack midi port");
return false;
}
std::cout << "client " << jackClient<< " device " << synth->getRuntime().midiDevice << " port " << jack_port_name(midiPort) << std::endl;
if (jack_connect(jackClient, synth->getRuntime().midiDevice.c_str(), jack_port_name(midiPort)))
{
synth->getRuntime().Log("Didn't find jack MIDI source '"
+ synth->getRuntime().midiDevice + "'");
//synth->getRuntime().midiDevice = "";
}
return true;
}
bool JackEngine::connectJackPorts(void)
{
const char** playback_ports = jack_get_ports(jackClient, NULL, NULL,
JackPortIsPhysical|JackPortIsInput);
if (!playback_ports)
{
synth->getRuntime().Log("No physical jack playback ports found.");
return false;
}
int ret;
for (int port = 0; port < 2 && (NULL != audio.ports[port + NUM_MIDI_PARTS*2]); ++port)
{
const char *port_name = jack_port_name(audio.ports[port + NUM_MIDI_PARTS * 2]);
if ((ret = jack_connect(jackClient, port_name, playback_ports[port])))
{
if (ret == EEXIST)
{
synth->getRuntime().Log(std::string(port_name)
+ " is already connected to jack port " + std::string(playback_ports[port])
+ ", status " + asString(ret));
}
else
{
synth->getRuntime().Log("Cannot connect " + std::string(port_name)
+ " to jack port " + std::string(playback_ports[port])
+ ", status " + asString(ret));
return false;
}
}
}
return true;
}
int JackEngine::clientId(void)
{
if (jackClient)
return long(jack_client_thread_id(jackClient));
else
return -1;
}
std::string JackEngine::clientName(void)
{
if (jackClient)
return std::string(jack_get_client_name(jackClient));
else
synth->getRuntime().Log("clientName() with null jackClient");
return std::string("Oh, yoshimi :-(");
}
int JackEngine::_processCallback(jack_nframes_t nframes, void *arg)
{
return static_cast(arg)->processCallback(nframes);
}
int JackEngine::processCallback(jack_nframes_t nframes)
{
bool okaudio = true;
bool okmidi = true;
if (midiPort) {
// input exists, using jack midi
handleBeatValues(nframes);
okmidi = processMidi(nframes);
}
if (audio.ports[NUM_MIDI_PARTS * 2] && audio.ports[NUM_MIDI_PARTS * 2 + 1])
// (at least) main outputs exist, using jack audio
okaudio = processAudio(nframes);
return (okaudio && okmidi) ? 0 : -1;
}
bool JackEngine::processAudio(jack_nframes_t nframes)
{
// Part buffers
for (int port = 0; port < 2 * NUM_MIDI_PARTS; ++port)
{
if (audio.ports [port])
{
audio.portBuffs[port] =
(float*)jack_port_get_buffer(audio.ports[port], nframes);
if (!audio.portBuffs[port])
{
synth->getRuntime().Log("Failed to get jack audio port buffer: " + asString(port));
return false;
}
}
}
// And mixed outputs
audio.portBuffs[2 * NUM_MIDI_PARTS] = (float*)jack_port_get_buffer(audio.ports[2 * NUM_MIDI_PARTS], nframes);
if (!audio.portBuffs[2 * NUM_MIDI_PARTS])
{
synth->getRuntime().Log("Failed to get jack audio port buffer: " + asString(2 * NUM_MIDI_PARTS));
return false;
}
audio.portBuffs[2 * NUM_MIDI_PARTS + 1] = (float*)jack_port_get_buffer(audio.ports[2 * NUM_MIDI_PARTS + 1], nframes);
if (!audio.portBuffs[2 * NUM_MIDI_PARTS + 1])
{
synth->getRuntime().Log("Failed to get jack audio port buffer: " + asString(2 * NUM_MIDI_PARTS + 1));
return false;
}
BeatTracker::BeatValues beats(beatTracker->getBeatValues());
int framesize;
if (nframes <= internalbuff)
{
synth->setBeatValues(beats.songBeat, beats.monotonicBeat, beats.bpm);
framesize = sizeof(float) * nframes;
synth->MasterAudio(zynLeft, zynRight, nframes);
sendAudio(framesize, 0);
}
else
{
framesize = sizeof(float) * internalbuff;
for (unsigned int pos = 0; pos < nframes; pos += internalbuff)
{
float bpmInc = (float)pos * beats.bpm / (audio.jackSamplerate * 60.0f);
synth->setBeatValues(beats.songBeat + bpmInc, beats.monotonicBeat + bpmInc, beats.bpm);
synth->MasterAudio(zynLeft, zynRight, internalbuff);
sendAudio(framesize, pos);
}
}
return true;
}
void JackEngine::sendAudio(int framesize, unsigned int offset)
{
// Part outputs
int currentmax = synth->getRuntime().NumAvailableParts;
for (int port = 0, idx = 0; idx < 2 * NUM_MIDI_PARTS; port++ , idx += 2)
{
if (audio.ports [idx])
{
if (jack_port_connected(audio.ports[idx])) // just a few % improvement.
{
float *lpoint = audio.portBuffs[idx] + offset;
float *rpoint = audio.portBuffs[idx + 1] + offset;
if ((synth->part[port]->Paudiodest & 2) && port < currentmax)
{
memcpy(lpoint, zynLeft[port], framesize);
memcpy(rpoint, zynRight[port], framesize);
}
else
{
memset(lpoint, 0, framesize);
memset(rpoint, 0, framesize);
}
}
}
}
// And mixed outputs
float *Lpoint = audio.portBuffs[2 * NUM_MIDI_PARTS] + offset;
float *Rpoint = audio.portBuffs[2 * NUM_MIDI_PARTS + 1] + offset;
memcpy(Lpoint, zynLeft[NUM_MIDI_PARTS], framesize);
memcpy(Rpoint, zynRight[NUM_MIDI_PARTS], framesize);
}
bool JackEngine::processMidi(jack_nframes_t nframes)
{
void *portBuf = jack_port_get_buffer(midiPort, nframes);
if (!portBuf)
{
synth->getRuntime().Log("Bad midi jack_port_get_buffer");
return false;
}
unsigned int idx;
jack_midi_event_t jEvent;
jack_nframes_t eventCount = jack_midi_get_event_count(portBuf);
for (idx = 0; idx < eventCount; ++idx)
{
if (!jack_midi_event_get(&jEvent, portBuf, idx))
{
if (jEvent.size >= 1 && jEvent.size <= 4) // no interest in zero sized or long events
{
//std::cout << "Offset " << int(jEvent.time) << std::endl;
setMidi(jEvent.buffer[0], jEvent.buffer[1], jEvent.buffer[2]);
}
}
}
return true;
}
void JackEngine::handleBeatValues(jack_nframes_t nframes)
{
jack_position_t pos;
jack_transport_state_t state = jack_transport_query(jackClient, &pos);
BeatTracker::BeatValues beats(beatTracker->getBeatValues());
if (pos.valid & JackPositionBBT)
beats.bpm = pos.beats_per_minute;
float bpmInc = (float)nframes * beats.bpm
/ ((float)audio.jackSamplerate * 60.0f);
beats.monotonicBeat += bpmInc;
if (!(pos.valid & JackPositionBBT) || state == JackTransportStopped)
// If stopped, keep oscillating.
beats.songBeat += bpmInc;
else
{
// If rolling, sync to exact beat.
beats.songBeat = (float)pos.tick / (float)pos.ticks_per_beat;
beats.songBeat += pos.beat - 1;
beats.songBeat += (pos.bar - 1) * pos.beats_per_bar;
}
beatTracker->setBeatValues(beats);
}
int JackEngine::_xrunCallback(void *arg)
{
((JackEngine *)arg)->synth->getRuntime().Log("xrun reported", _SYS_::LogNotSerious);
return 0;
}
bool JackEngine::latencyPrep(void)
{
#if defined(JACK_LATENCY) // >= 0.120.1 API
if (jack_set_latency_callback(jackClient, _latencyCallback, this))
{
synth->getRuntime().Log("Set latency callback failed");
return false;
}
return true;
#else // < 0.120.1 API
for (int i = 0; i < 2 * NUM_MIDI_PARTS + 2; ++i)
{
if (jack_port_set_latency && audio.ports[i])
jack_port_set_latency(audio.ports[i], jack_get_buffer_size(jackClient));
}
if (jack_recompute_total_latencies)
jack_recompute_total_latencies(jackClient);
return true;
#endif
}
#if defined(JACK_SESSION)
void JackEngine::_jsessionCallback(jack_session_event_t *event, void *arg)
{
return static_cast(arg)->jsessionCallback(event);
}
void JackEngine::jsessionCallback(jack_session_event_t *event)
{
std::string uuid = std::string(event->client_uuid);
std::string filename = std::string("yoshimi-") + uuid + std::string(".xml");
std::string filepath = std::string(event->session_dir) + filename;
synth->getRuntime().setJackSessionSave((int)event->type, filepath);
std::string cmd = synth->getRuntime().programCmd() + std::string(" -U ") + uuid
+ std::string(" -u ${SESSION_DIR}") + filename;
event->command_line = strdup(cmd.c_str());
if (jack_session_reply(jackClient, event))
synth->getRuntime().Log("Jack session reply failed");
jack_session_event_free(event);
}
#endif
#if defined(JACK_LATENCY)
void JackEngine::_latencyCallback(jack_latency_callback_mode_t mode, void *arg)
{
return static_cast(arg)->latencyCallback(mode);
}
void JackEngine::latencyCallback(jack_latency_callback_mode_t mode)
{
if (mode == JackCaptureLatency)
{
for (int i = 0; i < 2 * NUM_MIDI_PARTS + 2; ++i)
{
jack_latency_range_t range;
if (audio.ports[i])
{
jack_port_get_latency_range(audio.ports[i], mode, &range);
range.min++;
range.max += audio.jackNframes;
jack_port_set_latency_range(audio.ports[i], JackPlaybackLatency, &range);
}
}
}
}
#endif