1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

マイコンを使ってUSB-MIDIインターフェースを作る

Last updated at Posted at 2025-06-16

背景

先日、MacとiPhoneでStandard MIDI Fileを再生するアプリを公開しました。
その動作を確認するため、メルカリで昔所有していたRoland社のSC-88Proを見つけたので、早速購入しました。

スクリーンショット 2025-06-16 16.37.21.png
(島村楽器のサイトより)

Macのアプリで再生するには、USB-MIDIインターフェースを使い、SC-88ProのMIDI IN端子にケーブルを接続します。

iPhoneのアプリで再生するには、Bluetooth-MIDIインターフェースを使い、やはり同じくSC-88ProのMIDI IN端子にケーブルを接続します。

そう、ケーブルをMacとiPhoneで繋ぎかえなきゃいけないんです。
地味に面倒なんですよねぇ。こういうのって。

MIDI信号のミキサーがあればなぁ...
昔はMIDI信号をミキシングするものがありましたし、私も結構高性能なのを所有していました。

以前からこの分野には興味があり、ちょうど個人的にも自由に使える時間があるので、今のうちにやってみよう!ということで、MIDIインターフェースを作ってみることにしました。

手作りで感触を試す

IMG_0781.jpeg

手持ちのSTM32F407 Discovery基板を使い、ユニバーサル基板上にLEDやディップスイッチ、MIDIコネクタを載せて、ひとまず必要なものを用意しました。

MIDIコネクタは、秋月電子に周辺回路もついた便利なものがありました。
これをちょこっと改造して、スタックして6IN/6OUTを実現します。

USB-MIDIはどうやって実現する?

これが一番難しいところでした。
なかなか資料が見つかりません...探し方が悪いのかもしれません。
ネットを調べていて、GitHubにSTM32FマイコンでUSB-MIDIを実現したものを公開されている先人がいました!

この方々のプロジェクトを見ると、STの署名が入ったMIDIのミドルウェアを使っていることがわかりました。
STのウェブサイトでは見つけられませんでしたが、もしかすると過去に配布していたのかもしれません。
これを利用してUSB-MIDIインターフェースを作ってみます。

USBデバイスの設定〜CubeMX

USBはDevice_Onlyに設定します。

スクリーンショット 2025-06-16 16.57.10.png

ミドルウェアはHuman Interface Device Classを選択します。
他の設定は何も変更しなくてOKです。
必要に応じてVIDを変更したりすればいいかと思います。

スクリーンショット 2025-06-16 16.58.23.png

MIDIミドルウェアの組み込み

usbd_midi.cusbd_midi.hをプロジェクトに組み込みます。

スクリーンショット 2025-06-16 17.01.46.png

コンパイラのインクルードパスにusbd_midi.hのパスを追加します。

スクリーンショット 2025-06-16 17.03.35.png

ソースコードの修正

usb_device.cを修正します。

スクリーンショット 2025-06-16 17.05.47.png

usb_device.c(ヘッダインクルードの変更)
// #include "usbd_hid.h"    //コメントアウトする

/* USER CODE BEGIN Includes */
#include "usbd_midi.h"      //インクルードヘッダを追加する
/* USER CODE END Includes */
usb_device.c(USBD_HIDをUSBD_MIDIに変更)
  // if (USBD_RegisterClass(&hUsbDeviceHS, &USBD_HID) != USBD_OK)
  if (USBD_RegisterClass(&hUsbDeviceHS, &USBD_MIDI) != USBD_OK)

もう一つ、usbd_conf.cを修正します。
スクリーンショット 2025-06-16 17.11.20.png

usbd_conf.c
// #include "usbd_hid.h"     //コメントアウトする

/* USER CODE BEGIN Includes */
#include "usbd_midi.h"      //インクルードヘッダを追加する
/* USER CODE END Includes */
usbd_conf.c(USBD_HID_HandleTypeDefをUSBD_MIDI_HandleTypeDef)に変更
  // static uint32_t mem[(sizeof(USBD_HID_HandleTypeDef)/4)+1];/* On 32-bit boundary */
  static uint32_t mem[(sizeof(USBD_MIDI_HandleTypeDef)/4)+1];/* On 32-bit boundary */

