/* * Caitlib * Copyright (C) 2007 Nedko Arnaudov * Copyright (C) 2012 Filipe Coelho * * 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 * 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. * * For a full copy of the GNU General Public License see the COPYING file */ #include #include #include #include #include #include #include #include #include "caitlib.h" #include "list.h" #include "memory_atomic.h" #define MAX_EVENT_DATA_SIZE 100 #define MIN_PREALLOCATED_EVENT_COUNT 100 #define MAX_PREALLOCATED_EVENT_COUNT 1000 //#define USE_LIST_HEAD_OUTS // incomplete // ------------------------------------------------------------------------------------------ typedef struct list_head ListHead; typedef pthread_mutex_t Mutex; typedef struct _RawMidiEvent { ListHead siblings; jack_midi_data_t data[MAX_EVENT_DATA_SIZE]; size_t dataSize; jack_nframes_t time; double value; // used for special events } RawMidiEvent; typedef struct _CaitlibOutPort { #ifdef USE_LIST_HEAD_OUTS ListHead siblings; #endif uint32_t id; ListHead queue; jack_port_t* port; } CaitlibOutPort; typedef struct _CaitlibInstance { bool doProcess, wantEvents; jack_client_t* client; jack_port_t* midiIn; #ifdef USE_LIST_HEAD_OUTS ListHead outPorts; #else uint32_t midiOutCount; CaitlibOutPort** midiOuts; #endif ListHead inQueue; ListHead inQueuePendingRT; Mutex mutex; rtsafe_memory_pool_handle mempool; } CaitlibInstance; // ------------------------------------------------------------------------------------------ #define handlePtr ((CaitlibInstance*)ptr) static int jack_process(jack_nframes_t nframes, void* ptr) { if (! handlePtr->doProcess) return 0; void* portBuffer; RawMidiEvent* eventPtr; jack_position_t transportPos; jack_transport_state_t transportState = jack_transport_query(handlePtr->client, &transportPos); if (transportPos.unique_1 != transportPos.unique_2) transportPos.frame = 0; // MIDI In if (handlePtr->wantEvents) { jack_midi_event_t inEvent; jack_nframes_t inEventCount; portBuffer = jack_port_get_buffer(handlePtr->midiIn, nframes); inEventCount = jack_midi_get_event_count(portBuffer); for (jack_nframes_t i = 0 ; i < inEventCount; i++) { if (jack_midi_event_get(&inEvent, portBuffer, i) != 0) break; if (inEvent.size > MAX_EVENT_DATA_SIZE) continue; /* allocate memory for buffer copy */ eventPtr = rtsafe_memory_pool_allocate(handlePtr->mempool); if (eventPtr == NULL) { //LOG_ERROR("Ignored midi event with size %u because memory allocation failed.", (unsigned int)inEvent.size); continue; } /* copy buffer data */ memcpy(eventPtr->data, inEvent.buffer, inEvent.size); eventPtr->dataSize = inEvent.size; eventPtr->time = transportPos.frame + inEvent.time; /* Add event buffer to inQueuePendingRT list */ list_add(&eventPtr->siblings, &handlePtr->inQueuePendingRT); } } if (pthread_mutex_trylock(&handlePtr->mutex) != 0) return 0; if (handlePtr->wantEvents) list_splice_init(&handlePtr->inQueuePendingRT, &handlePtr->inQueue); #ifdef USE_LIST_HEAD_OUTS if (transportState == JackTransportRolling) { } #else // MIDI Out if (transportState == JackTransportRolling && handlePtr->midiOutCount > 0) { ListHead* entryPtr; CaitlibOutPort* outPortPtr; for (uint32_t i = 0; i < handlePtr->midiOutCount; i++) { outPortPtr = handlePtr->midiOuts[i]; portBuffer = jack_port_get_buffer(outPortPtr->port, nframes); jack_midi_clear_buffer(portBuffer); list_for_each(entryPtr, &outPortPtr->queue) { eventPtr = list_entry(entryPtr, RawMidiEvent, siblings); if (transportPos.frame > eventPtr->time || transportPos.frame + nframes <= eventPtr->time) continue; if (jack_midi_event_write(portBuffer, eventPtr->time - transportPos.frame, eventPtr->data, eventPtr->dataSize) != 0) break; } } } #endif pthread_mutex_unlock(&handlePtr->mutex); return 0; } #undef handlePtr // ------------------------------------------------------------------------------------------ // Initialization CaitlibHandle caitlib_init(const char* instanceName) { CaitlibInstance* handlePtr = (CaitlibInstance*)malloc(sizeof(CaitlibInstance)); if (handlePtr == NULL) goto fail; handlePtr->doProcess = true; handlePtr->wantEvents = false; #ifdef USE_LIST_HEAD_OUTS INIT_LIST_HEAD(&handlePtr->outPorts); #else handlePtr->midiOuts = NULL; handlePtr->midiOutCount = 0; #endif INIT_LIST_HEAD(&handlePtr->inQueue); INIT_LIST_HEAD(&handlePtr->inQueuePendingRT); if (! rtsafe_memory_pool_create(sizeof(RawMidiEvent), MIN_PREALLOCATED_EVENT_COUNT, MAX_PREALLOCATED_EVENT_COUNT, &handlePtr->mempool)) goto fail_free; pthread_mutex_init(&handlePtr->mutex, NULL); handlePtr->client = jack_client_open(instanceName, JackNullOption, 0); if (handlePtr->client == NULL) goto fail_destroyMutex; handlePtr->midiIn = jack_port_register(handlePtr->client, "midi-in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); if (handlePtr->midiIn == NULL) goto fail_closeJack; if (jack_set_process_callback(handlePtr->client, jack_process, handlePtr) != 0) goto fail_closeJack; if (jack_activate(handlePtr->client) != 0) goto fail_closeJack; return handlePtr; fail_closeJack: jack_client_close(handlePtr->client); fail_destroyMutex: pthread_mutex_destroy(&handlePtr->mutex); //fail_destroy_pool: rtsafe_memory_pool_destroy(handlePtr->mempool); fail_free: free(handlePtr); fail: return NULL; } #define handlePtr ((CaitlibInstance*)handle) void caitlib_close(CaitlibHandle handle) { // wait for jack processing to end handlePtr->doProcess = false; pthread_mutex_lock(&handlePtr->mutex); if (handlePtr->client) { #ifdef USE_LIST_HEAD_OUTS ListHead* entryPtr; CaitlibOutPort* outPortPtr; #endif jack_deactivate(handlePtr->client); jack_port_unregister(handlePtr->client, handlePtr->midiIn); #ifdef USE_LIST_HEAD_OUTS list_for_each(entryPtr, &handlePtr->outPorts) { outPortPtr = list_entry(entryPtr, CaitlibOutPort, siblings); jack_port_unregister(handlePtr->client, outPortPtr->port); } #else for (uint32_t i = 0; i < handlePtr->midiOutCount; i++) { jack_port_unregister(handlePtr->client, handlePtr->midiOuts[i]->port); free(handlePtr->midiOuts[i]); } #endif jack_client_close(handlePtr->client); } pthread_mutex_unlock(&handlePtr->mutex); pthread_mutex_destroy(&handlePtr->mutex); rtsafe_memory_pool_destroy(handlePtr->mempool); free(handlePtr); } uint32_t caitlib_create_port(CaitlibHandle handle, const char* portName) { uint32_t nextId = 0; // wait for jack processing to end handlePtr->doProcess = false; pthread_mutex_lock(&handlePtr->mutex); // re-allocate pointers (midiOutCount + 1) and find next available ID { #ifdef USE_LIST_HEAD_OUTS ListHead* entryPtr; CaitlibOutPort* outPortPtr; list_for_each(entryPtr, &handlePtr->outPorts) { outPortPtr = list_entry(entryPtr, CaitlibOutPort, siblings); if (outPortPtr->id == nextId) { nextId++; continue; } } #else CaitlibOutPort* oldMidiOuts[handlePtr->midiOutCount]; for (uint32_t i = 0; i < handlePtr->midiOutCount; i++) { oldMidiOuts[i] = handlePtr->midiOuts[i]; if (handlePtr->midiOuts[i]->id == nextId) nextId++; } if (handlePtr->midiOuts) free(handlePtr->midiOuts); handlePtr->midiOuts = (CaitlibOutPort**)malloc(sizeof(CaitlibOutPort*) * (handlePtr->midiOutCount+1)); for (uint32_t i = 0; i < handlePtr->midiOutCount; i++) handlePtr->midiOuts[i] = oldMidiOuts[i]; #endif } // we can continue normal operation now pthread_mutex_unlock(&handlePtr->mutex); handlePtr->doProcess = true; #ifdef USE_LIST_HEAD_OUTS #else // create new port { CaitlibOutPort* newPort = (CaitlibOutPort*)malloc(sizeof(CaitlibOutPort)); newPort->id = nextId; newPort->port = jack_port_register(handlePtr->client, portName, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); INIT_LIST_HEAD(&newPort->queue); handlePtr->midiOuts[handlePtr->midiOutCount++] = newPort; } #endif return nextId; } void caitlib_destroy_port(CaitlibHandle handle, uint32_t port) { // wait for jack processing to end handlePtr->doProcess = false; pthread_mutex_lock(&handlePtr->mutex); #ifdef USE_LIST_HEAD_OUTS #else // re-allocate pointers (midiOutCount - 1) { CaitlibOutPort* oldMidiOuts[handlePtr->midiOutCount]; for (uint32_t i = 0; i < handlePtr->midiOutCount; i++) oldMidiOuts[i] = handlePtr->midiOuts[i]; if (handlePtr->midiOuts) free(handlePtr->midiOuts); if (handlePtr->midiOutCount == 1) { handlePtr->midiOuts = NULL; } else { handlePtr->midiOuts = (CaitlibOutPort**)malloc(sizeof(CaitlibOutPort*) * (handlePtr->midiOutCount-1)); for (uint32_t i = 0, j = 0; i < handlePtr->midiOutCount; i++) { if (oldMidiOuts[i]->id != port) handlePtr->midiOuts[j++] = oldMidiOuts[i]; } } handlePtr->midiOutCount--; } #endif pthread_mutex_unlock(&handlePtr->mutex); handlePtr->doProcess = true; return; (void)port; } // ------------------------------------------------------------------------------------------ // Input void caitlib_want_events(CaitlibHandle handle, bool yesNo) { pthread_mutex_lock(&handlePtr->mutex); handlePtr->wantEvents = yesNo; pthread_mutex_unlock(&handlePtr->mutex); } MidiEvent* caitlib_get_event(CaitlibHandle handle) { static MidiEvent midiEvent; ListHead* nodePtr; RawMidiEvent* rawEventPtr; pthread_mutex_lock(&handlePtr->mutex); if (list_empty(&handlePtr->inQueue)) { pthread_mutex_unlock(&handlePtr->mutex); return NULL; } nodePtr = handlePtr->inQueue.prev; list_del(nodePtr); rawEventPtr = list_entry(nodePtr, RawMidiEvent, siblings); pthread_mutex_unlock(&handlePtr->mutex); midiEvent.channel = rawEventPtr->data[0] & 0x0F; midiEvent.time = rawEventPtr->time; // note off if (rawEventPtr->dataSize == 3 && (rawEventPtr->data[0] & 0xF0) == MIDI_EVENT_TYPE_NOTE_OFF) { midiEvent.type = MIDI_EVENT_TYPE_NOTE_OFF; midiEvent.data.note.note = rawEventPtr->data[1]; midiEvent.data.note.velocity = rawEventPtr->data[2]; } // note on else if (rawEventPtr->dataSize == 3 && (rawEventPtr->data[0] & 0xF0) == MIDI_EVENT_TYPE_NOTE_ON) { midiEvent.type = MIDI_EVENT_TYPE_NOTE_ON; midiEvent.data.note.note = rawEventPtr->data[1]; midiEvent.data.note.velocity = rawEventPtr->data[2]; } // aftertouch else if (rawEventPtr->dataSize == 3 && (rawEventPtr->data[0] & 0xF0) == MIDI_EVENT_TYPE_AFTER_TOUCH) { midiEvent.type = MIDI_EVENT_TYPE_AFTER_TOUCH; midiEvent.data.note.note = rawEventPtr->data[1]; midiEvent.data.note.velocity = rawEventPtr->data[2]; } // control else if (rawEventPtr->dataSize == 3 && (rawEventPtr->data[0] & 0xF0) == MIDI_EVENT_TYPE_CONTROL) { midiEvent.type = MIDI_EVENT_TYPE_CONTROL; midiEvent.data.control.controller = rawEventPtr->data[1]; midiEvent.data.control.value = rawEventPtr->data[2]; } // program else if (rawEventPtr->dataSize == 2 && (rawEventPtr->data[0] & 0xF0) == MIDI_EVENT_TYPE_PROGRAM) { midiEvent.type = MIDI_EVENT_TYPE_PROGRAM; midiEvent.data.program.value = rawEventPtr->data[1]; } // channel pressure else if (rawEventPtr->dataSize == 2 && (rawEventPtr->data[0] & 0xF0) == MIDI_EVENT_TYPE_CHANNEL_PRESSURE) { midiEvent.type = MIDI_EVENT_TYPE_CHANNEL_PRESSURE; midiEvent.data.pressure.value = rawEventPtr->data[1]; } // pitch wheel else if (rawEventPtr->dataSize == 3 && (rawEventPtr->data[0] & 0xF0) == MIDI_EVENT_TYPE_PITCH_WHEEL) { midiEvent.type = MIDI_EVENT_TYPE_PITCH_WHEEL; midiEvent.data.pitchwheel.value = ((rawEventPtr->data[2] << 7) | rawEventPtr->data[1]) - 8192; } else { midiEvent.type = MIDI_EVENT_TYPE_NULL; } rtsafe_memory_pool_deallocate(handlePtr->mempool, rawEventPtr); return &midiEvent; } // ------------------------------------------------------------------------------------------ // Output (utils) void caitlib_midi_encode_control(RawMidiEvent* eventPtr, uint8_t channel, uint8_t control, uint8_t value) { eventPtr->data[0] = MIDI_EVENT_TYPE_CONTROL | (channel & 0x0F); eventPtr->data[1] = control; eventPtr->data[2] = value; eventPtr->dataSize = 3; } void caitlib_midi_encode_note_on(RawMidiEvent* eventPtr, uint8_t channel, uint8_t note, uint8_t velocity) { eventPtr->data[0] = MIDI_EVENT_TYPE_NOTE_ON | (channel & 0x0F); eventPtr->data[1] = note; eventPtr->data[2] = velocity; eventPtr->dataSize = 3; } void caitlib_midi_encode_note_off(RawMidiEvent* eventPtr, uint8_t channel, uint8_t note, uint8_t velocity) { eventPtr->data[0] = MIDI_EVENT_TYPE_NOTE_OFF | (channel & 0x0F); eventPtr->data[1] = note; eventPtr->data[2] = velocity; eventPtr->dataSize = 3; } void caitlib_midi_encode_aftertouch(RawMidiEvent* eventPtr, uint8_t channel, uint8_t note, uint8_t pressure) { eventPtr->data[0] = MIDI_EVENT_TYPE_AFTER_TOUCH | (channel & 0x0F); eventPtr->data[1] = note; eventPtr->data[2] = pressure; eventPtr->dataSize = 3; } void caitlib_midi_encode_channel_pressure(RawMidiEvent* eventPtr, uint8_t channel, uint8_t pressure) { eventPtr->data[0] = MIDI_EVENT_TYPE_CHANNEL_PRESSURE | (channel & 0x0F); eventPtr->data[1] = pressure; eventPtr->dataSize = 2; } void caitlib_midi_encode_program(RawMidiEvent* eventPtr, uint8_t channel, uint8_t program) { eventPtr->data[0] = MIDI_EVENT_TYPE_PROGRAM | (channel & 0x0F); eventPtr->data[1] = program; eventPtr->dataSize = 2; } void caitlib_midi_encode_pitchwheel(RawMidiEvent* eventPtr, uint8_t channel, int16_t pitchwheel) { pitchwheel += 8192; eventPtr->data[0] = MIDI_EVENT_TYPE_PITCH_WHEEL | (channel & 0x0F); eventPtr->data[1] = pitchwheel & 0x7F; eventPtr->data[2] = pitchwheel >> 7; eventPtr->dataSize = 3; } // ------------------------------------------------------------------------------------------ // Output void caitlib_put_event(CaitlibHandle handle, uint32_t port, const MidiEvent* event) { #ifdef USE_LIST_HEAD_OUTS ListHead* entryPtr; #endif RawMidiEvent* eventPtr; CaitlibOutPort* outPortPtr; bool portFound = false; eventPtr = rtsafe_memory_pool_allocate_sleepy(handlePtr->mempool); eventPtr->time = event->time; switch (event->type) { case MIDI_EVENT_TYPE_CONTROL: caitlib_midi_encode_control(eventPtr, event->channel, event->data.control.controller, event->data.control.value); break; case MIDI_EVENT_TYPE_NOTE_ON: caitlib_midi_encode_note_on(eventPtr, event->channel, event->data.note.note, event->data.note.velocity); break; case MIDI_EVENT_TYPE_NOTE_OFF: caitlib_midi_encode_note_off(eventPtr, event->channel, event->data.note.note, event->data.note.velocity); break; case MIDI_EVENT_TYPE_AFTER_TOUCH: caitlib_midi_encode_aftertouch(eventPtr, event->channel, event->data.note.note, event->data.note.velocity); break; case MIDI_EVENT_TYPE_CHANNEL_PRESSURE: caitlib_midi_encode_channel_pressure(eventPtr, event->channel, event->data.pressure.value); break; case MIDI_EVENT_TYPE_PROGRAM: caitlib_midi_encode_program(eventPtr, event->channel, event->data.program.value); break; case MIDI_EVENT_TYPE_PITCH_WHEEL: caitlib_midi_encode_pitchwheel(eventPtr, event->channel, event->data.pitchwheel.value); break; default: return; } pthread_mutex_lock(&handlePtr->mutex); #ifdef USE_LIST_HEAD_OUTS list_for_each(entryPtr, &handlePtr->outPorts) { outPortPtr = list_entry(entryPtr, CaitlibOutPort, siblings); if (outPortPtr->id == port) { list_add_tail(&eventPtr->siblings, &outPortPtr->queue); break; } } #else for (uint32_t i = 0; i < handlePtr->midiOutCount; i++) { outPortPtr = handlePtr->midiOuts[i]; if (outPortPtr->id == port) { portFound = true; list_add_tail(&eventPtr->siblings, &outPortPtr->queue); break; } } #endif if (! portFound) printf("caitlib_put_event(%p, %i, %p) - failed to find port", handle, port, event); pthread_mutex_unlock(&handlePtr->mutex); } void caitlib_put_control(CaitlibHandle handle, uint32_t port, uint32_t time, uint8_t channel, uint8_t control, uint8_t value) { MidiEvent event; event.type = MIDI_EVENT_TYPE_CONTROL; event.channel = channel; event.time = time; event.data.control.controller = control; event.data.control.value = value; caitlib_put_event(handle, port, &event); } void caitlib_put_note_on(CaitlibHandle handle, uint32_t port, uint32_t time, uint8_t channel, uint8_t note, uint8_t velocity) { MidiEvent event; event.type = MIDI_EVENT_TYPE_NOTE_ON; event.channel = channel; event.time = time; event.data.note.note = note; event.data.note.velocity = velocity; caitlib_put_event(handle, port, &event); } void caitlib_put_note_off(CaitlibHandle handle, uint32_t port, uint32_t time, uint8_t channel, uint8_t note, uint8_t velocity) { MidiEvent event; event.type = MIDI_EVENT_TYPE_NOTE_OFF; event.channel = channel; event.time = time; event.data.note.note = note; event.data.note.velocity = velocity; caitlib_put_event(handle, port, &event); } void caitlib_put_aftertouch(CaitlibHandle handle, uint32_t port, uint32_t time, uint8_t channel, uint8_t note, uint8_t pressure) { MidiEvent event; event.type = MIDI_EVENT_TYPE_AFTER_TOUCH; event.channel = channel; event.time = time; event.data.note.note = note; event.data.note.velocity = pressure; caitlib_put_event(handle, port, &event); } void caitlib_put_channel_pressure(CaitlibHandle handle, uint32_t port, uint32_t time, uint8_t channel, uint8_t pressure) { MidiEvent event; event.type = MIDI_EVENT_TYPE_CHANNEL_PRESSURE; event.channel = channel; event.time = time; event.data.pressure.value = pressure; caitlib_put_event(handle, port, &event); } void caitlib_put_program(CaitlibHandle handle, uint32_t port, uint32_t time, uint8_t channel, uint8_t program) { MidiEvent event; event.type = MIDI_EVENT_TYPE_PROGRAM; event.channel = channel; event.time = time; event.data.program.value = program; caitlib_put_event(handle, port, &event); } void caitlib_put_pitchwheel(CaitlibHandle handle, uint32_t port, uint32_t time, uint8_t channel, int16_t value) { MidiEvent event; event.type = MIDI_EVENT_TYPE_PITCH_WHEEL; event.channel = channel; event.time = time; event.data.pitchwheel.value = value; caitlib_put_event(handle, port, &event); }