My friend and colleague ValenS is a big fan of chiptune music, and a few months ago discovered what he claims to be the ultimate chiptune tracker, especially because it allows FM synthesis: klystrack.
Problem is, he wanted to input his music onto the PC using a conventional piano keyboard, while apparently the software does not support MIDI input.
Originally ValenS contacted me to help him to scavenge a conventional PC keyboard, and make it into a musical keyboard. However, that would have involved some mechanical hacking that be both seemed to fear. Moreover, we did not even had an USB keyboard to scavenge at hand, which made the task of buying a keyboard in purpose for that, a little bit silly (at least to me).
After some brainstorming, that passed through also reinventing the C64 keyboard overlay, and buying a fatar keybed, I proposed instead to go the other way round: taking an existing musical keyboard, and translate its output into a conventional QWERTY. This could have been done with a microcontroller with a MIDI input, and an USB output: sounds more like MY type of tinkering.
After some little research, I found that the Arduino Micro had the needed USB output, and some compatible Arduino PRO Micro were available for cheap.
While Arduino MIDI shields are available, I find them to be a bit expensive and bulky, therefore I decided to go for making my own board.
MIDI protocol has turned 36 (being born in 1983 like those hands that are writing), and it is essentially an UART port, with a non-PC standard speed of 31.25 kbit/s, and an opto-coupled circuit to avoid ground-loops.
The circuit to implement is found easily everywhere, and I report it here.
I could buy all the parts for very reasonable price on a Chinese market site.
NB: probably other optocouplers (cheaper) than 6N138 could be used, however care must be taken in verifying the commutation speed. Long story short: I stuck with the "safe" choice.
I have started prototyping the circuit with a breadboard, as you see below.
I have enjoyed using again my rockband 3 keyboard controller... and it worked!
So now a few technical details: I have created an enumerator to contain the received MIDI pitch, and mapped in a global variable the corresponding letter to be generated.
enum midi_pitch {
MIDI_B2 = 47,
MIDI_C3 = 48,
MIDI_D3b,
MIDI_D3,
MIDI_E3b,
MIDI_E3,
MIDI_F3,
MIDI_G3b,
MIDI_G3,
MIDI_A3b,
MIDI_A3,
MIDI_B3b,
MIDI_B3,
MIDI_C4,
MIDI_D4b,
MIDI_D4,
MIDI_E4b,
MIDI_E4,
MIDI_F4,
MIDI_G4b,
MIDI_G4,
MIDI_A4b,
MIDI_A4,
MIDI_B4b,
MIDI_B4,
MIDI_C5,
MIDI_D5b
};
#define FIRST_MIDI_NOTE MIDI_B2
#define LAST_MIDI_NOTE MIDI_D5b
char midi_map[] = {
' ', // [MIDI_B2]
'z', // [MIDI_C3]
's', // [MIDI_D3b]
'x', // [MIDI_D3]
'd', // [MIDI_E3b]
'c', // [MIDI_E3]
'v', // [MIDI_F3]
'g', // [MIDI_G3b]
'b', // [MIDI_G3]
'h', // [MIDI_A3b]
'n', // [MIDI_A3]
'j', // [MIDI_B3b]
'm', // [MIDI_B3]
'q', // [MIDI_C4]
'2', // [MIDI_D4b]
'w', // [MIDI_D4]
'3', // [MIDI_E4b]
'e', // [MIDI_E4]
'r', // [MIDI_F4]
'5', // [MIDI_G4b]
't', // [MIDI_G4]
'6', // [MIDI_A4b]
'y', // [MIDI_A4]
'7', // [MIDI_B4b]
'u', // [MIDI_B4]
'i', // [MIDI_C5]
' ' // [MIDI_D5b]
};
The loop only deals with the Note On and Note Off messages.
case midi::NoteOff:
pitch = MIDIUART.getData1();
midi_note_on[ pitch - FIRST_MIDI_NOTE ] = false;
break;
case midi::NoteOn:
pitch = MIDIUART.getData1();
midi_note_on[ pitch - FIRST_MIDI_NOTE ] = true;
break;
So at this point a keypress was generated when pushing a key, but unfortunately that is NOT how a PC keyboard works: actually if you keep a key presse, several key-strikes are generated at some rate, and apparently this has an impact in usability of the tracker program.
In order to imitate this behavior, I have created a map of the notes pressed, and called a function that regenerates the key-press at each iteration of the loop.
bool midi_note_on[LAST_MIDI_NOTE - FIRST_MIDI_NOTE] = {false};
void midi_retrigger(void *)
{
uint8_t pitch;
for(pitch = MIDI_B2; pitch <= MIDI_D5b; pitch++)
{
if(midi_note_on[pitch])
Keyboard.print( midi_map[ pitch ] );
}
}
void loop()
{
uint8_t pitch;
delay(100); // wait for 100ms
midi_retrigger();
...
}
However, in order to reduce the rate of the generation of the key-strokes, I had to include a delay function, that in practice also generates a latency in the responsivity of the software.
I think the right way to solve this is to use a non-blocking timer instead, that only takes care of re-triggering the key presses, while the main loop reads the MIDI input. Fortunately arduino supports the timer library that just does that. However when I have included the timer library, I started having a weird warning about memory space, and the arduino got bricked at next reboot, so I will stick with the first implementation, and live with the latency!
EDIT:
I was able to reduce the RAM occupation, by allocating the variables midi_map
and midi_note_on
only for the notes that are actually mapped. This freed enough RAM to be able to load the timer.h and Timer.h libraries, but despite my efforts, I was not able to get the periodic interrupt working. Instead, I have added a delay into the midi_retrigger function, that is applied only when one (or more) note(s) is actually re-triggered. This solution stays quite responsive, while re-triggering key strokes at a relatively low pace.
Below a photo of the final assembly.
Last but not least, I have worked on a MIDI IN or OUT expansion board that could also support the wemos D1 mini.
Comments