見出し画像

KORG Gadget for Nintendo SwitchにUSB MIDIキーボードを接続する「MIDI Converter Gadget」

はじめに

MIDI Converter GadgetはKORG Gadget for Nintendo SwitchにUSB MIDIキーボードを接続するためのガジェットです。簡単な電子工作で作成できます。

注意事項

メーカーの想定外の利用法のため使用はご自身の判断、責任において行なってください。本機の使用において故障や損害が生じても一切の責任を負いません。予めご了承ください。

概要

KORG Gadget for Nintendo Switch(以下KORG Gadget)はUSB MIDIキーボードを認識しません。
しかしPCキーボードを鍵盤として使用できます。
KORG GadgetでスケールをChromatic、キーをCに設定してScale Onにすると
図1の並びでキーボードのZから6の間にC3からB5の音階が割り当てられます。

図1

そこでMIDIの鍵盤情報をPCキーボードのZから6の信号に変換してKORG Gadgetを演奏します。
そのため以下のような制限があります。
1.同時発音数は6音です。
2.音域は3オクターブの36音です。
3.ベロシティはありません。
4.その他のMIDIの機能は使えません。
5.オクターブを変更したい場合はKORG GadgetのOCTAVEで変更します。

MIDIキーボードには図2のように機能を割り振りました。

図2

C2はTabキーに割り当てています。Scale On/offの切り替えに使用します。
TabキーはPCキーボードを図1かピアノ風の配置に切り替えます。
C1〜B1はKORG Gadgetのキー設定と組み合わせて演奏できる音域を変更するために使用します。

必要な部品

電子工作の経験がない初心者向けにはんだ付けが必要ない構成で説明します。

1.シリアル接続版・キーボード/マウス エミュレータ

「みんなのラボ」製。マルツオンラインから購入できます。

2.Raspberry Pi Pico H

写真のようにピンヘッダー(金属製の突起)が付いているモデルです。
電子部品・電子パーツのショップで購入できます。

3.ブレッドボード

写真のようなものです。400穴タイプです。

4.ジャンパーワイヤ

4本必要です。

5.USB OTGケーブル

USB端子をUSB Hostとして使用するために使います。別名ホストケーブル。
サンワサプライAD-USB19BKとバッファローBSMPC11C01BKで動作確認とれています。

6.Micro USB - USB Type-CケーブルまたはMicro USB - USB Type-Aケーブル

Raspberry Pi Picoとパソコンを接続するために使用します。
写真はRaspberry Pi Picoに接続するMicro USB端子です。

7.パソコン

WindowsまたはMacのどちらでもいいです。Raspberry Pi Picoにソフトウェアをインストールするために使います。

8.USBのPCキーボード

必ずしも必要というわけではないのですがトラブルが起きた際に
KORG GadgetとMIDI Converter Gadgetで問題の切り分けができます。
MIDI Converter Gadgetと併用が可能なので操作が便利になります。
作ってみる前にPCキーボードを繋げてみてKORG Gadgetで何ができるのかを確認した方がいいでしょう。

作り方

1.Raspberry Pi Pico Hをブレッドボードにはめ込む

写真のようにRaspberry Pi Pico Hをブレッドボードにはめ込みます。

2.パソコンからRaspberry Pi Picoのソフトウェアをダウンロード

midi_converter_gadget.uf2をダウンロードしてパソコンに保存します。

3.Raspberry Pi Picoのソフトウェアのインストール

Raspberry Pi PicoにUSBケーブル(ここではUSB OTGケーブルを使用しないでください)を接続してRaspberry Pi PicoのBOOTSELボタンを押しながらパソコンにUSBケーブルを接続しましす。成功すると「RPI-RP2」というドライブがマウントされます。先ほどダウンロードしたmidi_converter_gadget.uf2を「RPI-RP2」ドライブにコピーします。インストールに成功すると自動的に「RPI-RP2」ドライブは強制切断されます。パソコンからRaspberry Pi Picoを取り外します。

4.キーボード/マウス エミュレータとRaspberry Pi Picoの接続

キーボード/マウス エミュレータの電源スイッチを5Vに設定します。
キーボード/マウス エミュレータとRaspberry Pi Picoを以下のように接続します。
Tx →Rxを7番(ブレッドボードの左7番)
Rx←Txを6番(ブレッドボードの左6番)
VDDを40番(ブレッドボードの右1番)
GNDを38番(ブレッドボードの右3番)
最後にUSB OTGケーブルを取り付けて完成です。

