MuseScore/mscore/jackaudio.cpp

987 lines
36 KiB
C++

//=============================================================================
// MusE Score
// Linux Music Score Editor
//
// Copyright (C) 2002-2010 Werner Schweer and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#if (defined (_MSCVER) || defined (_MSC_VER))
// Include stdint.h and #define _STDINT_H to prevent <systemdeps.h> from redefining types
// #undef UNICODE to force LoadLibrary to use the char-based implementation instead of the wchar_t one.
#include <stdint.h>
#define _STDINT_H 1
#endif
#include "jackaudio.h"
#include "musescore.h"
#include "libmscore/mscore.h"
#include "preferences.h"
// #include "msynth/synti.h"
#include "seq.h"
#include "libmscore/score.h"
#include "libmscore/repeatlist.h"
#include "mscore/playpanel.h"
#include <jack/midiport.h>
// Prevent killing sequencer with wrong data
#define less128(__less) ((__less >=0 && __less <= 127) ? __less : 0)
namespace Ms {
//---------------------------------------------------------
// JackAudio
//---------------------------------------------------------
JackAudio::JackAudio(Seq* s)
: Driver(s)
{
client = 0;
}
//---------------------------------------------------------
// ~JackAudio
//---------------------------------------------------------
JackAudio::~JackAudio()
{
if (client) {
stop();
if (jack_client_close(client)) {
qDebug("jack_client_close() failed: %s",
strerror(errno));
}
}
}
//---------------------------------------------------------
// updateOutPortCount
// Add/remove JACK MIDI Out ports
//---------------------------------------------------------
void JackAudio::updateOutPortCount(int maxport)
{
if (!preferences.getBool(PREF_IO_JACK_USEJACKMIDI) || maxport == midiOutputPorts.size())
return;
if (MScore::debugMode)
qDebug()<<"JACK number of ports:"<<midiOutputPorts.size()<<", change to:"<<maxport;
bool oldremember = preferences.getBool(PREF_IO_JACK_REMEMBERLASTCONNECTIONS);
preferences.setPreference(PREF_IO_JACK_REMEMBERLASTCONNECTIONS, true);
if (maxport > midiOutputPorts.size()) {
for (int i = midiOutputPorts.size(); i < maxport; ++i)
registerPort(QString("mscore-midi-%1").arg(i+1), false, true);
restoreMidiConnections();
}
else if (maxport < midiOutputPorts.size()) {
rememberMidiConnections();
for(int i = midiOutputPorts.size() - 1; i >= maxport; --i) {
unregisterPort(midiOutputPorts[i]);
midiOutputPorts.removeAt(i);
}
}
preferences.setPreference(PREF_IO_JACK_REMEMBERLASTCONNECTIONS, oldremember);
}
//---------------------------------------------------------
// registerPort
//---------------------------------------------------------
void JackAudio::registerPort(const QString& name, bool input, bool midi)
{
int portFlag = input ? JackPortIsInput : JackPortIsOutput;
const char* portType = midi ? JACK_DEFAULT_MIDI_TYPE : JACK_DEFAULT_AUDIO_TYPE;
jack_port_t* port = jack_port_register(client, qPrintable(name), portType, portFlag, 0);
if (port == 0) {
qDebug("JackAudio:registerPort(%s) failed", qPrintable(name));
return;
}
if (midi) {
if (input)
midiInputPorts.append(port);
else
midiOutputPorts.append(port);
}
else
ports.append(port);
}
//---------------------------------------------------------
// unregisterPort
//---------------------------------------------------------
void JackAudio::unregisterPort(jack_port_t* port)
{
if (jack_port_is_mine(client,port)) {
jack_port_unregister(client, port);
port = 0;
}
else
qDebug("Trying to unregister port that is not my!");
}
//---------------------------------------------------------
// inputPorts
//---------------------------------------------------------
QList<QString> JackAudio::inputPorts()
{
const char** prts = jack_get_ports(client, 0, 0, 0);
QList<QString> clientList;
for (const char** p = prts; p && *p; ++p) {
jack_port_t* port = jack_port_by_name(client, *p);
int flags = jack_port_flags(port);
if (!(flags & JackPortIsInput))
continue;
char buffer[128];
strncpy(buffer, *p, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = 0;
if (strncmp(buffer, "Mscore", 6) == 0)
continue;
clientList.append(QString(buffer));
}
return clientList;
}
//---------------------------------------------------------
// connect
//---------------------------------------------------------
void JackAudio::connect(void* src, void* dst)
{
const char* sn = jack_port_name((jack_port_t*) src);
const char* dn = jack_port_name((jack_port_t*) dst);
if (sn == 0 || dn == 0) {
qDebug("JackAudio::connect: unknown jack ports");
return;
}
if (jack_connect(client, sn, dn)) {
qDebug("jack connect <%s>%p - <%s>%p failed",
sn, src, dn, dst);
}
}
//---------------------------------------------------------
// connect
//---------------------------------------------------------
void JackAudio::connect(const char* src, const char* dst)
{
if (src == 0 || dst == 0) {
qDebug("JackAudio::connect: unknown jack ports");
return;
}
qDebug("JackAudio::connect <%s> <%s>", src, dst);
int rv = jack_connect(client, src, dst);
if (rv)
qDebug("jack connect port <%s> - <%s> failed: %d", src, dst, rv);
}
//---------------------------------------------------------
// disconnect
//---------------------------------------------------------
void JackAudio::disconnect(void* src, void* dst)
{
const char* sn = jack_port_name((jack_port_t*) src);
const char* dn = jack_port_name((jack_port_t*) dst);
if (sn == 0 || dn == 0) {
qDebug("JackAudio::disconnect: unknown jack ports");
return;
}
if (jack_disconnect(client, sn, dn)) {
qDebug("jack disconnect <%s> - <%s> failed", sn, dn);
}
}
//---------------------------------------------------------
// start
// return false on error
//---------------------------------------------------------
bool JackAudio::start(bool hotPlug)
{
bool oldremember = preferences.getBool(PREF_IO_JACK_REMEMBERLASTCONNECTIONS);
if (hotPlug)
preferences.setPreference(PREF_IO_JACK_REMEMBERLASTCONNECTIONS, true);
if (jack_activate(client)) {
qDebug("JACK: cannot activate client");
return false;
}
/* connect the ports. Note: you can't do this before
the client is activated, because we can't allow
connections to be made to clients that aren't
running.
*/
if (preferences.getBool(PREF_IO_JACK_USEJACKAUDIO))
restoreAudioConnections();
if (preferences.getBool(PREF_IO_JACK_USEJACKMIDI))
restoreMidiConnections();
if (hotPlug)
preferences.setPreference(PREF_IO_JACK_REMEMBERLASTCONNECTIONS, oldremember);
return true;
}
//---------------------------------------------------------
// stop
// return false on error
//---------------------------------------------------------
bool JackAudio::stop()
{
if (preferences.getBool(PREF_IO_JACK_USEJACKMIDI))
rememberMidiConnections();
if (preferences.getBool(PREF_IO_JACK_USEJACKAUDIO))
rememberAudioConnections();
if (jack_deactivate(client)) {
qDebug("cannot deactivate client");
return false;
}
return true;
}
//---------------------------------------------------------
// framePos
//---------------------------------------------------------
int JackAudio::framePos() const
{
jack_nframes_t n = jack_frame_time(client);
return (int)n;
}
//---------------------------------------------------------
// freewheel_callback
//---------------------------------------------------------
static void freewheel_callback(int /*starting*/, void*)
{
}
//---------------------------------------------------------
// sampleRateCallback
//---------------------------------------------------------
int sampleRateCallback(jack_nframes_t sampleRate, void*)
{
qDebug("JACK: sample rate changed: %d", sampleRate);
MScore::sampleRate = sampleRate;
return 0;
}
//---------------------------------------------------------
// bufferSizeCallback called if JACK buffer changed
//---------------------------------------------------------
int bufferSizeCallback(jack_nframes_t nframes, void *arg)
{
JackAudio* audio = (JackAudio*)arg;
audio->setBufferSize(nframes);
return 0;
}
static void registration_callback(jack_port_id_t, int, void*)
{
// qDebug("JACK: registration changed");
}
static int graph_callback(void*)
{
// qDebug("JACK: graph changed");
return 0;
}
//---------------------------------------------------------
// timebase
//---------------------------------------------------------
void JackAudio::timebase(jack_transport_state_t state, jack_nframes_t /*nframes*/, jack_position_t *pos, int /*new_pos*/, void *arg)
{
JackAudio* audio = (JackAudio*)arg;
if (!audio->seq->score()) {
if (state==JackTransportLooping || state==JackTransportRolling)
audio->stopTransport();
}
else if (audio->seq->isRunning()) {
if (!audio->seq->score()->masterScore())
return;
pos->valid = JackPositionBBT;
int curTick = audio->seq->score()->repeatList().utick2tick(audio->seq->getCurTick());
int bar,beat,tick;
audio->seq->score()->sigmap()->tickValues(curTick, &bar, &beat, &tick);
// Providing the final tempo
pos->beats_per_minute = 60 * audio->seq->curTempo() * audio->seq->score()->tempomap()->relTempo();
pos->ticks_per_beat = MScore::division;
pos->tick = tick;
pos->bar = bar+1;
pos->beat = beat+1;
if (audio->timeSigTempoChanged) {
Fraction timeSig = audio->seq->score()->sigmap()->timesig(curTick).nominal();
pos->beats_per_bar = timeSig.numerator();
pos->beat_type = timeSig.denominator();
audio->timeSigTempoChanged = false;
qDebug()<<"Time signature changed: "<< pos->beats_per_minute<<", bar: "<< pos->bar<<",beat: "<<pos->beat<<", tick:"<<pos->tick<<", time sig: "<<pos->beats_per_bar<<"/"<<pos->beat_type;
}
}
// TODO: Handle new_pos
}
//---------------------------------------------------------
// processAudio
// JACK callback
//---------------------------------------------------------
int JackAudio::processAudio(jack_nframes_t frames, void* p)
{
JackAudio* audio = (JackAudio*)p;
// Prevent from crash if score not opened yet
if(!audio->seq->score())
return 0;
float* l;
float* r;
if (preferences.getBool(PREF_IO_JACK_USEJACKAUDIO) && audio->ports.size() == 2) {
l = (float*)jack_port_get_buffer(audio->ports[0], frames);
r = (float*)jack_port_get_buffer(audio->ports[1], frames);
}
else {
l = 0;
r = 0;
}
if (preferences.getBool(PREF_IO_JACK_USEJACKMIDI)) {
foreach(jack_port_t* port, audio->midiOutputPorts) {
void* portBuffer = jack_port_get_buffer(port, frames);
jack_midi_clear_buffer(portBuffer);
}
foreach(jack_port_t* port, audio->midiInputPorts) {
void* portBuffer = jack_port_get_buffer(port, frames);
if (portBuffer) {
jack_nframes_t n = jack_midi_get_event_count(portBuffer);
for (jack_nframes_t i = 0; i < n; ++i) {
jack_midi_event_t event;
if (jack_midi_event_get(&event, portBuffer, i) != 0)
continue;
size_t nn = event.size;
int type = event.buffer[0];
if (nn && (type == ME_CLOCK || type == ME_SENSE))
continue;
Event e;
e.setChannel(type & 0xf);
type &= 0xf0;
e.setType(type);
if (type == ME_NOTEON || type == ME_NOTEOFF) {
e.setPitch(event.buffer[1]);
e.setVelo(event.buffer[2]);
audio->seq->eventToGui(e);
}
else if (type == ME_CONTROLLER) {
e.setController(event.buffer[1]);
e.setValue(event.buffer[2]);
audio->seq->eventToGui(e);
}
}
}
}
}
if (l && r) {
#if (!defined (_MSCVER) && !defined (_MSC_VER))
float buffer[frames * 2];
#else
// MSVC does not support VLA. Replace with std::vector. If profiling determines that the
// heap allocation is slow, an optimization might be used.
std::vector<float> vBuffer(frames * 2);
float* buffer = vBuffer.data();
#endif
audio->seq->process((unsigned)frames, buffer);
float* sp = buffer;
for (unsigned i = 0; i < frames; ++i) {
*l++ = *sp++;
*r++ = *sp++;
}
}
else {
// JACK MIDI only
#if (!defined (_MSCVER) && !defined (_MSC_VER))
float buffer[frames * 2];
#else
// MSVC does not support VLA. Replace with std::vector. If profiling determines that the
// heap allocation is slow, an optimization might be used.
std::vector<float> vBuffer(frames * 2);
float* buffer = vBuffer.data();
#endif
audio->seq->process((unsigned)frames, buffer);
}
return 0;
}
//---------------------------------------------------------
// jackError
//---------------------------------------------------------
static void jackError(const char *s)
{
qDebug("JACK ERROR: %s", s);
}
//---------------------------------------------------------
// noJackError
//---------------------------------------------------------
static void noJackError(const char* s )
{
qDebug("noJACK ERROR: %s", s);
}
//---------------------------------------------------------
// init
// return false on error
//---------------------------------------------------------
bool JackAudio::init(bool hot)
{
if (hot) {
hotPlug();
return true;
}
jack_set_error_function(noJackError);
client = 0;
timeSigTempoChanged = false;
fakeState = Transport::STOP;
strcpy(_jackName, "mscore");
jack_options_t options = (jack_options_t)0;
jack_status_t status;
client = jack_client_open(_jackName, options, &status);
if (client == 0) {
qDebug("JackAudio()::init(): failed, status 0x%0x", status);
return false;
}
jack_set_error_function(jackError);
jack_set_process_callback(client, processAudio, this);
//jack_on_shutdown(client, processShutdown, this);
jack_set_sample_rate_callback(client, sampleRateCallback, this);
jack_set_port_registration_callback(client, registration_callback, this);
jack_set_graph_order_callback(client, graph_callback, this);
jack_set_freewheel_callback (client, freewheel_callback, this);
if (preferences.getBool(PREF_IO_JACK_TIMEBASEMASTER))
setTimebaseCallback();
if (jack_set_buffer_size_callback (client, bufferSizeCallback, this) != 0)
qDebug("Can not set bufferSizeCallback");
_segmentSize = jack_get_buffer_size(client);
MScore::sampleRate = sampleRate();
// register mscore left/right output ports
if (preferences.getBool(PREF_IO_JACK_USEJACKAUDIO)) {
registerPort("left", false, false);
registerPort("right", false, false);
}
if (preferences.getBool(PREF_IO_JACK_USEJACKMIDI)) {
registerPort(QString("mscore-midi-1"), false, true);
registerPort(QString("mscore-midiin-1"), true, true);
}
return true;
}
//---------------------------------------------------------
// startTransport
//---------------------------------------------------------
void JackAudio::startTransport()
{
if (preferences.getBool(PREF_IO_JACK_USEJACKTRANSPORT))
jack_transport_start(client);
else
fakeState = Transport::PLAY;
}
//---------------------------------------------------------
// stopTrasnport
//---------------------------------------------------------
void JackAudio::stopTransport()
{
if (preferences.getBool(PREF_IO_JACK_USEJACKTRANSPORT))
jack_transport_stop(client);
else
fakeState = Transport::STOP;
}
//---------------------------------------------------------
// getState
//---------------------------------------------------------
Transport JackAudio::getState()
{
if (!preferences.getBool(PREF_IO_JACK_USEJACKTRANSPORT))
return fakeState;
int transportState = jack_transport_query(client, NULL);
switch (transportState) {
case JackTransportStopped: return Transport::STOP;
case JackTransportLooping:
case JackTransportRolling: return Transport::PLAY;
case JackTransportStarting: return seq->isPlaying()?Transport::PLAY:Transport::STOP;// Keep current state
default:
return Transport::STOP;
}
}
//---------------------------------------------------------
// putEvent
//---------------------------------------------------------
void JackAudio::putEvent(const NPlayEvent& e, unsigned framePos)
{
if (!preferences.getBool(PREF_IO_JACK_USEJACKMIDI))
return;
int portIdx = seq->score()->midiPort(e.channel());
int chan = seq->score()->midiChannel(e.channel());
// qDebug("JackAudio::putEvent %d:%d pos %d(%d)", portIdx, chan, framePos, _segmentSize);
if (portIdx < 0 || portIdx >= midiOutputPorts.size()) {
qDebug("JackAudio::putEvent: invalid port %d", portIdx);
return;
}
jack_port_t* port = midiOutputPorts[portIdx];
if (midiOutputTrace) {
const char* portName = jack_port_name(port);
int a = e.dataA();
int b = e.dataB();
qDebug("MidiOut<%s>: jackMidi: %02x %02x %02x, chan: %i", portName, e.type(), a, b, chan);
// e.dump();
}
void* pb = jack_port_get_buffer(port, _segmentSize);
if (pb == NULL) {
qDebug()<<"jack_port_get_buffer failed, cannot send anything";
}
if (framePos >= _segmentSize) {
qDebug("JackAudio::putEvent: time out of range %d(seg=%d)", framePos, _segmentSize);
if (framePos > _segmentSize)
framePos = _segmentSize - 1;
}
switch(e.type()) {
case ME_NOTEON:
case ME_NOTEOFF:
case ME_POLYAFTER:
case ME_CONTROLLER:
// Catch CTRL_PROGRAM and let other ME_CONTROLLER events to go
if (e.dataA() == CTRL_PROGRAM) {
// Convert CTRL_PROGRAM event to ME_PROGRAM
unsigned char* p = jack_midi_event_reserve(pb, framePos, 2);
if (p == 0) {
qDebug("JackMidi: buffer overflow, event lost");
return;
}
p[0] = ME_PROGRAM | chan;
p[1] = less128(e.dataB());
break;
}
//fall through
case ME_PITCHBEND:
{
unsigned char* p = jack_midi_event_reserve(pb, framePos, 3);
if (p == 0) {
qDebug("JackMidi: buffer overflow, event lost");
return;
}
p[0] = e.type() | chan;
p[1] = less128(e.dataA());
p[2] = less128(e.dataB());
}
break;
case ME_PROGRAM:
case ME_AFTERTOUCH:
{
unsigned char* p = jack_midi_event_reserve(pb, framePos, 2);
if (p == 0) {
qDebug("JackMidi: buffer overflow, event lost");
return;
}
p[0] = e.type() | chan;
p[1] = less128(e.dataA());
}
break;
// Do we really need to handle ME_SYSEX?
/* case ME_SYSEX:
{
const unsigned char* data = e.edata();
int len = e.len();
unsigned char* p = jack_midi_event_reserve(pb, framePos, len+2);
if (p == 0) {
qDebug("JackMidi: buffer overflow, event lost");
return;
}
p[0] = 0xf0;
p[len+1] = 0xf7;
memcpy(p+1, data, len);
}
break;*/
case ME_SONGPOS:
case ME_CLOCK:
case ME_START:
case ME_CONTINUE:
case ME_STOP:
qDebug("JackMidi: event type %x not supported", e.type());
break;
}
}
//---------------------------------------------------------
// midiRead
//---------------------------------------------------------
void JackAudio::midiRead()
{
// midiDriver->read();
}
//---------------------------------------------------------
// handleTimeSigTempoChanged
// Called after tempo or time signature
// changed while playback
//---------------------------------------------------------
void JackAudio::handleTimeSigTempoChanged()
{
timeSigTempoChanged = true;
}
//---------------------------------------------------------
// checkTransportSeek
// The opposite of Timebase master:
// check JACK Transport for a new position or tempo.
//---------------------------------------------------------
void JackAudio::checkTransportSeek(int cur_frame, int nframes, bool inCountIn)
{
if (!seq || !seq->score() || inCountIn)
return;
// Obtaining the current JACK Transport position
jack_position_t pos;
jack_transport_query(client, &pos);
if (preferences.getBool(PREF_IO_JACK_USEJACKTRANSPORT)) {
if (mscore->getPlayPanel() && mscore->getPlayPanel()->isTempoSliderPressed())
return;
int cur_utick = seq->score()->utime2utick((qreal)cur_frame / MScore::sampleRate);
int utick = seq->score()->utime2utick((qreal)pos.frame / MScore::sampleRate);
// Conversion is not precise, should check frames and uticks
if (labs((long int)cur_frame - (long int)pos.frame)>nframes + 1 && abs(utick - cur_utick)> seq->score()->utime2utick((qreal)nframes / MScore::sampleRate) + 1) {
if (MScore::debugMode)
qDebug()<<"JACK Transport position changed, cur_frame: "<<cur_frame<<",pos.frame: "<<pos.frame<<", frame diff: "<<labs((long int)cur_frame - (long int)pos.frame)<<"cur utick:"<<cur_utick<<",seek to utick: "<<utick<<", tick diff: "<<abs(utick - cur_utick);
seq->seekRT(utick);
}
}
// Tempo
if (!preferences.getBool(PREF_IO_JACK_TIMEBASEMASTER) && (pos.valid & JackPositionBBT)) {
if (!seq->score()->tempomap())
return;
if (int(pos.beats_per_minute) != int(60 * seq->curTempo() * seq->score()->tempomap()->relTempo())) {
if (MScore::debugMode)
qDebug()<<"JACK Transport tempo changed! JACK bpm: "<<(int)pos.beats_per_minute<<", current bpm: "<<int(60 * seq->curTempo() * seq->score()->tempomap()->relTempo());
if (60 * seq->curTempo() == 0.0)
return;
qreal newRelTempo = pos.beats_per_minute / (60* seq->curTempo());
seq->setRelTempo(newRelTempo);
// Update UI
if (mscore->getPlayPanel()) {
mscore->getPlayPanel()->setRelTempo(newRelTempo);
mscore->getPlayPanel()->setTempo(seq->curTempo() * newRelTempo);
}
}
}
}
//---------------------------------------------------------
// seekTransport
//---------------------------------------------------------
void JackAudio::seekTransport(int utick)
{
if (MScore::debugMode)
qDebug()<<"jack locate to utick: "<<utick<<", frame: "<<int(seq->score()->utick2utime(utick) * MScore::sampleRate);
jack_transport_locate(client, seq->score()->utick2utime(utick) * MScore::sampleRate);
}
//---------------------------------------------------------
// setTimebaseCallback
//---------------------------------------------------------
void JackAudio::setTimebaseCallback()
{
int errCode = jack_set_timebase_callback(client, 0, timebase, this); // 0: force set timebase
if (errCode == 0) {
if (MScore::debugMode)
qDebug("Registered as JACK Timebase Master.");
}
else {
preferences.setPreference(PREF_IO_JACK_TIMEBASEMASTER, false);
qDebug("Unable to take over JACK Timebase, error code: %i",errCode);
}
}
//---------------------------------------------------------
// releaseTimebaseCallback
//---------------------------------------------------------
void JackAudio::releaseTimebaseCallback()
{
int errCode = jack_release_timebase(client);
if (errCode == 0)
qDebug("Unregistered as JACK Timebase Master");
else
qDebug("Unable to unregister as JACK Timebase Master (not a Timebase Master?), error code: %i", errCode);
}
//---------------------------------------------------------
// rememberAudioConnections
//---------------------------------------------------------
void JackAudio::rememberAudioConnections()
{
if (!preferences.getBool(PREF_IO_JACK_REMEMBERLASTCONNECTIONS))
return;
if (MScore::debugMode)
qDebug("Saving audio connections...");
QSettings settings;
settings.setValue(QString("audio-0-connections"), 0);
settings.setValue(QString("audio-1-connections"), 0);
int port = 0;
foreach(jack_port_t* mp, ports) {
const char** cc = jack_port_get_connections(mp);
const char** c = cc;
int idx = 0;
while (c) {
const char* p = *c++;
if (p == 0)
break;
settings.setValue(QString("audio-%1-%2").arg(port).arg(idx), p);
++idx;
}
settings.setValue(QString("audio-%1-connections").arg(port), idx);
free((void*)cc);
++port;
}
}
//---------------------------------------------------------
// restoreAudioConnections
// Connect to the ports in Preferences->I/O
//---------------------------------------------------------
void JackAudio::restoreAudioConnections()
{
for (auto p : ports)
jack_port_disconnect(client, p);
QList<QString> portList = inputPorts();
QList<QString>::iterator pi = portList.begin();
QSettings settings;
// Number of saved ports
int n = settings.value(QString("audio-0-connections"), 0).toInt() + settings.value(QString("audio-1-connections"), 0).toInt();
// Connecting to system ports
if (!preferences.getBool(PREF_IO_JACK_REMEMBERLASTCONNECTIONS) || n == 0) {
if (MScore::debugMode)
qDebug("Connecting to system ports...");
for (auto p : ports) {
const char* src = jack_port_name(p);
if (pi != portList.end()) {
connect(src, qPrintable(*pi));
++pi;
}
}
return;
}
if (MScore::debugMode)
qDebug("Restoring audio connections...");
// Connecting to saved ports
int nPorts = ports.size();
for (int i = 0; i < nPorts; ++i) {
int j = settings.value(QString("audio-%1-connections").arg(i), 0).toInt();
const char* src = jack_port_name(ports[i]);
for (int k = 0; k < j; ++k) {
QString dst = settings.value(QString("audio-%1-%2").arg(i).arg(k), "").toString();
if (!dst.isEmpty()) {
if (jack_port_connected_to(ports[i], qPrintable(dst)))
qDebug()<<"Audio port "<<src<<" ("<<i<<") already connected to "<<qPrintable(dst);
else
connect(src, qPrintable(dst));
}
}
}
}
//---------------------------------------------------------
// rememberMidiConnections
//---------------------------------------------------------
void JackAudio::rememberMidiConnections()
{
if (!preferences.getBool(PREF_IO_JACK_REMEMBERLASTCONNECTIONS))
return;
if (MScore::debugMode)
qDebug("Saving midi connections...");
QSettings settings;
int port = 0;
foreach(jack_port_t* mp, midiOutputPorts) {
const char** cc = jack_port_get_connections(mp);
const char** c = cc;
int idx = 0;
while (c) {
const char* p = *c++;
if (p == 0)
break;
settings.setValue(QString("midi-%1-%2").arg(port).arg(idx), p);
++idx;
}
settings.setValue(QString("midi-%1-connections").arg(port), idx);
free((void*)cc);
++port;
}
port = 0;
foreach(jack_port_t* mp, midiInputPorts) {
const char** cc = jack_port_get_connections(mp);
const char** c = cc;
int idx = 0;
while (c) {
const char* p = *c++;
if (p == 0)
break;
settings.setValue(QString("midiin-%1-%2").arg(idx).arg(port), p);
++idx;
}
settings.setValue(QString("midiin-%1-connections").arg(port), idx);
free((void*)cc);
++port;
}
}
//---------------------------------------------------------
// restoreMidiConnections
// Connects to the ports from previous connection
//---------------------------------------------------------
void JackAudio::restoreMidiConnections()
{
if (!preferences.getBool(PREF_IO_JACK_REMEMBERLASTCONNECTIONS))
return;
if (MScore::debugMode)
qDebug("Restoring midi connections...");
QSettings settings;
int nPorts = midiOutputPorts.size();
for (int i = 0; i < nPorts; ++i) {
int n = settings.value(QString("midi-%1-connections").arg(i), 0).toInt();
const char* src = jack_port_name(midiOutputPorts[i]);
for (int k = 0; k < n; ++k) {
QString dst = settings.value(QString("midi-%1-%2").arg(i).arg(k), "").toString();
if (!dst.isEmpty()) {
if (jack_port_connected_to(midiOutputPorts[i], qPrintable(dst)))
continue;
connect(src, qPrintable(dst));
}
}
}
nPorts = midiInputPorts.size();
for (int i = 0; i < nPorts; ++i) {
int n = settings.value(QString("midiin-%1-connections").arg(i), 0).toInt();
const char* dst = jack_port_name(midiInputPorts[i]);
for (int k = 0; k < n; ++k) {
QString src = settings.value(QString("midiin-%1-%2").arg(k).arg(i), "").toString();
if (!src.isEmpty()) {
if (jack_port_connected_to(midiInputPorts[i], qPrintable(src)))
continue;
connect(qPrintable(src), dst);
}
}
}
}
//---------------------------------------------------------
// hotPlug
// Change driver settings without unload
//---------------------------------------------------------
void JackAudio::hotPlug()
{
bool oldremember = preferences.getBool(PREF_IO_JACK_REMEMBERLASTCONNECTIONS);
preferences.setPreference(PREF_IO_JACK_REMEMBERLASTCONNECTIONS, true);
// Remember connections before calling jack_deactivate() - it disconnects all ports
rememberMidiConnections();
if (ports.size() != 0)
rememberAudioConnections();
// We must set callbacks only on inactive client
if (jack_deactivate(client))
qDebug("cannot deactivate client");
// Audio connections
if (preferences.getBool(PREF_IO_JACK_USEJACKAUDIO)) {
if (ports.size() == 0) {
registerPort("left", false, false);
registerPort("right", false, false);
}
}
else if (!preferences.getBool(PREF_IO_JACK_USEJACKAUDIO)) {
foreach(jack_port_t* p, ports) {
unregisterPort(p);
ports.removeOne(p);
}
}
// Midi connections
if (preferences.getBool(PREF_IO_JACK_USEJACKMIDI)) {
if (midiInputPorts.size() == 0)
registerPort(QString("mscore-midiin-1"), true, true);
}
else { // No midi
updateOutPortCount(0);
if (midiInputPorts.size() != 0) {
unregisterPort(midiInputPorts[0]);
midiInputPorts.removeOne(midiInputPorts[0]);
}
}
// Timebase Master callback
if (preferences.getBool(PREF_IO_JACK_TIMEBASEMASTER))
setTimebaseCallback();
else
releaseTimebaseCallback();
preferences.setPreference(PREF_IO_JACK_REMEMBERLASTCONNECTIONS, oldremember);
}
}