わたしが最近ひいき目に(?)しているDAW として、Tracktion社のWaveform を使っているのですが、今日はこのTracktionまわりのhackを紹介します。*1
他所で作ったMIDI ファイルをTracktionに取り込む時*2 、たまにTracktionの挙動が不審で、予期しない謎の音楽が生成されることがあります。最近わたしが経験したものでは、METAイベントのテンポと拍子設定が入り乱れる音楽を取り込むと、テンポがめちゃくちゃになる(しかも説明が困難なかたちで)というものでした。
bogus imports of tempo and time signature
MIDI なんて使ってるやつそんなおらんやろ?と思われそうな気もしますが、Tracktionをはじめ大抵のDAW のMIDI トラックやらインストゥルメンタル トラックやら呼ばれるものは、そして各種オーディオプラグイン は、内部的にMIDI メッセージのやり取りで成り立っているので、この辺の問題のインパク トはそれなりにあります。この問題の場合は、テンポと拍子の扱いが「根本的に何かおかしい」可能性があります。
それはそれでせっかくgithub で公開されているのでissueとして登録した のですが、Tracktionを使った作業自体は継続したいわけです。なのでTracktionにはバグレポートしつつ、Tracktionが正常に動作するようなSMFを作って取り込もうと考えました。
バグの原因の探し方
バグの原因については先のgithub issueでちょくちょく追求した結果をコメントしているのですが、まずこの問題はtracktion_engineにも含まれるロジック部分にあるだろうと考えました。DAW 上ではエディットを開いている画面でMIDI ファイルの取り込みを指示する場面を追いかけます*3 。
Import an audio or MIDI file
(ちなみにこのスクショはWaveform9で撮ったものなのですが、Waveform10ではこの周辺のUIが微妙に変わっているので、そのつもりで読み進めてください。)
"Import an audio or MIDI file" という項目なのですが、これはOSS 化されていないGUI のリソースなのでソースコード には含まれていません。しかし取り込みを続行するとダイアログが出現します。
tracktion dialog
このダイアログのメッセージなら出てくるのではないか?と探します。
/sources/tracktion_engine$ grep -nR "Do you want to import tempo and time signature"
modules/tracktion_engine/selection/tracktion_Clipboard.cpp:227: TRANS("Do you want to import tempo and time signature changes from the MIDI clip?"),
Binary file examples/projects/StepSequencerDemo/Builds/LinuxMakefile/build/StepSequencerDemo matches
なるほど確かにあります 。この辺からこの関数を呼び出しているコードなどを漁っていると、pasteMIDIFileIntoEdit
という「現在位置にMIDI ファイルの内容をペーストする」関数に行き着いて、MidiList::readSeparateTracksFromFile()
という関数が実際の解析を行っている部分らしいことがわかります。これが先のgithub issueの最初のコメントでリンクしたコードになっています。C++ のコードをある程度読めれば何とかなります*4 。こんな感じで問題の箇所をざっくり掘り当てます。
ちなみに、バグの原因を特定できても、DAW 全体をビルドできるわけではないので、修正を作ってpull requestを作るところまではなかなか至らないかもしれません。多分tracktion側も外部からのpull requestを受け付けていないと思います(JUCEもそんな感じです)。
MIDI ファイルでは、テンポの設定と拍子の設定はMETAイベントとして記述されます。METAイベントは他にもいろいろあるのですが、テンポと拍子は演奏時間にダイレクトに影響する情報なので、MIDI 演奏処理系ではこれらを取り出して処理することになりますし*5 、Tracktionでもまずこれらのみを抽出して 処理しています。
この中で「同じタイミングで存在しているイベントは(処理しても無駄なので)後のイベントだけを処理する」というロジックが含まれているのですが、ここで拍子とテンポを同時に変更していると一方が無視されるように見えたので、とりあえず「これおかしくね?」と指摘して後はTracktionの中の人に任せることにしました。
問題を切り分けるためにいろいろな条件でSMFを生成する
さてバグの追及がひと段落したので、次は問題が生じないようなSMFの条件を探し出す作業です(Tracktionを使った作業自体は進めないと困るわけで)。MML で生成したSMFを取り込んでいたので、MML 中で「テンポと拍子を同時に変更している箇所」を全部洗い出して書き換える…のは面倒なので、MML 中でテンポ指定命令を上書きして「1ステップ後にずらす」ようにしました。自作MML コンパイラ はこういうハックが簡単に出来て良い…
#macro t n:number { r%1 TEMPO $n r%-1 }
さてこれで直るかな?と思って再度インポートしてみましたが、やっぱり直らないんですね。原因が違ったか…というわけで、もう少し大胆に「拍子変更を全部消す」内容にして試してみたら、さすがに今度は正しくインポートされました。
ということは、もしかして、そもそも拍子設定が含まれている曲のテンポは全般的におかしいことになるんじゃないか…と思って先のtracktionのコードを見直したら、(github issueでも追記しましたが)やっぱりおかしい 、テンポ値の意味が拍子の変更で変わるところがある…というのを発見したのでした。
tracktionのデータを直接書き換えて問題のあるMIDI インポートを回避する
4/4拍子でないものを4/4で打ち込み続けるというのは割と苦痛です。普通の音楽では拍子の変更など滅多に発生しないのですが、今回の曲はこれが割と頻繁にありました(わたしがそういうジャンルに傾倒しているせいですが…)。
B MARKER "Section B"
[ r1 BEAT7,8r2..BEAT4,4 ]3 r1 BEAT3,4r2.BEAT4,4
[ r1 BEAT7,8r2..BEAT4,4 r1 BEAT3,4r2.BEAT4,4 ]2
C MARKER "Section C"
t120
[ BEAT3,4r2.r2. BEAT4,4r1BEAT7,8r2..]2
BEAT3,4r2.r2. BEAT4,4r1BEAT7,8r2..
BEAT3,4r2.r2. t_120,80,0,1..,8 BEAT4,4r1BEAT7,8r2..
D MARKER "Section D"
t125 [ BEAT3,4r2. BEAT9,8r1r8 BEAT3,4r2. BEAT7,8r2..]2
今回のバグはtracktion_engine部分にありますが、わたしが必要としているのはWaveformという完成されたDAW 製品で、しかも次のリリースまで待っていられるほど時間が無いので、今あるリソースだけで何とか作業できるようにしなければなりません。どうすれば良いでしょう…?
実は、*.tracktion
プロジェクトファイルはフォーマット不明のバイナリ形式なのですが、その中のEditをあらわす*.tracktionedit
ファイルはXML 形式なので、これにテキストエディタ などで手を加えることで、データを加工することができます。内容はもちろん独自形式なので、ある程度解読する作業が必要になりますが、所詮XML なので特別に難しいことはあまりないです。特にトラックデータなどSMFとあまり変わらない内容です。
(ちなみにVocaloid V3の.vsqxなんかも似たような感じで解析できます。V3はもともとSMFの派生フォーマットだったV2の.vsqと同じような情報を含んでいるはずです。V5もJSON になっただけだろうと思っています。踏み込んでいませんが。)
*.tracktionedit
にどんな情報が含まれているのかを調べる目的も兼ねて、.NETで*6 このtracktioneditファイルの内容を読み書きできるライブラリを作りました。
github.com
ただ、まだTracktionが正常にロードできるファイルをゼロから作り出して書き出す方法がわかっていないので、既存のデータを読んで加工する程度の使い方しかできません。今これを掘り下げる時間が無いので現状有姿です。
別にコレに特化したライブラリを作らなくても、一般的なDOMやXPath /XSLT などを使えるツールでいくらでも加工できる…と言いたいところなのですが、ここにはひとつ罠があって、*.tracktionedit
はXML Namespace仕様 (Namespaces in XML )に準拠していません。なので、たとえば.NETのXmlReader.Create()
を使って読み込もうとすると失敗します。(上記のntractiveでは.NET 1.1時代のXmlTextReaderを使っています。) 根本的にはJUCEの問題 です。
さて、今回問題になっているテンポと拍子の設定は、TEMPOSEQUENCE
という要素に含まれています。4/4から9/8に変更しながらテンポを緩やかに変更するSMFを取り込むと、こんな感じになります。bpm
属性の値がおよそ半分になっていることがわかります。startBeat
属性は、演奏データの先頭からのデルタタイムをquarter note単位で表したものになります。
<TEMPO startBeat="28.0" bpm="115.00002875000720337084" curve="1.0"/>
<TEMPO startBeat="72.0" bpm="57.50001437500360168542" curve="1.0"/>
<TEMPO startBeat="76.0" bpm="115.00002875000720337084" curve="1.0"/>
<TEMPO startBeat="80.0" bpm="57.50001437500360168542" curve="1.0"/>
拍子の変更を取り除くとこうなります。bpm
の値が正常な範囲で動いています。
<TEMPO startBeat="28.0" bpm="115.00002875000720337084" curve="1.0"/>
<TEMPO startBeat="127.0" bpm="120.0" curve="1.0"/>
<TEMPO startBeat="174.0" bpm="117.14288503402025298783" curve="1.0"/>
<TEMPO startBeat="174.0" bpm="114.2857142857142775938" curve="1.0"/>
拍子設定はXML 中でどう表現されるかというと、TEMPOSEQUENCE
要素の中に、TEMPO
要素の後にTIMESIG
要素がずらっと並ぶかたちになります。
<TIMESIG numerator="4" denominator="4" startBeat="0.00000000000000000000"/>
<TIMESIG numerator="4" denominator="8" startBeat="4.00000000000000000000"/>
ということは、拍子設定なしでMIDI ファイルを正常に取り込んだ*.tracktionedit
ファイルに、手作業で後からTIMESIG
要素を追加してやれば、当初期待していた通りの結果が生成できる、というわけです。ただ、拍子設定を取り込んで壊れているeditに含まれるTIMESIG
要素のstartBeat
の値はデタラメになるので、自分で計算し直さないといけません。面倒ですね…SMFを解析するライブラリを使って、TIME SIGNATUREメタイベントを全部デルタタイム付きで取得して、このXML 要素のリストを生成するプログラムを書くと良いでしょう。
最後は面倒になってきたので考え方だけでまとめとしますが、ともあれ、これでTracktionの取り込みがおかしいとしても打ち込み作業で致命傷を受けずに済むと思います。tracktioneditファイルはXML なので手作業で補正できる ということを覚えておくと、他の問題があった場合にもたぶん役に立つと思います。