Tech

GoでSMF(MIDI)ファイルを読み込んでみた

投稿日:

はじめに

私の最近の趣味はDTMで、時々曲を作っているのですが、単に曲を作るだけでなく、その打ち込みデータを他のこともにも活用できないかと考えました。そこで、DTMで作成したMIDIデータをGoで読み込み、演奏情報を読み込み、音符情報に変換する部分をコードで書いてみました。

MIDIとは?

MIDIとは、電子楽器の演奏データを、機器間で転送するための規格です。例えば、MIDIキーボードでの演奏(INPUT)に対応して、音源から音を鳴らす(OUTPUT)ために、INPUT側の機器からOUTPUT側の機器にリアルタイムにデータを転送するための規格です。(実際には、INPUTがシーケンサであったり、OUTPUTが楽器以外にも照明機器など、さまざまなシーンで使用されています))
このMIDIデータをファイルに記録し、後から再生したり、楽譜として表示したりするための規格の1つにSMF(Standard Midi File)があります。今回はこのSMFを読み込んで、音符の情報に変換したいと思います。

SMFファイルについて

SMFファイルは、ヘッダチャンクとトラックチャンクという2つのチャンクから構成されています。ヘッダチャンクにはSMFファイル全体に関する内容が、トラックチャンクには演奏情報が含まれています。
詳しくは、こちらのサイトに詳しく解説されているのですが、最低限紹介しておきたい内容をピックアップします。

Tickと分解能

SMFでは、音の長さの最小単位がTickとなります。Tickの実際の音価(音の長さ)は、分解能によって決まります。分解能は、MIDIのヘッダ部に含まれていて、四分音符が何Tickで表されるかを示す数値です。分解能が細かければ細かいほど、きめ細かい間隔で音符を並べることができます。
例えば、分解能が1だと、四分音符=1となるため、4分音符より短い音符を置くことができません。通常や32分音符や、16分3連符など、より細かい音符を使うと思うので、分解能は480となっているシーケンサが多いようです。
(四分音符あたりの分解能を指定する他に、タイムコードによって分解能を決めることもできますが、ここでの説明は省略します。)

トラックチャンク

トラックチャンクには、演奏データが含まれます。今回は、音符に変換するにあたって必要な点のみ紹介します。

フィールド名 意味
Delta 480 tick 480 tick 待つ
Event ノートオン | キー番号: 72 | ベロシティ: 127 キー番号72 (真ん中のド) をベロシティ127で鳴らす
Delta 480 tick 480 tick 待つ
Event ノートオフ | キー番号: 72 キー番号72 (真ん中のド) を止める
・・・

上記のように、DeltaとEventを繰り返しながら演奏データが格納されていきます。EventはMIDIイベントのことで、今回はノートオンとノートオフのみを扱います。ちなみに、音を止めるために、ノートオンでベロシティ0と記録される場合もあるため、その場合でも音が止まったものとみなす必要があります。

gomidiを使ってSMFファイルを読み込む

それでは、実際にSMFファイルを読み込みたいと思います。

gomidiの導入

MIDIやSMFを扱うことができるgoのパッケージはいくつかあるのですが、その中でも最近もメンテナンスされているパッケージがgomidiです。

Gitlab:
https://gitlab.com/gomidi/midi

Documentation:
https://pkg.go.dev/gitlab.com/gomidi/midi/v2

以下のコマンドでgomidiを追加します。

実装

まずはソースの全景です。
今回は、SMFファイルの音符データを扱いやすくするために、音を鳴らすタイミング、音の長さ、キー、ベロシティをまとめたNote structを生成していきます。

