You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2478 lines
72 KiB
2478 lines
72 KiB
/*
|
|
Rosegarden
|
|
A sequencer and musical notation editor.
|
|
|
|
This program is Copyright 2000-2008
|
|
Guillaume Laurent <glaurent@telegraph-road.org>,
|
|
Chris Cannam <cannam@all-day-breakfast.com>,
|
|
Richard Bown <bownie@bownie.com>
|
|
|
|
The moral right of the authors to claim authorship of this work
|
|
has been asserted.
|
|
|
|
This program 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. See the file
|
|
COPYING included with this distribution for more information.
|
|
*/
|
|
|
|
#include "JackDriver.h"
|
|
#include "AlsaDriver.h"
|
|
#include "MappedStudio.h"
|
|
#include "AudioProcess.h"
|
|
#include "Profiler.h"
|
|
#include "AudioLevel.h"
|
|
#include "Audit.h"
|
|
#include "PluginFactory.h"
|
|
|
|
#ifdef HAVE_ALSA
|
|
#ifdef HAVE_LIBJACK
|
|
|
|
//#define DEBUG_JACK_DRIVER 1
|
|
//#define DEBUG_JACK_TRANSPORT 1
|
|
//#define DEBUG_JACK_PROCESS 1
|
|
//#define DEBUG_JACK_XRUN 1
|
|
|
|
namespace Rosegarden
|
|
{
|
|
|
|
#if (defined(DEBUG_JACK_DRIVER) || defined(DEBUG_JACK_PROCESS) || defined(DEBUG_JACK_TRANSPORT))
|
|
static unsigned long framesThisPlay = 0;
|
|
static RealTime startTime;
|
|
#endif
|
|
|
|
JackDriver::JackDriver(AlsaDriver *alsaDriver) :
|
|
m_client(0),
|
|
m_bufferSize(0),
|
|
m_sampleRate(0),
|
|
m_tempOutBuffer(0),
|
|
m_jackTransportEnabled(false),
|
|
m_jackTransportMaster(false),
|
|
m_waiting(false),
|
|
m_waitingState(JackTransportStopped),
|
|
m_waitingToken(0),
|
|
m_ignoreProcessTransportCount(0),
|
|
m_bussMixer(0),
|
|
m_instrumentMixer(0),
|
|
m_fileReader(0),
|
|
m_fileWriter(0),
|
|
m_alsaDriver(alsaDriver),
|
|
m_masterLevel(1.0),
|
|
m_directMasterAudioInstruments(0L),
|
|
m_directMasterSynthInstruments(0L),
|
|
m_haveAsyncAudioEvent(false),
|
|
m_kickedOutAt(0),
|
|
m_framesProcessed(0),
|
|
m_ok(false)
|
|
{
|
|
assert(sizeof(sample_t) == sizeof(float));
|
|
initialise();
|
|
}
|
|
|
|
JackDriver::~JackDriver()
|
|
{
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver::~JackDriver" << std::endl;
|
|
#endif
|
|
|
|
m_ok = false; // prevent any more work in process()
|
|
|
|
if (m_client) {
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver::shutdown - deactivating JACK client"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
if (jack_deactivate(m_client)) {
|
|
std::cerr << "JackDriver::shutdown - deactivation failed"
|
|
<< std::endl;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver::~JackDriver: terminating buss mixer" << std::endl;
|
|
#endif
|
|
|
|
AudioBussMixer *bussMixer = m_bussMixer;
|
|
m_bussMixer = 0;
|
|
if (bussMixer)
|
|
bussMixer->terminate();
|
|
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver::~JackDriver: terminating instrument mixer" << std::endl;
|
|
#endif
|
|
|
|
AudioInstrumentMixer *instrumentMixer = m_instrumentMixer;
|
|
m_instrumentMixer = 0;
|
|
if (instrumentMixer) {
|
|
instrumentMixer->terminate();
|
|
instrumentMixer->destroyAllPlugins();
|
|
}
|
|
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver::~JackDriver: terminating file reader" << std::endl;
|
|
#endif
|
|
|
|
AudioFileReader *reader = m_fileReader;
|
|
m_fileReader = 0;
|
|
if (reader)
|
|
reader->terminate();
|
|
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver::~JackDriver: terminating file writer" << std::endl;
|
|
#endif
|
|
|
|
AudioFileWriter *writer = m_fileWriter;
|
|
m_fileWriter = 0;
|
|
if (writer)
|
|
writer->terminate();
|
|
|
|
if (m_client) {
|
|
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver::shutdown - tearing down JACK client"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
for (unsigned int i = 0; i < m_inputPorts.size(); ++i) {
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "unregistering input " << i << std::endl;
|
|
#endif
|
|
|
|
if (jack_port_unregister(m_client, m_inputPorts[i])) {
|
|
std::cerr << "JackDriver::shutdown - "
|
|
<< "can't unregister input port " << i + 1
|
|
<< std::endl;
|
|
}
|
|
}
|
|
|
|
for (unsigned int i = 0; i < m_outputSubmasters.size(); ++i) {
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "unregistering output sub " << i << std::endl;
|
|
#endif
|
|
|
|
if (jack_port_unregister(m_client, m_outputSubmasters[i])) {
|
|
std::cerr << "JackDriver::shutdown - "
|
|
<< "can't unregister output submaster " << i + 1 << std::endl;
|
|
}
|
|
}
|
|
|
|
for (unsigned int i = 0; i < m_outputMonitors.size(); ++i) {
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "unregistering output mon " << i << std::endl;
|
|
#endif
|
|
|
|
if (jack_port_unregister(m_client, m_outputMonitors[i])) {
|
|
std::cerr << "JackDriver::shutdown - "
|
|
<< "can't unregister output monitor " << i + 1 << std::endl;
|
|
}
|
|
}
|
|
|
|
for (unsigned int i = 0; i < m_outputMasters.size(); ++i) {
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "unregistering output master " << i << std::endl;
|
|
#endif
|
|
|
|
if (jack_port_unregister(m_client, m_outputMasters[i])) {
|
|
std::cerr << "JackDriver::shutdown - "
|
|
<< "can't unregister output master " << i + 1 << std::endl;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "closing client" << std::endl;
|
|
#endif
|
|
|
|
jack_client_close(m_client);
|
|
std::cerr << "done" << std::endl;
|
|
m_client = 0;
|
|
}
|
|
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver: deleting mixers etc" << std::endl;
|
|
#endif
|
|
|
|
delete bussMixer;
|
|
delete instrumentMixer;
|
|
delete reader;
|
|
delete writer;
|
|
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
|
|
std::cerr << "JackDriver::~JackDriver exiting" << std::endl;
|
|
#endif
|
|
}
|
|
|
|
void
|
|
JackDriver::initialise(bool reinitialise)
|
|
{
|
|
m_ok = false;
|
|
|
|
Audit audit;
|
|
audit << std::endl;
|
|
|
|
std::string jackClientName = "rosegarden";
|
|
|
|
// attempt connection to JACK server
|
|
//
|
|
if ((m_client = jack_client_new(jackClientName.c_str())) == 0) {
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "JACK server not running"
|
|
<< std::endl;
|
|
return ;
|
|
}
|
|
|
|
InstrumentId instrumentBase;
|
|
int instrumentCount;
|
|
m_alsaDriver->getAudioInstrumentNumbers(instrumentBase, instrumentCount);
|
|
for (InstrumentId id = instrumentBase;
|
|
id < instrumentBase + instrumentCount; ++id) {
|
|
// prefill so that we can refer to the map without a lock (as
|
|
// the number of instruments won't change)
|
|
m_recordInputs[id] = RecordInputDesc(1000, -1, 0.0);
|
|
}
|
|
|
|
// set callbacks
|
|
//
|
|
jack_set_process_callback(m_client, jackProcessStatic, this);
|
|
jack_set_buffer_size_callback(m_client, jackBufferSize, this);
|
|
jack_set_sample_rate_callback(m_client, jackSampleRate, this);
|
|
jack_on_shutdown(m_client, jackShutdown, this);
|
|
jack_set_xrun_callback(m_client, jackXRun, this);
|
|
jack_set_sync_callback(m_client, jackSyncCallback, this);
|
|
|
|
// get and report the sample rate and buffer size
|
|
//
|
|
m_sampleRate = jack_get_sample_rate(m_client);
|
|
m_bufferSize = jack_get_buffer_size(m_client);
|
|
|
|
audit << "JackDriver::initialiseAudio - JACK sample rate = "
|
|
<< m_sampleRate << "Hz, buffer size = " << m_bufferSize
|
|
<< std::endl;
|
|
|
|
PluginFactory::setSampleRate(m_sampleRate);
|
|
|
|
// Get the initial buffer size before we activate the client
|
|
//
|
|
|
|
if (!reinitialise) {
|
|
|
|
// create processing buffer(s)
|
|
//
|
|
m_tempOutBuffer = new sample_t[m_bufferSize];
|
|
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "creating disk thread" << std::endl;
|
|
|
|
m_fileReader = new AudioFileReader(m_alsaDriver, m_sampleRate);
|
|
m_fileWriter = new AudioFileWriter(m_alsaDriver, m_sampleRate);
|
|
m_instrumentMixer = new AudioInstrumentMixer
|
|
(m_alsaDriver, m_fileReader, m_sampleRate, m_bufferSize);
|
|
m_bussMixer = new AudioBussMixer
|
|
(m_alsaDriver, m_instrumentMixer, m_sampleRate, m_bufferSize);
|
|
m_instrumentMixer->setBussMixer(m_bussMixer);
|
|
|
|
// We run the file reader whatever, but we only run the other
|
|
// threads (instrument mixer, buss mixer, file writer) when we
|
|
// actually need them. (See updateAudioData and createRecordFile.)
|
|
m_fileReader->run();
|
|
}
|
|
|
|
// Create and connect the default numbers of ports. We always create
|
|
// one stereo pair each of master and monitor outs, and then we create
|
|
// record ins, fader outs and submaster outs according to the user's
|
|
// preferences. Since we don't know the user's preferences yet, we'll
|
|
// start by creating one pair of record ins and no fader or submaster
|
|
// outs.
|
|
//
|
|
m_outputMasters.clear();
|
|
m_outputMonitors.clear();
|
|
m_outputSubmasters.clear();
|
|
m_outputInstruments.clear();
|
|
m_inputPorts.clear();
|
|
|
|
if (!createMainOutputs()) { // one stereo pair master, one pair monitor
|
|
audit << "JackDriver::initialise - "
|
|
<< "failed to create main outputs!" << std::endl;
|
|
return ;
|
|
}
|
|
|
|
if (!createRecordInputs(1)) {
|
|
audit << "JackDriver::initialise - "
|
|
<< "failed to create record inputs!" << std::endl;
|
|
return ;
|
|
}
|
|
|
|
if (jack_activate(m_client)) {
|
|
audit << "JackDriver::initialise - "
|
|
<< "client activation failed" << std::endl;
|
|
return ;
|
|
}
|
|
|
|
// Now set up the default connections.
|
|
|
|
std::string playback_1, playback_2;
|
|
|
|
const char **ports =
|
|
jack_get_ports(m_client, NULL, NULL,
|
|
JackPortIsPhysical | JackPortIsInput);
|
|
|
|
if (ports) {
|
|
if (ports[0])
|
|
playback_1 = std::string(ports[0]);
|
|
if (ports[1])
|
|
playback_2 = std::string(ports[1]);
|
|
|
|
// count ports
|
|
unsigned int i = 0;
|
|
for (i = 0; ports[i]; i++)
|
|
;
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "found " << i << " JACK physical outputs"
|
|
<< std::endl;
|
|
} else
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "no JACK physical outputs found"
|
|
<< std::endl;
|
|
free(ports);
|
|
|
|
if (playback_1 != "") {
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "connecting from "
|
|
<< "\"" << jack_port_name(m_outputMasters[0])
|
|
<< "\" to \"" << playback_1.c_str() << "\""
|
|
<< std::endl;
|
|
|
|
// connect our client up to the ALSA ports - first left output
|
|
//
|
|
if (jack_connect(m_client, jack_port_name(m_outputMasters[0]),
|
|
playback_1.c_str())) {
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "cannot connect to JACK output port" << std::endl;
|
|
return ;
|
|
}
|
|
|
|
/*
|
|
if (jack_connect(m_client, jack_port_name(m_outputMonitors[0]),
|
|
playback_1.c_str()))
|
|
{
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "cannot connect to JACK output port" << std::endl;
|
|
return;
|
|
}
|
|
*/
|
|
}
|
|
|
|
if (playback_2 != "") {
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "connecting from "
|
|
<< "\"" << jack_port_name(m_outputMasters[1])
|
|
<< "\" to \"" << playback_2.c_str() << "\""
|
|
<< std::endl;
|
|
|
|
if (jack_connect(m_client, jack_port_name(m_outputMasters[1]),
|
|
playback_2.c_str())) {
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "cannot connect to JACK output port" << std::endl;
|
|
}
|
|
|
|
/*
|
|
if (jack_connect(m_client, jack_port_name(m_outputMonitors[1]),
|
|
playback_2.c_str()))
|
|
{
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "cannot connect to JACK output port" << std::endl;
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
std::string capture_1, capture_2;
|
|
|
|
ports =
|
|
jack_get_ports(m_client, NULL, NULL,
|
|
JackPortIsPhysical | JackPortIsOutput);
|
|
|
|
if (ports) {
|
|
if (ports[0])
|
|
capture_1 = std::string(ports[0]);
|
|
if (ports[1])
|
|
capture_2 = std::string(ports[1]);
|
|
|
|
// count ports
|
|
unsigned int i = 0;
|
|
for (i = 0; ports[i]; i++)
|
|
;
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "found " << i << " JACK physical inputs"
|
|
<< std::endl;
|
|
} else
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "no JACK physical inputs found"
|
|
<< std::endl;
|
|
free(ports);
|
|
|
|
if (capture_1 != "") {
|
|
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "connecting from "
|
|
<< "\"" << capture_1.c_str()
|
|
<< "\" to \"" << jack_port_name(m_inputPorts[0]) << "\""
|
|
<< std::endl;
|
|
|
|
if (jack_connect(m_client, capture_1.c_str(),
|
|
jack_port_name(m_inputPorts[0]))) {
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "cannot connect to JACK input port" << std::endl;
|
|
}
|
|
}
|
|
|
|
if (capture_2 != "") {
|
|
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "connecting from "
|
|
<< "\"" << capture_2.c_str()
|
|
<< "\" to \"" << jack_port_name(m_inputPorts[1]) << "\""
|
|
<< std::endl;
|
|
|
|
if (jack_connect(m_client, capture_2.c_str(),
|
|
jack_port_name(m_inputPorts[1]))) {
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "cannot connect to JACK input port" << std::endl;
|
|
}
|
|
}
|
|
|
|
audit << "JackDriver::initialiseAudio - "
|
|
<< "initialised JACK audio subsystem"
|
|
<< std::endl;
|
|
|
|
m_ok = true;
|
|
}
|
|
|
|
bool
|
|
JackDriver::createMainOutputs()
|
|
{
|
|
if (!m_client)
|
|
return false;
|
|
|
|
jack_port_t *port = jack_port_register
|
|
(m_client, "master out L",
|
|
JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
|
|
if (!port)
|
|
return false;
|
|
m_outputMasters.push_back(port);
|
|
|
|
port = jack_port_register
|
|
(m_client, "master out R",
|
|
JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
|
|
if (!port)
|
|
return false;
|
|
m_outputMasters.push_back(port);
|
|
|
|
port = jack_port_register
|
|
(m_client, "record monitor out L",
|
|
JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
|
|
if (!port)
|
|
return false;
|
|
m_outputMonitors.push_back(port);
|
|
|
|
port = jack_port_register
|
|
(m_client, "record monitor out R",
|
|
JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
|
|
if (!port)
|
|
return false;
|
|
m_outputMonitors.push_back(port);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JackDriver::createFaderOutputs(int audioPairs, int synthPairs)
|
|
{
|
|
if (!m_client)
|
|
return false;
|
|
|
|
int pairs = audioPairs + synthPairs;
|
|
int pairsNow = m_outputInstruments.size() / 2;
|
|
if (pairs == pairsNow)
|
|
return true;
|
|
|
|
for (int i = pairsNow; i < pairs; ++i) {
|
|
|
|
char namebuffer[22];
|
|
jack_port_t *port;
|
|
|
|
if (i < audioPairs) {
|
|
snprintf(namebuffer, 21, "audio fader %d out L", i + 1);
|
|
} else {
|
|
snprintf(namebuffer, 21, "synth fader %d out L", i - audioPairs + 1);
|
|
}
|
|
|
|
port = jack_port_register(m_client,
|
|
namebuffer,
|
|
JACK_DEFAULT_AUDIO_TYPE,
|
|
JackPortIsOutput,
|
|
0);
|
|
if (!port)
|
|
return false;
|
|
m_outputInstruments.push_back(port);
|
|
|
|
if (i < audioPairs) {
|
|
snprintf(namebuffer, 21, "audio fader %d out R", i + 1);
|
|
} else {
|
|
snprintf(namebuffer, 21, "synth fader %d out R", i - audioPairs + 1);
|
|
}
|
|
|
|
port = jack_port_register(m_client,
|
|
namebuffer,
|
|
JACK_DEFAULT_AUDIO_TYPE,
|
|
JackPortIsOutput,
|
|
0);
|
|
if (!port)
|
|
return false;
|
|
m_outputInstruments.push_back(port);
|
|
}
|
|
|
|
while ((int)m_outputInstruments.size() > pairs * 2) {
|
|
std::vector<jack_port_t *>::iterator itr = m_outputInstruments.end();
|
|
--itr;
|
|
jack_port_unregister(m_client, *itr);
|
|
m_outputInstruments.erase(itr);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JackDriver::createSubmasterOutputs(int pairs)
|
|
{
|
|
if (!m_client)
|
|
return false;
|
|
|
|
int pairsNow = m_outputSubmasters.size() / 2;
|
|
if (pairs == pairsNow)
|
|
return true;
|
|
|
|
for (int i = pairsNow; i < pairs; ++i) {
|
|
|
|
char namebuffer[22];
|
|
jack_port_t *port;
|
|
|
|
snprintf(namebuffer, 21, "submaster %d out L", i + 1);
|
|
port = jack_port_register(m_client,
|
|
namebuffer,
|
|
JACK_DEFAULT_AUDIO_TYPE,
|
|
JackPortIsOutput,
|
|
0);
|
|
if (!port)
|
|
return false;
|
|
m_outputSubmasters.push_back(port);
|
|
|
|
snprintf(namebuffer, 21, "submaster %d out R", i + 1);
|
|
port = jack_port_register(m_client,
|
|
namebuffer,
|
|
JACK_DEFAULT_AUDIO_TYPE,
|
|
JackPortIsOutput,
|
|
0);
|
|
if (!port)
|
|
return false;
|
|
m_outputSubmasters.push_back(port);
|
|
}
|
|
|
|
while ((int)m_outputSubmasters.size() > pairs * 2) {
|
|
std::vector<jack_port_t *>::iterator itr = m_outputSubmasters.end();
|
|
--itr;
|
|
jack_port_unregister(m_client, *itr);
|
|
m_outputSubmasters.erase(itr);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JackDriver::createRecordInputs(int pairs)
|
|
{
|
|
if (!m_client)
|
|
return false;
|
|
|
|
int pairsNow = m_inputPorts.size() / 2;
|
|
if (pairs == pairsNow)
|
|
return true;
|
|
|
|
for (int i = pairsNow; i < pairs; ++i) {
|
|
|
|
char namebuffer[22];
|
|
jack_port_t *port;
|
|
|
|
snprintf(namebuffer, 21, "record in %d L", i + 1);
|
|
port = jack_port_register(m_client,
|
|
namebuffer,
|
|
JACK_DEFAULT_AUDIO_TYPE,
|
|
JackPortIsInput,
|
|
0);
|
|
if (!port)
|
|
return false;
|
|
m_inputPorts.push_back(port);
|
|
|
|
snprintf(namebuffer, 21, "record in %d R", i + 1);
|
|
port = jack_port_register(m_client,
|
|
namebuffer,
|
|
JACK_DEFAULT_AUDIO_TYPE,
|
|
JackPortIsInput,
|
|
0);
|
|
if (!port)
|
|
return false;
|
|
m_inputPorts.push_back(port);
|
|
}
|
|
|
|
while ((int)m_outputSubmasters.size() > pairs * 2) {
|
|
std::vector<jack_port_t *>::iterator itr = m_outputSubmasters.end();
|
|
--itr;
|
|
jack_port_unregister(m_client, *itr);
|
|
m_outputSubmasters.erase(itr);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void
|
|
JackDriver::setAudioPorts(bool faderOuts, bool submasterOuts)
|
|
{
|
|
if (!m_client)
|
|
return ;
|
|
|
|
Audit audit;
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
|
|
std::cerr << "JackDriver::setAudioPorts(" << faderOuts << "," << submasterOuts << ")" << std::endl;
|
|
#endif
|
|
|
|
if (!m_client) {
|
|
std::cerr << "JackDriver::setAudioPorts(" << faderOuts << "," << submasterOuts << "): no client yet" << std::endl;
|
|
return ;
|
|
}
|
|
|
|
if (faderOuts) {
|
|
InstrumentId instrumentBase;
|
|
int audioInstruments;
|
|
int synthInstruments;
|
|
m_alsaDriver->getAudioInstrumentNumbers(instrumentBase, audioInstruments);
|
|
m_alsaDriver->getSoftSynthInstrumentNumbers(instrumentBase, synthInstruments);
|
|
if (!createFaderOutputs(audioInstruments, synthInstruments)) {
|
|
m_ok = false;
|
|
audit << "Failed to create fader outs!" << std::endl;
|
|
return ;
|
|
}
|
|
} else {
|
|
createFaderOutputs(0, 0);
|
|
}
|
|
|
|
if (submasterOuts) {
|
|
|
|
// one fewer than returned here, because the master has a buss object too
|
|
if (!createSubmasterOutputs
|
|
(m_alsaDriver->getMappedStudio()->getObjectCount
|
|
(MappedObject::AudioBuss) - 1)) {
|
|
m_ok = false;
|
|
audit << "Failed to create submaster outs!" << std::endl;
|
|
return ;
|
|
}
|
|
|
|
} else {
|
|
createSubmasterOutputs(0);
|
|
}
|
|
}
|
|
|
|
RealTime
|
|
JackDriver::getAudioPlayLatency() const
|
|
{
|
|
if (!m_client)
|
|
return RealTime::zeroTime;
|
|
|
|
jack_nframes_t latency =
|
|
jack_port_get_total_latency(m_client, m_outputMasters[0]);
|
|
|
|
return RealTime::frame2RealTime(latency, m_sampleRate);
|
|
}
|
|
|
|
RealTime
|
|
JackDriver::getAudioRecordLatency() const
|
|
{
|
|
if (!m_client)
|
|
return RealTime::zeroTime;
|
|
|
|
jack_nframes_t latency =
|
|
jack_port_get_total_latency(m_client, m_inputPorts[0]);
|
|
|
|
return RealTime::frame2RealTime(latency, m_sampleRate);
|
|
}
|
|
|
|
RealTime
|
|
JackDriver::getInstrumentPlayLatency(InstrumentId id) const
|
|
{
|
|
if (m_instrumentLatencies.find(id) == m_instrumentLatencies.end()) {
|
|
return RealTime::zeroTime;
|
|
} else {
|
|
return m_instrumentLatencies.find(id)->second;
|
|
}
|
|
}
|
|
|
|
RealTime
|
|
JackDriver::getMaximumPlayLatency() const
|
|
{
|
|
return m_maxInstrumentLatency;
|
|
}
|
|
|
|
int
|
|
JackDriver::jackProcessStatic(jack_nframes_t nframes, void *arg)
|
|
{
|
|
JackDriver *inst = static_cast<JackDriver*>(arg);
|
|
if (inst)
|
|
return inst->jackProcess(nframes);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
JackDriver::jackProcess(jack_nframes_t nframes)
|
|
{
|
|
if (!m_ok || !m_client) {
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
std::cerr << "JackDriver::jackProcess: not OK" << std::endl;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (!m_bussMixer) {
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
std::cerr << "JackDriver::jackProcess: no buss mixer" << std::endl;
|
|
#endif
|
|
|
|
return jackProcessEmpty(nframes);
|
|
}
|
|
|
|
if (m_alsaDriver->areClocksRunning()) {
|
|
m_alsaDriver->checkTimerSync(m_framesProcessed);
|
|
} else {
|
|
m_alsaDriver->checkTimerSync(0);
|
|
}
|
|
|
|
bool lowLatencyMode = m_alsaDriver->getLowLatencyMode();
|
|
bool clocksRunning = m_alsaDriver->areClocksRunning();
|
|
bool playing = m_alsaDriver->isPlaying();
|
|
bool asyncAudio = m_haveAsyncAudioEvent;
|
|
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
|
|
Profiler profiler("jackProcess", true);
|
|
#else
|
|
#ifdef DEBUG_JACK_XRUN
|
|
|
|
Profiler profiler("jackProcess", false);
|
|
#endif
|
|
#endif
|
|
|
|
if (lowLatencyMode) {
|
|
if (clocksRunning) {
|
|
if (playing || asyncAudio) {
|
|
|
|
if (m_instrumentMixer->tryLock() == 0) {
|
|
m_instrumentMixer->kick(false);
|
|
m_instrumentMixer->releaseLock();
|
|
//#ifdef DEBUG_JACK_PROCESS
|
|
} else {
|
|
std::cerr << "JackDriver::jackProcess: no instrument mixer lock available" << std::endl;
|
|
//#endif
|
|
}
|
|
if (m_bussMixer->getBussCount() > 0) {
|
|
if (m_bussMixer->tryLock() == 0) {
|
|
m_bussMixer->kick(false, false);
|
|
m_bussMixer->releaseLock();
|
|
//#ifdef DEBUG_JACK_PROCESS
|
|
} else {
|
|
std::cerr << "JackDriver::jackProcess: no buss mixer lock available" << std::endl;
|
|
//#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (jack_cpu_load(m_client) > 97.0) {
|
|
reportFailure(MappedEvent::FailureCPUOverload);
|
|
return jackProcessEmpty(nframes);
|
|
}
|
|
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
Profiler profiler2("jackProcess post mix", true);
|
|
#else
|
|
#ifdef DEBUG_JACK_XRUN
|
|
|
|
Profiler profiler2("jackProcess post mix", false);
|
|
#endif
|
|
#endif
|
|
|
|
SequencerDataBlock *sdb = m_alsaDriver->getSequencerDataBlock();
|
|
|
|
jack_position_t position;
|
|
jack_transport_state_t state = JackTransportRolling;
|
|
bool doneRecord = false;
|
|
|
|
int ignoreCount = m_ignoreProcessTransportCount;
|
|
if (ignoreCount > 0)
|
|
--m_ignoreProcessTransportCount;
|
|
|
|
InstrumentId audioInstrumentBase;
|
|
int audioInstruments;
|
|
m_alsaDriver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
|
|
|
|
if (m_jackTransportEnabled) {
|
|
|
|
state = jack_transport_query(m_client, &position);
|
|
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
|
|
std::cerr << "JackDriver::jackProcess: JACK transport state is " << state << std::endl;
|
|
#endif
|
|
|
|
if (state == JackTransportStopped) {
|
|
if (playing && clocksRunning && !m_waiting) {
|
|
ExternalTransport *transport =
|
|
m_alsaDriver->getExternalTransportControl();
|
|
if (transport) {
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
std::cerr << "JackDriver::jackProcess: JACK transport stopped externally at " << position.frame << std::endl;
|
|
#endif
|
|
|
|
m_waitingToken =
|
|
transport->transportJump
|
|
(ExternalTransport::TransportStopAtTime,
|
|
RealTime::frame2RealTime(position.frame,
|
|
position.frame_rate));
|
|
}
|
|
} else if (clocksRunning) {
|
|
if (!asyncAudio) {
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
std::cerr << "JackDriver::jackProcess: no interesting async events" << std::endl;
|
|
#endif
|
|
// do this before record monitor, otherwise we lose monitor out
|
|
jackProcessEmpty(nframes);
|
|
}
|
|
|
|
// for monitoring:
|
|
int rv = 0;
|
|
for (InstrumentId id = audioInstrumentBase;
|
|
id < audioInstrumentBase + audioInstruments; ++id) {
|
|
int irv = jackProcessRecord(id, nframes, 0, 0, clocksRunning);
|
|
if (irv != 0)
|
|
rv = irv;
|
|
}
|
|
doneRecord = true;
|
|
|
|
if (!asyncAudio) {
|
|
return rv;
|
|
}
|
|
|
|
} else {
|
|
return jackProcessEmpty(nframes);
|
|
}
|
|
} else if (state == JackTransportStarting) {
|
|
return jackProcessEmpty(nframes);
|
|
} else if (state != JackTransportRolling) {
|
|
std::cerr << "JackDriver::jackProcess: unexpected JACK transport state " << state << std::endl;
|
|
}
|
|
}
|
|
|
|
if (state == JackTransportRolling) { // also covers not-on-transport case
|
|
if (m_waiting) {
|
|
if (ignoreCount > 0) {
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
std::cerr << "JackDriver::jackProcess: transport rolling, but we're ignoring it (count = " << ignoreCount << ")" << std::endl;
|
|
#endif
|
|
|
|
} else {
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
std::cerr << "JackDriver::jackProcess: transport rolling, telling ALSA driver to go!" << std::endl;
|
|
#endif
|
|
|
|
m_alsaDriver->startClocksApproved();
|
|
m_waiting = false;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
std::cerr << "JackDriver::jackProcess (rolling or not on JACK transport)" << std::endl;
|
|
#endif
|
|
|
|
if (!clocksRunning) {
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
std::cerr << "JackDriver::jackProcess: clocks stopped" << std::endl;
|
|
#endif
|
|
|
|
return jackProcessEmpty(nframes);
|
|
|
|
} else if (!playing) {
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
std::cerr << "JackDriver::jackProcess: not playing" << std::endl;
|
|
#endif
|
|
|
|
if (!asyncAudio) {
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
std::cerr << "JackDriver::jackProcess: no interesting async events" << std::endl;
|
|
#endif
|
|
// do this before record monitor, otherwise we lose monitor out
|
|
jackProcessEmpty(nframes);
|
|
}
|
|
|
|
// for monitoring:
|
|
int rv = 0;
|
|
for (InstrumentId id = audioInstrumentBase;
|
|
id < audioInstrumentBase + audioInstruments; ++id) {
|
|
int irv = jackProcessRecord(id, nframes, 0, 0, clocksRunning);
|
|
if (irv != 0)
|
|
rv = irv;
|
|
}
|
|
doneRecord = true;
|
|
|
|
if (!asyncAudio) {
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
Profiler profiler3("jackProcess post transport", true);
|
|
#else
|
|
#ifdef DEBUG_JACK_XRUN
|
|
|
|
Profiler profiler3("jackProcess post transport", false);
|
|
#endif
|
|
#endif
|
|
|
|
InstrumentId synthInstrumentBase;
|
|
int synthInstruments;
|
|
m_alsaDriver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
|
|
|
|
// We always have the master out
|
|
|
|
sample_t *master[2] = {
|
|
static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputMasters[0], nframes)),
|
|
static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputMasters[1], nframes))
|
|
};
|
|
|
|
memset(master[0], 0, nframes * sizeof(sample_t));
|
|
memset(master[1], 0, nframes * sizeof(sample_t));
|
|
|
|
// Reset monitor outs (if present) here prior to mixing
|
|
|
|
if (m_outputMonitors.size() > 0) {
|
|
sample_t *buffer = static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputMonitors[0], nframes));
|
|
if (buffer)
|
|
memset(buffer, 0, nframes * sizeof(sample_t));
|
|
}
|
|
|
|
if (m_outputMonitors.size() > 1) {
|
|
sample_t *buffer = static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputMonitors[1], nframes));
|
|
if (buffer)
|
|
memset(buffer, 0, nframes * sizeof(sample_t));
|
|
}
|
|
|
|
int bussCount = m_bussMixer->getBussCount();
|
|
|
|
// If we have any busses, then we just mix from them (but we still
|
|
// need to keep ourselves up to date by reading and monitoring the
|
|
// instruments). If we have no busses, mix direct from instruments.
|
|
|
|
for (int buss = 0; buss < bussCount; ++buss) {
|
|
|
|
sample_t *submaster[2] = { 0, 0 };
|
|
sample_t peak[2] = { 0.0, 0.0 };
|
|
|
|
if ((int)m_outputSubmasters.size() > buss * 2 + 1) {
|
|
submaster[0] = static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputSubmasters[buss * 2], nframes));
|
|
submaster[1] = static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputSubmasters[buss * 2 + 1], nframes));
|
|
}
|
|
|
|
if (!submaster[0])
|
|
submaster[0] = m_tempOutBuffer;
|
|
if (!submaster[1])
|
|
submaster[1] = m_tempOutBuffer;
|
|
|
|
for (int ch = 0; ch < 2; ++ch) {
|
|
|
|
RingBuffer<AudioBussMixer::sample_t> *rb =
|
|
m_bussMixer->getRingBuffer(buss, ch);
|
|
|
|
if (!rb || m_bussMixer->isBussDormant(buss)) {
|
|
if (rb)
|
|
rb->skip(nframes);
|
|
if (submaster[ch])
|
|
memset(submaster[ch], 0, nframes * sizeof(sample_t));
|
|
} else {
|
|
size_t actual = rb->read(submaster[ch], nframes);
|
|
if (actual < nframes) {
|
|
reportFailure(MappedEvent::FailureBussMixUnderrun);
|
|
}
|
|
for (size_t i = 0; i < nframes; ++i) {
|
|
sample_t sample = submaster[ch][i];
|
|
if (sample > peak[ch])
|
|
peak[ch] = sample;
|
|
master[ch][i] += sample;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sdb) {
|
|
LevelInfo info;
|
|
info.level = AudioLevel::multiplier_to_fader
|
|
(peak[0], 127, AudioLevel::LongFader);
|
|
info.levelRight = AudioLevel::multiplier_to_fader
|
|
(peak[1], 127, AudioLevel::LongFader);
|
|
|
|
sdb->setSubmasterLevel(buss, info);
|
|
}
|
|
|
|
for (InstrumentId id = audioInstrumentBase;
|
|
id < audioInstrumentBase + audioInstruments; ++id) {
|
|
if (buss + 1 == m_recordInputs[id].input) {
|
|
jackProcessRecord(id, nframes, submaster[0], submaster[1], clocksRunning);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
std::cerr << "JackDriver::jackProcess: have " << audioInstruments << " audio and " << synthInstruments << " synth instruments and " << bussCount << " busses" << std::endl;
|
|
#endif
|
|
|
|
bool allInstrumentsDormant = true;
|
|
static RealTime dormantTime = RealTime::zeroTime;
|
|
|
|
for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
|
|
|
|
InstrumentId id;
|
|
if (i < audioInstruments)
|
|
id = audioInstrumentBase + i;
|
|
else
|
|
id = synthInstrumentBase + (i - audioInstruments);
|
|
|
|
if (m_instrumentMixer->isInstrumentEmpty(id))
|
|
continue;
|
|
|
|
sample_t *instrument[2] = { 0, 0 };
|
|
sample_t peak[2] = { 0.0, 0.0 };
|
|
|
|
if (int(m_outputInstruments.size()) > i * 2 + 1) {
|
|
instrument[0] = static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputInstruments[i * 2], nframes));
|
|
instrument[1] = static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputInstruments[i * 2 + 1], nframes));
|
|
}
|
|
|
|
if (!instrument[0])
|
|
instrument[0] = m_tempOutBuffer;
|
|
if (!instrument[1])
|
|
instrument[1] = m_tempOutBuffer;
|
|
|
|
for (int ch = 0; ch < 2; ++ch) {
|
|
|
|
// We always need to read from an instrument's ring buffer
|
|
// to keep the instrument moving along, as well as for
|
|
// monitoring. If the instrument is connected straight to
|
|
// the master, then we also need to mix from it. (We have
|
|
// that information cached courtesy of updateAudioData.)
|
|
|
|
bool directToMaster = false;
|
|
if (i < audioInstruments) {
|
|
directToMaster = (m_directMasterAudioInstruments & (1 << i));
|
|
} else {
|
|
directToMaster = (m_directMasterSynthInstruments &
|
|
(1 << (i - audioInstruments)));
|
|
}
|
|
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
if (id == 1000 || id == 10000) {
|
|
std::cerr << "JackDriver::jackProcess: instrument id " << id << ", base " << audioInstrumentBase << ", direct masters " << m_directMasterAudioInstruments << ": " << directToMaster << std::endl;
|
|
}
|
|
#endif
|
|
|
|
RingBuffer<AudioInstrumentMixer::sample_t, 2> *rb =
|
|
m_instrumentMixer->getRingBuffer(id, ch);
|
|
|
|
if (!rb || m_instrumentMixer->isInstrumentDormant(id)) {
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
if (id == 1000 || id == 10000) {
|
|
if (rb) {
|
|
std::cerr << "JackDriver::jackProcess: instrument " << id << " dormant" << std::endl;
|
|
} else {
|
|
std::cerr << "JackDriver::jackProcess: instrument " << id << " has no ring buffer for channel " << ch << std::endl;
|
|
}
|
|
}
|
|
#endif
|
|
if (rb)
|
|
rb->skip(nframes);
|
|
if (instrument[ch])
|
|
memset(instrument[ch], 0, nframes * sizeof(sample_t));
|
|
|
|
} else {
|
|
|
|
allInstrumentsDormant = false;
|
|
|
|
size_t actual = rb->read(instrument[ch], nframes);
|
|
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
|
|
if (id == 1000) {
|
|
std::cerr << "JackDriver::jackProcess: read " << actual << " of " << nframes << " frames for instrument " << id << " channel " << ch << std::endl;
|
|
}
|
|
#endif
|
|
|
|
if (actual < nframes) {
|
|
|
|
std::cerr << "JackDriver::jackProcess: read " << actual << " of " << nframes << " frames for " << id << " ch " << ch << " (pl " << playing << ", cl " << clocksRunning << ", aa " << asyncAudio << ")" << std::endl;
|
|
|
|
reportFailure(MappedEvent::FailureMixUnderrun);
|
|
}
|
|
for (size_t f = 0; f < nframes; ++f) {
|
|
sample_t sample = instrument[ch][f];
|
|
if (sample > peak[ch])
|
|
peak[ch] = sample;
|
|
if (directToMaster)
|
|
master[ch][f] += sample;
|
|
}
|
|
}
|
|
|
|
// If the instrument is connected straight to master we
|
|
// also need to skip() on the buss mixer's reader for it,
|
|
// otherwise it'll block because the buss mixer isn't
|
|
// needing to read it.
|
|
|
|
if (rb && directToMaster) {
|
|
rb->skip(nframes, 1); // 1 is the buss mixer's reader (magic)
|
|
}
|
|
}
|
|
|
|
if (sdb) {
|
|
LevelInfo info;
|
|
info.level = AudioLevel::multiplier_to_fader
|
|
(peak[0], 127, AudioLevel::LongFader);
|
|
info.levelRight = AudioLevel::multiplier_to_fader
|
|
(peak[1], 127, AudioLevel::LongFader);
|
|
|
|
sdb->setInstrumentLevel(id, info);
|
|
}
|
|
}
|
|
|
|
if (asyncAudio) {
|
|
if (!allInstrumentsDormant) {
|
|
dormantTime = RealTime::zeroTime;
|
|
} else {
|
|
dormantTime = dormantTime +
|
|
RealTime::frame2RealTime(m_bufferSize, m_sampleRate);
|
|
if (dormantTime > RealTime(10, 0)) {
|
|
std::cerr << "JackDriver: dormantTime = " << dormantTime << ", resetting m_haveAsyncAudioEvent" << std::endl;
|
|
m_haveAsyncAudioEvent = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get master fader levels. There's no pan on the master.
|
|
float gain = AudioLevel::dB_to_multiplier(m_masterLevel);
|
|
float masterPeak[2] = { 0.0, 0.0 };
|
|
|
|
for (int ch = 0; ch < 2; ++ch) {
|
|
for (size_t i = 0; i < nframes; ++i) {
|
|
sample_t sample = master[ch][i] * gain;
|
|
if (sample > masterPeak[ch])
|
|
masterPeak[ch] = sample;
|
|
master[ch][i] = sample;
|
|
}
|
|
}
|
|
|
|
if (sdb) {
|
|
LevelInfo info;
|
|
info.level = AudioLevel::multiplier_to_fader
|
|
(masterPeak[0], 127, AudioLevel::LongFader);
|
|
info.levelRight = AudioLevel::multiplier_to_fader
|
|
(masterPeak[1], 127, AudioLevel::LongFader);
|
|
|
|
sdb->setMasterLevel(info);
|
|
}
|
|
|
|
for (InstrumentId id = audioInstrumentBase;
|
|
id < audioInstrumentBase + audioInstruments; ++id) {
|
|
if (m_recordInputs[id].input == 0) {
|
|
jackProcessRecord(id, nframes, master[0], master[1], clocksRunning);
|
|
} else if (m_recordInputs[id].input < 1000) { // buss, already done
|
|
// nothing
|
|
} else if (!doneRecord) {
|
|
jackProcessRecord(id, nframes, 0, 0, clocksRunning);
|
|
}
|
|
}
|
|
|
|
if (playing) {
|
|
if (!lowLatencyMode) {
|
|
if (m_bussMixer->getBussCount() == 0) {
|
|
m_instrumentMixer->signal();
|
|
} else {
|
|
m_bussMixer->signal();
|
|
}
|
|
}
|
|
}
|
|
|
|
m_framesProcessed += nframes;
|
|
|
|
#if (defined(DEBUG_JACK_DRIVER) || defined(DEBUG_JACK_PROCESS) || defined(DEBUG_JACK_TRANSPORT))
|
|
|
|
framesThisPlay += nframes; //!!!
|
|
#endif
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
|
|
std::cerr << "JackDriver::jackProcess: " << nframes << " frames, " << framesThisPlay << " this play, " << m_framesProcessed << " total" << std::endl;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
JackDriver::jackProcessEmpty(jack_nframes_t nframes)
|
|
{
|
|
sample_t *buffer;
|
|
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
|
|
std::cerr << "JackDriver::jackProcessEmpty" << std::endl;
|
|
#endif
|
|
|
|
buffer = static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputMasters[0], nframes));
|
|
if (buffer)
|
|
memset(buffer, 0, nframes * sizeof(sample_t));
|
|
|
|
buffer = static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputMasters[1], nframes));
|
|
if (buffer)
|
|
memset(buffer, 0, nframes * sizeof(sample_t));
|
|
|
|
buffer = static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputMonitors[0], nframes));
|
|
if (buffer)
|
|
memset(buffer, 0, nframes * sizeof(sample_t));
|
|
|
|
buffer = static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputMonitors[1], nframes));
|
|
if (buffer)
|
|
memset(buffer, 0, nframes * sizeof(sample_t));
|
|
|
|
for (unsigned int i = 0; i < m_outputSubmasters.size(); ++i) {
|
|
buffer = static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputSubmasters[i], nframes));
|
|
if (buffer)
|
|
memset(buffer, 0, nframes * sizeof(sample_t));
|
|
}
|
|
|
|
for (unsigned int i = 0; i < m_outputInstruments.size(); ++i) {
|
|
buffer = static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputInstruments[i], nframes));
|
|
if (buffer)
|
|
memset(buffer, 0, nframes * sizeof(sample_t));
|
|
}
|
|
|
|
m_framesProcessed += nframes;
|
|
|
|
#if (defined(DEBUG_JACK_DRIVER) || defined(DEBUG_JACK_PROCESS) || defined(DEBUG_JACK_TRANSPORT))
|
|
|
|
framesThisPlay += nframes;
|
|
#endif
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
|
|
std::cerr << "JackDriver::jackProcess: " << nframes << " frames, " << framesThisPlay << " this play, " << m_framesProcessed << " total" << std::endl;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
JackDriver::jackProcessRecord(InstrumentId id,
|
|
jack_nframes_t nframes,
|
|
sample_t *sourceBufferLeft,
|
|
sample_t *sourceBufferRight,
|
|
bool clocksRunning)
|
|
{
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
Profiler profiler("jackProcessRecord", true);
|
|
#else
|
|
#ifdef DEBUG_JACK_XRUN
|
|
|
|
Profiler profiler("jackProcessRecord", false);
|
|
#endif
|
|
#endif
|
|
|
|
SequencerDataBlock *sdb = m_alsaDriver->getSequencerDataBlock();
|
|
bool wroteSomething = false;
|
|
sample_t peakLeft = 0.0, peakRight = 0.0;
|
|
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
|
|
std::cerr << "JackDriver::jackProcessRecord(" << id << "): clocksRunning " << clocksRunning << std::endl;
|
|
#endif
|
|
|
|
// Get input buffers
|
|
//
|
|
sample_t *inputBufferLeft = 0, *inputBufferRight = 0;
|
|
|
|
int recInput = m_recordInputs[id].input;
|
|
|
|
int channel = m_recordInputs[id].channel;
|
|
int channels = (channel == -1 ? 2 : 1);
|
|
if (channels == 2)
|
|
channel = 0;
|
|
|
|
float level = m_recordInputs[id].level;
|
|
|
|
if (sourceBufferLeft) {
|
|
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
std::cerr << "JackDriver::jackProcessRecord(" << id << "): buss input provided" << std::endl;
|
|
#endif
|
|
|
|
inputBufferLeft = sourceBufferLeft;
|
|
if (sourceBufferRight)
|
|
inputBufferRight = sourceBufferRight;
|
|
|
|
} else if (recInput < 1000) {
|
|
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
std::cerr << "JackDriver::jackProcessRecord(" << id << "): no known input" << std::endl;
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
std::cerr << "JackDriver::jackProcessRecord(" << id << "): record input " << recInput << std::endl;
|
|
#endif
|
|
|
|
int input = recInput - 1000;
|
|
|
|
int port = input * channels + channel;
|
|
int portRight = input * channels + 1;
|
|
|
|
if (port < int(m_inputPorts.size())) {
|
|
inputBufferLeft = static_cast<sample_t*>
|
|
(jack_port_get_buffer(m_inputPorts[port], nframes));
|
|
}
|
|
|
|
if (channels == 2 && portRight < int(m_inputPorts.size())) {
|
|
inputBufferRight = static_cast<sample_t*>
|
|
(jack_port_get_buffer(m_inputPorts[portRight], nframes));
|
|
}
|
|
}
|
|
|
|
float gain = AudioLevel::dB_to_multiplier(level);
|
|
|
|
if (m_alsaDriver->getRecordStatus() == RECORD_ON &&
|
|
clocksRunning &&
|
|
m_fileWriter->haveRecordFileOpen(id)) {
|
|
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
std::cerr << "JackDriver::jackProcessRecord(" << id << "): recording" << std::endl;
|
|
#endif
|
|
|
|
memset(m_tempOutBuffer, 0, nframes * sizeof(sample_t));
|
|
|
|
if (inputBufferLeft) {
|
|
for (size_t i = 0; i < nframes; ++i) {
|
|
sample_t sample = inputBufferLeft[i] * gain;
|
|
if (sample > peakLeft)
|
|
peakLeft = sample;
|
|
m_tempOutBuffer[i] = sample;
|
|
}
|
|
|
|
if (m_outputMonitors.size() > 0) {
|
|
sample_t *buf =
|
|
static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputMonitors[0], nframes));
|
|
if (buf) {
|
|
for (size_t i = 0; i < nframes; ++i) {
|
|
buf[i] += m_tempOutBuffer[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
m_fileWriter->write(id, m_tempOutBuffer, 0, nframes);
|
|
}
|
|
|
|
if (channels == 2) {
|
|
|
|
if (inputBufferRight) {
|
|
for (size_t i = 0; i < nframes; ++i) {
|
|
sample_t sample = inputBufferRight[i] * gain;
|
|
if (sample > peakRight)
|
|
peakRight = sample;
|
|
m_tempOutBuffer[i] = sample;
|
|
}
|
|
if (m_outputMonitors.size() > 1) {
|
|
sample_t *buf =
|
|
static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputMonitors[1], nframes));
|
|
if (buf) {
|
|
for (size_t i = 0; i < nframes; ++i) {
|
|
buf[i] += m_tempOutBuffer[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_fileWriter->write(id, m_tempOutBuffer, 1, nframes);
|
|
}
|
|
|
|
wroteSomething = true;
|
|
|
|
} else {
|
|
|
|
// want peak levels and monitors anyway, even if not recording
|
|
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
std::cerr << "JackDriver::jackProcessRecord(" << id << "): monitoring only" << std::endl;
|
|
#endif
|
|
|
|
if (inputBufferLeft) {
|
|
|
|
sample_t *buf = 0;
|
|
if (m_outputMonitors.size() > 0) {
|
|
buf = static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputMonitors[0], nframes));
|
|
}
|
|
|
|
for (size_t i = 0; i < nframes; ++i) {
|
|
sample_t sample = inputBufferLeft[i] * gain;
|
|
if (sample > peakLeft)
|
|
peakLeft = sample;
|
|
if (buf)
|
|
buf[i] = sample;
|
|
}
|
|
|
|
if (channels == 2 && inputBufferRight) {
|
|
|
|
buf = 0;
|
|
if (m_outputMonitors.size() > 1) {
|
|
buf = static_cast<sample_t *>
|
|
(jack_port_get_buffer(m_outputMonitors[1], nframes));
|
|
}
|
|
|
|
for (size_t i = 0; i < nframes; ++i) {
|
|
sample_t sample = inputBufferRight[i] * gain;
|
|
if (sample > peakRight)
|
|
peakRight = sample;
|
|
if (buf)
|
|
buf[i] = sample;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (channels < 2)
|
|
peakRight = peakLeft;
|
|
|
|
if (sdb) {
|
|
LevelInfo info;
|
|
info.level = AudioLevel::multiplier_to_fader
|
|
(peakLeft, 127, AudioLevel::LongFader);
|
|
info.levelRight = AudioLevel::multiplier_to_fader
|
|
(peakRight, 127, AudioLevel::LongFader);
|
|
sdb->setInstrumentRecordLevel(id, info);
|
|
}
|
|
|
|
if (wroteSomething) {
|
|
m_fileWriter->signal();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
JackDriver::jackSyncCallback(jack_transport_state_t state,
|
|
jack_position_t *position,
|
|
void *arg)
|
|
{
|
|
JackDriver *inst = (JackDriver *)arg;
|
|
if (!inst)
|
|
return true; // or rather, return "huh?"
|
|
|
|
inst->m_alsaDriver->checkTimerSync(0); // reset, as not processing
|
|
|
|
if (!inst->m_jackTransportEnabled)
|
|
return true; // ignore
|
|
|
|
ExternalTransport *transport =
|
|
inst->m_alsaDriver->getExternalTransportControl();
|
|
if (!transport)
|
|
return true;
|
|
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
|
|
std::cerr << "JackDriver::jackSyncCallback: state " << state << " [" << (state == 0 ? "stopped" : state == 1 ? "rolling" : state == 2 ? "looping" : state == 3 ? "starting" : "unknown") << "], frame " << position->frame << ", waiting " << inst->m_waiting << ", playing " << inst->m_alsaDriver->isPlaying() << std::endl;
|
|
|
|
std::cerr << "JackDriver::jackSyncCallback: m_waitingState " << inst->m_waitingState << ", unique_1 " << position->unique_1 << ", unique_2 " << position->unique_2 << std::endl;
|
|
|
|
std::cerr << "JackDriver::jackSyncCallback: rate " << position->frame_rate << ", bar " << position->bar << ", beat " << position->beat << ", tick " << position->tick << ", bpm " << position->beats_per_minute << std::endl;
|
|
|
|
#endif
|
|
|
|
ExternalTransport::TransportRequest request =
|
|
ExternalTransport::TransportNoChange;
|
|
|
|
if (inst->m_alsaDriver->isPlaying()) {
|
|
|
|
if (state == JackTransportStarting) {
|
|
request = ExternalTransport::TransportJumpToTime;
|
|
} else if (state == JackTransportStopped) {
|
|
request = ExternalTransport::TransportStop;
|
|
}
|
|
|
|
} else {
|
|
|
|
if (state == JackTransportStarting) {
|
|
request = ExternalTransport::TransportStartAtTime;
|
|
} else if (state == JackTransportStopped) {
|
|
request = ExternalTransport::TransportNoChange;
|
|
}
|
|
}
|
|
|
|
if (!inst->m_waiting || inst->m_waitingState != state) {
|
|
|
|
if (request == ExternalTransport::TransportJumpToTime ||
|
|
request == ExternalTransport::TransportStartAtTime) {
|
|
|
|
RealTime rt = RealTime::frame2RealTime(position->frame,
|
|
position->frame_rate);
|
|
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
|
|
std::cerr << "JackDriver::jackSyncCallback: Requesting jump to " << rt << std::endl;
|
|
#endif
|
|
|
|
inst->m_waitingToken = transport->transportJump(request, rt);
|
|
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
|
|
std::cerr << "JackDriver::jackSyncCallback: My token is " << inst->m_waitingToken << std::endl;
|
|
#endif
|
|
|
|
} else if (request == ExternalTransport::TransportStop) {
|
|
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
std::cerr << "JackDriver::jackSyncCallback: Requesting state change to " << request << std::endl;
|
|
#endif
|
|
|
|
inst->m_waitingToken = transport->transportChange(request);
|
|
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
|
|
std::cerr << "JackDriver::jackSyncCallback: My token is " << inst->m_waitingToken << std::endl;
|
|
#endif
|
|
|
|
} else if (request == ExternalTransport::TransportNoChange) {
|
|
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
std::cerr << "JackDriver::jackSyncCallback: Requesting no state change!" << std::endl;
|
|
#endif
|
|
|
|
inst->m_waitingToken = transport->transportChange(request);
|
|
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
|
|
std::cerr << "JackDriver::jackSyncCallback: My token is " << inst->m_waitingToken << std::endl;
|
|
#endif
|
|
|
|
}
|
|
|
|
inst->m_waiting = true;
|
|
inst->m_waitingState = state;
|
|
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
|
|
std::cerr << "JackDriver::jackSyncCallback: Setting waiting to " << inst->m_waiting << " and waiting state to " << inst->m_waitingState << " (request was " << request << ")" << std::endl;
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
if (transport->isTransportSyncComplete(inst->m_waitingToken)) {
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
std::cerr << "JackDriver::jackSyncCallback: Sync complete" << std::endl;
|
|
#endif
|
|
|
|
return 1;
|
|
} else {
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
std::cerr << "JackDriver::jackSyncCallback: Sync not complete" << std::endl;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
JackDriver::relocateTransportInternal(bool alsoStart)
|
|
{
|
|
if (!m_client)
|
|
return true;
|
|
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
|
|
const char *fn = (alsoStart ?
|
|
"JackDriver::startTransport" :
|
|
"JackDriver::relocateTransport");
|
|
#endif
|
|
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
|
|
std::cerr << fn << std::endl;
|
|
#else
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
|
|
std::cerr << "JackDriver::relocateTransportInternal" << std::endl;
|
|
#endif
|
|
#endif
|
|
|
|
// m_waiting is true if we are waiting for the JACK transport
|
|
// to finish a change of state.
|
|
|
|
if (m_jackTransportEnabled) {
|
|
|
|
// If on the transport, we never return true here -- instead
|
|
// the JACK process calls startClocksApproved() to signal to
|
|
// the ALSA driver that it's time to go. But we do use this
|
|
// to manage our JACK transport state requests.
|
|
|
|
// Where did this request come from? Are we just responding
|
|
// to an external sync?
|
|
|
|
ExternalTransport *transport =
|
|
m_alsaDriver->getExternalTransportControl();
|
|
|
|
if (transport) {
|
|
if (transport->isTransportSyncComplete(m_waitingToken)) {
|
|
|
|
// Nope, this came from Rosegarden
|
|
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
std::cerr << fn << ": asking JACK transport to start, setting wait state" << std::endl;
|
|
#endif
|
|
|
|
m_waiting = true;
|
|
m_waitingState = JackTransportStarting;
|
|
|
|
long frame = RealTime::realTime2Frame
|
|
(m_alsaDriver->getSequencerTime(), m_sampleRate);
|
|
|
|
if (frame < 0) {
|
|
// JACK Transport doesn't support preroll and
|
|
// can't set transport position to before zero
|
|
// (frame count is unsigned), so there's no very
|
|
// satisfactory fix for what to do for count-in
|
|
// bars. Let's just start at zero instead.
|
|
jack_transport_locate(m_client, 0);
|
|
} else {
|
|
jack_transport_locate(m_client, frame);
|
|
}
|
|
|
|
if (alsoStart) {
|
|
jack_transport_start(m_client);
|
|
m_ignoreProcessTransportCount = 1;
|
|
} else {
|
|
m_ignoreProcessTransportCount = 2;
|
|
}
|
|
} else {
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
std::cerr << fn << ": waiting already" << std::endl;
|
|
#endif
|
|
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if (defined(DEBUG_JACK_DRIVER) || defined(DEBUG_JACK_PROCESS) || defined(DEBUG_JACK_TRANSPORT))
|
|
framesThisPlay = 0; //!!!
|
|
struct timeval tv;
|
|
(void)gettimeofday(&tv, 0);
|
|
startTime = RealTime(tv.tv_sec, tv.tv_usec * 1000); //!!!
|
|
#endif
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
|
|
std::cerr << fn << ": not on JACK transport, accepting right away" << std::endl;
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JackDriver::startTransport()
|
|
{
|
|
return relocateTransportInternal(true);
|
|
}
|
|
|
|
bool
|
|
JackDriver::relocateTransport()
|
|
{
|
|
|
|
return relocateTransportInternal(false);
|
|
}
|
|
|
|
void
|
|
JackDriver::stopTransport()
|
|
{
|
|
if (!m_client)
|
|
return ;
|
|
|
|
std::cerr << "JackDriver::stopTransport: resetting m_haveAsyncAudioEvent" << std::endl;
|
|
m_haveAsyncAudioEvent = false;
|
|
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
|
|
struct timeval tv;
|
|
(void)gettimeofday(&tv, 0);
|
|
RealTime endTime = RealTime(tv.tv_sec, tv.tv_usec * 1000); //!!!
|
|
std::cerr << "\nJackDriver::stop: frames this play: " << framesThisPlay << ", elapsed " << (endTime - startTime) << std::endl;
|
|
#endif
|
|
|
|
if (m_jackTransportEnabled) {
|
|
|
|
// Where did this request come from? Is this a result of our
|
|
// sync to a transport that has in fact already stopped?
|
|
|
|
ExternalTransport *transport =
|
|
m_alsaDriver->getExternalTransportControl();
|
|
|
|
if (transport) {
|
|
if (transport->isTransportSyncComplete(m_waitingToken)) {
|
|
|
|
// No, we have no outstanding external requests; this
|
|
// must have genuinely been requested from within
|
|
// Rosegarden, so:
|
|
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
std::cerr << "JackDriver::stop: internal request, asking JACK transport to stop" << std::endl;
|
|
#endif
|
|
|
|
jack_transport_stop(m_client);
|
|
|
|
} else {
|
|
// Nothing to do
|
|
|
|
#ifdef DEBUG_JACK_TRANSPORT
|
|
std::cerr << "JackDriver::stop: external request, JACK transport is already stopped" << std::endl;
|
|
#endif
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_instrumentMixer)
|
|
m_instrumentMixer->resetAllPlugins(true); // discard events too
|
|
}
|
|
|
|
|
|
// Pick up any change of buffer size
|
|
//
|
|
int
|
|
JackDriver::jackBufferSize(jack_nframes_t nframes, void *arg)
|
|
{
|
|
JackDriver *inst = static_cast<JackDriver*>(arg);
|
|
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
|
|
std::cerr << "JackDriver::jackBufferSize - buffer size changed to "
|
|
<< nframes << std::endl;
|
|
#endif
|
|
|
|
inst->m_bufferSize = nframes;
|
|
|
|
// Recreate our temporary mix buffers to the new size
|
|
//
|
|
//!!! need buffer size change callbacks on plugins (so long as they
|
|
// have internal buffers) and the mix manager, with locks acquired
|
|
// appropriately
|
|
|
|
delete [] inst->m_tempOutBuffer;
|
|
inst->m_tempOutBuffer = new sample_t[inst->m_bufferSize];
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Sample rate change
|
|
//
|
|
int
|
|
JackDriver::jackSampleRate(jack_nframes_t nframes, void *arg)
|
|
{
|
|
JackDriver *inst = static_cast<JackDriver*>(arg);
|
|
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
|
|
std::cerr << "JackDriver::jackSampleRate - sample rate changed to "
|
|
<< nframes << std::endl;
|
|
#endif
|
|
|
|
inst->m_sampleRate = nframes;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
JackDriver::jackShutdown(void *arg)
|
|
{
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver::jackShutdown() - callback received - "
|
|
<< "informing GUI" << std::endl;
|
|
#endif
|
|
|
|
#ifdef DEBUG_JACK_XRUN
|
|
|
|
std::cerr << "JackDriver::jackShutdown" << std::endl;
|
|
Profiles::getInstance()->dump();
|
|
#endif
|
|
|
|
JackDriver *inst = static_cast<JackDriver*>(arg);
|
|
inst->m_ok = false;
|
|
inst->m_kickedOutAt = time(0);
|
|
inst->reportFailure(MappedEvent::FailureJackDied);
|
|
}
|
|
|
|
int
|
|
JackDriver::jackXRun(void *arg)
|
|
{
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver::jackXRun" << std::endl;
|
|
#endif
|
|
|
|
#ifdef DEBUG_JACK_XRUN
|
|
|
|
std::cerr << "JackDriver::jackXRun" << std::endl;
|
|
Profiles::getInstance()->dump();
|
|
#endif
|
|
|
|
// Report to GUI
|
|
//
|
|
JackDriver *inst = static_cast<JackDriver*>(arg);
|
|
inst->reportFailure(MappedEvent::FailureXRuns);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
|
|
JackDriver::restoreIfRestorable()
|
|
{
|
|
if (m_kickedOutAt == 0)
|
|
return ;
|
|
|
|
if (m_client) {
|
|
jack_client_close(m_client);
|
|
std::cerr << "closed client" << std::endl;
|
|
m_client = 0;
|
|
}
|
|
|
|
time_t now = time(0);
|
|
|
|
if (now < m_kickedOutAt || now >= m_kickedOutAt + 3) {
|
|
|
|
if (m_instrumentMixer)
|
|
m_instrumentMixer->resetAllPlugins(true);
|
|
std::cerr << "reset plugins" << std::endl;
|
|
|
|
initialise(true);
|
|
|
|
if (m_ok) {
|
|
reportFailure(MappedEvent::FailureJackRestart);
|
|
} else {
|
|
reportFailure(MappedEvent::FailureJackRestartFailed);
|
|
}
|
|
|
|
m_kickedOutAt = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
JackDriver::prepareAudio()
|
|
{
|
|
if (!m_instrumentMixer)
|
|
return ;
|
|
|
|
// This is used when restarting clocks after repositioning, but
|
|
// when not actually playing (yet). We need to do things like
|
|
// regenerating the processing buffers here. prebufferAudio()
|
|
// also does all of this, but rather more besides.
|
|
|
|
m_instrumentMixer->allocateBuffers();
|
|
m_instrumentMixer->resetAllPlugins(false);
|
|
}
|
|
|
|
void
|
|
JackDriver::prebufferAudio()
|
|
{
|
|
if (!m_instrumentMixer)
|
|
return ;
|
|
|
|
// We want this to happen when repositioning during playback, and
|
|
// stopTransport no longer happens then, so we call it from here.
|
|
// NB. Don't want to discard events here as this is called after
|
|
// pushing events to the soft synth queues at startup
|
|
m_instrumentMixer->resetAllPlugins(false);
|
|
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
|
|
std::cerr << "JackDriver::prebufferAudio: sequencer time is "
|
|
<< m_alsaDriver->getSequencerTime() << std::endl;
|
|
#endif
|
|
|
|
RealTime sliceStart = getNextSliceStart(m_alsaDriver->getSequencerTime());
|
|
|
|
m_fileReader->fillBuffers(sliceStart);
|
|
|
|
if (m_bussMixer->getBussCount() > 0) {
|
|
m_bussMixer->fillBuffers(sliceStart); // also calls on m_instrumentMixer
|
|
} else {
|
|
m_instrumentMixer->fillBuffers(sliceStart);
|
|
}
|
|
}
|
|
|
|
void
|
|
JackDriver::kickAudio()
|
|
{
|
|
#ifdef DEBUG_JACK_PROCESS
|
|
std::cerr << "JackDriver::kickAudio" << std::endl;
|
|
#endif
|
|
|
|
if (m_fileReader)
|
|
m_fileReader->kick();
|
|
if (m_instrumentMixer)
|
|
m_instrumentMixer->kick();
|
|
if (m_bussMixer)
|
|
m_bussMixer->kick();
|
|
if (m_fileWriter)
|
|
m_fileWriter->kick();
|
|
}
|
|
|
|
void
|
|
JackDriver::updateAudioData()
|
|
{
|
|
if (!m_ok || !m_client)
|
|
return ;
|
|
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
// std::cerr << "JackDriver::updateAudioData starting" << std::endl;
|
|
#endif
|
|
|
|
MappedAudioBuss *mbuss =
|
|
m_alsaDriver->getMappedStudio()->getAudioBuss(0);
|
|
|
|
if (mbuss) {
|
|
float level = 0.0;
|
|
(void)mbuss->getProperty(MappedAudioBuss::Level, level);
|
|
m_masterLevel = level;
|
|
}
|
|
|
|
unsigned long directMasterAudioInstruments = 0L;
|
|
unsigned long directMasterSynthInstruments = 0L;
|
|
|
|
InstrumentId audioInstrumentBase;
|
|
int audioInstruments;
|
|
m_alsaDriver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
|
|
|
|
InstrumentId synthInstrumentBase;
|
|
int synthInstruments;
|
|
m_alsaDriver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
|
|
|
|
RealTime jackLatency = getAudioPlayLatency();
|
|
RealTime maxLatency = RealTime::zeroTime;
|
|
|
|
for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
|
|
|
|
InstrumentId id;
|
|
if (i < audioInstruments)
|
|
id = audioInstrumentBase + i;
|
|
else
|
|
id = synthInstrumentBase + (i - audioInstruments);
|
|
|
|
MappedAudioFader *fader = m_alsaDriver->getMappedStudio()->getAudioFader(id);
|
|
if (!fader)
|
|
continue;
|
|
|
|
float f = 2;
|
|
(void)fader->getProperty(MappedAudioFader::Channels, f);
|
|
int channels = (int)f;
|
|
|
|
int inputChannel = -1;
|
|
if (channels == 1) {
|
|
float f = 0;
|
|
(void)fader->getProperty(MappedAudioFader::InputChannel, f);
|
|
inputChannel = (int)f;
|
|
}
|
|
|
|
float level = 0.0;
|
|
(void)fader->getProperty(MappedAudioFader::FaderRecordLevel, level);
|
|
|
|
// Like in base/Instrument.h, we use numbers < 1000 to
|
|
// mean buss numbers and >= 1000 to mean record ins
|
|
// when recording the record input number.
|
|
|
|
MappedObjectValueList connections = fader->getConnections
|
|
(MappedConnectableObject::In);
|
|
int input = 1000;
|
|
|
|
if (connections.empty()) {
|
|
|
|
std::cerr << "No connections in for record instrument "
|
|
<< (id) << " (mapped id " << fader->getId() << ")" << std::endl;
|
|
|
|
// oh dear.
|
|
input = 1000;
|
|
|
|
} else if (*connections.begin() == mbuss->getId()) {
|
|
|
|
input = 0;
|
|
|
|
} else {
|
|
|
|
MappedObject *obj = m_alsaDriver->getMappedStudio()->
|
|
getObjectById(MappedObjectId(*connections.begin()));
|
|
|
|
if (!obj) {
|
|
|
|
std::cerr << "No such object as " << *connections.begin() << std::endl;
|
|
input = 1000;
|
|
} else if (obj->getType() == MappedObject::AudioBuss) {
|
|
input = (int)((MappedAudioBuss *)obj)->getBussId();
|
|
} else if (obj->getType() == MappedObject::AudioInput) {
|
|
input = (int)((MappedAudioInput *)obj)->getInputNumber()
|
|
+ 1000;
|
|
} else {
|
|
std::cerr << "Object " << *connections.begin() << " is not buss or input" << std::endl;
|
|
input = 1000;
|
|
}
|
|
}
|
|
|
|
if (m_recordInputs[id].input != input) {
|
|
std::cerr << "Changing record input for instrument "
|
|
<< id << " to " << input << std::endl;
|
|
}
|
|
m_recordInputs[id] = RecordInputDesc(input, inputChannel, level);
|
|
|
|
size_t pluginLatency = 0;
|
|
bool empty = m_instrumentMixer->isInstrumentEmpty(id);
|
|
|
|
if (!empty) {
|
|
pluginLatency = m_instrumentMixer->getPluginLatency(id);
|
|
}
|
|
|
|
// If we find the object is connected to no output, or to buss
|
|
// number 0 (the master), then we set the bit appropriately.
|
|
|
|
connections = fader->getConnections(MappedConnectableObject::Out);
|
|
|
|
if (connections.empty() || (*connections.begin() == mbuss->getId())) {
|
|
if (i < audioInstruments) {
|
|
directMasterAudioInstruments |= (1 << i);
|
|
} else {
|
|
directMasterSynthInstruments |= (1 << (i - audioInstruments));
|
|
}
|
|
} else if (!empty) {
|
|
pluginLatency +=
|
|
m_instrumentMixer->getPluginLatency((unsigned int) * connections.begin());
|
|
}
|
|
|
|
if (empty) {
|
|
m_instrumentLatencies[id] = RealTime::zeroTime;
|
|
} else {
|
|
m_instrumentLatencies[id] = jackLatency +
|
|
RealTime::frame2RealTime(pluginLatency, m_sampleRate);
|
|
if (m_instrumentLatencies[id] > maxLatency) {
|
|
maxLatency = m_instrumentLatencies[id];
|
|
}
|
|
}
|
|
}
|
|
|
|
m_maxInstrumentLatency = maxLatency;
|
|
m_directMasterAudioInstruments = directMasterAudioInstruments;
|
|
m_directMasterSynthInstruments = directMasterSynthInstruments;
|
|
m_maxInstrumentLatency = maxLatency;
|
|
|
|
int inputs = m_alsaDriver->getMappedStudio()->
|
|
getObjectCount(MappedObject::AudioInput);
|
|
|
|
if (m_client) {
|
|
// this will return with no work if the inputs are already correct:
|
|
createRecordInputs(inputs);
|
|
}
|
|
|
|
m_bussMixer->updateInstrumentConnections();
|
|
m_instrumentMixer->updateInstrumentMuteStates();
|
|
|
|
if (m_bussMixer->getBussCount() == 0 || m_alsaDriver->getLowLatencyMode()) {
|
|
if (m_bussMixer->running()) {
|
|
m_bussMixer->terminate();
|
|
}
|
|
} else {
|
|
if (!m_bussMixer->running()) {
|
|
m_bussMixer->run();
|
|
}
|
|
}
|
|
|
|
if (m_alsaDriver->getLowLatencyMode()) {
|
|
if (m_instrumentMixer->running()) {
|
|
m_instrumentMixer->terminate();
|
|
}
|
|
} else {
|
|
if (!m_instrumentMixer->running()) {
|
|
m_instrumentMixer->run();
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
// std::cerr << "JackDriver::updateAudioData exiting" << std::endl;
|
|
#endif
|
|
}
|
|
|
|
void
|
|
JackDriver::setAudioBussLevels(int bussNo, float dB, float pan)
|
|
{
|
|
if (m_bussMixer) {
|
|
m_bussMixer->setBussLevels(bussNo, dB, pan);
|
|
}
|
|
}
|
|
|
|
void
|
|
JackDriver::setAudioInstrumentLevels(InstrumentId instrument, float dB, float pan)
|
|
{
|
|
if (m_instrumentMixer) {
|
|
m_instrumentMixer->setInstrumentLevels(instrument, dB, pan);
|
|
}
|
|
}
|
|
|
|
RealTime
|
|
JackDriver::getNextSliceStart(const RealTime &now) const
|
|
{
|
|
jack_nframes_t frame;
|
|
bool neg = false;
|
|
|
|
if (now < RealTime::zeroTime) {
|
|
neg = true;
|
|
frame = RealTime::realTime2Frame(RealTime::zeroTime - now, m_sampleRate);
|
|
} else {
|
|
frame = RealTime::realTime2Frame(now, m_sampleRate);
|
|
}
|
|
|
|
jack_nframes_t rounded = frame;
|
|
rounded /= m_bufferSize;
|
|
rounded *= m_bufferSize;
|
|
|
|
RealTime roundrt;
|
|
|
|
if (rounded == frame)
|
|
roundrt = RealTime::frame2RealTime(rounded, m_sampleRate);
|
|
else if (neg)
|
|
roundrt = RealTime::frame2RealTime(rounded - m_bufferSize, m_sampleRate);
|
|
else
|
|
roundrt = RealTime::frame2RealTime(rounded + m_bufferSize, m_sampleRate);
|
|
|
|
if (neg)
|
|
roundrt = RealTime::zeroTime - roundrt;
|
|
|
|
return roundrt;
|
|
}
|
|
|
|
|
|
int
|
|
JackDriver::getAudioQueueLocks()
|
|
{
|
|
// We have to lock the mixers first, because the mixers can try to
|
|
// lock the disk manager from within a locked section -- so if we
|
|
// locked the disk manager first we would risk deadlock when
|
|
// trying to acquire the instrument mixer lock
|
|
|
|
int rv = 0;
|
|
if (m_bussMixer) {
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver::getAudioQueueLocks: trying to lock buss mixer" << std::endl;
|
|
#endif
|
|
|
|
rv = m_bussMixer->getLock();
|
|
if (rv)
|
|
return rv;
|
|
}
|
|
if (m_instrumentMixer) {
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver::getAudioQueueLocks: ok, now trying for instrument mixer" << std::endl;
|
|
#endif
|
|
|
|
rv = m_instrumentMixer->getLock();
|
|
if (rv)
|
|
return rv;
|
|
}
|
|
if (m_fileReader) {
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver::getAudioQueueLocks: ok, now trying for disk reader" << std::endl;
|
|
#endif
|
|
|
|
rv = m_fileReader->getLock();
|
|
if (rv)
|
|
return rv;
|
|
}
|
|
if (m_fileWriter) {
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver::getAudioQueueLocks: ok, now trying for disk writer" << std::endl;
|
|
#endif
|
|
|
|
rv = m_fileWriter->getLock();
|
|
}
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver::getAudioQueueLocks: ok" << std::endl;
|
|
#endif
|
|
|
|
return rv;
|
|
}
|
|
|
|
int
|
|
JackDriver::tryAudioQueueLocks()
|
|
{
|
|
int rv = 0;
|
|
if (m_bussMixer) {
|
|
rv = m_bussMixer->tryLock();
|
|
if (rv)
|
|
return rv;
|
|
}
|
|
if (m_instrumentMixer) {
|
|
rv = m_instrumentMixer->tryLock();
|
|
if (rv) {
|
|
if (m_bussMixer) {
|
|
m_bussMixer->releaseLock();
|
|
}
|
|
}
|
|
}
|
|
if (m_fileReader) {
|
|
rv = m_fileReader->tryLock();
|
|
if (rv) {
|
|
if (m_instrumentMixer) {
|
|
m_instrumentMixer->releaseLock();
|
|
}
|
|
if (m_bussMixer) {
|
|
m_bussMixer->releaseLock();
|
|
}
|
|
}
|
|
}
|
|
if (m_fileWriter) {
|
|
rv = m_fileWriter->tryLock();
|
|
if (rv) {
|
|
if (m_fileReader) {
|
|
m_fileReader->releaseLock();
|
|
}
|
|
if (m_instrumentMixer) {
|
|
m_instrumentMixer->releaseLock();
|
|
}
|
|
if (m_bussMixer) {
|
|
m_bussMixer->releaseLock();
|
|
}
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
int
|
|
JackDriver::releaseAudioQueueLocks()
|
|
{
|
|
int rv = 0;
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
|
|
std::cerr << "JackDriver::releaseAudioQueueLocks" << std::endl;
|
|
#endif
|
|
|
|
if (m_fileWriter)
|
|
rv = m_fileWriter->releaseLock();
|
|
if (m_fileReader)
|
|
rv = m_fileReader->releaseLock();
|
|
if (m_instrumentMixer)
|
|
rv = m_instrumentMixer->releaseLock();
|
|
if (m_bussMixer)
|
|
rv = m_bussMixer->releaseLock();
|
|
return rv;
|
|
}
|
|
|
|
|
|
void
|
|
JackDriver::setPluginInstance(InstrumentId id, TQString identifier,
|
|
int position)
|
|
{
|
|
if (m_instrumentMixer) {
|
|
m_instrumentMixer->setPlugin(id, position, identifier);
|
|
}
|
|
if (!m_alsaDriver->isPlaying()) {
|
|
prebufferAudio(); // to ensure the plugin's ringbuffers are generated
|
|
}
|
|
}
|
|
|
|
void
|
|
JackDriver::removePluginInstance(InstrumentId id, int position)
|
|
{
|
|
if (m_instrumentMixer)
|
|
m_instrumentMixer->removePlugin(id, position);
|
|
}
|
|
|
|
void
|
|
JackDriver::removePluginInstances()
|
|
{
|
|
if (m_instrumentMixer)
|
|
m_instrumentMixer->removeAllPlugins();
|
|
}
|
|
|
|
void
|
|
JackDriver::setPluginInstancePortValue(InstrumentId id, int position,
|
|
unsigned long portNumber,
|
|
float value)
|
|
{
|
|
if (m_instrumentMixer)
|
|
m_instrumentMixer->setPluginPortValue(id, position, portNumber, value);
|
|
}
|
|
|
|
float
|
|
JackDriver::getPluginInstancePortValue(InstrumentId id, int position,
|
|
unsigned long portNumber)
|
|
{
|
|
if (m_instrumentMixer)
|
|
return m_instrumentMixer->getPluginPortValue(id, position, portNumber);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
JackDriver::setPluginInstanceBypass(InstrumentId id, int position, bool value)
|
|
{
|
|
if (m_instrumentMixer)
|
|
m_instrumentMixer->setPluginBypass(id, position, value);
|
|
}
|
|
|
|
TQStringList
|
|
JackDriver::getPluginInstancePrograms(InstrumentId id, int position)
|
|
{
|
|
if (m_instrumentMixer)
|
|
return m_instrumentMixer->getPluginPrograms(id, position);
|
|
return TQStringList();
|
|
}
|
|
|
|
TQString
|
|
JackDriver::getPluginInstanceProgram(InstrumentId id, int position)
|
|
{
|
|
if (m_instrumentMixer)
|
|
return m_instrumentMixer->getPluginProgram(id, position);
|
|
return TQString();
|
|
}
|
|
|
|
TQString
|
|
JackDriver::getPluginInstanceProgram(InstrumentId id, int position,
|
|
int bank, int program)
|
|
{
|
|
if (m_instrumentMixer)
|
|
return m_instrumentMixer->getPluginProgram(id, position, bank, program);
|
|
return TQString();
|
|
}
|
|
|
|
unsigned long
|
|
JackDriver::getPluginInstanceProgram(InstrumentId id, int position, TQString name)
|
|
{
|
|
if (m_instrumentMixer)
|
|
return m_instrumentMixer->getPluginProgram(id, position, name);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
JackDriver::setPluginInstanceProgram(InstrumentId id, int position, TQString program)
|
|
{
|
|
if (m_instrumentMixer)
|
|
m_instrumentMixer->setPluginProgram(id, position, program);
|
|
}
|
|
|
|
TQString
|
|
JackDriver::configurePlugin(InstrumentId id, int position, TQString key, TQString value)
|
|
{
|
|
if (m_instrumentMixer)
|
|
return m_instrumentMixer->configurePlugin(id, position, key, value);
|
|
return TQString();
|
|
}
|
|
|
|
RunnablePluginInstance *
|
|
JackDriver::getSynthPlugin(InstrumentId id)
|
|
{
|
|
if (m_instrumentMixer)
|
|
return m_instrumentMixer->getSynthPlugin(id);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
JackDriver::clearSynthPluginEvents()
|
|
{
|
|
if (!m_instrumentMixer) return;
|
|
|
|
#ifdef DEBUG_JACK_DRIVER
|
|
std::cerr << "JackDriver::clearSynthPluginEvents" << std::endl;
|
|
#endif
|
|
|
|
m_instrumentMixer->discardPluginEvents();
|
|
}
|
|
|
|
bool
|
|
JackDriver::openRecordFile(InstrumentId id,
|
|
const std::string &filename)
|
|
{
|
|
if (m_fileWriter) {
|
|
if (!m_fileWriter->running()) {
|
|
m_fileWriter->run();
|
|
}
|
|
return m_fileWriter->openRecordFile(id, filename);
|
|
} else {
|
|
std::cerr << "JackDriver::openRecordFile: No file writer available!" << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool
|
|
JackDriver::closeRecordFile(InstrumentId id,
|
|
AudioFileId &returnedId)
|
|
{
|
|
if (m_fileWriter) {
|
|
return m_fileWriter->closeRecordFile(id, returnedId);
|
|
if (m_fileWriter->running() && !m_fileWriter->haveRecordFilesOpen()) {
|
|
m_fileWriter->terminate();
|
|
}
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
|
|
void
|
|
JackDriver::reportFailure(MappedEvent::FailureCode code)
|
|
{
|
|
if (m_alsaDriver)
|
|
m_alsaDriver->reportFailure(code);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
#endif // HAVE_LIBJACK
|
|
#endif // HAVE_ALSA
|