Hi @fullcane ,
I use a Pro Micro to convert Midi signals from a keyboard to USB Midi and it works brilliant ...
The Midi input is based on SoftwareSerial, here just the necessary lines of code:
#include <SoftwareSerial.h>
constexpr byte rxPin{ 9 };
constexpr byte txPin{ 8 };
SoftwareSerial mySerial(rxPin, txPin, false); // false => Use standard logic,
// true => inverse logic
You may give it a try ...
Good luck!
ec2021
If you are interested here the full sketch I wrote:
MidiInToUSBMidi
Sketch:
#include <SoftwareSerial.h>
#include "verysimplemidi.h"
#include "MIDIUSB.h"
constexpr byte rxPin{ 9 };
constexpr byte txPin{ 8 };
VerySimpleMidi myMidi;
SoftwareSerial mySerial(rxPin, txPin, false);
void setup() {
mySerial.begin(31250);
}
boolean zeroOk = false;
void loop() {
if (mySerial.available()) {
byte inByte = mySerial.read();
if (myMidi.dataReceived(inByte)) {
DataStruct midiData = myMidi.getData();
switch (midiData.cmd) {
case NOTEON:
USB_noteOn(1, midiData.key, midiData.velocity);
//printNoteData("NoteON", midiData);
break;
case NOTEOFF:
USB_noteOff(1, midiData.key, midiData.velocity);
//printNoteData("NoteOFF", midiData);
break;
case AFTERTOUCH:
//printNoteData("AfterTouch", midiData);
break;
case CTRLCHANGE:
//printCCData("ControlChange", midiData);
break;
case PROGCHANGE:
//printPCData("ProgramChange", midiData);
break;
default:
//Serial.println(midiData.cmd, HEX);
break;
}
}
}
}
void printNoteData(char* msg, DataStruct& mD) {
Serial.print(msg);
Serial.print("\t key 0x");
Serial.print(mD.key, HEX);
Serial.print("\t velocity 0x");
Serial.println(mD.velocity, HEX);
}
void printCCData(char* msg, DataStruct& mD) {
Serial.print(msg);
Serial.print("\t controller 0x");
Serial.print(mD.controller, HEX);
Serial.print("\t value 0x");
Serial.println(mD.velocity, HEX);
}
void printPCData(char* msg, DataStruct& mD) {
Serial.print(msg);
Serial.print("\t programm number 0x");
Serial.println(mD.program, HEX);
}
void USB_noteOn(byte channel, byte pitch, byte velocity) {
midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity};
MidiUSB.sendMIDI(noteOn);
MidiUSB.flush();
}
void USB_noteOff(byte channel, byte pitch, byte velocity) {
midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity};
MidiUSB.sendMIDI(noteOff);
MidiUSB.flush();
}
"verysimplemidi.h"
#pragma once
// V0.3
// ec2021
struct DataStruct {
boolean isValid = false;
byte cmd;
byte channel;
byte key;
byte velocity;
byte &controller = key;
byte &program = key;
};
constexpr byte NOTEON{ 0x90 };
constexpr byte NOTEOFF{ 0x80 };
constexpr byte AFTERTOUCH{ 0xA0 };
constexpr byte CTRLCHANGE{ 0xB0 };
constexpr byte PROGCHANGE{ 0xC0 };
enum CmdStates { DONTCARE,
CMDNOTEON,
CMDNOTEOFF,
CMDAFTERTOUCH,
CMDCTRLCHANGE,
CMDPROGCHANGE };
class VerySimpleMidi {
private:
CmdStates cmdState = DONTCARE;
DataStruct midiData;
byte cmdData[2];
byte dataRead = 0;
byte channel = 0;
byte inByte = 0;
void changeState();
void handleData();
void handleRTMsg();
void evaluateInByte();
void prepareData();
void copyDataToStruct();
byte getCmdFromState();
public:
boolean dataReceived(byte byteRead);
DataStruct getData();
};
DataStruct VerySimpleMidi::getData() {
return midiData;
}
boolean VerySimpleMidi::dataReceived(byte byteRead) {
inByte = byteRead;
evaluateInByte();
return midiData.isValid;
}
void VerySimpleMidi::evaluateInByte() {
byte mode = 0;
midiData.isValid = false;
if (inByte < 0x80) {
mode = 1;
};
if (inByte >= 0xF8) {
mode = 2;
};
switch (mode) {
case 0:
changeState();
break;
case 1:
handleData();
break;
case 2:
handleRTMsg();
break;
}
}
void VerySimpleMidi::changeState() {
byte cmd = inByte & 0xF0;
switch (cmd) {
case NOTEON:
cmdState = CMDNOTEON;
prepareData();
break;
case NOTEOFF:
cmdState = CMDNOTEOFF;
prepareData();
break;
case AFTERTOUCH:
cmdState = CMDAFTERTOUCH;
prepareData();
break;
case CTRLCHANGE:
cmdState = CMDCTRLCHANGE;
break;
case PROGCHANGE:
cmdState = CMDPROGCHANGE;
break;
default:
cmdState = DONTCARE;
break;
}
}
void VerySimpleMidi::prepareData() {
dataRead = 0;
midiData.isValid = false;
channel = (inByte & 0x0F);
}
void VerySimpleMidi::handleData() {
switch (cmdState) {
case CMDNOTEON:
case CMDNOTEOFF:
case CMDAFTERTOUCH:
case CMDCTRLCHANGE:
cmdData[dataRead] = inByte;
dataRead++;
if (dataRead == 2) {
copyDataToStruct();
}
break;
case CMDPROGCHANGE:
cmdData[0] = inByte;
copyDataToStruct();
break;
default:
// Ignore all other data
break;
}
}
byte VerySimpleMidi::getCmdFromState() {
byte returnValue = DONTCARE;
switch (cmdState) {
case CMDNOTEON:
returnValue = NOTEON;
break;
case CMDNOTEOFF:
returnValue = NOTEOFF;
break;
case CMDAFTERTOUCH:
returnValue = AFTERTOUCH;
break;
case CMDCTRLCHANGE:
returnValue = CTRLCHANGE;
break;
case CMDPROGCHANGE:
returnValue = PROGCHANGE;
break;
default:
returnValue = DONTCARE;
}
return returnValue;
}
void VerySimpleMidi::copyDataToStruct() {
midiData.cmd = getCmdFromState();
midiData.channel = channel;
midiData.key = cmdData[0];
midiData.velocity = cmdData[1];
midiData.isValid = true;
dataRead = 0;
}
void VerySimpleMidi::handleRTMsg() {
// TBD in future
}
Just put both files in the same directory.
I wrote the library to handle the situation that Midi allows to send a command like "NoteOn" and the data of several keys without repeating the command to save time ...
So it could be a sequence like
- "NoteOn Pitch1 Velocity1 Pitch2 Velocity2 Pitch3 Velocity3"
instead of
- "NoteOn Pitch1 Velocity1 NoteOn Pitch2 Velocity2 NoteOn Pitch3 Velocity3"
see Midi Running Status
I did not have the need to handle AFTERTOUCH, CTRLCHANGE or PROGCHANGE so they are untested.
And there is only a placeholder to handle "Real Time Messages".
See here https://www.recordingblogs.com/wiki/midi-system-realtime-messages