MuseScore/mscore/jackaudio.cpp
Dag Henning Liodden Sørbø 2b6cb8b432 Change to new preferences model
The old Preferences struct holding all preferences are removed in favor of a
new Preferences class which acts as a proxy for QSettings. The settings stored
in QSettings are accessed directly through access methods like getBool(key),
getInt(key), etc. and changed with setPreference(key, value).

Since we are using QSettings directly the preferences are stored automatically
without the need for a custom write() and read() method like before.

The preferences.cpp/.h and prefdialog.cpp/h are refactored to have fewer
responsibilities than before. The Preferences class are all about storing and
retrieving preferences - it should not contain any code to handle any other
aspect of MuseScore.

Testing:
The Preferences class can be used in tests. All preferences are initialized with
default values in mtest. If a test requires that a preference has a specific
value it can be changed using setPreference() for that single test. In the tests
the preferences are stored in memory only.

The Preference class is supposed to be used as a singleton. In preferences.h an
'extern Preferences preferences' is set and it is defined in preferences.cpp. All
files which includes preferences.h have access to the 'preferences' singleton
and should use this to get and set preferences.
2018-02-08 16:59:10 +01:00

967 lines
35 KiB
C++

//=============================================================================
// MusE Score
// Linux Music Score Editor
// $Id: jackaudio.cpp 5660 2012-05-22 14:17:39Z wschweer $
//
// 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.
//=============================================================================
#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** ports = jack_get_ports(client, 0, 0, 0);
QList<QString> clientList;
for (const char** p = ports; 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, 128);
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()->repeatList() || !audio->seq->score()->sigmap())
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;
int r = jack_midi_event_get(&event, portBuffer, i);
if (r != 0)
continue;
int 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) {
float buffer[frames * 2];
audio->seq->process((unsigned)frames, buffer);
float* sp = buffer;
for (unsigned i = 0; i < frames; ++i) {
*l++ = *sp++;
*r++ = *sp++;
}
}
else {
// JACK MIDI only
float buffer[frames * 2];
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 n = settings.value(QString("audio-%1-connections").arg(i), 0).toInt();
const char* src = jack_port_name(ports[i]);
for (int k = 0; k < n; ++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);
}
}