27e8a6ff46
also shown in https://youtu.be/PKQmOsJ-g1I
270 lines
6.6 KiB
Arduino
270 lines
6.6 KiB
Arduino
/*
|
|
* a simple implementation to use midi
|
|
*
|
|
* Author: Marcel Licence
|
|
*/
|
|
|
|
|
|
/*
|
|
* look for midi interface using 1N136
|
|
* to convert the MIDI din signal to
|
|
* a uart compatible signal
|
|
*/
|
|
#if 0
|
|
#define RXD2 16 /* U2RRXD */
|
|
#define TXD2 17
|
|
#else
|
|
#define RXD2 22
|
|
#define TXD2 21
|
|
#endif
|
|
|
|
|
|
/*
|
|
* structure is used to build the mapping table
|
|
*/
|
|
struct midiControllerMapping
|
|
{
|
|
uint8_t channel;
|
|
uint8_t data1;
|
|
const char *desc;
|
|
void(*callback_mid)(uint8_t ch, uint8_t data1, uint8_t data2);
|
|
void(*callback_val)(uint8_t userdata, float value);
|
|
uint8_t user_data;
|
|
};
|
|
|
|
/*
|
|
* this mapping is used for the edirol pcr-800
|
|
* this should be changed when using another controller
|
|
*/
|
|
struct midiControllerMapping edirolMapping[] =
|
|
{
|
|
{ 0x8, 0x52, "back", NULL, Loop_ResetToStart, 0},
|
|
{ 0x8, 0x52, "back", NULL, Click_Reset, 0},
|
|
{ 0xD, 0x52, "stop", NULL, Loop_Stop, 0},
|
|
{ 0xe, 0x52, "start", NULL, Loop_PlayNormal, 0},
|
|
{ 0xe, 0x52, "start", NULL, Loop_StartAll, 0},
|
|
{ 0xa, 0x52, "rec", NULL, Loop_SetLength, 0},
|
|
|
|
/* upper row of buttons */
|
|
{ 0x0, 0x50, "A1", NULL, Loop_SelectTrack, 0},
|
|
{ 0x1, 0x50, "A2", NULL, Loop_SelectTrack, 1},
|
|
{ 0x2, 0x50, "A3", NULL, Loop_SelectTrack, 2},
|
|
{ 0x3, 0x50, "A4", NULL, Loop_SelectTrack, 3},
|
|
|
|
{ 0x4, 0x50, "A5", NULL, Click_ToggleOnOff, 0},
|
|
{ 0x5, 0x50, "A6", NULL, Loop_JumpPosQuarter, 1},
|
|
{ 0x6, 0x50, "A7", NULL, Loop_JumpPosQuarter, 2},
|
|
{ 0x7, 0x50, "A8", NULL, Loop_JumpPosQuarter, 3},
|
|
{ 0x5, 0x50, "A6", NULL, Click_JumpPosQuarter, 1},
|
|
{ 0x6, 0x50, "A7", NULL, Click_JumpPosQuarter, 2},
|
|
{ 0x7, 0x50, "A8", NULL, Click_JumpPosQuarter, 3},
|
|
|
|
{ 0x0, 0x53, "A9", NULL, Click_OnOff, 0},
|
|
|
|
/* lower row of buttons */
|
|
{ 0x0, 0x51, "B1", NULL, Loop_StopChannel, 0},
|
|
{ 0x1, 0x51, "B2", NULL, Loop_StopChannel, 1},
|
|
{ 0x2, 0x51, "B3", NULL, Loop_StopChannel, 2},
|
|
{ 0x3, 0x51, "B4", NULL, Loop_StopChannel, 3},
|
|
|
|
{ 0x4, 0x51, "B5", NULL, Loop_EraseTrack, 0},
|
|
{ 0x5, 0x51, "B6", NULL, Loop_EraseTrack, 1},
|
|
{ 0x6, 0x51, "B7", NULL, Loop_EraseTrack, 2},
|
|
{ 0x7, 0x51, "B8", NULL, Loop_EraseTrack, 3},
|
|
|
|
{ 0x1, 0x53, "B9", NULL, MTLooper_ToggleSource, 0},
|
|
|
|
/* pedal */
|
|
{ 0x0, 0x0b, "VolumePedal", NULL, NULL, 0},
|
|
|
|
{ 0x0, 0x11, "S1", NULL, Loop_SetChannelGainOut, 0},
|
|
{ 0x1, 0x11, "S2", NULL, Loop_SetChannelGainOut, 1},
|
|
{ 0x2, 0x11, "S3", NULL, Loop_SetChannelGainOut, 2},
|
|
{ 0x3, 0x11, "S4", NULL, Loop_SetChannelGainOut, 3},
|
|
|
|
{ 0x4, 0x11, "S5", NULL, NULL, 0},
|
|
{ 0x5, 0x11, "S6", NULL, NULL, 0},
|
|
{ 0x6, 0x11, "S7", NULL, NULL, 0},
|
|
{ 0x7, 0x11, "S8", NULL, NULL, 0},
|
|
|
|
/* rotary */
|
|
{ 0x0, 0x10, "R1", NULL, Loop_SetChannelPan, 0},
|
|
{ 0x1, 0x10, "R2", NULL, Loop_SetChannelPan, 1},
|
|
{ 0x2, 0x10, "R3", NULL, Loop_SetChannelPan, 2},
|
|
{ 0x3, 0x10, "R4", NULL, Loop_SetChannelPan, 3},
|
|
|
|
{ 0x4, 0x10, "R5", NULL, NULL, 0},
|
|
{ 0x5, 0x10, "R6", NULL, NULL, 0},
|
|
{ 0x6, 0x10, "R7", NULL, Click_SetTempo, 0},
|
|
{ 0x7, 0x10, "R8", NULL, Loop_SetSpeed, 0},
|
|
|
|
{ 0x0, 0x12, "R9", NULL, Loop_SetChannelGainIn, 0},
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* use define to dump midi data */
|
|
//#define DUMP_SERIAL2_TO_SERIAL
|
|
|
|
/* constant to normalize midi value to 0.0 - 1.0f */
|
|
#define NORM127MUL 0.007874f
|
|
|
|
inline void Midi_NoteOn(uint8_t ch, uint8_t note)
|
|
{
|
|
Loop_NoteOn(ch, note);
|
|
// Synth_NoteOn(note);
|
|
}
|
|
|
|
inline void Midi_NoteOff(uint8_t ch, uint8_t note)
|
|
{
|
|
// Synth_NoteOff(note);
|
|
}
|
|
|
|
/*
|
|
* this function will be called when a control change message has been received
|
|
*/
|
|
inline void Midi_ControlChange(uint8_t channel, uint8_t data1, uint8_t data2)
|
|
{
|
|
for (int i = 0; i < (sizeof(edirolMapping) / sizeof(edirolMapping[0])); i++)
|
|
{
|
|
if ((edirolMapping[i].channel == channel) && (edirolMapping[i].data1 == data1))
|
|
{
|
|
if (edirolMapping[i].callback_mid != NULL)
|
|
{
|
|
edirolMapping[i].callback_mid(channel, data1, data2);
|
|
}
|
|
if (edirolMapping[i].callback_val != NULL)
|
|
{
|
|
edirolMapping[i].callback_val(edirolMapping[i].user_data, (float)data2 * NORM127MUL);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data1 == 17)
|
|
{
|
|
if (channel < 10)
|
|
{
|
|
Synth_SetSlider(channel, data2 * NORM127MUL);
|
|
}
|
|
}
|
|
#if 0
|
|
if (data1 == 17)
|
|
{
|
|
if (channel < 10)
|
|
{
|
|
Synth_SetSlider(channel, data2 * NORM127MUL);
|
|
}
|
|
}
|
|
if ((data1 == 18) && (channel == 1))
|
|
{
|
|
Synth_SetSlider(8, data2 * NORM127MUL);
|
|
}
|
|
#endif
|
|
if ((data1 == 16) && (channel < 9))
|
|
{
|
|
Synth_SetRotary(channel, data2 * NORM127MUL);
|
|
|
|
}
|
|
if ((data1 == 18) && (channel == 0))
|
|
{
|
|
Synth_SetRotary(8, data2 * NORM127MUL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* function will be called when a short message has been received over midi
|
|
*/
|
|
inline void HandleShortMsg(uint8_t *data)
|
|
{
|
|
uint8_t ch = data[0] & 0x0F;
|
|
|
|
switch (data[0] & 0xF0)
|
|
{
|
|
/* note on */
|
|
case 0x90:
|
|
Midi_NoteOn(ch, data[1]);
|
|
break;
|
|
/* note off */
|
|
case 0x80:
|
|
Midi_NoteOff(ch, data[1]);
|
|
break;
|
|
case 0xb0:
|
|
Midi_ControlChange(ch, data[1], data[2]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Midi_Setup()
|
|
{
|
|
Serial2.begin(31250, SERIAL_8N1, RXD2, TXD2);
|
|
pinMode(RXD2, INPUT_PULLUP); /* 25: GPIO 16, u2_RXD */
|
|
}
|
|
|
|
/*
|
|
* this function should be called continuously to ensure that incoming messages can be processed
|
|
*/
|
|
void Midi_Process()
|
|
{
|
|
/*
|
|
* watchdog to avoid getting stuck by receiving incomplete or wrong data
|
|
*/
|
|
static uint32_t inMsgWd = 0;
|
|
static uint8_t inMsg[3];
|
|
static uint8_t inMsgIndex = 0;
|
|
|
|
//Choose Serial1 or Serial2 as required
|
|
|
|
if (Serial2.available())
|
|
{
|
|
uint8_t incomingByte = Serial2.read();
|
|
|
|
#ifdef DUMP_SERIAL2_TO_SERIAL
|
|
Serial.printf("%02x", incomingByte);
|
|
#endif
|
|
/* ignore live messages */
|
|
if ((incomingByte & 0xF0) == 0xF0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (inMsgIndex == 0)
|
|
{
|
|
if ((incomingByte & 0x80) != 0x80)
|
|
{
|
|
inMsgIndex = 1;
|
|
}
|
|
}
|
|
|
|
inMsg[inMsgIndex] = incomingByte;
|
|
inMsgIndex += 1;
|
|
|
|
if (inMsgIndex >= 3)
|
|
{
|
|
#ifdef DUMP_SERIAL2_TO_SERIAL
|
|
Serial.printf(">%02x %02x %02x\n", inMsg[0], inMsg[1], inMsg[2]);
|
|
#endif
|
|
HandleShortMsg(inMsg);
|
|
inMsgIndex = 0;
|
|
}
|
|
|
|
/*
|
|
* reset watchdog to allow new bytes to be received
|
|
*/
|
|
inMsgWd = 0;
|
|
}
|
|
else
|
|
{
|
|
if (inMsgIndex > 0)
|
|
{
|
|
inMsgWd++;
|
|
if (inMsgWd == 0xFFF)
|
|
{
|
|
inMsgIndex = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|