main.hに仮想MIDIポート数を記述します。

main.h
/* Exported macro ---------------------------*/
/* USER CODE BEGIN EM */
#define MIDI_IN_PORTS_NUM              0x04
#define MIDI_OUT_PORTS_NUM             0x04

/* USER CODE END EM */

これでコンパイルし、USBケーブルでMacに接続すると、MIDIデバイスとして認識されます。
スクリーンショット 2025-06-16 17.16.10.png

USBから送られてくるMIDIパケット

void USBD_MIDI_OnPacketsReceived(uint8_t *data, uint8_t len)をオーバーライドします。
MIDIデータのポインタ(data)と、データ長(len)が引数で与えられます。
USB-MIDIでは1パケットは4バイトです。
lenは4の倍数、dataのポインタも4ずつ進めていけばOK!

パケットの詳細はUSB-IFが公開している仕様書から確認できます。
Cable NumberがMIDIの番号です。
今回作るMIDIインターフェースは4IN/4OUTなので、Cable Numberは0〜3が入ります。
それぞれに対応したMIDIポート(UART)に振り分けます。

スクリーンショット 2025-06-16 17.25.22.png

スクリーンショット 2025-06-16 17.25.36.png

スクリーンショット 2025-06-16 17.25.48.png

USBから送られたMIDIパケットをUARTで送る

USBから受けたタイミングでそのままUARTで送ればOKです。
単純なUSB-MIDIインターフェースの動作ですね。

さらに付加価値を高めるには、USBから送られてきたMIDIパケットをUARTで送信しつつ保持しておいて、後からいろんな機能実現のために利用します。

USBにMIDIパケットを送る

uint8_t USBD_MIDI_SendPackets(USBD_HandleTypeDef *pdev, uint8_t *data, uint16_t len)を使います。

UARTではパケットという概念がないため、MIDIデータを受けた後解析し、パケットが揃った時にUSBパケット(バイト列)を作ります。
Cable Number、Code Index NumberをUSBパケットの最初に入れ、以降にデータを入れます。

MIDIメッセージはシステム・エクスクルーシブ以外固定長なので、比較的容易に送れますね。
しかも、みんな1パケットあたり4バイト以内で収まります。
システム・エクスクルーシブは全て受け取った後にデータ数を数え、うまくパケットに分割してUSBに送ります。

MIDIと違い、ランニング・ステータスはサポートされません。
省略せず、必ずすべてのデータを送ります。

これらの機能を使えば、標準的なUSB-MIDIインターフェースが作れます。

付加価値〜MIDI-MIDI間のデータプロセッシング

USBとMIDIの間は、標準的なUSB-MIDIインターフェースでいいと思います。
素直にUSBデータをMIDIに送る、素直にMIDIデータをUSBに送る...これでいいです。
実際、USBホスト(Mac /PC)がいろんな機能を持っていたりするので、そっちに任せればいいでしょう。

MIDI-MIDI間のデータプロセッシング...一体どんなものがあるかというと、

  • MIDI INとMIDI OUTのルーティング
  • MIDI INからきたデータのフィルタリング
  • MIDI OUTへのフィルタリング
  • MIDIチャンネルの入れ替え
  • MIDIデータのマージ(ミキシング)

という感じでしょうか。

これはUSBが台頭する前に行われていたMIDIインターフェースのMIDIデータプロセッシングです。
今ならMac / PCでやっちゃんでしょうが、昔はMIDIインターフェースにそういう機能を持っているものがあり、レコーディングスタジオで使われていました。

私が昔使っていたMark of the Unicorn社のMIDI TIME PIECEが世界中で使われていました。

ファームウェアを作っているうちに、このMIDI TIME PIECEを再現しよう!
そう思うようになりました。

データ処理としては難しくなく、複雑...という感じでしょうか。
それでも地道にやっていけば実現は可能です。

この作業をしているときに、ハマったことがあるので、それを記録に残しておきます。

ランニング・ステータス+アクティブ・センシングでデータ復元の失敗

症状

  • 自作USB-MIDIでStandard MIDI Fileは正しく再生できる
  • R社USB-MIDIの出力を自作インターフェースに入れて再生するとメロメロ
  • R社シンセサイザーのMIDI OUTを自作インターフェースに入れて再生すると正常