回路図
キーボード/マウス エミュレータの電源スイッチを5Vに設定
ジャンパーワイヤを取り付けます
ジャンパーワイヤをブレッドボードに取り付けます
USB OTGケーブルを取り付けて完成

KORG Gadget に接続して使ってみる

1.Nintendo SwitchとMIDIキーボードを接続する

キーボード/マウス エミュレータをSwitchに接続します。
Raspberry Pi PicoをMIDIキーボードに接続します。
環境に合わせて変換ケーブル、USBハブ、ドックを使用してください。

接続例
写真はホリのテーブルモード専用ポータブルUSBハブスタンド2ポートを使用しています

2.KORG Gadgetを起動する

KORG Gadgetを起動してソフトシンセの画面を表示します。
左下のSCALEを選択してキーボードの設定を開きます。スケールに「Chromatic」、キーに「C」、コード に「オフ」を選択します。
設定しましたらキーボードの設定を閉じます。

MIDIキーボードを弾いてみてください。Switchから音が出たでしょうか?
出なかった場合は最初からやり直してみてください。
音が出たが音程がおかしい、または音が出ない鍵盤がある場合はScalc offになっています。MIDIキーボードのC2の鍵盤またはPCキーボードのTabキーを押してScalc Onに変更してください。

3.キーの変更

曲によっては演奏できるキーの範囲を高音側に移動した方がいい場合があります。
例として最低音をF3にする場合の説明です。
・KORG Gadgetのキーボードの設定でキーを「F」に変更します。
・MIDIキーボードのF1の鍵盤を押します。
・演奏できる範囲がF3〜E6に変わります。

その他

1.KORG Gadgetのバグ

いくつかKORG Gadgetにバグがあります。PCキーボードで再現するのでMIDI Converter Gadgetのバグではありません。
・オクターブ違いの音の同時押しで音が鳴らなくなる。例えばC3を押しっぱなしにしてC4を連打するとC3の音が止まります。
・ストリング系の音を弾くと音が止まらなくなることがある。バグの再現方法は不明ですが長時間KORG Gadgetを起動させると発生するようです。Switchの画面の鍵盤で音を鳴らすと音は止まりますがしばらくするとまた発生します。こうなったらKORG Gadgetを再起動するしかありません。

2.MIDI端子の場合

MIDI端子をRoland UM-ONE mk2でUSB端子に変換してみました。[COMP/TAB]スイッチを[TAB]に設定することで使用することができました。

3.複数MIDI機器

複数のMIDI機器の接続はサポートしていません。

4.ジャンパーワイヤ

ジャンパーワイヤはやっぱり外れやすいです。実用に使うならはんだ付けが必要でしょう。

5.謝辞

わかりやすい解説書を書いてくださったICHI様
キーボード/マウス エミュレータを作ってくださったみんなのラボ様
TinyUSBのMIDI Hostを公開してくれたrppicomidi氏に感謝します。
おかげで組み込み系は素人の私でも簡単に実装することができました。

6.連絡先

不明点やバグなどがありましたらコメントまたはXの@hemamushonyudoまでお願いします。

7.ソース

ソースはそのうちGitHubで公開します。とりあえず仮で公開します。
ビルドには最新のTinyUSBとusb_midi_hostが必要です。MITライセンスです。

midi_converter_gadget.c

