Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
fab672000 committed Nov 30, 2016
1 parent df52ebc commit b45a8b2
Show file tree
Hide file tree
Showing 8 changed files with 570 additions and 0 deletions.
11 changes: 11 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.6)
project(MIDIUSB_1x1)

find_package(GTest REQUIRED)
include_directories(. ./test ${GTEST_INCLUDE_DIR})

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

set(SOURCE_FILES test/test_midi_bridge.cpp )
add_executable(MIDIUSB_1x1 ${SOURCE_FILES})
target_link_libraries(MIDIUSB_1x1 ${GTEST_LIBRARIES})
50 changes: 50 additions & 0 deletions Leds.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#pragma once
//------------------------------------------------------------------------------
// Generic led pin and level mapping, defines LED, LED_OFF, LED_ON
// Author: Fabien
// License: GPL V3 © 2016 Fabien (https://github.com/fab672000)
//-----------------------------------------------------------------------------
#if defined(ARDUINO_SAM_DUE)
//#pragma message("Due")
const byte LED = 13;
#elif defined(STM32_MCU_SERIES)
//#pragma message("STM32 MCU")
const byte LED = PC13;
#elif defined(ARDUINO_AVR_MICRO)
//#pragma message("Micro")
const byte LED = 17; // TX led on micro
#elif defined(ARDUINO_AVR_PRO) || defined(ARDUINO_AVR_MINI)
//#pragma message("Mini")
const byte LED = 13; // TX led on micro
#elif defined(ARDUINO_AVR_LEONARDO)
//#pragma message("Leonardo")
const byte LED = 13; // TX led on micro
#elif defined(ESP8266_ESP12)
//#pragma message("NodeMCU 1.0")
const byte LED = 13; // TX led on micro
#else
//#pragma message("Default")
const byte LED = 17;
#endif
#if defined(STM32_MCU_SERIES) || defined(ARDUINO_AVR_MICRO)
# define LED_ON LOW
# define LED_OFF HIGH
#else
# define LED_OFF LOW
# define LED_ON HIGH
#endif

const int LED_DELAY_MS = 500;

class Leds
{
public:
static void blink(byte led, int del=LED_DELAY_MS)
{
digitalWrite(led, LED_ON);
delay(del);
digitalWrite(led, LED_OFF);
delay(del);
}
};

132 changes: 132 additions & 0 deletions MIDIUSB_1x1.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#define ARDUINO_MAX_POWER_MA 20
/*
* MIDIUSB 1x1 interface, works with iPad.
*
* Important Note: needs D_CONFIG macro in USBCore.h to be patched to change USB_CONFIG_POWER_MA(500)
* with a lower value (i.e 20) in order to work with iPad, as it is bus powered by the tablet.
*
* Created: October, 11 2016
* Author: Fabien
* License: GPL V3 © 2016 Fabien (https://github.com/fab672000)
*/

#include <avr/power.h>
#include "TimerOne.h"
#include "MIDIUSB.h"
#include "MIDI.h"
#include "MidiBridge.h"
#include "Leds.h"

USING_NAMESPACE_MIDI

//------------------------------------------------------------------------------
const byte led1 = LED;
const byte led2 = TXLED1;

static bool timer_elapsed = false;

// WARNING on leonardo / Pro Micro serial is Serial1 !!, Serial is USB
MIDIBRIDGE_CREATE_INSTANCE(HardwareSerial, Serial1, midiA);

//------------------------------------------------------------------------------
void power_save()
{ // Disable ADC, SPI, I2C, Timer 2 and Timer 3
power_adc_disable();
power_spi_disable();
power_twi_disable();

power_timer2_disable();
power_timer3_disable();
}
//------------------------------------------------------------------------------
void leds_off()
{
digitalWrite(led1, LED_OFF);
TXLED1;
}
//------------------------------------------------------------------------------
void note(byte pitch, int duration=250)
{
const uint8_t channel =1, velocity = 100;

midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity};
MidiUSB.sendMIDI(noteOn);
MidiUSB.flush();
//midiA.sendNoteOn(pitch, velocity, channel);

