SMFプレイヤーをつくろう!リベンジ「セットテンポと分解能」

ツール制作
スポンサーリンク

はじめに

以前(2007/4/30)、HSPでMCI(Media Control Interface)を利用せずに、SMF(スタンダードMIDIファイル .mid)を再生するためのモジュールを作ってSMFプレーヤーを作りました。(ここ)

ただ実はこれには色々と問題が山積みになっています。

  1. なんかメタイベント,SysExイベントが正常に送れていないっぽい?
  2. 何秒から再生という指定できない
  3. 曲全体の再生時間を取得できない
  4. プログラムチェンジできてない?(全部ピアノに…。うまくいく音楽もあるから1.のせい?謎。)
  5. なんか音がぽちぽち切れる
  6. 連続で聴くと音が出ないトラックが?(リセットメッセージがうまくいってない?)

遊び程度には使えるのですが、あまりにも実装が適当すぎが原因だと思います。あれから2年以上も経ってるし、自分の技術(CとJavaが使えるようになりました!)は進歩しているはずなので、今回作成しようとしているJava版で解決したいと思います。

今回は、特に、2,3と再生時間の取得について書きます。

SMFの再生時間について

SMFは、音のイベント情報が秒単位で記録されていないのというのが一番の問題です。調べるためには、SMFの分解能(解像度)と、デルタタイムと、テンポ情報を把握しつつ、最初から計算していくしかありません。

今回は、Javaでこれを実装しようと思っています。一応Javaには、シーケンサっていうのが用意されているのでこっち使えみたいなのもありますが、この機能には、SMFに埋め込んだ歌詞情報とか曲名とか著作権情報とか見られないとのことです。(どうやら、Metaイベント用のリスナー(MetaEventListener)を作ればいけるらしい。また、Java1.5からMidiFileFormat getMidiFileFormatを利用すれば取得できるとのこと)

それで、SMFプレイヤーの設計なのですが、以前と同じように、読み込み時にSMFを解析して再生しやすい形に変更させたいと思います。

アルゴリズム

順序としては以下の通りです。

  1. 可変長で格納されたデルタタイムを、固定長にする。
  2. MIDIイベントと、SysExイベントと、メタイベントを、可変長のデータだけ他の配列に移動させておいて、あとは、指定された順番に配列を見るだけで、これらのイベントが分かるようにする。
  3. 音符と音符の間の時間であるデルタタイムから、曲の始めからその音符までの時間になおす。
  4. トラックごとに、トラックで起きるイベント全てを格納という形で、2次元配列に格納する。(SMFのフォーマット1からフォーマット0のような変更はしない形でトラックという概念はそのままで……。以前作成したHSPのモジュールバージョンでは、ソートして1トラックにしているので、今回はもうちょっと丁寧にしたいと思います。)

Javaは、2次元配列でも1つずつメモリを使用する領域を変更できるので恐らく以前より簡単に出来ると思います。(補足ですが、Cで実装するなら、ポインタの配列(ジャグ配列)を使わないといけない)

以下、実装にあたり、テンポ情報やデルタタイムなど時間に関するメモとなります。

セットテンポ (set tempo)

セットテンポは、メタイベントでSMFのテンポを指定するものです。指定方法は、4分音符の時間[μsec]です。この『4分音符の時間[μsec]』から『BPM(Beats Per Minute)』を求めるにはどうすればいいでしょうか。

『BPM』は1[min]=60[sec]に4分音符が60回あるとBPMは60[beat]となります。つまり、4分音符の長さをx[sec]のときは、BPMが(60[sec]/x[sec])[beat]となります。

ここで、1[sec] = 1000[ms] = 1000000[μsec]なので

60[sec]÷BPM[beat] = settempo[μsec] ÷ 1000000

を計算すると、セットテンポの値から、BPMの計算が出来ます。
つまり、

60000000÷settempo[μsec] = BPM[beat]

です。

ちなみに、曲中にセットテンポ情報がありBPMが変わることもあります。これが単純に計算できない理由です。

分解能 (time base)

SMFでの4分音符の分解能(タイムベース)です。この情報は、ヘッダチャンクに記述されており、分解能は途中で変わることありません。よくあるのは、分解能=480とかです。分解能=480とした場合、SMFの中では、4分音符分の長さを480、8部音符分の長さを240と記述することになります。

さて、ここで問題です。上の480とか240とか数値が出ましたが、分解能がTIMEBASEのとき、この数値が1は何秒に対応するでしょうか。考えるとややこしくなってきますよね。SMFの中では、この単位をデルタタイムとして記述されているだけなのです。

以下、計算方法となります。

分解能がaのとき、4分音符の長さはaです。単位は秒ではありません。分解能がTIMEBASEのとき、4分音符の長さをx[sec]で表そうとした場合

60[sec]÷x[sec]=BPM[beat] ⇒  60[sec]÷BPM[beat]=x[sec] … (1)
1[deltatime] = x[sec]÷TIMEBASE … (2)

(1) (2) から

1[deltatime] = (60[sec] ÷ (BPM[beat] × TIMEBASE))[sec] です。

これでデルタタイムが1の時の秒での時間が分かりました。

というわけで、SMFで全体の長さを秒単位で調べるには、1デルタタイムずつ調べていき、そのたびにBPMも更新して、その時の秒を加算していく必要があるわけです。

このようにしないと、分解能が480なのに、1デルタタイムごとにテンポを変更しまくるといった怪しげなMIDファイルの長さを正確に調べることが出来ないのです。

テンポ情報がでるまで調べて、そのテンポ情報までのデルタタイムを利用するっていう
手の方が計算時間は早いかもしれませんが、今回は説明しません

後で気が付きましたが、今回一度BPMを経由しているのですがsettempoの情報をそのまま使った方が1[deltatime]あたりの秒が自然に計算できるかもしれません。

おわりに

続く… SMFプレイヤーをつくろう!リベンジ「拍子とタイムベース」

完成したJava製のSMFプレイヤーのダウンロード

コメント

  1. […] 実は、昨日のうちにあっけなく完成。ワ――ヾ(o・ω・)ノ――イ SMFプレイヤーをつくろう!リベンジ「セットテンポと分解能」の問題もいつのまにか全部解決してました。 […]

  2. […] natade コンテンツへ移動 ホーム紹介 ← SMFプレイヤーをつくろう!リベンジ「セットテンポと分解能」 SMFプレイヤーをつくろう!リベンジ「完成した」 […]

  3. […] 参考サイト Web MIDI API (日本語訳) MIDIデバイスの準備不要、Web MIDI APIの基礎 Web MIDI APIを触ってみた Web MIDI APIを扱うためのMIDI基礎知識 Web MIDIを例にChromeにAPIを追加する手順概要を追ってみる Web Audio / Web MIDI にまつわる時間管理の話(1) – Webにおける3種類の時刻 SMF (Standard MIDI Files) の構造 SMF(Standard MIDI File)フォーマット解説 DTM技術情報 – 5.SMFフォーマット SMFプレイヤーをつくろう!リベンジ「セットテンポと分解能」 « なたで日… […]

  4. […] […]

タイトルとURLをコピーしました