/*
 * The MIT License (MIT)

 *
 * Copyright (c) 2023 rppicomidi
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

#include <stdio.h>

#include "bsp/board_api.h"
#include "pico/binary_info.h"
#include "pico/multicore.h"
#include "pico/stdlib.h"
#include "tusb.h"
#include "usb_midi_host.h"

#define UART_ID uart1
const uint BAUD_RATE = 9600;
const uint UART_TX_PIN = 4;
const uint UART_RX_PIN = 5;

const uint8_t NOTE_C1 = 24;
const uint8_t NOTE_B1 = 35;
const uint8_t NOTE_C2 = 36;
const uint8_t NOTE_C3 = 48;
const uint8_t NOTE_B5 = 83;

const int BUFFER_SIZE = 8;

const uint8_t LOCK = 1;
const uint8_t UNLOCK = 0;

// On-board LED mapping. If no LED, set to NO_LED_GPIO
const uint NO_LED_GPIO = 255;
const uint LED_GPIO = 25;

static uint8_t midi_dev_addr = 0;

uint8_t convert_table[] = {
    0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36, 0x37, 0x38, 0x04, 0x16,
    0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f, 0x33, 0x14, 0x1a, 0x08, 0x15,
    0x17, 0x1c, 0x18, 0x0c, 0x12, 0x13, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23};

uint8_t pressed_keycode[6] = {0, 0, 0, 0, 0, 0};

int buffer_index = 0;
uint8_t buffer_keycode[][7] = {{0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0},
                               {0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0},
                               {0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0},
                               {0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0}};

uint8_t key = 0;

static void turn_on_len(void) { gpio_put(LED_GPIO, true); }
static void turn_off_len(void) { gpio_put(LED_GPIO, false); }

void uart_start(void) {
  stdio_init_all();
  uart_init(UART_ID, BAUD_RATE);
  uart_set_format(UART_ID, 8, 1, UART_PARITY_NONE);
  uart_set_fifo_enabled(UART_ID, false);
  gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
  gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);
}

void press(uint8_t ckey, uint8_t ukey1, uint8_t ukey2, uint8_t ukey3,
           uint8_t ukey4, uint8_t ukey5, uint8_t ukey6) {
  uint8_t sum = 0x10C + ckey + ukey1 + ukey2 + ukey3 + ukey4 + ukey5 + ukey6;
  uint8_t keyPress[14] = {0x57,  0xAB,  0x00,  0x02,  0x08,  ckey,  0x00,
                          ukey1, ukey2, ukey3, ukey4, ukey5, ukey6, sum};
  uart_write_blocking(UART_ID, (const uint8_t *)keyPress, 14);
}

static void send_next_note(bool connected) {
  // device must be attached and have at least one endpoint ready to receive a
  // message
  if (!connected || tuh_midih_get_num_tx_cables(midi_dev_addr) < 1) {
    return;
  }
  tuh_midi_stream_flush(midi_dev_addr);
}

uint8_t convert_midi_to_keycode(uint8_t note) {
  // Scale On/off
  if (note == NOTE_C2) {
    return 0x2b;  // Tab key
  }
  // change Key
  if (note >= NOTE_C1 && note <= NOTE_B1) {
    key = note - NOTE_C1;
    return 0;
  }
  note = note - key;
  if (note < NOTE_C3 || note > NOTE_B5) {
    return 0;
  }
  return convert_table[note - NOTE_C3];
}

void press_key(uint8_t keycode) {
  for (int i = 0; i < 6; i++) {
    if (pressed_keycode[i] == 0) {
      pressed_keycode[i] = keycode;
      return;
    }
  }
}

void pull_key(uint8_t keycode) {
  for (int i = 0; i < 6; i++) {
    if (pressed_keycode[i] == keycode) {
      pressed_keycode[i] = 0;
      return;
    }
  }
}

bool set_buffer(void) {
  if (buffer_keycode[buffer_index][6] == UNLOCK) {
    buffer_keycode[buffer_index][0] = pressed_keycode[0];
    buffer_keycode[buffer_index][1] = pressed_keycode[1];
    buffer_keycode[buffer_index][2] = pressed_keycode[2];
    buffer_keycode[buffer_index][3] = pressed_keycode[3];
    buffer_keycode[buffer_index][4] = pressed_keycode[4];
    buffer_keycode[buffer_index][5] = pressed_keycode[5];
    buffer_keycode[buffer_index][6] = LOCK;
    return true;
  }
  return false;
}

void clear_current_buffer(void) {
  buffer_keycode[buffer_index][0] = 0;
  buffer_keycode[buffer_index][1] = 0;
  buffer_keycode[buffer_index][2] = 0;
  buffer_keycode[buffer_index][3] = 0;
  buffer_keycode[buffer_index][4] = 0;
  buffer_keycode[buffer_index][5] = 0;
}

void inincrement_buffer_index(void) {
  buffer_index = buffer_index + 1;
  if (buffer_index == BUFFER_SIZE) buffer_index = 0;
}

// Core 1 Main Code
void core1_entry(void) {
  uart_start();

  while (1) {
    int index = multicore_fifo_pop_blocking();
    press(0, buffer_keycode[index][0], buffer_keycode[index][1],
          buffer_keycode[index][2], buffer_keycode[index][3],
          buffer_keycode[index][4], buffer_keycode[index][5]);
    buffer_keycode[index][6] = UNLOCK;
  }
}

int main(void) {
  bi_decl(bi_program_description("A USB MIDI host example."));
  bi_decl(bi_1pin_with_name(LED_GPIO, "On-board LED"));

  multicore_launch_core1(core1_entry);  // Start core 1 - Do this before any
                                        // interrupt configuration

  board_init();
  printf("Pico MIDI Host Example\r\n");
  tusb_init();

  // Map the pins to functions
  gpio_init(LED_GPIO);
  gpio_set_dir(LED_GPIO, GPIO_OUT);

  turn_on_len();

  while (1) {
    tuh_task();

    bool connected = midi_dev_addr != 0 && tuh_midi_configured(midi_dev_addr);

    send_next_note(connected);
  }
}

//--------------------------------------------------------------------+
// TinyUSB Callbacks
//--------------------------------------------------------------------+

// Invoked when device with hid interface is mounted
// Report descriptor is also available for use.
// tuh_hid_parse_report_descriptor() can be used to parse common/simple enough
// descriptor. Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE,
// it will be skipped therefore report_desc = NULL, desc_len = 0
void tuh_midi_mount_cb(uint8_t dev_addr, uint8_t in_ep, uint8_t out_ep,
                       uint8_t num_cables_rx, uint16_t num_cables_tx) {
  printf(
      "MIDI device address = %u, IN endpoint %u has %u cables, OUT endpoint %u "
      "has %u cables\r\n",
      dev_addr, in_ep & 0xf, num_cables_rx, out_ep & 0xf, num_cables_tx);

  if (midi_dev_addr == 0) {
    // then no MIDI device is currently connected
    midi_dev_addr = dev_addr;
  } else {
    printf(
        "A different USB MIDI Device is already connected.\r\nOnly one device "
        "at a time is supported in this program\r\nDevice is disabled\r\n");
  }
}

// Invoked when device with hid interface is un-mounted
void tuh_midi_umount_cb(uint8_t dev_addr, uint8_t instance) {
  if (dev_addr == midi_dev_addr) {
    midi_dev_addr = 0;
    printf("MIDI device address = %d, instance = %d is unmounted\r\n", dev_addr,
           instance);
  } else {
    printf("Unused MIDI device address = %d, instance = %d is unmounted\r\n",
           dev_addr, instance);
  }
}

void tuh_midi_rx_cb(uint8_t dev_addr, uint32_t num_packets) {
  uint8_t keycode = 0;
  if (midi_dev_addr == dev_addr) {
    if (num_packets != 0) {
      uint8_t cable_num;
      uint8_t buffer[48];
      while (1) {
        uint32_t bytes_read =
            tuh_midi_stream_read(dev_addr, &cable_num, buffer, sizeof(buffer));
        if (bytes_read == 0) return;
        printf("MIDI RX Cable #%u:", cable_num);
        for (uint32_t idx = 0; idx < bytes_read; idx = idx + 3) {
          printf("%02x ", buffer[idx]);
          printf("%02x ", buffer[idx + 1]);
          printf("%02x ", buffer[idx + 2]);
          keycode = convert_midi_to_keycode(buffer[idx + 1]);
          if (keycode == 0) {
            continue;
          }
          if (buffer[idx] >= 0x80 && buffer[idx] <= 0x8f) {
            pull_key(keycode);
          }
          if (buffer[idx] >= 0x90 && buffer[idx] <= 0x9f) {
            if (buffer[idx + 2] == 0) {
              pull_key(keycode);
            } else {
              press_key(keycode);
            }
          }

          if (multicore_fifo_wready() && set_buffer()) {
            multicore_fifo_push_blocking(buffer_index);
            inincrement_buffer_index();
          } else {
            clear_current_buffer();
          }
        }
        printf("\r\n");
      }
    }
  }
}

void tuh_midi_tx_cb(uint8_t dev_addr) { (void)dev_addr; }

この記事が気に入ったらサポートをしてみませんか?