みどりの工具箱

プログラミングや電子工作について書きます

Picoを使ってPSS-A50にサステインペダルを付けてみた

Raspberry Pi Picoを使って、無改造でYAMAHAのミニキーボード PSS-A50にサステインペダルを取り付けられるようにしました。

実現方法

PSS-A50のMIDI入力機能を利用します。PSS-A50から出力されたMIDI情報をそのままPSS-A50に送り返し、その上にペダル情報を乗せることで実現します。

そのためにはMIDIホストが必要になります。PCでもラズパイでもESP32でもよいのですが、私はRaspberry Pi Picoを使用しました。Raspberry Pi PicoはUSBホストになることができ、TinyUSBというライブラリを使ってホスト機能を実現できます。

しかし本家TinyUSBにはMIDIバイスの実装はあるのですが、MIDIホストはありません。自分で実装するしかないかと思っていましたが、探してみるとMIDIホストを実装している方がいたのでそれを使いました。

使用したMIDIホスト実装→GitHub - rppicomidi/tinyusb at midihost

完成図

完成図

使ったものを記載します。

  • Raspberry Pi Pico
  • MicroUSBケーブル
  • USB OTGケーブル(A to MicroB)
  • ブレッドボード
  • ブレッドボード用5V電源
  • ジャンパワイヤ
  • ペダル(YAMAHA FC5)
  • φ6.3mm ジャック
  • ミノムシクリップ
  • モバイルバッテリー

実装

実装とビルド済みのバイナリは GitHubに上げています。
GitHub - ym0268/pico_midi_pedal: Pedal Controller for PSS-A50

ほぼサンプルコードをまねして作ったので大したことはしていませんが、作っている中で気づいたことなどを書きます。

各ファイルの内容は下記のようにしました。

  • main.c
    メインループ処理。TinyUSB, ステータスLED, ペダル検知処理, MIDIホスト処理を実行。 tinyusb/examples/host/midi/src/main.c を参考にして作成。

  • midi_app.c, midi_app.h
    MIDI関係の処理を実行。 tinyusb/examples/host/midi/src/midi_app.c を参考にして作成。

  • midi_gpio.c, midi_gpio.h
    ペダル検知のための処理

  • tusb_config.h
    TinyUSBの設定ファイル。サンプルからコピーした。

MIDIホストの処理

MIDIホストのサンプルコードでポイントになると思った箇所は以下の3つです。

1. sleepを使わない
// midi_app.c (サンプルコードから一部抜粋)
if ( board_millis() - start_ms < interval_ms)
  {
    return; // not enough time
  }
  start_ms += interval_ms;

このように書いているのはUSBタスクを止めないためでしょう。重たい処理を行う場合はPicoのもう1つのコアにやらせるとか、RTOSを使うといった工夫が必要になりそうです。今回はGPIOにつないだペダルの状態を読むだけなので、sleep系の処理を使わないことだけ気を付けました。

2. MIDIの送信は一度キューに格納してから行う

キューへの格納は、 tuh_midi_stream_write() で行い、デバイスへの送信処理は tuh_midi_stream_flush() で行っているようです。キューのデフォルト最大サイズが64 (CFG_TUH_MIDI_TX_BUFSIZE) なので、無限ループで値を格納し続けたりするとあふれます。

// midi_app.c (サンプルコードから一部抜粋)
static void test_tx(void)
{
// (略)
  // transmit any previously queued bytes
  tuh_midi_stream_flush(midi_dev_addr);  // ★デバイスへの送信処理
  // Blink every interval ms
  if ( board_millis() - start_ms < interval_ms)
  {
    return; // not enough time
  }
  start_ms += interval_ms;
// (略)
    nwritten += tuh_midi_stream_write(midi_dev_addr, cable, message, sizeof(message));  // ★ キューへ格納
// (略)
}

私は、使いやすいように2つの関数に分けて書きました(midi_tx()は無限ループで呼び出します)。

// midi_app.c (一部抜粋)
uint32_t midi_stream_write(uint8_t const *buf, uint32_t bufsize)
{
    uint32_t nwritten = 0;
    uint8_t  cable = 0;

    cable = tuh_midih_get_num_tx_cables(midi_dev_addr) - 1;
    nwritten = tuh_midi_stream_write(midi_dev_addr, cable, buf, bufsize);

    return nwritten;
}

static void midi_tx(void)
{
    if(!get_midi_tx_is_active()){
        return;
    }
    tuh_midi_stream_flush(midi_dev_addr);
}
3. MIDIの受信データはコールバックで取得する

tuh_midi_rx_cb() で受信したデータを取得しています。今回は受信データをそのまま送り返すので、この中で tuh_midi_stream_write() を呼び出すようにしました。

// midi_app.c(一部抜粋)
void tuh_midi_rx_cb(uint8_t dev_addr, uint32_t num_packets)
{
    if (midi_dev_addr == dev_addr)
    {
        if (num_packets != 0)
        {
        uint8_t cable_num;
        uint8_t buffer[48];
        uint32_t bytes_read = tuh_midi_stream_read(dev_addr, &cable_num, buffer, sizeof(buffer));
        TU_LOG1("Read bytes %u cable %u", bytes_read, cable_num);
        TU_LOG1_MEM(buffer, bytes_read, 2);

        /* loop back to midi device */
        tuh_midi_stream_write(dev_addr, cable_num, buffer, bytes_read);  // ★ここに追加した
        }
    }
}

MIDIをそのまま送り返してよいのか、というところですが、良くはないと思います。それについては別の機会に考えたいと思います。

ペダルの状態を取得