このことから、

  • 自作インターフェースでUSB->MIDIは正常に再生できる
  • 自作インターフェースのMIDI IN->MIDI OUTに問題がある

ことは確定です。

MIDIデータプロセッシングをするため、MIDI INで受けたデータはひとまず受信バッファに貯めます。
その後、mainループの中で解析して送信先を振り分けたり、フィルタリングで捨てたりします。
MIDIデータプロセッシングのために、ランニング・ステータスで省略されたステータス・バイトも復元して、完全なパケットとして保持します。

R社のUSB-MIDIインターフェース、R社のシンセサイザーとも、MIDIデータを観測すると、両方ともランニング・ステータスを使用しており、状況に応じてステータス・バイトを省略しています。

もちろん、私もランニング・ステータスの対応はしており、ちゃんと処理はできているはず...
調べていくと、アクティブ・センシングがランニング・ステータスの途中に入ってくると、私のファームウェアが正しくランニング・ステータスを復元できないことがわかりました。

R社のシンセサイザーでは、アクティブ・センシングを出力する・しないを設定できるようになっており、アクティブ・センシングは出力しないようにしていました。
そのため、シンセサイザーのMIDI OUTを自作インターフェースに受けた場合は、正しく再生ができた...ということでした。

ranning status.png

ステータス・バイトが来た時、内部処理ではステータス・バイトを更新していたのですが、1バイトのデータ自体はランニング・ステータスを使うことがないので、ランニング・ステータスを利用されることがありません。
ここを直したことで、R社のUSB-MIDIインターフェースで正しく再生できるようになりました。

MIDIでパケットを詰めて送信した時に問題発生

ランニング・ステータスの処理を改善したことで正しく再生できるようになった...ように思えたのですが、ごく稀に音が鳴りっぱなしになるという症状が発生していました。

これは鍵盤を離すというデータが欠けたときに発生します。

自作インターフェースのMIDIデータプロセッシングにミスがあって、データが欠けるのかも...と思い、自作インターフェースのMIDI出力へ送っているノート・オンとノート・オフの組み合わせが正しいかを調べ、さらにMIDI OUTをMIDI INに入れて同じようにノート・オンとノート・オフの組み合わせを調べました。
結果として、どちらもちゃんと組み合わせが正しく、ノート・オンした鍵盤はちゃんとノート・オフしています。
つまり、自作インターフェースはちゃんとデータを出していることになります。

様々なテストや検証をした結果、自作インターフェースのMIDIデータプロセッシングのタイミングによっては、MIDIパケットがつながってUARTで送信することがあります。
この時、(あくまでも想像ですが)SC-88Proの内部状態によってはパケット解析が間に合わず、ノート・オフの情報が内部的に欠落すると、音が鳴りっぱなしになるようです。

MIDIパケット間(UARTデータ間ではない)に若干の余裕が必要な可能性を考えました。
ソフトウェアでわざと時間を作る(waitのような処理)をするのは、個人的には避けたいところ。

そこで、過去の経験からUARTのストップビットを1から2へ変更してみました。
そうしたら、なんと症状が出なくなりました!

これがカラクリ
UARTセッティング.png

ストップビットが増えても論理的にはアイドルなので、受信を待つ動作になります。
ストップビットが増えたことで、パケット間に32usecの余裕が生まれます。
パケット間だけでなく、データ間にも32usecの余分な時間が入りますが、SC-88Proのデータ受信に影響はないでしょう。

SC-88Proを分解したところ、MIDIプロセッシングには日立製作所のH8が使われていました。
時代的にも連続でMIDIパケットを送りつけると、処理が間に合わない時が発生するのかもしれません。
これは仕方ないことで、歩み寄るべき事項でしょう。
幸い、ハードウェア設定で対応できたので、これはラッキーでした。

最後に

ひとまずUDB-MIDIの機能、MIDIデータプロセッシング、そして多くのStandard MIDI Fileの再生が正常にできるようになりました。

今は基板の修正をして、さらに使い勝手の良いものにしようと思っています。
最終版の基板が出来上がったら、報告&レポートします。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?