delay(duration);
midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity};
MidiUSB.sendMIDI(noteOff);
MidiUSB.flush();
//midiA.sendNoteOff(pitch, velocity, channel);

}
//------------------------------------------------------------------------------
void timer1_callback()
{
timer_elapsed = true;
}
//------------------------------------------------------------------------------
// Midi Interface Setup: initialize leds, serial midi, timer and power save
//------------------------------------------------------------------------------
void setup() {
pinMode(led1, OUTPUT);
pinMode(led2, OUTPUT);

// briefly blink leds as an alive signal
for (int i=0; i< 2; i++) {
delay(150);
digitalWrite(led1, LED_ON);
TXLED1;
delay(150);
digitalWrite(led1, LED_OFF);
TXLED0;
}
leds_off();

midiA.begin(MIDI_CHANNEL_OMNI);

// Set timer to save even more power after a period of inactivity
Timer1.initialize(500000); // initialize timer1, and set a period in us
Timer1.attachInterrupt(timer1_callback);

power_save(); // save extra millamps by disabling what we do not use
}
//------------------------------------------------------------------------------
// Main loop: Handle serial rx first and send it to host if any,
// then read host and send to serial if any.
//------------------------------------------------------------------------------
void loop() {
bool sent = false, recv=false;

// Handle serial midi messages, if any ; send them to host:
while (midiA.read())

This comment has been minimized.

Copy link
@giovannibotta

giovannibotta Jan 30, 2019

IIUC, the example simply bridges serial->USB and USB->serial correct?
Would it be possible to also add a test example to produce MIDI outputs on both USB and serial? That would cover applications like Arduino-based MIDI controller that want to use both serial and USB ports. I can send a pull request if you think that's feasible.

This comment has been minimized.

Copy link
@fab672000

fab672000 Jan 30, 2019

Author Owner

This example does a bit more though as it cares about microcontroller specific power consumption. There is a midi bridge implementation that you can see below..
Let's discuss on the issue you opened please instead of here for more general questions like this one, I'd like to keep the commit comments only for specific code problems/improvement.

{
sent = midiA.SendToUSB();
// state = state==LED_OFF ? LED_ON : LED_OFF;
}
if(sent) {MidiUSB.flush();}

// Handle host messages, if any ; send them to serial:
midiEventPacket_t rx;
do
{
rx = MidiUSB.read();
midiA.SendToSerial(rx);
} while (rx.header != 0);

// Handle timer and leds to save power while no midi is received / sent
if(sent || recv)
{
timer_elapsed = sent = recv = false;
Timer1.restart();
}
else if(timer_elapsed)
{
leds_off();
timer_elapsed = false;
}
}
//------------------------------------------------------------------------------
// EOF
//------------------------------------------------------------------------------
186 changes: 186 additions & 0 deletions MidiBridge.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#pragma once
//
// MidiBridge Class declaration
//
// Author: Fabien
// License: GPL V3.0 © 2016 Fabien (https://github.com/fab672000)

#include "MIDI.h"
#include "MIDIUSB.h"
#include <stdint.h>
#include <stdio.h>
#define MIDIBRIDGE_CREATE_INSTANCE(Type, SerialPort, Name) MidiBridge<Type> Name((Type&)SerialPort);

