|
- /*
- * Caitlib
- * Copyright (C) 2007 Nedko Arnaudov <nedko@arnaudov.name>
- * Copyright (C) 2012 Filipe Coelho <falktx@falktx.com>
- *
- * 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 <math.h>
- #include <stdbool.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <pthread.h>
- #include <jack/jack.h>
- #include <jack/midiport.h>
-
- #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);
- }
|