지난 4월 말쯤에 저는 문뜩 옆에 있는 오래된 신디사이저를 컴퓨터와 연결하면 어떨가 생각 했습니다. 뒤쪽에 연결 가능한 포트를 보기 전 까진 말이죠.

USB가 없다니 이게 무슨 소리야!

그래서 포기를 하려는 찰나에 집에서 MIDI to USB MIDI를 본것 같아서 찾아보았고 실제로 있더군요.

MIDIMAN MIDISPORT 2x2. 출처: https://commons.wikimedia.org/wiki/File:MIDIMAN_USB_MIDISPORT_2x2.jpg

그래서 사용을 하려고 연결을 한 후 사이트에서 드라이버를 다운로드 받으려 했는데,  macOS 10.15 버전이 없더군요! 왜인지 곰곰히 생각을 해봤는데, 이 제품은 2000년에 출시가 되었으며 저 친구를 지원하는 가장 최신 버전의 드라이버 릴리스 날자 역시 2009년에 머물러 있었습니다. 그런데 2009년이면 제조사에선 32-Bit 맥을 포기 할 수 없었을 것이고, 모두가 알다시피 macOS 10.15에선 32-Bit 호환성을 버렸습니다. 그래서 이 드라이버를 깔아도 사용할 수 없을것이라 생각을 했고 역시나 사용이 불가능 했습니다.

그런데 문뜩 든 생각이, 'Arduino를 이용하면 가능하지 않을까?' 였습니다. 그래서 바로 검색에 착수하였고 역시나 Arduino를 이용해 MIDI 신호를 읽는 자료가 많이 있더군요.


회로 설계

그래서 검색한 결과를 토대로 회로를 짜려고 보는데, 쓰기는 상관 없지만 읽기를 하려면 1N924 다이오드와 옵토커플러가 필요했습니다. 구매해야 하나 고민하던 도중 위에 언급한 MIDISPORT 2x2에 있지 않을까 하며 분해를 하였고 실제로 있었습니다.

출처: https://youtu.be/qOnSU3csuE4?t=132

하지만 문제가 생겼습니다. 일단 다이오드는 MIDISPORT 2x2도 1N924를 사용하여 문제가 없었지만, 제가 찾아본 Arduino MIDI 읽기 회로들은 대부분 6N138 옵토커플러를 사용했습니다. 하지만 저에게 있는 옵토커플러는 위 사진에서 보시다시피 IS900 옵토커플러 였습니다.

대부분 MIDI 읽기 할 때 사용하는 회로도. 출처: https://www.instructables.com/id/Send-and-Receive-MIDI-with-Arduino/

하지만 다행히도, PC-900을 사용한 예제가 있었고, PC-900은 핀 갯수와 각 핀들의 역할도 같기에 그 예제를 사용하기로 하였습니다.

PC-900을 이용한 회로도, MIDI_RX가 우리가 읽는 신호. 출처: https://www.midi.org/midi/forum/1397-how-to-make-midi-project-that-have-3-midi-input-channels
PC-900 핀 번호와 역할들. 출처: http://pdf.datasheetcatalog.com/datasheets/1150/36157_DS.pdf
IS900 핀 번호와 역할들. 출처: https://datasheet.datasheetarchive.com/originals/distributors/Datasheets-15/DSA-299441.pdf

그리고 회로 완성! 이제 코딩을 해봅시다.

회로 완성!

코딩할 시간!

먼저 코딩을 하기 전 MIDI 신호를 읽고 쓰는걸 편하게 해줄 라이브러리를 구하였습니다. USB MIDI는 Arduino 공식 라이브러리를 사용 하였으며 MIDI 라이브러리는 아래의 라이브러리를 사용하였습니다.

FortySevenEffects/arduino_midi_library
MIDI for Arduino. Contribute to FortySevenEffects/arduino_midi_library development by creating an account on GitHub.

먼저 아래의 예제를 가져왔습니다.

#include <MIDI.h>

MIDI_CREATE_DEFAULT_INSTANCE();

// -----------------------------------------------------------------------------

// This function will be automatically called when a NoteOn is received.
// It must be a void-returning function with the correct parameters,
// see documentation here:
// https://github.com/FortySevenEffects/arduino_midi_library/wiki/Using-Callbacks

void handleNoteOn(byte channel, byte pitch, byte velocity)
{
    // Do whatever you want when a note is pressed.

    // Try to keep your callbacks short (no delays ect)
    // otherwise it would slow down the loop() and have a bad impact
    // on real-time performance.
}

void handleNoteOff(byte channel, byte pitch, byte velocity)
{
    // Do something when the note is released.
    // Note that NoteOn messages with 0 velocity are interpreted as NoteOffs.
}

// -----------------------------------------------------------------------------

