size_t CoreAudioBackend::_max_buffer_size = 8192;
std::vector<std::string> CoreAudioBackend::_midi_options;
std::vector<AudioBackend::DeviceStatus> CoreAudioBackend::_audio_device_status;
-std::vector<AudioBackend::DeviceStatus> CoreAudioBackend::_midi_device_status;
/* static class instance access */
, _freewheel_ack (false)
, _reinit_thread_callback (false)
, _measure_latency (false)
+ , _last_process_start (0)
, _audio_device("")
, _midi_driver_option(_("None"))
, _samplerate (48000)
_instance_name = s_instance_name;
pthread_mutex_init (&_port_callback_mutex, 0);
pthread_mutex_init (&_process_callback_mutex, 0);
+ pthread_mutex_init (&_freewheel_mutex, 0);
+ pthread_cond_init (&_freewheel_signal, 0);
_pcmio = new CoreAudioPCM ();
_midiio = new CoreMidiIo ();
delete _midiio; _midiio = 0;
pthread_mutex_destroy (&_port_callback_mutex);
pthread_mutex_destroy (&_process_callback_mutex);
+ pthread_mutex_destroy (&_freewheel_mutex);
+ pthread_cond_destroy (&_freewheel_signal);
}
/* AUDIOBACKEND API */
return 0;
}
-int
-CoreAudioBackend::set_systemic_midi_input_latency (std::string const device, uint32_t sl)
-{
- struct CoreMidiDeviceInfo * nfo = midi_device_info(device);
- if (!nfo) return -1;
- nfo->systemic_input_latency = sl;
- return 0;
-}
-
-int
-CoreAudioBackend::set_systemic_midi_output_latency (std::string const device, uint32_t sl)
-{
- struct CoreMidiDeviceInfo * nfo = midi_device_info(device);
- if (!nfo) return -1;
- nfo->systemic_output_latency = sl;
- return 0;
-}
-
/* Retrieving parameters */
std::string
CoreAudioBackend::device_name () const
return _systemic_audio_output_latency;
}
-uint32_t
-CoreAudioBackend::systemic_midi_input_latency (std::string const device) const
-{
- struct CoreMidiDeviceInfo * nfo = midi_device_info(device);
- if (!nfo) return 0;
- return nfo->systemic_input_latency;
-}
-
-uint32_t
-CoreAudioBackend::systemic_midi_output_latency (std::string const device) const
-{
- struct CoreMidiDeviceInfo * nfo = midi_device_info(device);
- if (!nfo) return 0;
- return nfo->systemic_output_latency;
-}
-
/* MIDI */
-struct CoreAudioBackend::CoreMidiDeviceInfo *
-CoreAudioBackend::midi_device_info(std::string const name) const {
- return 0;
-}
std::vector<std::string>
CoreAudioBackend::enumerate_midi_options () const
return _midi_options;
}
-std::vector<AudioBackend::DeviceStatus>
-CoreAudioBackend::enumerate_midi_devices () const
-{
- _midi_device_status.clear();
- std::map<std::string, std::string> devices;
- //_midi_device_status.push_back (DeviceStatus (_("CoreMidi"), true));
- return _midi_device_status;
-}
-
int
CoreAudioBackend::set_midi_option (const std::string& opt)
{
return _midi_driver_option;
}
-int
-CoreAudioBackend::set_midi_device_enabled (std::string const device, bool enable)
-{
- struct CoreMidiDeviceInfo * nfo = midi_device_info(device);
- if (!nfo) return -1;
- nfo->enabled = enable;
- return 0;
-}
-
-bool
-CoreAudioBackend::midi_device_enabled (std::string const device) const
-{
- struct CoreMidiDeviceInfo * nfo = midi_device_info(device);
- if (!nfo) return false;
- return nfo->enabled;
-}
-
void
CoreAudioBackend::launch_control_app ()
{
return 0;
}
-static int process_callback_ptr (void *arg)
+static int process_callback_ptr (void *arg, const uint32_t n_samples, const uint64_t host_time)
{
CoreAudioBackend *d = static_cast<CoreAudioBackend*> (arg);
- return d->process_callback();
+ return d->process_callback(n_samples, host_time);
}
int
_freewheel_ack = false;
_reinit_thread_callback = true;
+ _last_process_start = 0;
_pcmio->set_error_callback (error_callback_ptr, this);
_pcmio->set_buffer_size_callback (buffer_size_callback_ptr, this);
if (_midi_driver_option == _("CoreMidi")) {
_midiio->set_enabled(true);
_midiio->set_port_changed_callback(midi_port_change, this);
- _midiio->start();
+ _midiio->start(); // triggers port discovery, callback coremidi_rediscover()
}
if (register_system_audio_ports()) {
return -1;
}
- engine.reconnect_ports ();
-
if (pthread_create (&_freeewheel_thread, NULL, pthread_freewheel, this))
{
PBD::error << _("CoreAudioBackend: failed to create process thread.") << endmsg;
_run = false;
return -1;
}
- _preinit = false;
+
+ engine.reconnect_ports ();
+
+ // force an initial registration_callback() & latency re-compute
+ _port_change_flag = true;
+ pre_process ();
+
+ // all systems go.
_pcmio->set_xrun_callback (xrun_callback_ptr, this);
+ _preinit = false;
return 0;
}
_midiio->set_port_changed_callback(NULL, NULL);
_midiio->stop();
+ pthread_mutex_lock (&_freewheel_mutex);
+ pthread_cond_signal (&_freewheel_signal);
+ pthread_mutex_unlock (&_freewheel_mutex);
+
if (pthread_join (_freeewheel_thread, &status)) {
PBD::error << _("CoreAudioBackend: failed to terminate.") << endmsg;
return -1;
return 0;
}
_freewheeling = onoff;
+ // wake up freewheeling thread
+ if (0 == pthread_mutex_trylock (&_freewheel_mutex)) {
+ pthread_cond_signal (&_freewheel_signal);
+ pthread_mutex_unlock (&_freewheel_mutex);
+ }
return 0;
}
pframes_t
CoreAudioBackend::samples_since_cycle_start ()
{
- return 0;
+ if (!_active_ca || !_run || _freewheeling || _freewheel) {
+ return 0;
+ }
+ if (_last_process_start == 0) {
+ return 0;
+ }
+
+ const uint64_t now = AudioGetCurrentHostTime ();
+ const int64_t elapsed_time_ns = AudioConvertHostTimeToNanos(now - _last_process_start);
+ return std::max((pframes_t)0, (pframes_t)rint(1e-9 * elapsed_time_ns * _samplerate));
}
uint32_t
return static_cast<CoreBackendPort*>(port)->name ();
}
+int
+CoreAudioBackend::get_port_property (PortHandle port, const std::string& key, std::string& value, std::string& type) const
+{
+ if (!valid_port (port)) {
+ PBD::error << _("CoreAudioBackend::get_port_name: Invalid Port(s)") << endmsg;
+ return -1;
+ }
+ if (key == "http://jackaudio.org/metadata/pretty-name") {
+ type = "";
+ value = static_cast<CoreBackendPort*>(port)->pretty_name ();
+ if (!value.empty()) {
+ return 0;
+ }
+ }
+ return -1;
+}
+
PortEngine::PortHandle
CoreAudioBackend::get_port_by_name (const std::string& name) const
{
{
LatencyRange lr;
- const int a_ins = _n_inputs;
- const int a_out = _n_outputs;
+ const uint32_t a_ins = _n_inputs;
+ const uint32_t a_out = _n_outputs;
const uint32_t coreaudio_reported_input_latency = _pcmio->get_latency(name_to_id(_audio_device), true);
const uint32_t coreaudio_reported_output_latency = _pcmio->get_latency(name_to_id(_audio_device), false);
#endif
/* audio ports */
- lr.min = lr.max = _samples_per_period + coreaudio_reported_input_latency + (_measure_latency ? 0 : _systemic_audio_input_latency);
- for (int i = 0; i < a_ins; ++i) {
+ lr.min = lr.max = coreaudio_reported_input_latency + (_measure_latency ? 0 : _systemic_audio_input_latency);
+ for (uint32_t i = 0; i < a_ins; ++i) {
char tmp[64];
- snprintf(tmp, sizeof(tmp), "system:capture_%s", _pcmio->cached_port_name(i, true).c_str());
+ snprintf(tmp, sizeof(tmp), "system:capture_%d", i+1);
PortHandle p = add_port(std::string(tmp), DataType::AUDIO, static_cast<PortFlags>(IsOutput | IsPhysical | IsTerminal));
if (!p) return -1;
set_latency_range (p, false, lr);
- _system_inputs.push_back(static_cast<CoreBackendPort*>(p));
+ CoreBackendPort *cp = static_cast<CoreBackendPort*>(p);
+ cp->set_pretty_name (_pcmio->cached_port_name(i, true));
+ _system_inputs.push_back(cp);
}
- lr.min = lr.max = _samples_per_period + coreaudio_reported_output_latency + (_measure_latency ? 0 : _systemic_audio_output_latency);
- for (int i = 0; i < a_out; ++i) {
+ lr.min = lr.max = coreaudio_reported_output_latency + (_measure_latency ? 0 : _systemic_audio_output_latency);
+ for (uint32_t i = 0; i < a_out; ++i) {
char tmp[64];
- snprintf(tmp, sizeof(tmp), "system:playback_%s", _pcmio->cached_port_name(i, false).c_str());
+ snprintf(tmp, sizeof(tmp), "system:playback_%d", i+1);
PortHandle p = add_port(std::string(tmp), DataType::AUDIO, static_cast<PortFlags>(IsInput | IsPhysical | IsTerminal));
if (!p) return -1;
set_latency_range (p, true, lr);
- _system_outputs.push_back(static_cast<CoreBackendPort*>(p));
+ CoreBackendPort *cp = static_cast<CoreBackendPort*>(p);
+ cp->set_pretty_name (_pcmio->cached_port_name(i, false));
+ _system_outputs.push_back(cp);
}
return 0;
}
for (std::vector<CoreBackendPort*>::iterator it = _system_midi_out.begin (); it != _system_midi_out.end ();) {
bool found = false;
- for (int i = 0; i < _midiio->n_midi_outputs(); ++i) {
- if ((*it)->name() == _midiio->port_name(i, false)) {
+ for (size_t i = 0; i < _midiio->n_midi_outputs(); ++i) {
+ if ((*it)->name() == _midiio->port_id(i, false)) {
found = true;
break;
}
for (std::vector<CoreBackendPort*>::iterator it = _system_midi_in.begin (); it != _system_midi_in.end ();) {
bool found = false;
- for (int i = 0; i < _midiio->n_midi_inputs(); ++i) {
- if ((*it)->name() == _midiio->port_name(i, true)) {
+ for (size_t i = 0; i < _midiio->n_midi_inputs(); ++i) {
+ if ((*it)->name() == _midiio->port_id(i, true)) {
found = true;
break;
}
}
}
- for (int i = 0; i < _midiio->n_midi_inputs(); ++i) {
- std::string name = _midiio->port_name(i, true);
+ for (size_t i = 0; i < _midiio->n_midi_inputs(); ++i) {
+ std::string name = _midiio->port_id(i, true);
if (find_port_in(_system_midi_in, name)) {
continue;
}
LatencyRange lr;
lr.min = lr.max = _samples_per_period; // TODO add per-port midi-systemic latency
set_latency_range (p, false, lr);
- _system_midi_in.push_back(static_cast<CoreBackendPort*>(p));
+ CoreBackendPort *pp = static_cast<CoreBackendPort*>(p);
+ pp->set_pretty_name(_midiio->port_name(i, true));
+ _system_midi_in.push_back(pp);
_port_change_flag = true;
}
- for (int i = 0; i < _midiio->n_midi_outputs(); ++i) {
- std::string name = _midiio->port_name(i, false);
+ for (size_t i = 0; i < _midiio->n_midi_outputs(); ++i) {
+ std::string name = _midiio->port_id(i, false);
if (find_port_in(_system_midi_out, name)) {
continue;
}
LatencyRange lr;
lr.min = lr.max = _samples_per_period; // TODO add per-port midi-systemic latency
set_latency_range (p, false, lr);
- _system_midi_out.push_back(static_cast<CoreBackendPort*>(p));
+ CoreBackendPort *pp = static_cast<CoreBackendPort*>(p);
+ pp->set_pretty_name(_midiio->port_name(i, false));
+ _system_midi_out.push_back(pp);
_port_change_flag = true;
}
size_t& size, uint8_t** buf, void* port_buffer,
uint32_t event_index)
{
- assert (buf && port_buffer);
+ if (!buf || !port_buffer) return -1;
CoreMidiBuffer& source = * static_cast<CoreMidiBuffer*>(port_buffer);
if (event_index >= source.size ()) {
return -1;
pframes_t timestamp,
const uint8_t* buffer, size_t size)
{
- assert (buffer && port_buffer);
+ if (!buffer || !port_buffer) return -1;
CoreMidiBuffer& dst = * static_cast<CoreMidiBuffer*>(port_buffer);
if (dst.size () && (pframes_t)dst.back ()->timestamp () > timestamp) {
- fprintf (stderr, "CoreMidiBuffer: it's too late for this event. %d > %d\n",
+#ifndef NDEBUG
+ // nevermind, ::get_buffer() sorts events
+ fprintf (stderr, "CoreMidiBuffer: unordered event: %d > %d\n",
(pframes_t)dst.back ()->timestamp (), timestamp);
- return -1;
+#endif
}
dst.push_back (boost::shared_ptr<CoreMidiEvent>(new CoreMidiEvent (timestamp, buffer, size)));
return 0;
uint32_t
CoreAudioBackend::get_midi_event_count (void* port_buffer)
{
- assert (port_buffer);
+ if (!port_buffer) return 0;
return static_cast<CoreMidiBuffer*>(port_buffer)->size ();
}
void
CoreAudioBackend::midi_clear (void* port_buffer)
{
- assert (port_buffer);
+ if (!port_buffer) return;
CoreMidiBuffer * buf = static_cast<CoreMidiBuffer*>(port_buffer);
assert (buf);
buf->clear ();
LatencyRange
CoreAudioBackend::get_latency_range (PortEngine::PortHandle port, bool for_playback)
{
+ LatencyRange r;
if (!valid_port (port)) {
PBD::error << _("CoreBackendPort::get_latency_range (): invalid port.") << endmsg;
- LatencyRange r;
r.min = 0;
r.max = 0;
return r;
}
- return static_cast<CoreBackendPort*>(port)->latency_range (for_playback);
+ CoreBackendPort* p = static_cast<CoreBackendPort*>(port);
+ assert(p);
+
+ r = p->latency_range (for_playback);
+ if (p->is_physical() && p->is_terminal() && p->type() == DataType::AUDIO) {
+ if (p->is_input() && for_playback) {
+ r.min += _samples_per_period;
+ r.max += _samples_per_period;
+ }
+ if (p->is_output() && !for_playback) {
+ r.min += _samples_per_period;
+ r.max += _samples_per_period;
+ }
+ }
+ return r;
}
/* Discovering physical ports */
void*
CoreAudioBackend::get_buffer (PortEngine::PortHandle port, pframes_t nframes)
{
- assert (port);
- assert (valid_port (port));
+ if (!port || !valid_port (port)) return NULL;
return static_cast<CoreBackendPort*>(port)->get_buffer (nframes);
}
void
-CoreAudioBackend::post_process ()
+CoreAudioBackend::pre_process ()
{
bool connections_changed = false;
bool ports_changed = false;
{
_active_fw = true;
bool first_run = false;
+ /* Freewheeling - use for export. The first call to
+ * engine.process_callback() after engine.freewheel_callback will
+ * if the first export cycle.
+ * For reliable precise export timing, the calls need to be in sync.
+ *
+ * Furthermore we need to make sure the registered process thread
+ * is correct.
+ *
+ * _freewheeling = GUI thread state as set by ::freewheel()
+ * _freewheel = in sync here (export thread)
+ */
+ pthread_mutex_lock (&_freewheel_mutex);
while (_run) {
// check if we should run,
if (_freewheeling != _freewheel) {
if (!_freewheeling) {
- // handshake w/ coreaudio
- _reinit_thread_callback = true;
- _freewheel_ack = false;
+ // prepare leaving freewheeling mode
+ _freewheel = false; // first mark as disabled
+ _reinit_thread_callback = true; // hand over _main_thread
+ _freewheel_ack = false; // prepare next handshake
_midiio->set_enabled(true);
+ } else {
+ first_run = true;
+ _freewheel = true;
}
-
- engine.freewheel_callback (_freewheeling);
- first_run = true;
- _freewheel = _freewheeling;
}
if (!_freewheel || !_freewheel_ack) {
- // TODO use a pthread sync/sleep
- Glib::usleep(200000);
+ // wait for a change, we use a timed wait to
+ // terminate early in case some error sets _run = 0
+ struct timeval tv;
+ struct timespec ts;
+ gettimeofday (&tv, NULL);
+ ts.tv_sec = tv.tv_sec + 3;
+ ts.tv_nsec = 0;
+ pthread_cond_timedwait (&_freewheel_signal, &_freewheel_mutex, &ts);
continue;
}
if (first_run) {
+ // tell the engine we're ready to GO.
+ engine.freewheel_callback (_freewheeling);
first_run = false;
_main_thread = pthread_self();
AudioEngine::thread_init_callback (this);
_midiio->set_enabled(false);
}
- post_process();
+ // process port updates first in every cycle.
+ pre_process();
+ // prevent coreaudio device changes
pthread_mutex_lock (&_process_callback_mutex);
- // Freewheelin'
+
+ /* Freewheelin' */
+
+ // clear input buffers
for (std::vector<CoreBackendPort*>::const_iterator it = _system_inputs.begin (); it != _system_inputs.end (); ++it) {
memset ((*it)->get_buffer (_samples_per_period), 0, _samples_per_period * sizeof (Sample));
}
static_cast<CoreMidiBuffer*>((*it)->get_buffer(0))->clear ();
}
+ _last_process_start = 0;
if (engine.process_callback (_samples_per_period)) {
pthread_mutex_unlock (&_process_callback_mutex);
break;
Glib::usleep (100); // don't hog cpu
}
+ pthread_mutex_unlock (&_freewheel_mutex);
+
_active_fw = false;
if (_run) {
+ // engine.process_callback() returner error
engine.halted_callback("CoreAudio Freehweeling aborted.");
}
return 0;
}
int
-CoreAudioBackend::process_callback ()
+CoreAudioBackend::process_callback (const uint32_t n_samples, const uint64_t host_time)
{
uint32_t i = 0;
uint64_t clock1, clock2;
_active_ca = true;
if (_run && _freewheel && !_freewheel_ack) {
- _freewheel_ack = true;
+ // acknowledge freewheeling; hand-over thread ID
+ pthread_mutex_lock (&_freewheel_mutex);
+ if (_freewheel) _freewheel_ack = true;
+ pthread_cond_signal (&_freewheel_signal);
+ pthread_mutex_unlock (&_freewheel_mutex);
}
if (!_run || _freewheel || _preinit) {
- return 1;
- }
-
- if (pthread_mutex_trylock (&_process_callback_mutex)) {
+ // NB if we return 1, the output is
+ // zeroed by the coreaudio callback
return 1;
}
_reinit_thread_callback = false;
_main_thread = pthread_self();
AudioEngine::thread_init_callback (this);
-
- manager.registration_callback();
- manager.graph_order_callback();
}
+ if (pthread_mutex_trylock (&_process_callback_mutex)) {
+ // block while devices are added/removed
+#ifndef NDEBUG
+ printf("Xrun due to device change\n");
+#endif
+ engine.Xrun();
+ return 1;
+ }
/* port-connection change */
- post_process();
-
- const uint32_t n_samples = _pcmio->n_samples();
+ pre_process();
// cycle-length in usec
- const int64_t nominal_time = 1e6 * n_samples / _samplerate;
+ const double nominal_time = 1e6 * n_samples / _samplerate;
clock1 = g_get_monotonic_time();
CoreMidiBuffer* mbuf = static_cast<CoreMidiBuffer*>((*it)->get_buffer(0));
mbuf->clear();
uint64_t time_ns;
- uint8_t data[64]; // match MaxAlsaEventSize in alsa_rawmidi.cc
+ uint8_t data[128]; // matches CoreMidi's MIDIPacket
size_t size = sizeof(data);
while (_midiio->recv_event (i, nominal_time, time_ns, data, size)) {
pframes_t time = floor((float) time_ns * _samplerate * 1e-9);
}
_midiio->start_cycle();
+ _last_process_start = host_time;
if (engine.process_callback (n_samples)) {
fprintf(stderr, "ENGINE PROCESS ERROR\n");
/* queue outgoing midi */
i = 0;
for (std::vector<CoreBackendPort*>::const_iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it, ++i) {
+#if 0 // something's still b0rked with CoreMidiIo::send_events()
+ const CoreMidiBuffer *src = static_cast<const CoreMidiPort*>(*it)->const_buffer();
+ _midiio->send_events (i, nominal_time, (void*)src);
+#else // works..
const CoreMidiBuffer *src = static_cast<const CoreMidiPort*>(*it)->const_buffer();
for (CoreMidiBuffer::const_iterator mit = src->begin (); mit != src->end (); ++mit) {
_midiio->send_event (i, (*mit)->timestamp() / nominal_time, (*mit)->data(), (*mit)->size());
}
+#endif
}
/* write back audio */
/* calc DSP load. */
clock2 = g_get_monotonic_time();
const int64_t elapsed_time = clock2 - clock1;
- _dsp_load = elapsed_time / (float) nominal_time;
+ _dsp_load = elapsed_time / nominal_time;
pthread_mutex_unlock (&_process_callback_mutex);
return 0;