このソースコードについて説明します。

  1. SMFファイルの読み込み
    SMFファイルを読み込むと、 SMF 型のインスタンスが取得できます。この中には、先ほど説明したヘッダチャンクとトラックチャンクに該当するフィールドを持っています。
  2. 分解能の取得 (ヘッダチャンク)
    分解能は1で取得したSMF内のtimeFormatフィールドで取得できます。こちらも先ほど説明した通り、四分音符あたりの分解能か、タイムコードによる分解能のどちらかの指定が入るため、それぞれに対応したMetricTicks型かTimeCode型のいずれかが入ります。
    いずれの型もTimeForma型を実装しているのですが、今回使用したいメソッドはMetricTicks型にしか実装されていなかったため、キャストしています。
  3. トラック1の読み込み (トラックチャンク)
    演奏データを読み込みます。(今回はトラック1のみにデータが入っています。)
    トラック0には、テンポや拍子など、全トラックに影響するデータが入っていて、実際の演奏情報はトラック1以降に入っています。
  4. MIDIイベントを順番に取得
    3で取得したトラックをforループで回すことで、イベントを取得します。
  5. 絶対時間の記録
    SMFファイル内には絶対時間は存在せず、イベント間のDeltaTime(経過時間)でのみ時間経過が表されているため、DeltaTimeを加算していくことで、絶対時間を保持します。
  6. メッセージによる分岐
    音の鳴り始めと鳴り終わりを取得するため、メッセージにより分岐しています。

    • ev.Message.GetNoteStart(&channel, &key, &velocity) は、メッセージがベロシティ1以上のノートオンメッセージである場合はtrueを返し、そうでなければfalseを返します。引数に与えられたポインタにメッセージの値を代入します。
      ノートオンの場合、Noteを生成して、startedNotesに一旦appendしておきます。
    • ev.Message.GetNoteEnd(&channel, &key) は、メッセージがノートオフメッセージ、もしくはベロシティ0のノートオンメッセージである場合はtrueを返し、そうでなければfalseを返します。引数に与えられたポインタに、メッセージの値を代入します。
      このメッセージである場合は、startedNotesから同じキーのNoteを探し、見つかった場合はその音の鳴り終わりとして、音の長さを代入し、notes配列にappendします。
  7. notesの出力
    ここで、 ticks.In64ths(v.Value) によって、Valueが実際に64分音符が何個分であるか取得しています。このメソッドの中身は次のようになっています。

    64分音符は、4分音符の1/16の長さです。
    仮に、分解能(q)が1だったとして計算してみると、16がreturnされます。

さいごに

gomidiを使うと、SMFファイルがパースされ、MIDIメッセージを取り出せることが分かりました。今のところメンテナンスもされているので、おすすめです!

おすすめ書籍

実用 Go言語 ―システム開発の現場で知っておきたいアドバイス エキスパートたちのGo言語 一流のコードから応用力を学ぶ Software Design plus 詳解Go言語Webアプリケーション開発

blog-page_footer_336




blog-page_footer_336




-Tech
-, ,

執筆者:

免責事項

このブログは、記事上部に記載のある投稿日時点の一般的な情報を提供するものであり、投資等の勧誘・法的・税務上の助言を提供するものではありません。仮想通貨の投資・損益計算は複雑であり、個々の取引状況や法律の変更によって異なる可能性があります。ブログに記載された情報は参考程度のものであり、特定の状況に基づいた行動の決定には専門家の助言を求めることをお勧めします。当ブログの情報に基づいた行動に関連して生じた損失やリスクについて、筆者は責任を負いかねます。最新の法律や税務情報を確認し、必要に応じて専門家に相談することをお勧めします。


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


関連記事

入門スクラム〜スクラムフレームワーク

1 はじめに2 スクラムの特徴2.1 シンプルなフレームワークであること2.2 素早い反復を繰り返すこと2.3 検査・適応・透明性3 スクラムの役割3.1 プロダクトオーナー3.2 スクラムマスター3 ...

React初心者のRedux解説

1 はじめに2 Reduxとは2.1 そもそもReduxってなに?2.2 Fluxとは2.3 Reduxの3原則3 Reduxの要素3.1 Action3.2 Store3.3 State3.4 Re ...

icon

ドキュメント作成の技術「テクニカルライティング」とは

1 はじめに2 テクニカルライティングとは3 少しでも分かりやすく、簡潔にする3.1 一文一義3.2 長くしすぎない、繋げすぎない3.3 長さの目安は?4 伝わりやすく書く4.1 まず主題を書く4.2 ...

[Flutter]カメラのフレームデータを使ってリアルタイム顔認識

1 はじめに2 準備3 実装3.1 カメラプレビューの作成3.2 プレビューからフレームデータ取得3.3 フレームデータから顔認識3.4 顔認識した箇所に枠線の表示3.5 ランドマークと輪郭の検出4 ...

Go言語

Golang で図形描画してみた

1 はじめに2 Golangで画像描画する方法3 ggでの画像描画3.1 導入3.2 基本的な画像描画4 ジェネレーティブしてみた5 さいごに6 おすすめ書籍 はじめに 今回は、Goで2Dグラフィック ...

フォロー

blog-page_side_responsive

2022年11月
 12345
6789101112
13141516171819
20212223242526
27282930  

アプリ情報

私たちは無料アプリもリリースしています、ぜひご覧ください。 下記のアイコンから無料でダウンロードできます。