void setup()
{
    // Connect the handleNoteOn function to the library,
    // so it is called upon reception of a NoteOn.
    MIDI.setHandleNoteOn(handleNoteOn);  // Put only the name of the function

    // Do the same for NoteOffs
    MIDI.setHandleNoteOff(handleNoteOff);

    // Initiate MIDI communications, listen to all channels
    MIDI.begin(MIDI_CHANNEL_OMNI);
}

void loop()
{
    // Call MIDI.read the fastest you can for real-time performance.
    MIDI.read();

    // There is no need to check if there are messages incoming
    // if they are bound to a Callback function.
    // The attached method will be called automatically
    // when the corresponding message has been received.
}

// From https://github.com/FortySevenEffects/arduino_midi_library/blob/master/examples/Callbacks/Callbacks.ino

그 후 저는 불필요한 주석들을 제거 한 후 공식 USB MIDI 라이브러리를 불러오고 callback들을 수정 및 추가 하였습니다.

#include <MIDIUSB.h>
// ...

void handleNoteOn(byte channel, byte pitch, byte velocity) {
    midiEventPacket_t event = {0x09, 0x90 | channel, pitch, velocity};
    MidiUSB.sendMIDI(event);
    MidiUSB.flush();
}

void handleNoteOff(byte channel, byte pitch, byte velocity) {
    midiEventPacket_t event = {0x08, 0x80 | channel, pitch, velocity};
    MidiUSB.sendMIDI(event);
    MidiUSB.flush();
}

void handleCC(byte channel, byte number, byte value) {
    midiEventPacket_t event = {0x0B, 0xB0 | channel, number, value};
    MidiUSB.sendMIDI(event);
    MidiUSB.flush();
}

void handlePC(byte channel, byte number) {
    midiEventPacket_t event = {0x0C, 0xC0 | channel, number, 0x0};
    MidiUSB.sendMIDI(event);
    MidiUSB.flush();
}

void handlePitchBend(byte channel, int value) {
    int fix_value = value + 8192;
    byte lowValue = fix_value & 0x7F;
    byte highValue = fix_value >> 7;
    midiEventPacket_t event = {0x0E, 0xE0 | channel, lowValue, highValue};
    MidiUSB.sendMIDI(event);
    MidiUSB.flush();
}

void setup() {
    MIDI.setHandleControlChange(handleCC);
    MIDI.setHandlePitchBend(handlePitchBend);
    MIDI.setHandleProgramChange(handlePC);
    // ...
}

// ...

이제 Arduino를 연결하고 테스트 해봅시다!

보기 좋네요!

전체 코드:

#include <MIDIUSB.h>
#include <MIDI.h>

MIDI_CREATE_DEFAULT_INSTANCE();

void handleNoteOn(byte channel, byte pitch, byte velocity) {
    midiEventPacket_t event = {0x09, 0x90 | channel, pitch, velocity};
    MidiUSB.sendMIDI(event);
    MidiUSB.flush();
}

void handleNoteOff(byte channel, byte pitch, byte velocity) {
    midiEventPacket_t event = {0x08, 0x80 | channel, pitch, velocity};
    MidiUSB.sendMIDI(event);
    MidiUSB.flush();
}

void handleCC(byte channel, byte number, byte value) {
    midiEventPacket_t event = {0x0B, 0xB0 | channel, number, value};
    MidiUSB.sendMIDI(event);
    MidiUSB.flush();
}

void handlePC(byte channel, byte number) {
    midiEventPacket_t event = {0x0C, 0xC0 | channel, number, 0x0};
    MidiUSB.sendMIDI(event);
    MidiUSB.flush();
}

void handlePitchBend(byte channel, int value) {
    int fix_value = value + 8192;
    byte lowValue = fix_value & 0x7F;
    byte highValue = fix_value >> 7;
    midiEventPacket_t event = {0x0E, 0xE0 | channel, lowValue, highValue};
    MidiUSB.sendMIDI(event);
    MidiUSB.flush();
}

void setup() {
    MIDI.setHandleControlChange(handleCC);
    MIDI.setHandlePitchBend(handlePitchBend);
    MIDI.setHandleProgramChange(handlePC);
    MIDI.setHandleNoteOn(handleNoteOn);
    MIDI.setHandleNoteOff(handleNoteOff);
    MIDI.begin(MIDI_CHANNEL_OMNI);
}

void loop() {
    MIDI.read();
}

놀아봅시다!

이 모든 것을 끝내고 테스트를 했습니다.

매우 잘 작동합니다! 이제 드디어 옆에 있던 신디사이저가 공간 차지만 하는 괴물 취급을 벗어나겠네요.

결론

이 글에서 저는 제가 MIDI to USB MIDI를 제작한 과정을 담았습니다. 이후에 벨로시티 127 고정, MIDI Out to Piezo 등등 여러 작업을 한 결과 세상에 단 하나뿐인 MIDI to USB MIDI를 만들었네요. 긴 글 읽어주셔서 감사하며 여러분들도 따라해보시길 바랍니다.