Hard Core Audio
* allow to change buffersizes * subscribe to buffersize & samplerate changes * add support for half-duplex devices. * aggregate Devices (not yet used) code from JACK2 * unify deprecated API wrappers * properly keep track of MIDI ports * disable MidiI/O during freewheeling * various small fixes & cleanup
This commit is contained in:
parent
e99599c7db
commit
ce3adfd3d4
@ -61,6 +61,18 @@ static void xrun_callback_ptr (void *arg)
|
||||
d->xrun_callback();
|
||||
}
|
||||
|
||||
static void buffer_size_callback_ptr (void *arg)
|
||||
{
|
||||
CoreAudioBackend *d = static_cast<CoreAudioBackend*> (arg);
|
||||
d->buffer_size_callback();
|
||||
}
|
||||
|
||||
static void sample_rate_callback_ptr (void *arg)
|
||||
{
|
||||
CoreAudioBackend *d = static_cast<CoreAudioBackend*> (arg);
|
||||
d->sample_rate_callback();
|
||||
}
|
||||
|
||||
static void midi_port_change (void *arg)
|
||||
{
|
||||
CoreAudioBackend *d = static_cast<CoreAudioBackend *>(arg);
|
||||
@ -99,7 +111,6 @@ CoreAudioBackend::CoreAudioBackend (AudioEngine& e, AudioBackendInfo& info)
|
||||
|
||||
_pcmio->set_hw_changed_callback (hw_changed_callback_ptr, this);
|
||||
_pcmio->discover();
|
||||
_midiio->discover();
|
||||
}
|
||||
|
||||
CoreAudioBackend::~CoreAudioBackend ()
|
||||
@ -175,7 +186,7 @@ CoreAudioBackend::can_change_sample_rate_when_running () const
|
||||
bool
|
||||
CoreAudioBackend::can_change_buffer_size_when_running () const
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
@ -204,6 +215,7 @@ CoreAudioBackend::set_buffer_size (uint32_t bs)
|
||||
return -1;
|
||||
}
|
||||
_samples_per_period = bs;
|
||||
_pcmio->set_samples_per_period(bs);
|
||||
engine.buffer_size_change (bs);
|
||||
return 0;
|
||||
}
|
||||
@ -428,7 +440,8 @@ CoreAudioBackend::_start (bool for_latency_measurement)
|
||||
_ports.clear();
|
||||
}
|
||||
|
||||
uint32_t device_id = name_to_id(_audio_device);
|
||||
uint32_t device1 = name_to_id(_audio_device); // usually input, but in an aggregate, 1st defines the clock
|
||||
uint32_t device2 = name_to_id(_audio_device); // usually output
|
||||
|
||||
assert(_active_ca == false);
|
||||
assert(_active_fw == false);
|
||||
@ -437,7 +450,10 @@ CoreAudioBackend::_start (bool for_latency_measurement)
|
||||
_reinit_thread_callback = true;
|
||||
|
||||
_pcmio->set_error_callback (error_callback_ptr, this);
|
||||
_pcmio->pcm_start (device_id, device_id, _samplerate, _samples_per_period, process_callback_ptr, this);
|
||||
_pcmio->set_buffer_size_callback (buffer_size_callback_ptr, this);
|
||||
_pcmio->set_sample_rate_callback (sample_rate_callback_ptr, this);
|
||||
|
||||
_pcmio->pcm_start (device1, device2, _samplerate, _samples_per_period, process_callback_ptr, this);
|
||||
|
||||
switch (_pcmio->state ()) {
|
||||
case 0: /* OK */ break;
|
||||
@ -454,7 +470,7 @@ CoreAudioBackend::_start (bool for_latency_measurement)
|
||||
} else {
|
||||
_n_outputs = std::min (_n_outputs, _pcmio->n_playback_channels ());
|
||||
}
|
||||
PBD::warning << _("CoreAudioBackend: adjusted output channel count to match device.") << endmsg;
|
||||
PBD::info << _("CoreAudioBackend: adjusted output channel count to match device.") << endmsg;
|
||||
}
|
||||
|
||||
if (_n_inputs != _pcmio->n_capture_channels ()) {
|
||||
@ -463,18 +479,16 @@ CoreAudioBackend::_start (bool for_latency_measurement)
|
||||
} else {
|
||||
_n_inputs = std::min (_n_inputs, _pcmio->n_capture_channels ());
|
||||
}
|
||||
PBD::warning << _("CoreAudioBackend: adjusted input channel count to match device.") << endmsg;
|
||||
PBD::info << _("CoreAudioBackend: adjusted input channel count to match device.") << endmsg;
|
||||
}
|
||||
|
||||
#if 0 // TODO
|
||||
if (_pcmio->sample_per_period() != _samples_per_period) {
|
||||
_samples_per_period = _pcmio->sample_per_period();
|
||||
if (_pcmio->samples_per_period() != _samples_per_period) {
|
||||
_samples_per_period = _pcmio->samples_per_period();
|
||||
PBD::warning << _("CoreAudioBackend: samples per period does not match.") << endmsg;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_pcmio->current_sample_rate(name_to_id(_audio_device)) != _samplerate) {
|
||||
_samplerate = _pcmio->current_sample_rate(name_to_id(_audio_device));
|
||||
if (_pcmio->sample_rate() != _samplerate) {
|
||||
_samplerate = _pcmio->sample_rate();
|
||||
engine.sample_rate_change (_samplerate);
|
||||
PBD::warning << _("CoreAudioBackend: sample rate does not match.") << endmsg;
|
||||
}
|
||||
@ -486,8 +500,9 @@ CoreAudioBackend::_start (bool for_latency_measurement)
|
||||
_port_change_flag = false;
|
||||
|
||||
if (_midi_driver_option == _("CoreMidi")) {
|
||||
_midiio->set_enabled(true);
|
||||
_midiio->set_port_changed_callback(midi_port_change, this);
|
||||
_midiio->discover();
|
||||
_midiio->start();
|
||||
}
|
||||
|
||||
if (register_system_audio_ports()) {
|
||||
@ -555,6 +570,7 @@ CoreAudioBackend::stop ()
|
||||
_run = false;
|
||||
_pcmio->pcm_stop();
|
||||
_midiio->set_port_changed_callback(NULL, NULL);
|
||||
_midiio->stop();
|
||||
|
||||
if (pthread_join (_freeewheel_thread, &status)) {
|
||||
PBD::error << _("CoreAudioBackend: failed to terminate.") << endmsg;
|
||||
@ -866,8 +882,8 @@ CoreAudioBackend::register_system_audio_ports()
|
||||
{
|
||||
LatencyRange lr;
|
||||
|
||||
const int a_ins = _n_inputs > 0 ? _n_inputs : 2;
|
||||
const int a_out = _n_outputs > 0 ? _n_outputs : 2;
|
||||
const int a_ins = _n_inputs;
|
||||
const int 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);
|
||||
@ -901,42 +917,6 @@ CoreAudioBackend::register_system_audio_ports()
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
CoreAudioBackend::register_system_midi_ports()
|
||||
{
|
||||
int midi_ins = _system_midi_out.size();
|
||||
int midi_outs = _system_midi_in.size();
|
||||
|
||||
//TODO query port names
|
||||
for (uint32_t i = midi_ins; i < _midiio->n_midi_outputs(); ++i) {
|
||||
char tmp[64];
|
||||
snprintf(tmp, sizeof(tmp), "system:midi_playback_%d", ++midi_ins);
|
||||
PortHandle p = add_port(std::string(tmp), DataType::MIDI, static_cast<PortFlags>(IsInput | IsPhysical | IsTerminal));
|
||||
if (!p) {
|
||||
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));
|
||||
}
|
||||
|
||||
for (uint32_t i = midi_outs; i < _midiio->n_midi_inputs(); ++i) {
|
||||
char tmp[64];
|
||||
snprintf(tmp, sizeof(tmp), "system:midi_capture_%d", ++midi_outs);
|
||||
PortHandle p = add_port(std::string(tmp), DataType::MIDI, static_cast<PortFlags>(IsOutput | IsPhysical | IsTerminal));
|
||||
if (!p) {
|
||||
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));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
CoreAudioBackend::coremidi_rediscover()
|
||||
{
|
||||
@ -945,23 +925,92 @@ CoreAudioBackend::coremidi_rediscover()
|
||||
|
||||
pthread_mutex_lock (&_process_callback_mutex);
|
||||
|
||||
// TODO maintain device-specific connections, rather
|
||||
// than re-map.
|
||||
while (_system_midi_out.size() > _midiio->n_midi_outputs()) {
|
||||
CoreBackendPort* p = _system_midi_out.back();
|
||||
_system_midi_out.pop_back();
|
||||
unregister_port(p);
|
||||
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)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
while (_system_midi_in.size() > _midiio->n_midi_inputs()) {
|
||||
CoreBackendPort* p = _system_midi_in.back();
|
||||
_system_midi_in.pop_back();
|
||||
unregister_port(p);
|
||||
}
|
||||
|
||||
register_system_midi_ports();
|
||||
|
||||
if (found) {
|
||||
++it;
|
||||
} else {
|
||||
#ifndef NDEBUG
|
||||
printf("unregister MIDI Output: %s\n", (*it)->name().c_str());
|
||||
#endif
|
||||
_port_change_flag = true;
|
||||
unregister_port((*it));
|
||||
it = _system_midi_out.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
++it;
|
||||
} else {
|
||||
#ifndef NDEBUG
|
||||
printf("unregister MIDI Input: %s\n", (*it)->name().c_str());
|
||||
#endif
|
||||
_port_change_flag = true;
|
||||
unregister_port((*it));
|
||||
it = _system_midi_in.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _midiio->n_midi_inputs(); ++i) {
|
||||
std::string name = _midiio->port_name(i, true);
|
||||
if (find_port_in(_system_midi_in, name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
printf("register MIDI Input: %s\n", name.c_str());
|
||||
#endif
|
||||
PortHandle p = add_port(name, DataType::MIDI, static_cast<PortFlags>(IsOutput | IsPhysical | IsTerminal));
|
||||
if (!p) {
|
||||
fprintf(stderr, "failed to register MIDI IN: %s\n", name.c_str());
|
||||
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));
|
||||
_port_change_flag = true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _midiio->n_midi_outputs(); ++i) {
|
||||
std::string name = _midiio->port_name(i, false);
|
||||
if (find_port_in(_system_midi_out, name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
printf("register MIDI OUT: %s\n", name.c_str());
|
||||
#endif
|
||||
PortHandle p = add_port(name, DataType::MIDI, static_cast<PortFlags>(IsInput | IsPhysical | IsTerminal));
|
||||
if (!p) {
|
||||
fprintf(stderr, "failed to register MIDI OUT: %s\n", name.c_str());
|
||||
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));
|
||||
_port_change_flag = true;
|
||||
}
|
||||
|
||||
|
||||
assert(_system_midi_out.size() == _midiio->n_midi_outputs());
|
||||
assert(_system_midi_in.size() == _midiio->n_midi_inputs());
|
||||
|
||||
pthread_mutex_unlock (&_process_callback_mutex);
|
||||
}
|
||||
|
||||
@ -1339,6 +1388,7 @@ CoreAudioBackend::freewheel_thread ()
|
||||
// handshake w/ coreaudio
|
||||
_reinit_thread_callback = true;
|
||||
_freewheel_ack = false;
|
||||
_midiio->set_enabled(true);
|
||||
}
|
||||
|
||||
engine.freewheel_callback (_freewheeling);
|
||||
@ -1356,8 +1406,12 @@ CoreAudioBackend::freewheel_thread ()
|
||||
first_run = false;
|
||||
_main_thread = pthread_self();
|
||||
AudioEngine::thread_init_callback (this);
|
||||
_midiio->set_enabled(false);
|
||||
}
|
||||
|
||||
post_process();
|
||||
|
||||
pthread_mutex_lock (&_process_callback_mutex);
|
||||
// Freewheelin'
|
||||
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));
|
||||
@ -1367,17 +1421,18 @@ CoreAudioBackend::freewheel_thread ()
|
||||
}
|
||||
|
||||
if (engine.process_callback (_samples_per_period)) {
|
||||
pthread_mutex_unlock (&_process_callback_mutex);
|
||||
break;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock (&_process_callback_mutex);
|
||||
_dsp_load = 1.0;
|
||||
Glib::usleep (100); // don't hog cpu
|
||||
|
||||
post_process();
|
||||
}
|
||||
|
||||
_active_fw = false;
|
||||
|
||||
if (_run && _freewheel) {
|
||||
if (_run) {
|
||||
engine.halted_callback("CoreAudio Freehweeling aborted.");
|
||||
}
|
||||
return 0;
|
||||
@ -1412,16 +1467,10 @@ CoreAudioBackend::process_callback ()
|
||||
manager.graph_order_callback();
|
||||
}
|
||||
|
||||
const uint32_t n_samples = _pcmio->n_samples();
|
||||
/* port-connection change */
|
||||
post_process();
|
||||
|
||||
#if 0 // here in RT callback ?? XXX
|
||||
if (_samples_per_period != n_samples) {
|
||||
printf("CoreAudio Adjust SPP %zu -> %d\n", _samples_per_period, n_samples);
|
||||
_samples_per_period = n_samples;
|
||||
engine.buffer_size_change (_samples_per_period);
|
||||
// TODO update latencies
|
||||
}
|
||||
#endif
|
||||
const uint32_t n_samples = _pcmio->n_samples();
|
||||
|
||||
// cycle-length in usec
|
||||
const int64_t nominal_time = 1e6 * n_samples / _samplerate;
|
||||
@ -1492,8 +1541,6 @@ CoreAudioBackend::process_callback ()
|
||||
const int64_t elapsed_time = clock2 - clock1;
|
||||
_dsp_load = elapsed_time / (float) nominal_time;
|
||||
|
||||
/* port-connection change */
|
||||
post_process();
|
||||
pthread_mutex_unlock (&_process_callback_mutex);
|
||||
return 0;
|
||||
}
|
||||
@ -1502,7 +1549,11 @@ void
|
||||
CoreAudioBackend::error_callback ()
|
||||
{
|
||||
_pcmio->set_error_callback (NULL, NULL);
|
||||
_pcmio->set_sample_rate_callback (NULL, NULL);
|
||||
_pcmio->set_xrun_callback (NULL, NULL);
|
||||
_midiio->set_port_changed_callback(NULL, NULL);
|
||||
engine.halted_callback("CoreAudio Process aborted.");
|
||||
_active_ca = false;
|
||||
}
|
||||
|
||||
void
|
||||
@ -1511,6 +1562,28 @@ CoreAudioBackend::xrun_callback ()
|
||||
engine.Xrun ();
|
||||
}
|
||||
|
||||
void
|
||||
CoreAudioBackend::buffer_size_callback ()
|
||||
{
|
||||
uint32_t bs = _pcmio->samples_per_period();
|
||||
if (bs == _samples_per_period) {
|
||||
return;
|
||||
}
|
||||
_samples_per_period = bs;
|
||||
engine.buffer_size_change (_samples_per_period);
|
||||
}
|
||||
|
||||
void
|
||||
CoreAudioBackend::sample_rate_callback ()
|
||||
{
|
||||
_pcmio->set_error_callback (NULL, NULL);
|
||||
_pcmio->set_sample_rate_callback (NULL, NULL);
|
||||
_pcmio->set_xrun_callback (NULL, NULL);
|
||||
_midiio->set_port_changed_callback(NULL, NULL);
|
||||
engine.halted_callback("Sample Rate Changed.");
|
||||
stop();
|
||||
}
|
||||
|
||||
void
|
||||
CoreAudioBackend::hw_changed_callback ()
|
||||
{
|
||||
|
@ -215,6 +215,8 @@ class CoreAudioBackend : public AudioBackend {
|
||||
int process_callback();
|
||||
void error_callback();
|
||||
void xrun_callback();
|
||||
void buffer_size_callback();
|
||||
void sample_rate_callback();
|
||||
void hw_changed_callback();
|
||||
|
||||
protected:
|
||||
@ -376,7 +378,6 @@ class CoreAudioBackend : public AudioBackend {
|
||||
/* port engine */
|
||||
PortHandle add_port (const std::string& shortname, ARDOUR::DataType, ARDOUR::PortFlags);
|
||||
int register_system_audio_ports ();
|
||||
int register_system_midi_ports ();
|
||||
void unregister_ports (bool system_only = false);
|
||||
|
||||
std::vector<CoreBackendPort *> _ports;
|
||||
@ -423,6 +424,15 @@ class CoreAudioBackend : public AudioBackend {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CoreBackendPort * find_port_in (std::vector<CoreBackendPort *> plist, const std::string& port_name) const {
|
||||
for (std::vector<CoreBackendPort*>::const_iterator it = plist.begin (); it != plist.end (); ++it) {
|
||||
if ((*it)->name () == port_name) {
|
||||
return *it;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}; // class CoreAudioBackend
|
||||
|
||||
} // namespace
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -47,11 +47,16 @@ public:
|
||||
int available_sample_rates (uint32_t device_id, std::vector<float>& sampleRates);
|
||||
int available_buffer_sizes (uint32_t device_id, std::vector<uint32_t>& sampleRates);
|
||||
uint32_t available_channels (uint32_t device_id, bool input);
|
||||
float current_sample_rate(uint32 device_id, bool input = false);
|
||||
uint32_t get_latency(uint32 device_id, bool input);
|
||||
float current_sample_rate (uint32_t device_id, bool input = false);
|
||||
uint32_t get_latency (uint32_t device_id, bool input);
|
||||
|
||||
std::string cached_port_name (uint32_t portnum, bool input) const;
|
||||
|
||||
float sample_rate ();
|
||||
uint32_t samples_per_period () const { return _samples_per_period; };
|
||||
|
||||
int set_samples_per_period (uint32_t);
|
||||
|
||||
void launch_control_app (uint32_t device_id);
|
||||
|
||||
void pcm_stop (void);
|
||||
@ -79,6 +84,7 @@ public:
|
||||
_hw_changed_callback = callback;
|
||||
_hw_changed_arg = arg;
|
||||
}
|
||||
|
||||
void set_xrun_callback (
|
||||
void ( callback (void*)),
|
||||
void * arg
|
||||
@ -86,6 +92,20 @@ public:
|
||||
_xrun_callback = callback;
|
||||
_xrun_arg = arg;
|
||||
}
|
||||
void set_buffer_size_callback (
|
||||
void ( callback (void*)),
|
||||
void * arg
|
||||
) {
|
||||
_buffer_size_callback = callback;
|
||||
_buffer_size_arg = arg;
|
||||
}
|
||||
void set_sample_rate_callback (
|
||||
void ( callback (void*)),
|
||||
void * arg
|
||||
) {
|
||||
_sample_rate_callback = callback;
|
||||
_sample_rate_arg = arg;
|
||||
}
|
||||
|
||||
// must be called from process_callback;
|
||||
int get_capture_channel (uint32_t chn, float *input, uint32_t n_samples);
|
||||
@ -101,24 +121,39 @@ public:
|
||||
AudioBufferList* ioData);
|
||||
|
||||
void xrun_callback ();
|
||||
void buffer_size_callback ();
|
||||
void sample_rate_callback ();
|
||||
void hw_changed_callback ();
|
||||
|
||||
private:
|
||||
int set_device_sample_rate (uint32 device_id, float rate, bool input);
|
||||
void get_stream_latencies(uint32 device_id, bool input, std::vector<uint32>& latencies);
|
||||
void cache_port_names(uint32 device_id, bool input);
|
||||
float current_sample_rate_id (AudioDeviceID id, bool input);
|
||||
uint32_t current_buffer_size_id (AudioDeviceID id);
|
||||
int set_device_sample_rate_id (AudioDeviceID id, float rate, bool input);
|
||||
int set_device_buffer_size_id (AudioDeviceID id, uint32_t samples_per_period);
|
||||
int set_device_sample_rate (uint32_t device_id, float rate, bool input);
|
||||
void get_stream_latencies (uint32_t device_id, bool input, std::vector<uint32_t>& latencies);
|
||||
void cache_port_names (AudioDeviceID id, bool input);
|
||||
|
||||
void destroy_aggregate_device();
|
||||
int create_aggregate_device(
|
||||
AudioDeviceID input_device_id,
|
||||
AudioDeviceID output_device_id,
|
||||
uint32_t sample_rate,
|
||||
AudioDeviceID *created_device);
|
||||
|
||||
|
||||
AudioUnit _auhal;
|
||||
AudioDeviceID* _device_ids;
|
||||
AudioBufferList* _input_audio_buffer_list;
|
||||
AudioBufferList* _output_audio_buffer_list;
|
||||
|
||||
AudioDeviceID _active_input;
|
||||
AudioDeviceID _active_output;
|
||||
AudioDeviceID _active_device_id;
|
||||
AudioDeviceID _aggregate_device_id;
|
||||
AudioDeviceID _aggregate_plugin_id;
|
||||
|
||||
int _state;
|
||||
|
||||
uint32_t _max_samples_per_period;
|
||||
uint32_t _samples_per_period;
|
||||
uint32_t _cur_samples_per_period;
|
||||
uint32_t _capture_channels;
|
||||
uint32_t _playback_channels;
|
||||
@ -137,6 +172,12 @@ private:
|
||||
void (* _xrun_callback) (void*);
|
||||
void * _xrun_arg;
|
||||
|
||||
void (* _buffer_size_callback) (void*);
|
||||
void * _buffer_size_arg;
|
||||
|
||||
void (* _sample_rate_callback) (void*);
|
||||
void * _sample_rate_arg;
|
||||
|
||||
|
||||
// TODO proper device info struct
|
||||
std::map<size_t, std::string> _devices;
|
||||
|
358
libs/backends/coreaudio/coreaudio_pcmio_aggregate.cc
Normal file
358
libs/backends/coreaudio/coreaudio_pcmio_aggregate.cc
Normal file
@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Robin Gareus <robin@gareus.org>
|
||||
* Copyright (C) 2004-2008 Grame
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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 <vector>
|
||||
|
||||
void
|
||||
CoreAudioPCM::destroy_aggregate_device ()
|
||||
{
|
||||
if (_aggregate_plugin_id == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
OSStatus err;
|
||||
|
||||
AudioObjectPropertyAddress property_address;
|
||||
property_address.mSelector = kAudioPlugInDestroyAggregateDevice;
|
||||
property_address.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
property_address.mElement = kAudioObjectPropertyElementMaster;
|
||||
UInt32 outDataSize;
|
||||
|
||||
err = AudioObjectGetPropertyDataSize(_aggregate_plugin_id, &property_address, 0, NULL, &outDataSize);
|
||||
if (err != noErr) {
|
||||
fprintf(stderr, "DestroyAggregateDevice : AudioObjectGetPropertyDataSize error\n");
|
||||
return;
|
||||
}
|
||||
|
||||
err = AudioObjectGetPropertyData(_aggregate_plugin_id, &property_address, 0, NULL, &outDataSize, &_aggregate_device_id);
|
||||
if (err != noErr) {
|
||||
fprintf(stderr, "DestroyAggregateDevice : AudioObjectGetPropertyData error\n");
|
||||
return;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
printf("DestroyAggregateDevice : OK (plugin: %u device:%u)\n", _aggregate_plugin_id, _aggregate_device_id);
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
CoreAudioPCM::create_aggregate_device (
|
||||
AudioDeviceID input_device_id,
|
||||
AudioDeviceID output_device_id,
|
||||
uint32_t sample_rate,
|
||||
AudioDeviceID* created_device)
|
||||
{
|
||||
OSStatus err;
|
||||
AudioObjectID sub_device[32];
|
||||
UInt32 size = sizeof(sub_device);
|
||||
|
||||
/* look up sub-devices */
|
||||
err = GetPropertyWrapper (input_device_id, 0, 0, kAudioAggregateDevicePropertyActiveSubDeviceList, &size, sub_device);
|
||||
std::vector<AudioDeviceID> input_device_ids;
|
||||
|
||||
if (err != noErr) {
|
||||
input_device_ids.push_back(input_device_id);
|
||||
} else {
|
||||
uint32_t num_devices = size / sizeof(AudioObjectID);
|
||||
for (uint32_t i = 0; i < num_devices; ++i) {
|
||||
input_device_ids.push_back(sub_device[i]);
|
||||
}
|
||||
}
|
||||
|
||||
size = sizeof(sub_device);
|
||||
err = GetPropertyWrapper (output_device_id, 0, 0, kAudioAggregateDevicePropertyActiveSubDeviceList, &size, sub_device);
|
||||
std::vector<AudioDeviceID> output_device_ids;
|
||||
|
||||
if (err != noErr) {
|
||||
output_device_ids.push_back(output_device_id);
|
||||
} else {
|
||||
uint32_t num_devices = size / sizeof(AudioObjectID);
|
||||
for (uint32_t i = 0; i < num_devices; ++i) {
|
||||
output_device_ids.push_back(sub_device[i]);
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Setup SR of both devices otherwise creating AD may fail...
|
||||
//---------------------------------------------------------------------------
|
||||
UInt32 keptclockdomain = 0;
|
||||
UInt32 clockdomain = 0;
|
||||
size = sizeof(UInt32);
|
||||
bool need_clock_drift_compensation = false;
|
||||
|
||||
for (size_t i = 0; i < input_device_ids.size(); ++i) {
|
||||
set_device_sample_rate_id(input_device_ids[i], sample_rate, true);
|
||||
|
||||
// Check clock domain
|
||||
err = GetPropertyWrapper (input_device_ids[i], 0, 0, kAudioDevicePropertyClockDomain, &size, &clockdomain);
|
||||
if (err == noErr) {
|
||||
keptclockdomain = (keptclockdomain == 0) ? clockdomain : keptclockdomain;
|
||||
if (clockdomain != 0 && clockdomain != keptclockdomain) {
|
||||
#ifndef NDEBUG
|
||||
printf("AggregateDevice: devices do not share the same clock.\n");
|
||||
#endif
|
||||
need_clock_drift_compensation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (UInt32 i = 0; i < output_device_ids.size(); i++) {
|
||||
set_device_sample_rate_id(output_device_ids[i], sample_rate, true);
|
||||
|
||||
// Check clock domain
|
||||
err = GetPropertyWrapper (output_device_ids[i], 0, 0, kAudioDevicePropertyClockDomain, &size, &clockdomain);
|
||||
if (err == noErr) {
|
||||
keptclockdomain = (keptclockdomain == 0) ? clockdomain : keptclockdomain;
|
||||
if (clockdomain != 0 && clockdomain != keptclockdomain) {
|
||||
#ifndef NDEBUG
|
||||
printf("AggregateDevice: devices do not share the same clock.\n");
|
||||
#endif
|
||||
need_clock_drift_compensation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid clock domain was found, then assume we have to compensate...
|
||||
if (keptclockdomain == 0) {
|
||||
need_clock_drift_compensation = true;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Start to create a new aggregate by getting the base audio hardware plugin
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#ifndef NDEBUG
|
||||
char device_name[256];
|
||||
for (size_t i = 0; i < input_device_ids.size(); ++i) {
|
||||
GetDeviceNameFromID(input_device_ids[i], device_name);
|
||||
printf("Separated input = '%s'\n", device_name);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < output_device_ids.size(); ++i) {
|
||||
GetDeviceNameFromID(output_device_ids[i], device_name);
|
||||
printf("Separated output = '%s'\n", device_name);
|
||||
}
|
||||
#endif
|
||||
|
||||
err = GetHardwarePropertyInfoWrapper (kAudioHardwarePropertyPlugInForBundleID, &size);
|
||||
if (err != noErr) {
|
||||
fprintf(stderr, "AggregateDevice: AudioHardwareGetPropertyInfo kAudioHardwarePropertyPlugInForBundleID error\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
AudioValueTranslation pluginAVT;
|
||||
|
||||
CFStringRef inBundleRef = CFSTR("com.apple.audio.CoreAudio");
|
||||
|
||||
pluginAVT.mInputData = &inBundleRef;
|
||||
pluginAVT.mInputDataSize = sizeof(inBundleRef);
|
||||
pluginAVT.mOutputData = &_aggregate_plugin_id;
|
||||
pluginAVT.mOutputDataSize = sizeof(AudioDeviceID);
|
||||
|
||||
err = GetHardwarePropertyWrapper (kAudioHardwarePropertyPlugInForBundleID, &size, &pluginAVT);
|
||||
if (err != noErr) {
|
||||
fprintf(stderr, "AggregateDevice: AudioHardwareGetProperty kAudioHardwarePropertyPlugInForBundleID error\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
//-------------------------------------------------
|
||||
// Create a CFDictionary for our aggregate device
|
||||
//-------------------------------------------------
|
||||
|
||||
CFMutableDictionaryRef aggDeviceDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
|
||||
CFStringRef AggregateDeviceNameRef = CFSTR("ArdourDuplex");
|
||||
CFStringRef AggregateDeviceUIDRef = CFSTR("com.ardour.CoreAudio");
|
||||
CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceNameKey), AggregateDeviceNameRef);
|
||||
CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceUIDKey), AggregateDeviceUIDRef);
|
||||
|
||||
// hide from list
|
||||
int value = 1;
|
||||
CFNumberRef AggregateDeviceNumberRef = CFNumberCreate(NULL, kCFNumberIntType, &value);
|
||||
CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceIsPrivateKey), AggregateDeviceNumberRef);
|
||||
|
||||
//-------------------------------------------------
|
||||
// Create a CFMutableArray for our sub-device list
|
||||
//-------------------------------------------------
|
||||
|
||||
// we need to append the UID for each device to a CFMutableArray, so create one here
|
||||
CFMutableArrayRef subDevicesArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
|
||||
|
||||
std::vector<CFStringRef> captureDeviceUID;
|
||||
for (UInt32 i = 0; i < input_device_ids.size(); i++) {
|
||||
CFStringRef ref = GetDeviceName(input_device_ids[i]);
|
||||
if (ref == NULL) {
|
||||
return -1;
|
||||
}
|
||||
captureDeviceUID.push_back(ref);
|
||||
CFArrayAppendValue(subDevicesArray, ref);
|
||||
}
|
||||
|
||||
std::vector<CFStringRef> playbackDeviceUID;
|
||||
for (UInt32 i = 0; i < output_device_ids.size(); i++) {
|
||||
CFStringRef ref = GetDeviceName(output_device_ids[i]);
|
||||
if (ref == NULL) {
|
||||
return -1;
|
||||
}
|
||||
playbackDeviceUID.push_back(ref);
|
||||
CFArrayAppendValue(subDevicesArray, ref);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Feed the dictionary to the plugin, to create a blank aggregate device
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
AudioObjectPropertyAddress pluginAOPA;
|
||||
pluginAOPA.mSelector = kAudioPlugInCreateAggregateDevice;
|
||||
pluginAOPA.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
pluginAOPA.mElement = kAudioObjectPropertyElementMaster;
|
||||
UInt32 outDataSize;
|
||||
|
||||
err = AudioObjectGetPropertyDataSize(_aggregate_plugin_id, &pluginAOPA, 0, NULL, &outDataSize);
|
||||
if (err != noErr) {
|
||||
fprintf(stderr, "AggregateDevice: AudioObjectGetPropertyDataSize error\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = AudioObjectGetPropertyData(_aggregate_plugin_id, &pluginAOPA, sizeof(aggDeviceDict), &aggDeviceDict, &outDataSize, created_device);
|
||||
if (err != noErr) {
|
||||
fprintf(stderr, "AggregateDevice: AudioObjectGetPropertyData error\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
// pause for a bit to make sure that everything completed correctly
|
||||
// this is to work around a bug in the HAL where a new aggregate device seems to disappear briefly after it is created
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
|
||||
|
||||
//-------------------------
|
||||
// Set the sub-device list
|
||||
//-------------------------
|
||||
|
||||
pluginAOPA.mSelector = kAudioAggregateDevicePropertyFullSubDeviceList;
|
||||
pluginAOPA.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
pluginAOPA.mElement = kAudioObjectPropertyElementMaster;
|
||||
outDataSize = sizeof(CFMutableArrayRef);
|
||||
err = AudioObjectSetPropertyData(*created_device, &pluginAOPA, 0, NULL, outDataSize, &subDevicesArray);
|
||||
if (err != noErr) {
|
||||
fprintf(stderr, "AggregateDevice: AudioObjectSetPropertyData for sub-device list error\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
// pause again to give the changes time to take effect
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
|
||||
|
||||
//-----------------------
|
||||
// Set the master device
|
||||
//-----------------------
|
||||
|
||||
// set the master device manually (this is the device which will act as the master clock for the aggregate device)
|
||||
// pass in the UID of the device you want to use
|
||||
pluginAOPA.mSelector = kAudioAggregateDevicePropertyMasterSubDevice;
|
||||
pluginAOPA.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
pluginAOPA.mElement = kAudioObjectPropertyElementMaster;
|
||||
outDataSize = sizeof(CFStringRef);
|
||||
err = AudioObjectSetPropertyData(*created_device, &pluginAOPA, 0, NULL, outDataSize, &captureDeviceUID[0]);
|
||||
if (err != noErr) {
|
||||
fprintf(stderr, "AggregateDevice: AudioObjectSetPropertyData for playback-master device error\n");
|
||||
// try playback
|
||||
err = AudioObjectSetPropertyData(*created_device, &pluginAOPA, 0, NULL, outDataSize, &playbackDeviceUID[0]);
|
||||
}
|
||||
if (err != noErr) {
|
||||
fprintf(stderr, "AggregateDevice: AudioObjectSetPropertyData for capture-master device error\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
// pause again to give the changes time to take effect
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
|
||||
|
||||
// Prepare sub-devices for clock drift compensation
|
||||
// Workaround for bug in the HAL : until 10.6.2
|
||||
if (need_clock_drift_compensation) {
|
||||
|
||||
AudioObjectPropertyAddress theAddressOwned = { kAudioObjectPropertyOwnedObjects, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
|
||||
AudioObjectPropertyAddress theAddressDrift = { kAudioSubDevicePropertyDriftCompensation, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
|
||||
UInt32 theQualifierDataSize = sizeof(AudioObjectID);
|
||||
AudioClassID inClass = kAudioSubDeviceClassID;
|
||||
void* theQualifierData = &inClass;
|
||||
UInt32 subDevicesNum = 0;
|
||||
|
||||
#ifndef NDEBUG
|
||||
printf("Clock drift compensation activated...\n");
|
||||
#endif
|
||||
|
||||
// Get the property data size
|
||||
err = AudioObjectGetPropertyDataSize(*created_device, &theAddressOwned, theQualifierDataSize, theQualifierData, &size);
|
||||
if (err != noErr) {
|
||||
fprintf(stderr, "AggregateDevice: kAudioObjectPropertyOwnedObjects error\n");
|
||||
}
|
||||
|
||||
// Calculate the number of object IDs
|
||||
subDevicesNum = size / sizeof(AudioObjectID);
|
||||
#ifndef NDEBUG
|
||||
printf("AggregateDevice: clock drift compensation, number of sub-devices = %d\n", subDevicesNum);
|
||||
#endif
|
||||
AudioObjectID subDevices[subDevicesNum];
|
||||
size = sizeof(subDevices);
|
||||
|
||||
err = AudioObjectGetPropertyData(*created_device, &theAddressOwned, theQualifierDataSize, theQualifierData, &size, subDevices);
|
||||
if (err != noErr) {
|
||||
fprintf(stderr, "AggregateDevice: kAudioObjectPropertyOwnedObjects error\n");
|
||||
}
|
||||
|
||||
// Set kAudioSubDevicePropertyDriftCompensation property...
|
||||
for (UInt32 index = 0; index < subDevicesNum; ++index) {
|
||||
UInt32 theDriftCompensationValue = 1;
|
||||
err = AudioObjectSetPropertyData(subDevices[index], &theAddressDrift, 0, NULL, sizeof(UInt32), &theDriftCompensationValue);
|
||||
if (err != noErr) {
|
||||
fprintf(stderr, "AggregateDevice: kAudioSubDevicePropertyDriftCompensation error\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pause again to give the changes time to take effect
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
|
||||
|
||||
//----------
|
||||
// Clean up
|
||||
//----------
|
||||
|
||||
// release the private AD key
|
||||
CFRelease(AggregateDeviceNumberRef);
|
||||
|
||||
// release the CF objects we have created - we don't need them any more
|
||||
CFRelease(aggDeviceDict);
|
||||
CFRelease(subDevicesArray);
|
||||
|
||||
// release the device UID
|
||||
for (size_t i = 0; i < captureDeviceUID.size(); ++i) {
|
||||
CFRelease(captureDeviceUID[i]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < playbackDeviceUID.size(); ++i) {
|
||||
CFRelease(playbackDeviceUID[i]);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
printf("AggregateDevice: new aggregate device %u\n", *created_device);
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
error:
|
||||
destroy_aggregate_device();
|
||||
return -1;
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
#include "coremidi_io.h"
|
||||
#include <CoreAudio/HostTime.h>
|
||||
|
||||
@ -25,7 +26,11 @@ static void notifyProc (const MIDINotification *message, void *refCon) {
|
||||
}
|
||||
|
||||
static void midiInputCallback(const MIDIPacketList *list, void *procRef, void *srcRef) {
|
||||
// TODO skip while freewheeling
|
||||
CoreMidiIo *self = static_cast<CoreMidiIo*> (procRef);
|
||||
if (!self || !self->enabled()) {
|
||||
// skip while freewheeling
|
||||
return;
|
||||
}
|
||||
RingBuffer<uint8_t> * rb = static_cast<RingBuffer < uint8_t > *> (srcRef);
|
||||
if (!rb) return;
|
||||
for (UInt32 i = 0; i < list->numPackets; i++) {
|
||||
@ -38,6 +43,17 @@ static void midiInputCallback(const MIDIPacketList *list, void *procRef, void *s
|
||||
}
|
||||
}
|
||||
|
||||
static std::string getDisplayName(MIDIObjectRef object)
|
||||
{
|
||||
CFStringRef name = nil;
|
||||
if (noErr != MIDIObjectGetStringProperty(object, kMIDIPropertyDisplayName, &name) && name) {
|
||||
return "";
|
||||
}
|
||||
std::string rv (CFStringGetCStringPtr(name, kCFStringEncodingUTF8));
|
||||
CFRelease(name);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
CoreMidiIo::CoreMidiIo()
|
||||
: _midi_client (0)
|
||||
@ -51,21 +67,24 @@ CoreMidiIo::CoreMidiIo()
|
||||
, _n_midi_out (0)
|
||||
, _time_at_cycle_start (0)
|
||||
, _active (false)
|
||||
, _enabled (true)
|
||||
, _run (false)
|
||||
, _changed_callback (0)
|
||||
, _changed_arg (0)
|
||||
{
|
||||
OSStatus err;
|
||||
err = MIDIClientCreate(CFSTR("Ardour"), ¬ifyProc, this, &_midi_client);
|
||||
if (noErr != err) {
|
||||
fprintf(stderr, "Creating Midi Client failed\n");
|
||||
}
|
||||
|
||||
pthread_mutex_init (&_discovery_lock, 0);
|
||||
}
|
||||
|
||||
CoreMidiIo::~CoreMidiIo()
|
||||
{
|
||||
pthread_mutex_lock (&_discovery_lock);
|
||||
cleanup();
|
||||
MIDIClientDispose(_midi_client); _midi_client = 0;
|
||||
if (_midi_client) {
|
||||
MIDIClientDispose(_midi_client);
|
||||
_midi_client = 0;
|
||||
}
|
||||
pthread_mutex_unlock (&_discovery_lock);
|
||||
pthread_mutex_destroy (&_discovery_lock);
|
||||
}
|
||||
|
||||
void
|
||||
@ -208,12 +227,68 @@ CoreMidiIo::send_event (uint32_t port, double reltime_us, const uint8_t *d, cons
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string
|
||||
CoreMidiIo::port_name (uint32_t port, bool input)
|
||||
{
|
||||
std::stringstream ss;
|
||||
std::string pn;
|
||||
// XXX including the number will not yield persistent port-names
|
||||
// when disconnecting devices in the middle.
|
||||
if (input) {
|
||||
ss << "system:midi_capture_" << port;
|
||||
if (port < _n_midi_in) {
|
||||
pn = getDisplayName(_input_endpoints[port]);
|
||||
}
|
||||
} else {
|
||||
ss << "system:midi_playback_" << port;
|
||||
if (port < _n_midi_out) {
|
||||
pn = getDisplayName(_output_endpoints[port]);
|
||||
}
|
||||
}
|
||||
if (!pn.empty()) {
|
||||
ss << " - " << pn;
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void
|
||||
CoreMidiIo::start () {
|
||||
_run = true;
|
||||
if (!_midi_client) {
|
||||
OSStatus err;
|
||||
err = MIDIClientCreate(CFSTR("Ardour"), ¬ifyProc, this, &_midi_client);
|
||||
if (noErr != err) {
|
||||
fprintf(stderr, "Creating Midi Client failed\n");
|
||||
}
|
||||
}
|
||||
discover();
|
||||
}
|
||||
|
||||
void
|
||||
CoreMidiIo::stop ()
|
||||
{
|
||||
_run = false;
|
||||
pthread_mutex_lock (&_discovery_lock);
|
||||
cleanup();
|
||||
pthread_mutex_unlock (&_discovery_lock);
|
||||
#if 0
|
||||
if (_midi_client) {
|
||||
MIDIClientDispose(_midi_client);
|
||||
_midi_client = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
CoreMidiIo::discover()
|
||||
{
|
||||
cleanup();
|
||||
if (!_run || !_midi_client) { return; }
|
||||
|
||||
assert(!_active && _midi_client);
|
||||
if (pthread_mutex_trylock (&_discovery_lock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cleanup();
|
||||
|
||||
ItemCount srcCount = MIDIGetNumberOfSources();
|
||||
ItemCount dstCount = MIDIGetNumberOfDestinations();
|
||||
@ -232,16 +307,19 @@ CoreMidiIo::discover()
|
||||
for (ItemCount i = 0; i < srcCount; i++) {
|
||||
OSStatus err;
|
||||
MIDIEndpointRef src = MIDIGetSource(i);
|
||||
if (!src) continue;
|
||||
#ifndef NDEBUG
|
||||
printf("MIDI IN DEVICE: %s\n", getDisplayName(src).c_str());
|
||||
#endif
|
||||
|
||||
CFStringRef port_name;
|
||||
port_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("midi_capture_%lu"), i);
|
||||
|
||||
err = MIDIInputPortCreate (_midi_client, port_name, midiInputCallback, this, &_input_ports[_n_midi_in]);
|
||||
if (noErr != err) {
|
||||
fprintf(stderr, "Cannot create Midi Output\n");
|
||||
// TODO handle errors
|
||||
continue;
|
||||
}
|
||||
// TODO get device name/ID
|
||||
_rb[_n_midi_in] = new RingBuffer<uint8_t>(1024 * sizeof(MIDIPacket));
|
||||
_input_queue[_n_midi_in] = CoreMIDIQueue();
|
||||
MIDIPortConnectSource(_input_ports[_n_midi_in], src, (void*) _rb[_n_midi_in]);
|
||||
@ -259,10 +337,13 @@ CoreMidiIo::discover()
|
||||
err = MIDIOutputPortCreate (_midi_client, port_name, &_output_ports[_n_midi_out]);
|
||||
if (noErr != err) {
|
||||
fprintf(stderr, "Cannot create Midi Output\n");
|
||||
// TODO handle errors
|
||||
continue;
|
||||
}
|
||||
// TODO get device name/ID
|
||||
|
||||
#ifndef NDEBUG
|
||||
printf("MIDI OUT DEVICE: %s\n", getDisplayName(dst).c_str());
|
||||
#endif
|
||||
|
||||
MIDIPortConnectSource(_output_ports[_n_midi_out], dst, NULL);
|
||||
CFRelease(port_name);
|
||||
_output_endpoints[_n_midi_out] = dst;
|
||||
@ -274,4 +355,5 @@ CoreMidiIo::discover()
|
||||
}
|
||||
|
||||
_active = true;
|
||||
pthread_mutex_unlock (&_discovery_lock);
|
||||
}
|
||||
|
@ -64,8 +64,9 @@ public:
|
||||
CoreMidiIo (void);
|
||||
~CoreMidiIo (void);
|
||||
|
||||
// TODO explicit start/stop, add/remove devices as needed.
|
||||
void discover ();
|
||||
void start ();
|
||||
void stop ();
|
||||
|
||||
void start_cycle ();
|
||||
|
||||
int send_event (uint32_t, double, const uint8_t *, const size_t);
|
||||
@ -73,15 +74,20 @@ public:
|
||||
|
||||
uint32_t n_midi_inputs (void) const { return _n_midi_in; }
|
||||
uint32_t n_midi_outputs (void) const { return _n_midi_out; }
|
||||
std::string port_name (uint32_t, bool input);
|
||||
|
||||
void notify_proc (const MIDINotification *message);
|
||||
|
||||
void set_enabled (bool yn = true) { _enabled = yn; }
|
||||
bool enabled (void) const { return _active && _enabled; }
|
||||
|
||||
void set_port_changed_callback (void (changed_callback (void*)), void *arg) {
|
||||
_changed_callback = changed_callback;
|
||||
_changed_arg = arg;
|
||||
}
|
||||
|
||||
private:
|
||||
void discover ();
|
||||
void cleanup ();
|
||||
|
||||
MIDIClientRef _midi_client;
|
||||
@ -97,8 +103,12 @@ private:
|
||||
uint32_t _n_midi_out;
|
||||
|
||||
MIDITimeStamp _time_at_cycle_start;
|
||||
bool _active;
|
||||
bool _active; // internal deactivate during discovery etc
|
||||
bool _enabled; // temporary disable, e.g. during freewheeli
|
||||
bool _run; // general status
|
||||
|
||||
void (* _changed_callback) (void*);
|
||||
void * _changed_arg;
|
||||
|
||||
pthread_mutex_t _discovery_lock;
|
||||
};
|
||||
|
@ -32,7 +32,7 @@ def build(bld):
|
||||
obj.framework = [ 'CoreAudio', 'AudioToolbox', 'CoreServices', 'CoreMidi' ]
|
||||
obj.install_path = os.path.join(bld.env['LIBDIR'], 'backends')
|
||||
obj.defines = ['PACKAGE="' + I18N_PACKAGE + '"',
|
||||
'ARDOURBACKEND_DLL_EXPORTS', 'COREAUDIO_108'
|
||||
'ARDOURBACKEND_DLL_EXPORTS'
|
||||
]
|
||||
|
||||
# use new coreaudio API (the old one was deprecated in 10.6, yet still works)
|
||||
|
Loading…
Reference in New Issue
Block a user