You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
14 KiB
397 lines
14 KiB
/*
|
|
Calf Box, an open source musical instrument.
|
|
Copyright (C) 2010-2013 Krzysztof Foltman
|
|
|
|
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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#if USE_LIBUSB
|
|
|
|
#include "app.h"
|
|
#include "config-api.h"
|
|
#include "errors.h"
|
|
#include "hwcfg.h"
|
|
#include "io.h"
|
|
#include "meter.h"
|
|
#include "midi.h"
|
|
#include "recsrc.h"
|
|
#include "usbio_impl.h"
|
|
|
|
#include <unistd.h>
|
|
|
|
static void decode_events_mpd16(struct cbox_usb_midi_interface *umi, struct libusb_transfer *transfer)
|
|
{
|
|
for (int i = 0; i < transfer->actual_length;)
|
|
{
|
|
uint8_t *data = &transfer->buffer[i];
|
|
uint8_t len = data[0] & 15;
|
|
if (!len || i + len >= transfer->actual_length)
|
|
break;
|
|
cbox_midi_buffer_write_inline(&umi->input_port->hdr.buffer, 0, data[1], len > 1 ? data[2] : 0, len > 2 ? data[3] : 0);
|
|
i += len + 1;
|
|
}
|
|
}
|
|
|
|
static void decode_events_nocturn(struct cbox_usb_midi_interface *umi, struct libusb_transfer *transfer)
|
|
{
|
|
uint8_t control = 0;
|
|
for (int i = 0; i < transfer->actual_length;)
|
|
{
|
|
uint8_t *data = &transfer->buffer[i];
|
|
if (*data >= 128)
|
|
{
|
|
control = *data;
|
|
i++;
|
|
continue;
|
|
}
|
|
if (control != 176 || i + 2 > transfer->actual_length)
|
|
{
|
|
g_warning("Unrecognized combination of control, data and length: %02x %02x %02x", control, data[0], transfer->actual_length - i);
|
|
break;
|
|
}
|
|
cbox_midi_buffer_write_inline(&umi->input_port->hdr.buffer, 0, control, data[0], data[1]);
|
|
i += 2;
|
|
}
|
|
}
|
|
|
|
static void decode_events_class(struct cbox_usb_midi_interface *umi, struct libusb_transfer *transfer)
|
|
{
|
|
for (int i = 0; i + 3 < transfer->actual_length; i += 4)
|
|
{
|
|
uint8_t *data = &transfer->buffer[i];
|
|
uint8_t etype = data[0] & 15;
|
|
if (etype >= 8)
|
|
{
|
|
// normalise: note on with vel 0 -> note off
|
|
if ((data[1] & 0xF0) == 0x90 && data[3] == 0)
|
|
cbox_midi_buffer_write_inline(&umi->input_port->hdr.buffer, 0, data[1] - 0x10, data[2], data[3]);
|
|
else
|
|
cbox_midi_buffer_write_event(&umi->input_port->hdr.buffer, 0, data + 1, midi_cmd_size(data[1]));
|
|
}
|
|
else
|
|
if (etype == 2 || etype == 3)
|
|
{
|
|
cbox_midi_buffer_write_event(&umi->input_port->hdr.buffer, 0, data + 1, etype);
|
|
}
|
|
else
|
|
if (etype == 4)
|
|
{
|
|
if (umi->current_sysex_length + 3 <= MAX_SYSEX_SIZE)
|
|
memcpy(&umi->sysex_data[umi->current_sysex_length], data + 1, 3);
|
|
umi->current_sysex_length += 3;
|
|
}
|
|
else
|
|
if (etype >= 5 && etype <= 7)
|
|
{
|
|
int len = etype - 4;
|
|
if (umi->current_sysex_length + len <= MAX_SYSEX_SIZE)
|
|
memcpy(&umi->sysex_data[umi->current_sysex_length], data + 1, len);
|
|
umi->current_sysex_length += len;
|
|
if (umi->current_sysex_length <= MAX_SYSEX_SIZE)
|
|
cbox_midi_buffer_write_event(&umi->input_port->hdr.buffer, 0, umi->sysex_data, umi->current_sysex_length);
|
|
else
|
|
g_warning("Dropping oversized SysEx: length %d", umi->current_sysex_length);
|
|
umi->current_sysex_length = 0;
|
|
|
|
}
|
|
else
|
|
g_warning("Unrecognized USB MIDI initiating byte %02x\n", data[0]);
|
|
}
|
|
}
|
|
|
|
static void midi_transfer_cb(struct libusb_transfer *transfer)
|
|
{
|
|
struct usbio_transfer *xf = transfer->user_data;
|
|
struct cbox_usb_midi_interface *umi = xf->user_data;
|
|
xf->pending = FALSE;
|
|
|
|
if (transfer->status == LIBUSB_TRANSFER_CANCELLED)
|
|
{
|
|
xf->cancel_confirm = 1;
|
|
return;
|
|
}
|
|
if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT || transfer->status == LIBUSB_TRANSFER_ERROR || transfer->status == LIBUSB_TRANSFER_STALL)
|
|
{
|
|
if (transfer->status != LIBUSB_TRANSFER_TIMED_OUT)
|
|
g_warning("USB error on device %03d:%03d: transfer status %d", umi->busdevadr >> 8, umi->busdevadr & 255, transfer->status);
|
|
if (umi->uii->no_resubmit)
|
|
return;
|
|
int res = usbio_transfer_submit(xf);
|
|
if (res != 0)
|
|
g_warning("Error submitting URB to MIDI endpoint: error code %d", res);
|
|
return;
|
|
}
|
|
if (transfer->status == LIBUSB_TRANSFER_NO_DEVICE)
|
|
{
|
|
g_debug("No device %03d:%03d, unlinking", umi->busdevadr >> 8, umi->busdevadr & 255);
|
|
umi->uii->rt_midi_ports = g_list_remove(umi->uii->rt_midi_ports, umi);
|
|
return;
|
|
}
|
|
|
|
if (umi->protocol == USBMIDI_PROTOCOL_MPD16) // MPD16
|
|
decode_events_mpd16(umi, transfer);
|
|
else
|
|
if (umi->protocol == USBMIDI_PROTOCOL_NOCTURN) // Nocturn
|
|
decode_events_nocturn(umi, transfer);
|
|
else
|
|
decode_events_class(umi, transfer);
|
|
|
|
if (umi->uii->no_resubmit)
|
|
return;
|
|
usbio_transfer_submit(xf);
|
|
}
|
|
|
|
static gboolean push_data_to_umo(struct cbox_usb_midi_output *umo, const uint8_t *pdata, uint32_t size, uint8_t first_byte)
|
|
{
|
|
if (umo->endpoint_buffer_pos + 4 <= USB_MIDI_OUTPUT_QUEUE)
|
|
{
|
|
umo->endpoint_buffer[umo->endpoint_buffer_pos] = first_byte;
|
|
memcpy(&umo->endpoint_buffer[umo->endpoint_buffer_pos + 1], pdata, size);
|
|
umo->endpoint_buffer_pos += 4;
|
|
}
|
|
else
|
|
{
|
|
g_warning("Class MIDI buffer overflow.");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void encode_events_class(struct cbox_usb_midi_output *umo)
|
|
{
|
|
for (uint32_t i = 0; i < umo->hdr.buffer.count; i++)
|
|
{
|
|
const struct cbox_midi_event *event = cbox_midi_buffer_get_event(&umo->hdr.buffer, i);
|
|
const uint8_t *pdata = cbox_midi_event_get_data(event);
|
|
if (event->size <= 3)
|
|
{
|
|
if (!push_data_to_umo(umo, pdata, event->size, pdata[0] >> 4))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
int i = 0;
|
|
while(i + 3U < event->size)
|
|
{
|
|
push_data_to_umo(umo, pdata + i, 3, 4);
|
|
i += 3;
|
|
}
|
|
push_data_to_umo(umo, pdata + i, 3, 4 + event->size - i);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void encode_events_nocturn(struct cbox_usb_midi_output *umo)
|
|
{
|
|
for (uint32_t i = 0; i < umo->hdr.buffer.count; i++)
|
|
{
|
|
const struct cbox_midi_event *event = cbox_midi_buffer_get_event(&umo->hdr.buffer, i);
|
|
const uint8_t *pdata = cbox_midi_event_get_data(event);
|
|
// Only encode control change events on channel 1 - it's the only
|
|
// recognized type of events
|
|
if (event->size == 3 && pdata[0] == 0xB0)
|
|
{
|
|
if (umo->endpoint_buffer_pos + 2 <= 8)
|
|
{
|
|
umo->endpoint_buffer[umo->endpoint_buffer_pos] = pdata[1];
|
|
umo->endpoint_buffer[umo->endpoint_buffer_pos + 1] = pdata[2];
|
|
umo->endpoint_buffer_pos += 2;
|
|
}
|
|
else
|
|
g_warning("Nocturn MIDI buffer overflow.");
|
|
}
|
|
}
|
|
}
|
|
|
|
void usbio_fill_midi_output_buffer(struct cbox_usb_midi_output *umo)
|
|
{
|
|
cbox_midi_merger_render(&umo->hdr.merger);
|
|
if (!umo->hdr.buffer.count)
|
|
return;
|
|
|
|
if (umo->ifptr->protocol == USBMIDI_PROTOCOL_CLASS)
|
|
encode_events_class(umo);
|
|
else
|
|
if (umo->ifptr->protocol == USBMIDI_PROTOCOL_NOCTURN)
|
|
encode_events_nocturn(umo);
|
|
}
|
|
|
|
void usbio_send_midi_to_output(struct cbox_usb_midi_output *umo)
|
|
{
|
|
if (!umo->endpoint_buffer_pos)
|
|
return;
|
|
|
|
int transferred = 0;
|
|
int res = 0;
|
|
if (umo->ifptr->epdesc_out.interrupt)
|
|
res = libusb_interrupt_transfer(umo->ifptr->handle, umo->ifptr->epdesc_out.bEndpointAddress, umo->endpoint_buffer, umo->endpoint_buffer_pos, &transferred, 10);
|
|
else
|
|
res = libusb_bulk_transfer(umo->ifptr->handle, umo->ifptr->epdesc_out.bEndpointAddress, umo->endpoint_buffer, umo->endpoint_buffer_pos, &transferred, 10);
|
|
if (res == 0 && (uint32_t)transferred == umo->endpoint_buffer_pos)
|
|
umo->endpoint_buffer_pos = 0;
|
|
else
|
|
g_warning("Failed to send MIDI events, transferred = %d out of %d, result = %d", (int)transferred, (int)umo->endpoint_buffer_pos, res);
|
|
}
|
|
|
|
void usbio_update_port_routing(struct cbox_io_impl *ioi)
|
|
{
|
|
struct cbox_usb_io_impl *uii = (struct cbox_usb_io_impl *)ioi;
|
|
for(GList *p = uii->rt_midi_ports; p; p = p->next)
|
|
{
|
|
struct cbox_usb_midi_interface *umi = p->data;
|
|
if (umi->input_port)
|
|
{
|
|
if (!umi->input_port->hdr.output_set)
|
|
cbox_midi_merger_connect(&uii->midi_input_merger, &umi->input_port->hdr.buffer, app.rt, NULL);
|
|
else
|
|
cbox_midi_merger_disconnect(&uii->midi_input_merger, &umi->input_port->hdr.buffer, app.rt);
|
|
}
|
|
}
|
|
}
|
|
|
|
void usbio_start_midi_capture(struct cbox_usb_io_impl *uii)
|
|
{
|
|
uii->rt_midi_ports = g_list_copy(uii->midi_ports);
|
|
|
|
for(GList *p = uii->rt_midi_ports; p; p = p->next)
|
|
{
|
|
struct cbox_usb_midi_interface *umi = p->data;
|
|
cbox_midi_buffer_clear(&umi->input_port->hdr.buffer);
|
|
if (umi->epdesc_in.found)
|
|
{
|
|
umi->current_sysex_length = 0;
|
|
umi->transfer_in = usbio_transfer_new(uii->usbctx, "MIDI In", 0, 0, umi);
|
|
int pktsize = umi->epdesc_in.wMaxPacketSize;
|
|
if (pktsize > MAX_MIDI_PACKET_SIZE)
|
|
pktsize = MAX_MIDI_PACKET_SIZE;
|
|
if (umi->epdesc_in.interrupt)
|
|
libusb_fill_interrupt_transfer(umi->transfer_in->transfer, umi->handle, umi->epdesc_in.bEndpointAddress, umi->midi_recv_data, pktsize, midi_transfer_cb, umi->transfer_in, 0);
|
|
else
|
|
libusb_fill_bulk_transfer(umi->transfer_in->transfer, umi->handle, umi->epdesc_in.bEndpointAddress, umi->midi_recv_data, pktsize, midi_transfer_cb, umi->transfer_in, 0);
|
|
}
|
|
else
|
|
umi->transfer_in = NULL;
|
|
if (umi->epdesc_out.found)
|
|
umi->transfer_out = usbio_transfer_new(uii->usbctx, "MIDI Out", 0, 0, umi);
|
|
else
|
|
umi->transfer_out = NULL;
|
|
}
|
|
for(GList *p = uii->rt_midi_ports; p; p = p->next)
|
|
{
|
|
struct cbox_usb_midi_interface *umi = p->data;
|
|
int res = usbio_transfer_submit(umi->transfer_in);
|
|
if (res != 0)
|
|
{
|
|
usbio_transfer_destroy(umi->transfer_in);
|
|
umi->transfer_in = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void usbio_stop_midi_capture(struct cbox_usb_io_impl *uii)
|
|
{
|
|
for(GList *p = uii->rt_midi_ports; p; p = p->next)
|
|
{
|
|
struct cbox_usb_midi_interface *umi = p->data;
|
|
|
|
if (umi->transfer_in)
|
|
{
|
|
usbio_transfer_shutdown(umi->transfer_in);
|
|
usbio_transfer_destroy(umi->transfer_in);
|
|
umi->transfer_in = NULL;
|
|
cbox_midi_buffer_clear(&umi->input_port->hdr.buffer);
|
|
}
|
|
if (umi->transfer_out)
|
|
{
|
|
usbio_transfer_shutdown(umi->transfer_out);
|
|
usbio_transfer_destroy(umi->transfer_out);
|
|
umi->transfer_out = NULL;
|
|
}
|
|
}
|
|
for(GList *p = uii->rt_midi_ports; p; p = p->next)
|
|
{
|
|
struct cbox_usb_midi_interface *umi = p->data;
|
|
if (umi->epdesc_in.found)
|
|
cbox_midi_merger_disconnect(&uii->midi_input_merger, &umi->input_port->hdr.buffer, NULL);
|
|
}
|
|
g_list_free(uii->rt_midi_ports);
|
|
}
|
|
|
|
void cbox_usb_midi_info_init(struct cbox_usb_midi_info *umi, struct cbox_usb_device_info *udi)
|
|
{
|
|
umi->udi = udi;
|
|
umi->intf = -1;
|
|
umi->alt_setting = -1;
|
|
umi->epdesc_in.found = FALSE;
|
|
umi->epdesc_out.found = FALSE;
|
|
}
|
|
|
|
struct cbox_usb_midi_interface *usbio_open_midi_interface(struct cbox_usb_io_impl *uii, const struct cbox_usb_midi_info *uminf, struct libusb_device_handle *handle)
|
|
{
|
|
struct cbox_usb_device_info *devinfo = uminf->udi;
|
|
int bus = devinfo->bus;
|
|
int devadr = devinfo->devadr;
|
|
GError *error = NULL;
|
|
// printf("Has MIDI port\n");
|
|
// printf("Output endpoint address = %02x\n", ep->bEndpointAddress);
|
|
if (!configure_usb_interface(handle, bus, devadr, uminf->intf, uminf->alt_setting, "MIDI (class driver)", &error))
|
|
{
|
|
g_warning("%s", error->message);
|
|
g_error_free(error);
|
|
return NULL;
|
|
}
|
|
|
|
struct cbox_usb_midi_interface *umi = calloc(sizeof(struct cbox_usb_midi_interface), 1);
|
|
umi->uii = uii;
|
|
umi->devinfo = devinfo;
|
|
umi->handle = handle;
|
|
umi->busdevadr = devinfo->busdevadr;
|
|
uii->midi_ports = g_list_prepend(uii->midi_ports, umi);
|
|
umi->protocol = uminf->protocol;
|
|
memcpy(&umi->epdesc_in, &uminf->epdesc_in, sizeof(umi->epdesc_in));
|
|
memcpy(&umi->epdesc_out, &uminf->epdesc_out, sizeof(umi->epdesc_out));
|
|
|
|
// Drain the output buffer of the device - otherwise playing a few notes and running the program will cause
|
|
// those notes to play.
|
|
unsigned char flushbuf[256];
|
|
int transferred = 0;
|
|
int len = umi->epdesc_in.wMaxPacketSize;
|
|
if (len > MAX_MIDI_PACKET_SIZE)
|
|
len = MAX_MIDI_PACKET_SIZE;
|
|
if (umi->epdesc_in.interrupt)
|
|
{
|
|
while(0 == libusb_interrupt_transfer(handle, umi->epdesc_in.bEndpointAddress, flushbuf, len, &transferred, 10) && transferred > 0)
|
|
usleep(1000);
|
|
}
|
|
else
|
|
{
|
|
while(0 == libusb_bulk_transfer(handle, umi->epdesc_in.bEndpointAddress, flushbuf, len, &transferred, 10) && transferred > 0)
|
|
usleep(1000);
|
|
}
|
|
devinfo->status = CBOX_DEVICE_STATUS_OPENED;
|
|
|
|
// Initialise device - only used for Novation Nocturn, and it performs a
|
|
// device reset. Perhaps not the best implementation of hot swap - it
|
|
// will reset the device even when some other device was connected.
|
|
if (umi->protocol == USBMIDI_PROTOCOL_NOCTURN)
|
|
{
|
|
static uint8_t data1[] = { 0xB0, 0, 0 };
|
|
libusb_interrupt_transfer(handle, umi->epdesc_out.bEndpointAddress, data1, sizeof(data1), &transferred, 10);
|
|
}
|
|
|
|
return umi;
|
|
}
|
|
|
|
#endif
|
|
|