X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fbackends%2Fcoreaudio%2Fcoremidi_io.cc;h=1d4f6fbf025e37163a71ec4fe72af2fe4aa25574;hb=f9db9bf5fd8d5936be01802aa7f2825b4e386777;hp=750fd76e55bf5ef84b85d91038229ab75033fe44;hpb=6b87e706ebcc8fb45f8ae5b978b661f1912d1f9c;p=ardour.git diff --git a/libs/backends/coreaudio/coremidi_io.cc b/libs/backends/coreaudio/coremidi_io.cc index 750fd76e55..1d4f6fbf02 100644 --- a/libs/backends/coreaudio/coremidi_io.cc +++ b/libs/backends/coreaudio/coremidi_io.cc @@ -16,30 +16,124 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include "coremidi_io.h" +#include #include +#include "coremidi_io.h" +#include "coreaudio_backend.h" + +using namespace ARDOUR; + +#ifndef NDEBUG +static int _debug_mode = 0; +#endif + + +/** + * MIDI Data flow + * + * (A) INPUT (incoming from outside the application) + * + * - midiInputCallback (in its own thread, async WRT process callback): + * takes OS X MIDIPacket, copies into lock-free ringbuffer + * + * - processCallback (in its own thread): + * + * (1) loop on all input ports: + * 1A) call recv_event() to read from ringbuffer into stack buffer, also assign-timestamp, + * 1B) call parse_events() using stack buffer, when appropriate + * pushes CoreMidiEvent into std::vector + * + * (2) in MidiPort::cycle_start() (also part of the process callback call tree), MidiPort::get_midi_buffer() + * calls CoreAudioBackend::midi_event_get () returns a pointer to the data of the specified CoreMidiEvent + */ + static void notifyProc (const MIDINotification *message, void *refCon) { CoreMidiIo *self = static_cast(refCon); self->notify_proc(message); } +#ifndef NDEBUG +static void print_packet (const MIDIPacket *p) { + fprintf (stderr, "CoreMIDI: Packet %d bytes [ ", p->length); + for (int bb = 0; bb < p->length; ++bb) { + fprintf (stderr, "%02x ", ((const uint8_t*)p->data)[bb]); + } + fprintf (stderr, "]\n"); +} + +static void dump_packet_list (const UInt32 numPackets, MIDIPacket const *p) { + for (UInt32 i = 0; i < numPackets; ++i) { + print_packet (p); + p = MIDIPacketNext (p); + } +} +#endif + static void midiInputCallback(const MIDIPacketList *list, void *procRef, void *srcRef) { - // TODO skip while freewheeling + CoreMidiIo *self = static_cast (procRef); + if (!self || !self->enabled()) { + // skip while freewheeling +#ifndef NDEBUG + if (_debug_mode & 2) { + fprintf (stderr, "Ignored Midi Packet while freewheeling:\n"); + dump_packet_list (list->numPackets, &list->packet[0]); + } +#endif + return; + } RingBuffer * rb = static_cast *> (srcRef); - if (!rb) return; - for (UInt32 i = 0; i < list->numPackets; i++) { - const MIDIPacket *packet = &list->packet[i]; - if (rb->write_space() < sizeof(MIDIPacket)) { - fprintf(stderr, "CoreMIDI: dropped MIDI event\n"); - continue; + if (!rb) { +#ifndef NDEBUG + if (_debug_mode & 4) { + fprintf (stderr, "Ignored Midi Packet - no ringbuffer:\n"); + dump_packet_list (list->numPackets, &list->packet[0]); } - rb->write((uint8_t*)packet, sizeof(MIDIPacket)); +#endif + return; } + MIDIPacket const *p = &list->packet[0]; + for (UInt32 i = 0; i < list->numPackets; ++i) { + uint32_t len = ((p->length + 3)&~3) + sizeof(MIDITimeStamp) + sizeof(UInt16); +#ifndef NDEBUG + if (_debug_mode & 1) { + print_packet (p); + } +#endif + if (rb->write_space() > sizeof(uint32_t) + len) { + rb->write ((uint8_t*)&len, sizeof(uint32_t)); + rb->write ((uint8_t*)p, len); + } +#ifndef NDEBUG + else { + fprintf (stderr, "CoreMIDI: dropped MIDI event\n"); + } +#endif + p = MIDIPacketNext (p); + } +} + +static std::string getPropertyString (MIDIObjectRef object, CFStringRef key) +{ + CFStringRef name = NULL; + std::string rv = ""; + if (noErr == MIDIObjectGetStringProperty(object, key, &name)) { + const CFIndex size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(name), kCFStringEncodingUTF8); + char *tmp = (char*) malloc(size); + if (CFStringGetCString(name, tmp, size, kCFStringEncodingUTF8)) { + rv = tmp; + } + free(tmp); + CFRelease(name); + } + return rv; } +static std::string getDisplayName (MIDIObjectRef object) { + return getPropertyString(object, kMIDIPropertyDisplayName); +} -CoreMidiIo::CoreMidiIo() +CoreMidiIo::CoreMidiIo() : _midi_client (0) , _input_endpoints (0) , _output_endpoints (0) @@ -51,25 +145,33 @@ 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); +#ifndef NDEBUG + const char *p = getenv ("COREMIDIDEBUG"); + if (p && *p) _debug_mode = atoi (p); +#endif } -CoreMidiIo::~CoreMidiIo() +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 -CoreMidiIo::cleanup() +CoreMidiIo::cleanup() { _active = false; for (uint32_t i = 0 ; i < _n_midi_in ; ++i) { @@ -93,13 +195,13 @@ CoreMidiIo::cleanup() } void -CoreMidiIo::start_cycle() +CoreMidiIo::start_cycle() { _time_at_cycle_start = AudioGetCurrentHostTime(); } void -CoreMidiIo::notify_proc(const MIDINotification *message) +CoreMidiIo::notify_proc(const MIDINotification *message) { switch(message->messageID) { case kMIDIMsgSetupChanged: @@ -146,11 +248,17 @@ CoreMidiIo::recv_event (uint32_t port, double cycle_time_us, uint64_t &time, uin } assert(port < _n_midi_in); - while (_rb[port]->read_space() >= sizeof(MIDIPacket)) { + const size_t minsize = 1 + sizeof(uint32_t) + sizeof(MIDITimeStamp) + sizeof(UInt16); + + while (_rb[port]->read_space() > minsize) { MIDIPacket packet; - size_t rv = _rb[port]->read((uint8_t*)&packet, sizeof(MIDIPacket)); - assert(rv == sizeof(MIDIPacket)); - _input_queue[port].push_back(boost::shared_ptr(new _CoreMIDIPacket (&packet))); + size_t rv; + uint32_t s = 0; + rv = _rb[port]->read((uint8_t*)&s, sizeof(uint32_t)); + assert(rv == sizeof(uint32_t)); + rv = _rb[port]->read((uint8_t*)&packet, s); + assert(rv == s); + _input_queue[port].push_back(boost::shared_ptr(new _CoreMIDIPacket (&packet))); } UInt64 start = _time_at_cycle_start; @@ -160,10 +268,24 @@ CoreMidiIo::recv_event (uint32_t port, double cycle_time_us, uint64_t &time, uin if ((*it)->timeStamp < end) { if ((*it)->timeStamp < start) { uint64_t dt = AudioConvertHostTimeToNanos(start - (*it)->timeStamp); - //printf("Stale Midi Event dt:%.2fms\n", dt * 1e-6); - if (dt > 1e-4) { // 100ms, maybe too large + if (dt > 1e7 && (*it)->timeStamp != 0) { // 10ms slack and a timestamp is given +#ifndef NDEBUG + printf("Dropped Stale Midi Event. dt:%.2fms\n", dt * 1e-6); +#endif it = _input_queue[port].erase(it); continue; + } else { + /* events without a valid timestamp, or events that arrived + * less than 10ms in the past are allowed and + * queued at the beginning of the cycle: + * time (relative to cycle start) = 0 + * + * The latter use needed for the "Avid Artist" Control Surface + * the OSX driver sends no timestamps. + */ +#if 0 + printf("Stale Midi Event. dt:%.2fms\n", dt * 1e-6); +#endif } time = 0; } else { @@ -182,6 +304,40 @@ CoreMidiIo::recv_event (uint32_t port, double cycle_time_us, uint64_t &time, uin return 0; } +int +CoreMidiIo::send_events (uint32_t port, double timescale, const void *b) +{ + if (!_active || _time_at_cycle_start == 0) { + return 0; + } + + assert(port < _n_midi_out); + const UInt64 ts = AudioConvertHostTimeToNanos(_time_at_cycle_start); + + const CoreMidiBuffer *src = static_cast(b); + + int32_t bytes[8192]; // int for alignment + MIDIPacketList *mpl = (MIDIPacketList*)bytes; + MIDIPacket *cur = MIDIPacketListInit(mpl); + + for (CoreMidiBuffer::const_iterator mit = src->begin (); mit != src->end (); ++mit) { + assert((*mit)->size() < 256); + cur = MIDIPacketListAdd(mpl, sizeof(bytes), cur, + AudioConvertNanosToHostTime(ts + (*mit)->timestamp() / timescale), + (*mit)->size(), (*mit)->data()); + if (!cur) { +#ifndef DEBUG + printf("CoreMidi: Packet list overflow, dropped events\n"); +#endif + break; + } + } + if (mpl->numPackets > 0) { + MIDISend(_output_ports[port], _output_endpoints[port], mpl); + } + return 0; +} + int CoreMidiIo::send_event (uint32_t port, double reltime_us, const uint8_t *d, const size_t s) { @@ -208,12 +364,84 @@ CoreMidiIo::send_event (uint32_t port, double reltime_us, const uint8_t *d, cons return 0; } + +std::string +CoreMidiIo::port_id (uint32_t port, bool input) +{ + std::stringstream ss; + if (input) { + ss << "system:midi_capture_"; + SInt32 id; + if (noErr == MIDIObjectGetIntegerProperty(_input_endpoints[port], kMIDIPropertyUniqueID, &id)) { + ss << (unsigned int)id; + } else { + ss << port; + } + } else { + ss << "system:midi_playback_"; + SInt32 id; + if (noErr == MIDIObjectGetIntegerProperty(_output_endpoints[port], kMIDIPropertyUniqueID, &id)) { + ss << (unsigned int)id; + } else { + ss << port; + } + } + return ss.str(); +} + +std::string +CoreMidiIo::port_name (uint32_t port, bool input) +{ + if (input) { + if (port < _n_midi_in) { + return getDisplayName(_input_endpoints[port]); + } + } else { + if (port < _n_midi_out) { + return getDisplayName(_output_endpoints[port]); + } + } + return ""; +} + +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::discover() +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 +} - assert(!_active && _midi_client); +void +CoreMidiIo::discover() +{ + if (!_run || !_midi_client) { return; } + + if (pthread_mutex_trylock (&_discovery_lock)) { + return; + } + + cleanup(); ItemCount srcCount = MIDIGetNumberOfSources(); ItemCount dstCount = MIDIGetNumberOfDestinations(); @@ -232,17 +460,20 @@ 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(1024 * sizeof(MIDIPacket)); + _rb[_n_midi_in] = new RingBuffer(32768); _input_queue[_n_midi_in] = CoreMIDIQueue(); MIDIPortConnectSource(_input_ports[_n_midi_in], src, (void*) _rb[_n_midi_in]); CFRelease(port_name); @@ -259,10 +490,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 +508,5 @@ CoreMidiIo::discover() } _active = true; + pthread_mutex_unlock (&_discovery_lock); }