ペダルは踏んでいるときと踏んでいないときのどちらで短絡するかがメーカによって異なります。私が使用したYAMAHA FC5は踏んでいないときに短絡するタイプです。せっかくなのでPicoのピンを短絡するかどうかで切り替えできるようにしました。

// midi_gpio.c (一部)
bool get_pedal_is_normally_closed(void)  // ★起動時に呼び出してペダルのタイプを設定
{
    return !gpio_get(PIN_PEDAL_TYPE);
}

bool get_pedal_is_open(void)
{
    return gpio_get(PIN_PEDAL_0);
}

// main.c (一部)
    /* get pedal state */
    pedal_on = !get_pedal_is_open();     /* normally open: open=off, normally close: open=on */
    if(pedal_is_normally_closed){
        pedal_on = !pedal_on;
    }

ペダル情報送信

ペダル(サステイン)情報は、MIDIコントロールチェンジ(0xBn)の0x40番です。なので、
ペダルON :0xBn 0x40 0x7F
ペダルOFF:0xBn 0x40 0x00
(nはチャンネル番号)
を送信します。PSS-A50では鍵盤の演奏はチャンネル0になっているようなので、実装は0固定(つまり、0xB0)にしました。試していませんが、別のチャンネルにすると音色切り替えが効かなくなるのではないかと思います。

ペダルの状態を読む間隔は適当に10msにしました。また、MIDI機器が接続されていない状態でキューに書き込むとあふれてしまうので、 get_midi_tx_is_active() というのを作成して判定を入れています(判定はサンプルコードそのままです)。

// main.c (一部)
void pedal_task(void)
{
    uint8_t             buf[3];
    static  uint32_t    pedal_task_start_ms = 0;
    const   uint32_t    interval_ms = 10;  // ★ 10ms間隔でピン情報を取得
    static  bool        stored_pedal_on = false;
    bool                pedal_on;

    if(!get_midi_tx_is_active()){  //★MIDI機器が使用可能かの判定
        return;
    }
    if(board_millis() - pedal_task_start_ms < interval_ms){
        return;
    }
    pedal_task_start_ms += interval_ms;

    /* get pedal state */
    pedal_on = !get_pedal_is_open();     /* normally open: open=off, normally close: open=on */
    if(pedal_is_normally_closed){
        pedal_on = !pedal_on;
    }

    if(stored_pedal_on == pedal_on){  // ★前の状態から変化がなければ何もしない
        return;
    }
    stored_pedal_on = pedal_on;

    /* create sustain message */
    buf[0] = 0xB0;                      /* Control Change (0ch) */
    buf[1] = 0x40;                      /* Sustain pedal on/off */
    buf[2] = pedal_on ? 0x7F : 0x00;    /* ON/OFF               */

    midi_stream_write(buf, sizeof(buf));
}

配線

USBホストとして機能させるので、PicoについているUSB端子は給電には使用できません。よって、外部から電源を供給する必要があります。

PicoにはUSBホスト用にVBUSという端子が用意されており、ここに5Vを供給することでMicroUSB端子とPico本体に電源を供給できます(参考:Picoピンアサイン)。

ちなみに、PSS-A50をPicoにつないだ時の電流はイヤホン使用時で大体0.1A前後だったので、スピーカーで大きな音を出さないのであれば0.5Aくらいの電源で良いかと思います。

注:電源を供給した状態でPCなどにつなぐとPCが壊れるかもしれないのでファームウェア書き込み時は注意が必要です。

配線したところ
最小構成で配線すると上のような感じになります。左下のパーツがブレッドボード用電源モジュールで、赤スイッチがペダルです。ペダルや電源ケーブルをつなぐと最初に貼った画像のように汚くなるので、ちゃんと作るなら箱に入れたいですね。

ペダル接続部
上の画像はペダルを接続したところです。ブレッドボード上にオレンジ色の短いジャンパワイヤが2本並んでいますが、そのうちの1本でペダルの設定(踏んでいないときに短絡するペダル)を行っています。もう1本はGNDです。

ピンアサインはリポジトリのREADME.mdかmidi_gpio.hを参照してください。
GitHub - ym0268/pico_midi_pedal: Pedal Controller for PSS-A50

使ってみた感想

使うには、PSS-A50のLocal Controlをオフにし、Control Change, Program Changeをオンにする必要があります。

遅延は全く感じられず、十分ペダルとして機能しているように思います。

ただ、MIDIデータをそのまま送り返しているせいか、起動直後の音がNo.2(エレピ)の音になっていたり、録音したデータの初回再生時の音が全部No.1(グランドピアノ)になったりと、少し変な動作をします。 このあたりは今後の課題ですが、録音を使わなければ問題ないので結構満足です。

今回作ったのはサステインペダルでしたが、MIDI情報を送り返してその上に付加情報を乗せるという方法を使えばピッチベンドやエクスプレッション、ポルタメントタイムの設定など様々な機能を付加させることができるはずなので試してみようと思っています。

リンク

  1. 今回の実装
    GitHub - ym0268/pico_midi_pedal: Pedal Controller for PSS-A50
  2. TinyUSB
    GitHub - hathach/tinyusb: An open source cross-platform USB stack for embedded system
  3. TinyUSBのMIDIホスト実装
    GitHub - rppicomidi/tinyusb at midihost
  4. PSS-A50 MIDIリファレンス
    https://jp.yamaha.com/files/download/other_assets/6/1282286/pssa50_ja_mr_a0.pdf
  5. picoのサイト
    Raspberry Pi Documentation - Raspberry Pi Pico and Pico W