namespace midi
{

/**
Utility class template to convert between serial and usb midi.
*/
template<typename SerialPort, typename Settings = DefaultSettings>
class MidiBridge : public MidiInterface<SerialPort, Settings>
{
public:

MidiBridge(SerialPort &inSerial):MidiInterface<SerialPort, Settings>(inSerial){}

bool SendToSerial(const midiEventPacket_t& rx);
bool SendToUSB();

//! Given a midiusb data buffer, create and return the corresonding serial midi
//! contiguous buffer for sending to uart. Caller must delete[] the returned value when done.
static uint8_t* GetUsbToSerialSysExBuffer(const uint8_t *data, size_t size, size_t& outSize);

//! Given a serial midi data buffer, create and return the corresonding usb midi packets
//! for sending to USB connection. Caller must delete[] the returned value when done.
static uint8_t* GetSerialToUsbSysExBuffer(const uint8_t* data, size_t size, size_t& outSize);

//! Given a midiusb data buffer, send the corresonding serial midi data to uart
bool UsbToSerialSysEx(const uint8_t *data, size_t size);

//! Given a serial midi data buffer, send the corresonding serial midi data to USB
bool SerialToUsbSysEx(const uint8_t *data, size_t size);
};

// Template implementation
template<class SerialPort, class Settings>
uint8_t* MidiBridge<SerialPort, Settings>::GetUsbToSerialSysExBuffer(const uint8_t* data, size_t size, size_t& midiDataSize)
{
if (data == nullptr || size == 0 || (size % 4) != 0) return nullptr; // usb packets must be DWORDs
midiDataSize = (size >> 2) * 3;
uint8_t lastPacketHdr = data[size - 4];
if (lastPacketHdr > 4) midiDataSize -= (7 - lastPacketHdr); // adjust size if needed
auto midiData = new uint8_t[midiDataSize];
auto *p = midiData;
auto bytesRemaining = midiDataSize;

for (const uint8_t *d = data + 1; d<data + size; d += 4)
{
switch (bytesRemaining) {
case 1:
*p = d[0]; // SysEx ends with following single uint8_t
break;
case 2:
*p++ = d[0]; // SysEx ends with following two uint8_ts
*p = d[1];
break;
default:
*p++ = d[0]; // SysEx with following three uint8_ts
*p++ = d[1];
*p++ = d[2];
break;
}
bytesRemaining = bytesRemaining>3 ? bytesRemaining - 3 : 0;
}
return midiData; // it is caller responsibility to delete[] this data
}

template<class SerialPort, class Settings>
bool MidiBridge<SerialPort, Settings>::UsbToSerialSysEx(const uint8_t* data, size_t size)
{
size_t midiDataSize;
auto buffer = GetUsbToSerialSysExBuffer(data, size, midiDataSize);

if (buffer == nullptr) return false;

this->sendSysEx((unsigned)midiDataSize, buffer, true);
delete[] buffer;

return true;
}

template<class SerialPort, class Settings>
uint8_t* MidiBridge<SerialPort, Settings>::GetSerialToUsbSysExBuffer(const uint8_t* data, size_t size, size_t& outDataSize)
{
if (data == nullptr || size == 0) return nullptr;

outDataSize = (size + 2) / 3 * 4;
uint8_t* midiData = new uint8_t[outDataSize];
auto d = data;
auto p = midiData;
auto bytesRemaining = size;

while (bytesRemaining > 0) {
switch (bytesRemaining) {
case 1:
*p++ = 5; // SysEx ends with following single uint8_t
*p++ = *d;
*p++ = 0;
*p = 0;
bytesRemaining = 0;
break;
case 2:
*p++ = 6; // SysEx ends with following two uint8_ts
*p++ = *d++;
*p++ = *d;
*p = 0;
bytesRemaining = 0;
break;
case 3:
*p++ = 7; // SysEx ends with following three uint8_ts
*p++ = *d++;
*p++ = *d++;
*p = *d;
bytesRemaining = 0;
break;
default:
*p++ = 4; // SysEx starts or continues
*p++ = *d++;
*p++ = *d++;
*p++ = *d++;
bytesRemaining -= 3;
break;
}
}
return midiData;
}

template<class SerialPort, class Settings>
bool MidiBridge<SerialPort, Settings>::SerialToUsbSysEx(const uint8_t* data, size_t size)
{
size_t outSize = 0;
auto buffer = GetSerialToUsbSysExBuffer(data, size, outSize);
if (buffer == nullptr) return false;

MidiUSB.write(data, size);
delete[] buffer;
return true;
}

template<class SerialPort, class Settings>
bool MidiBridge<SerialPort, Settings>::SendToSerial(const midiEventPacket_t &rx) {
if (rx.header == 0) return false;
else if(rx.header==0xF)
{
this->send((MidiType)rx.byte1, rx.byte2, rx.byte3, (Channel)0);
}
else if (rx.header>=0x8)
{
this->send((MidiType)(rx.byte1&0xf0), rx.byte2, rx.byte3, (Channel) (rx.byte1&0x0f)+1);
}
else if(rx.header>=0x4)
{
UsbToSerialSysEx((const byte*) &rx, sizeof(rx));
}
return true;
}

template<class SerialPort, class Settings>
bool MidiBridge<SerialPort, Settings>::SendToUSB()
{
MidiType msgType = this->getType();

if (msgType==0) return false;
else if(msgType==0xF0)
{
SerialToUsbSysEx(this->getSysExArray(), this->getSysExArrayLength());
}
else
{
midiEventPacket_t tx= {byte(msgType>>4), byte(msgType | this->getChannel()), this->getData1(), this->getData2()};
MidiUSB.sendMIDI(tx);
}
return true;
}

}
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# MIDIUSB_1x1
MIDI Interface 1 in 1 out based on Arduino MidiUSB and Midi libraries

Works with iPad and ios native midi drivers (must patch MidiUSB power settings as instructed in the .ino file)
Loading

0 comments on commit b45a8b2

Please sign in to comment.