/* Copyright (C) 2009 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 "JackCoreMidiDriver.h" #include "JackGraphManager.h" #include "JackServer.h" #include "JackEngineControl.h" #include "JackDriverLoader.h" #include #include #include #include #include namespace Jack { static MIDITimeStamp MIDIGetCurrentHostTime() { return mach_absolute_time(); } void JackCoreMidiDriver::ReadProcAux(const MIDIPacketList *pktlist, jack_ringbuffer_t* ringbuffer) { // Write the number of packets size_t size = jack_ringbuffer_write(ringbuffer, (char*)&pktlist->numPackets, sizeof(UInt32)); if (size != sizeof(UInt32)) { jack_error("ReadProc : ring buffer is full, skip events..."); return; } for (unsigned int i = 0; i < pktlist->numPackets; ++i) { MIDIPacket *packet = (MIDIPacket *)pktlist->packet; // TODO : use timestamp // Check available size first.. size = jack_ringbuffer_write_space(ringbuffer); if (size < (sizeof(UInt16) + packet->length)) { jack_error("ReadProc : ring buffer is full, skip events..."); return; } // Write length of each packet first jack_ringbuffer_write(ringbuffer, (char*)&packet->length, sizeof(UInt16)); // Write event actual data jack_ringbuffer_write(ringbuffer, (char*)packet->data, packet->length); packet = MIDIPacketNext(packet); } } void JackCoreMidiDriver::ReadProc(const MIDIPacketList *pktlist, void *refCon, void *connRefCon) { jack_ringbuffer_t* ringbuffer = (jack_ringbuffer_t*)connRefCon; ReadProcAux(pktlist, ringbuffer); } void JackCoreMidiDriver::ReadVirtualProc(const MIDIPacketList *pktlist, void *refCon, void *connRefCon) { jack_ringbuffer_t* ringbuffer = (jack_ringbuffer_t*)refCon; ReadProcAux(pktlist, ringbuffer); } void JackCoreMidiDriver::NotifyProc(const MIDINotification *message, void *refCon) { jack_info("NotifyProc %d", message->messageID); } JackCoreMidiDriver::JackCoreMidiDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table) : JackMidiDriver(name, alias, engine, table), fMidiClient(NULL), fInputPort(NULL), fOutputPort(NULL), fRealCaptureChannels(0), fRealPlaybackChannels(0) {} JackCoreMidiDriver::~JackCoreMidiDriver() {} int JackCoreMidiDriver::Open(bool capturing, bool playing, int inchannels, int outchannels, bool monitor, const char* capture_driver_name, const char* playback_driver_name, jack_nframes_t capture_latency, jack_nframes_t playback_latency) { OSStatus err; CFStringRef coutputStr; std::string str; // Get real input/output number fRealCaptureChannels = MIDIGetNumberOfSources(); fRealPlaybackChannels = MIDIGetNumberOfDestinations(); // Generic JackMidiDriver Open if (JackMidiDriver::Open(capturing, playing, inchannels + fRealCaptureChannels, outchannels + fRealPlaybackChannels, monitor, capture_driver_name, playback_driver_name, capture_latency, playback_latency) != 0) return -1; coutputStr = CFStringCreateWithCString(0, "JackMidi", CFStringGetSystemEncoding()); err = MIDIClientCreate(coutputStr, NotifyProc, this, &fMidiClient); CFRelease(coutputStr); if (!fMidiClient) { jack_error("Cannot create CoreMidi client"); goto error; } err = MIDIInputPortCreate(fMidiClient, CFSTR("Input port"), ReadProc, this, &fInputPort); if (!fInputPort) { jack_error("Cannot open CoreMidi in port\n"); goto error; } err = MIDIOutputPortCreate(fMidiClient, CFSTR("Output port"), &fOutputPort); if (!fOutputPort) { jack_error("Cannot open CoreMidi out port\n"); goto error; } fMidiDestination = new MIDIEndpointRef[inchannels + fRealCaptureChannels]; assert(fMidiDestination); // Virtual input for (int i = 0; i < inchannels; i++) { std::stringstream num; num << i; str = "JackMidi" + num.str(); coutputStr = CFStringCreateWithCString(0, str.c_str(), CFStringGetSystemEncoding()); err = MIDIDestinationCreate(fMidiClient, coutputStr, ReadVirtualProc, fRingBuffer[i], &fMidiDestination[i]); CFRelease(coutputStr); if (!fMidiDestination[i]) { jack_error("Cannot create CoreMidi destination"); goto error; } } // Real input for (int i = 0; i < fRealCaptureChannels; i++) { fMidiDestination[i + inchannels] = MIDIGetSource(i); MIDIPortConnectSource(fInputPort, fMidiDestination[i + inchannels], fRingBuffer[i + inchannels]); } fMidiSource = new MIDIEndpointRef[outchannels + fRealPlaybackChannels]; assert(fMidiSource); // Virtual output for (int i = 0; i < outchannels; i++) { std::stringstream num; num << i; str = "JackMidi" + num.str(); coutputStr = CFStringCreateWithCString(0, str.c_str(), CFStringGetSystemEncoding()); err = MIDISourceCreate(fMidiClient, coutputStr, &fMidiSource[i]); CFRelease(coutputStr); if (!fMidiSource[i]) { jack_error("Cannot create CoreMidi source"); goto error; } } // Real output for (int i = 0; i < fRealPlaybackChannels; i++) { fMidiSource[i + outchannels] = MIDIGetDestination(i); } return 0; error: Close(); return -1; } int JackCoreMidiDriver::Close() { if (fInputPort) MIDIPortDispose(fInputPort); if (fOutputPort) MIDIPortDispose(fOutputPort); // Only dispose "virtual" endpoints for (int i = 0; i < fCaptureChannels - fRealCaptureChannels; i++) { if (fMidiDestination) MIDIEndpointDispose(fMidiDestination[i]); } delete[] fMidiDestination; // Only dispose "virtual" endpoints for (int i = 0; i < fPlaybackChannels - fRealPlaybackChannels; i++) { if (fMidiSource[i]) MIDIEndpointDispose(fMidiSource[i]); } delete[] fMidiSource; if (fMidiClient) MIDIClientDispose(fMidiClient); return 0; } int JackCoreMidiDriver::Attach() { OSStatus err; JackPort* port; CFStringRef pname; jack_port_id_t port_index; char name[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE]; char endpoint_name[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE]; char alias[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE]; int i; jack_log("JackCoreMidiDriver::Attach fBufferSize = %ld fSampleRate = %ld", fEngineControl->fBufferSize, fEngineControl->fSampleRate); for (i = 0; i < fCaptureChannels; i++) { err = MIDIObjectGetStringProperty(fMidiDestination[i], kMIDIPropertyName, &pname); if (err == noErr) { CFStringGetCString(pname, endpoint_name, sizeof(endpoint_name), 0); CFRelease(pname); snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", fAliasName, endpoint_name, i + 1); } else { snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", fAliasName, fCaptureDriverName, i + 1); } snprintf(name, sizeof(name) - 1, "%s:capture_%d", fClientControl.fName, i + 1); if ((port_index = fGraphManager->AllocatePort(fClientControl.fRefNum, name, JACK_DEFAULT_MIDI_TYPE, CaptureDriverFlags, fEngineControl->fBufferSize)) == NO_PORT) { jack_error("driver: cannot register port for %s", name); return -1; } port = fGraphManager->GetPort(port_index); port->SetAlias(alias); fCapturePortList[i] = port_index; jack_log("JackCoreMidiDriver::Attach fCapturePortList[i] port_index = %ld", port_index); } for (i = 0; i < fPlaybackChannels; i++) { err = MIDIObjectGetStringProperty(fMidiSource[i], kMIDIPropertyName, &pname); if (err == noErr) { CFStringGetCString(pname, endpoint_name, sizeof(endpoint_name), 0); CFRelease(pname); snprintf(alias, sizeof(alias) - 1, "%s:%s:in%d", fAliasName, endpoint_name, i + 1); } else { snprintf(alias, sizeof(alias) - 1, "%s:%s:in%d", fAliasName, fPlaybackDriverName, i + 1); } snprintf(name, sizeof(name) - 1, "%s:playback_%d", fClientControl.fName, i + 1); if ((port_index = fGraphManager->AllocatePort(fClientControl.fRefNum, name, JACK_DEFAULT_MIDI_TYPE, PlaybackDriverFlags, fEngineControl->fBufferSize)) == NO_PORT) { jack_error("driver: cannot register port for %s", name); return -1; } port = fGraphManager->GetPort(port_index); port->SetAlias(alias); fPlaybackPortList[i] = port_index; jack_log("JackCoreMidiDriver::Attach fPlaybackPortList[i] port_index = %ld", port_index); } return 0; } int JackCoreMidiDriver::Read() { for (int chan = 0; chan < fCaptureChannels; chan++) { if (fGraphManager->GetConnectionsNum(fCapturePortList[chan]) > 0) { // Get JACK port JackMidiBuffer* midi_buffer = GetInputBuffer(chan); if (jack_ringbuffer_read_space(fRingBuffer[chan]) == 0) { // Reset buffer midi_buffer->Reset(midi_buffer->nframes); } else { while (jack_ringbuffer_read_space(fRingBuffer[chan]) > 0) { // Read event number int ev_count = 0; jack_ringbuffer_read(fRingBuffer[chan], (char*)&ev_count, sizeof(int)); for (int j = 0; j < ev_count; j++) { // Read event length UInt16 event_len; jack_ringbuffer_read(fRingBuffer[chan], (char*)&event_len, sizeof(UInt16)); // Read event actual data jack_midi_data_t* dest = midi_buffer->ReserveEvent(0, event_len); jack_ringbuffer_read(fRingBuffer[chan], (char*)dest, event_len); } } } } else { // Consume ring buffer jack_ringbuffer_read_advance(fRingBuffer[chan], jack_ringbuffer_read_space(fRingBuffer[chan])); } } return 0; } int JackCoreMidiDriver::Write() { MIDIPacketList* pktlist = (MIDIPacketList*)fMIDIBuffer; for (int chan = 0; chan < fPlaybackChannels; chan++) { if (fGraphManager->GetConnectionsNum(fPlaybackPortList[chan]) > 0) { MIDIPacket* packet = MIDIPacketListInit(pktlist); JackMidiBuffer* midi_buffer = GetOutputBuffer(chan); // TODO : use timestamp for (unsigned int j = 0; j < midi_buffer->event_count; j++) { JackMidiEvent* ev = &midi_buffer->events[j]; packet = MIDIPacketListAdd(pktlist, sizeof(fMIDIBuffer), packet, MIDIGetCurrentHostTime(), ev->size, ev->GetData(midi_buffer)); } if (packet) { if (chan < fPlaybackChannels - fRealPlaybackChannels) { OSStatus err = MIDIReceived(fMidiSource[chan], pktlist); if (err != noErr) jack_error("MIDIReceived error"); } else { OSStatus err = MIDISend(fOutputPort, fMidiSource[chan], pktlist); if (err != noErr) jack_error("MIDISend error"); } } } } return 0; } } // end of namespace #ifdef __cplusplus extern "C" { #endif SERVER_EXPORT jack_driver_desc_t * driver_get_descriptor() { jack_driver_desc_t * desc; unsigned int i; desc = (jack_driver_desc_t*)calloc (1, sizeof (jack_driver_desc_t)); strcpy(desc->name, "coremidi"); // size MUST be less then JACK_DRIVER_NAME_MAX + 1 strcpy(desc->desc, "Apple CoreMIDI API based MIDI backend"); // size MUST be less then JACK_DRIVER_PARAM_DESC + 1 desc->nparams = 2; desc->params = (jack_driver_param_desc_t*)calloc (desc->nparams, sizeof (jack_driver_param_desc_t)); i = 0; strcpy(desc->params[i].name, "inchannels"); desc->params[i].character = 'i'; desc->params[i].type = JackDriverParamInt; desc->params[i].value.ui = 0; strcpy(desc->params[i].short_desc, "CoreMIDI virtual bus"); strcpy(desc->params[i].long_desc, desc->params[i].short_desc); i++; strcpy(desc->params[i].name, "outchannels"); desc->params[i].character = 'o'; desc->params[i].type = JackDriverParamInt; desc->params[i].value.ui = 0; strcpy(desc->params[i].short_desc, "CoreMIDI virtual bus"); strcpy(desc->params[i].long_desc, desc->params[i].short_desc); return desc; } SERVER_EXPORT Jack::JackDriverClientInterface* driver_initialize(Jack::JackLockedEngine* engine, Jack::JackSynchro* table, const JSList* params) { const JSList * node; const jack_driver_param_t * param; int virtual_in = 0; int virtual_out = 0; for (node = params; node; node = jack_slist_next (node)) { param = (const jack_driver_param_t *) node->data; switch (param->character) { case 'i': virtual_in = param->value.ui; break; case 'o': virtual_out = param->value.ui; break; } } Jack::JackDriverClientInterface* driver = new Jack::JackCoreMidiDriver("system_midi", "coremidi", engine, table); if (driver->Open(1, 1, virtual_in, virtual_out, false, "in", "out", 0, 0) == 0) { return driver; } else { delete driver; return NULL; } } #ifdef __cplusplus } #endif