/* * MissingLink Firmware * Copyright 2011 Jabrudian Industries LLC * * MissingLink Firmware 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. * * MissingLink Firmware 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 MissingLink Firmware. If not, see . * */ #include #include #include #include #include "led.h" #include "HandleMidi.h" #include "MLSerial.h" #include "WiShield.h" #include "Parse.h" #include "Output.h" #include "wiring.h" #include "udpapp.h" #include "udpapp-ml.h" extern "C" { #include "midi.h" #include "ML_Config.h" #include "twi.h" #include "bytequeue.h" #include "uip.h" } // // Device stuff // static MidiDevice md_serial; static MidiDevice md_usbToOSC; static MidiDevice md_serialToOSC; // sysex static uint8_t serial_sysex_index = 0; static uint8_t serial_sysex_command = 0xFF; static uint8_t usb_sysex_index = 0; static uint8_t usb_sysex_command = 0xFF; // USB out (via I2C) #define USB_OUT_BUFFER_SIZE 64 static uint8_t usb_buff[USB_OUT_BUFFER_SIZE]; static byteQueue_t usb_queue; uint8_t onReceiveService(uint8_t inByte); void onRequestService(); bool sendByteUSB(unsigned char b); void sendByte(unsigned char b) { if ((fromOSC & TO_MIDI) == TO_MIDI) { Serial.write(b); } if ((fromOSC & TO_USBMIDI) == TO_USBMIDI) { sendByteUSB(b); } } void midi_send_serial(MidiDevice * device, uint8_t count, uint8_t byte0, uint8_t byte1, uint8_t byte2) { uint8_t out[3]; out[0] = byte0; out[1] = byte1; out[2] = byte2; if (count > 3) count = 3; uint8_t i; for(i = 0; i < count; i++) { Serial.write(out[i]); } } void midi_check_sysex(MidiDevice * device, uint8_t count, uint8_t byte0, uint8_t byte1, uint8_t byte2) { // This below is a little hacky, but I think it's good for a couple of reasons. if (device == &md_serial) { if ((fromMIDI & TO_MIDI) == TO_MIDI) { // Here we send a full packet in one blast, which will minimize chances of // merge issues. (Still have long sysex to worry about) midi_send_serial(&md_serial, count, byte0, byte1, byte2); } if ((fromMIDI & TO_USBMIDI) == TO_USBMIDI) { // Here we have a full packet for the host to grab. Less time of // the host asking for packets repeatedly and we not being able to provide them. uint8_t out[3]; out[0] = byte0; out[1] = byte1; out[2] = byte2; if (count > 3) count = 3; uint8_t i; for(i = 0; i < count; i++) { sendByteUSB(out[i]); } } } // Header contents: start, dev id, "M", "L", "N", "K", Command, Data, F7 // TODO: PROGMEM this shit // 240, 125, 77, 76, 78, 75, static const uint8_t ML_HEADER[] = {0xF0, 0x7D, 0x4D, 0x4C, 0x4E, 0x4B}; static const uint8_t ML_HEADER_LEN = 6; uint8_t in[3]; in[0] = byte0; in[1] = byte1; in[2] = byte2; if (count > 3) count = 3; uint8_t& sysex_index = (device == &md_serial) ? serial_sysex_index : usb_sysex_index; uint8_t& sysex_command = (device == &md_serial) ? serial_sysex_command : usb_sysex_command; for (uint8_t i = 0; i < count; i++) { if (sysex_index < ML_HEADER_LEN) { if (in[i] == ML_HEADER[sysex_index]) { sysex_index++; } else { DEBUG_PRINTF("index: %d, mismatch: %d %d\n", sysex_index, in[i], ML_HEADER[sysex_index]); sysex_index = 0; } DEBUG_PRINTF("csysindex: %d\n", sysex_index); } else { if (sysex_index == ML_HEADER_LEN) { if (in[i] != SYSEX_END) { sysex_command = in[i]; DEBUG_PRINTF("ML SYSEX: %d\n", sysex_command); sysex_index++; } else { sysex_index = 0; // reset } } else { // dispatch switch (sysex_command) { case 0: // Reset configuration to defaut, (if we need to get aggressive about code size, kill this and use eeprom write, 0, 255, device reset to get back to defaults. { if (in[i] == SYSEX_END) { DEBUG_PRINTF("Resetting to default.\n"); setDefaultConfig(); sysex_index = 0xFF; } } break; case 2 : // write to eeprom location { static uint16_t eeprom_offset = 0; static uint8_t currdata = 0; uint16_t offset = sysex_index - ML_HEADER_LEN - 1; // for command // Accumlate offset if (offset < 4) { if (offset == 0) eeprom_offset = 0; DEBUG_PRINTF("\noffset: %d, %d\n", offset, in[i]); eeprom_offset |= in[i] << ((3-offset)*4); } else { // Actually write data, we split 4 bits between two bytes (super inefficient, but simple) offset -= 4; // 1 byte for command, 4 bytes for length if (offset++%2 == 0) { currdata = in[i] << 4; DEBUG_PRINTF("storing high byte: %d, offset: %d\n", currdata, offset); } else { currdata |= in[i]; offset /= 2; // Since we use two bytes to store one byte of data offset--; // -1 to account for the initial two bytes eeprom_write_byte((uint8_t*) (eeprom_offset + offset), currdata); DEBUG_PRINTF("writing %x @ offset %d\n", currdata, eeprom_offset + offset); } } } break; case 1 : // Reset (handled by PIC) break; case 3 : // Enter AVR bootloader, handled on PIC default: sysex_index = 0xFF; // reset (the increment that happens will overflow to 0) break; } if (in[i] != SYSEX_END) { sysex_index++; } else { sysex_index = 0; // reset } } } } } void sendMidiOSC(MidiDevice * device, uint8_t count, uint8_t byte0, uint8_t byte1, uint8_t byte2) { uip_udp_prep_buffer(); char* buf = (char*) uip_appdata; // TODO: Progmem me const char* msg = "/midi/0x"; const uint8_t msglen = 8; strcpy(buf, msg); buf += msglen; // Status byte itoa(byte0, buf, 16); buf += strlen(buf); // First byte if (count > 1) { *buf++ = ' '; itoa(byte1, buf, 10); buf += strlen(buf); } // Second byte if (count > 2) { *buf++ = ' '; *buf++ = 'x'; } // pad, add comma, typetag, data uint8_t pad = buf - (char*)uip_appdata; // current size pad = ((pad+4)&0xFC) - pad; // Clear this gap. for (uint8_t i = 0; i < pad; i++) { *buf++ = 0; } // comma *buf++ = ','; if (count > 2) { // typetag *buf++ = 'f'; *buf++ = 0; *buf++ = 0; // data float data = ((float) byte2) / 127.0f; uint8_t* tmpptr = (uint8_t*) &data; *buf++ = *(tmpptr+3); *buf++ = *(tmpptr+2); *buf++ = *(tmpptr+1); *buf++ = *(tmpptr+0); } sendAppBuffer(buf - (char*)uip_appdata, oscTargetIp[0], oscTargetIp[1], oscTargetIp[2], oscTargetIp[3], oscTargetPort); WiFi.sendUdp(); } void missinglink_init_midi() { midi_device_init(&md_serial); midi_device_set_send_func(&md_serial, midi_send_serial); midi_register_fallthrough_callback(&md_serial, midi_check_sysex); midi_device_init(&md_usbToOSC); midi_register_fallthrough_callback(&md_usbToOSC, sendMidiOSC); midi_device_init(&md_serialToOSC); midi_register_fallthrough_callback(&md_serialToOSC, sendMidiOSC); bytequeue_init(&usb_queue, usb_buff, USB_OUT_BUFFER_SIZE); twi_setAddress(4); twi_attachSlaveTxEvent(onRequestService); twi_attachSlaveRxEvent(onReceiveService); twi_init(); } void missinglink_midi_process() { // Serial uint8_t amt = Serial.available(); if (amt > 3) amt = 3; uint8_t buf[3]; for (uint8_t i = 0; i < amt; i++) buf[i] = Serial.read(); midi_device_input(&md_serial, amt, buf[0], buf[1], buf[2]); midi_device_process(&md_serial); // Blink LED, main loop will reset back if (amt > 0 && canBlink()) { LED_RED; } // Serial to OSC if ((fromMIDI & TO_OSC) == TO_OSC) { midi_device_input(&md_serialToOSC, amt, buf[0], buf[1], buf[2]); midi_device_process(&md_serialToOSC); } // Process this too! if ((fromUSBMIDI & TO_OSC) == TO_OSC) { midi_device_process(&md_usbToOSC); } } // // Message stuff // void handleSimpleMess(OSCMessage& oscMess, int msgOffset) { const char* addr = oscMess.getOSCAddress(); int addrLen = oscMess.getOSCAddressLen() - msgOffset; const char* currOffset = addr + msgOffset; sendChunk(currOffset, addrLen, oscMess); } // Handles a "touchosc midi" address which requires different parsing. void handleTouchOSCMidi(OSCMessage& oscMess, void* userArg) { MessageInfo mi; if (!getMessageInfo(oscMess, mi)) return; if (mi.isZ) return; // For any message without variable, send only when x > 0 if (!mi.hasX && !mi.hasY && oscMess.getArgsNum() > 0) { if (!(getTouchArg(oscMess, 0, 1.0f, 0.0f) > 0.0f)) return; } handleSimpleMess(oscMess, 6); // /midi/ } // Handles a "touchosc midi" address which requires different parsing. void handleTouchOSCMidiZ(OSCMessage& oscMess, void* userArg) { const char* addr = oscMess.getOSCAddress(); int addrLen = oscMess.getOSCAddressLen() - 6; // Find the z on, x or x/y slide, and z off chunks uint8_t chunks[4]; const int startPos = 8; // len(/midi/z/) chunks[0] = startPos; uint8_t currChunk = 1; for (uint8_t i = startPos; i < addrLen + 7 && currChunk < 4; i++) { if (addr[i] == '/') { chunks[currChunk++] = i; } } if (currChunk < 3) return; // bad message // Ok, if currChunk == 3 then it's an x or x/y message (since the /z will be missing from the address) if (currChunk == 3) { sendChunk(addr + (chunks[1]), chunks[2] - chunks[1], oscMess); } else { if (oscMess.getFloat(0) > 0.0f) { // push down sendChunk(addr + (chunks[0]), chunks[1] - chunks[0], oscMess); } else { // push up sendChunk(addr + (chunks[2]), chunks[3] - chunks[2], oscMess); } } } // Handles a "touchosc midi" address which requires different parsing. void handleMidiButton(OSCMessage& oscMess, void* userArg) { const char* addr = oscMess.getOSCAddress(); int addrLen = oscMess.getOSCAddressLen(); MessageInfo mi; if (!getMessageInfo(oscMess, mi)) return; if (mi.isZ) return; // Find the z on, x or x/y slide, and z off chunks const int startPos = 13; // len(/midi/button/) uint8_t chunks[2] = {startPos, startPos}; uint8_t currChunk = 1; for (uint8_t i = startPos; addr[i] != 0 && currChunk < 2; i++) { if (addr[i] == '/') { chunks[currChunk++] = i; } } if (currChunk < 2) return; // bad message // Ok, if currChunk == 3 then it's an x or x/y message (since the /z will be missing from the address) if (oscMess.getFloat(0) > 0.0f) { // push down sendChunk(addr + chunks[0], chunks[1] - chunks[0], oscMess); } else { // push up sendChunk(addr + chunks[1], addrLen - chunks[1], oscMess); } } // USB IN -> MIDI OUT void setDataReadyFlag() { if (bytequeue_length(&usb_queue) > 0) { PORTC |= _BV(1); // on! } else { PORTC &= ~(_BV(1)); // off! } } bool sendByteUSB(unsigned char b) { bool ret = bytequeue_enqueue(&usb_queue, b); setDataReadyFlag(); return ret; } uint8_t onReceiveService(uint8_t inByte) { if ((fromUSBMIDI & TO_USBMIDI) == TO_USBMIDI) { if (!sendByteUSB(inByte) || (bytequeue_length((byteQueue_t*) &usb_queue) >= USB_OUT_BUFFER_SIZE - 1)) { return 0; } } midi_check_sysex(NULL, 1, inByte, 0, 0); if ((fromUSBMIDI & TO_MIDI) == TO_MIDI) { Serial.write(inByte); } if ((fromUSBMIDI & TO_OSC) == TO_OSC) { midi_device_input(&md_usbToOSC, 1, inByte, 0, 0); } return 1; } void onRequestService() { unsigned char out[5]; // SYSEX message can span multiple packets, so we need to store that state. static uint8_t usb_in_sysex_mode = 0; // Figure out lengths, we never send more than 3 bytes at a time uint8_t qlen = bytequeue_length(&usb_queue); if (qlen > 3) qlen = 3; uint8_t midi_packet_len = 0; // Figure out how much data to send if (qlen > 0) { uint8_t status_byte = bytequeue_get(&usb_queue, 0); if (status_byte == SYSEX_BEGIN) { usb_in_sysex_mode = 1; } else { midi_packet_len = midi_packet_length(status_byte); } } if (usb_in_sysex_mode == 0) { // Not a sysex message, just wait until we have all the data we need and // send it off. if (qlen >= midi_packet_len) { if (midi_packet_len > 0) { out[0] = 4; out[1] = bytequeue_get(&usb_queue, 0) >> 4; for (uint8_t i = 0; i < midi_packet_len; i++) { out[i+2] = bytequeue_get(&usb_queue, i); } bytequeue_remove(&usb_queue, midi_packet_len); } else { // Hrm, we're not in sysex mode and there's no status byte. Just consume // the byte bytequeue_remove(&usb_queue, 1); } } } else { // Search ahead for sysex_end uint8_t end = 0xFF; for (uint8_t i = 0; i < qlen; i++) { if (bytequeue_get(&usb_queue, i) == SYSEX_END) { end = i+1; usb_in_sysex_mode = 0; break; } } // If this is the end of a message, or we have a full packet, send it. if ((end != 0xFF) || (qlen >= 3)) { out[0] = 4; // Figure out sysex flag byte out[1] = 0x4 + (end != 0xFF ? end : 0); // Loop until sysex_end or buffer end uint8_t consume = end != 0xFF ? end : 3; for (uint8_t i = 0; i < consume; i++) { out[i+2] = bytequeue_get(&usb_queue, i); } bytequeue_remove(&usb_queue, consume); } } // Finally, let's send this data! twi_transmit(out, out[0]+1); if (bytequeue_length(&usb_queue) == 0) { PORTC &= ~(_BV(1)); } }