/* 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 . */ #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 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