Android AMidi (Native MIDI) APIについて

概要

Android Qで新しく追加されるNative MIDI APIは、Android NDKを利用してビルドされるネイティブコードで、Androidシステム上に登録されている「MIDIバイス」にアクセスするためのAPIだ。ネイティブコードで記述することで、ある種のMIDI関連タスクの処理負荷が軽減され、リアルタイム性が向上することが期待される(「リアルタイム性」についてはこの過去記事などを参照)。

Native MIDI APIはC APIとして公開されており、amidi/AMidi.hをインクルードして、libamidi.soを共有ライブラリとして参照してビルドする。毎回Native MIDI APIと書くのは長いので、以降はAMidiと記す。

Android MIDI API (Java) のおさらい

Android MIDI APIAndroid 6.0(Marshmallow)で追加された。AndroidMIDIバイスには、USBデバイス、BLEデバイス、ユーザー/アプリケーション実装によるMIDIバイスサービスの3種類がある。よくiOSMIDIバイスとして販売されているBLE製品は、Appleの仕様に基づいてMMAMIDI Manifacturers Association)で規定されたMIDI-BLE仕様を実装しているAndroidでも使えるはずだ。

JavaAndroid MIDI APIandroid.media.midiパッケージにある。Android上でシステムに登録されたMIDIバイスを利用するためには、Androidのシステムサービスとして登録されたMidiManagerにアクセスして、Javaオブジェクトとして存在するMidiDeviceを取得する必要があり、ART上のJavaオブジェクトを経由せずにAndroidシステムが管理するMIDIバイスにアクセスすることはできない(C/C++のコードだけで完結するとしてもJNIEnvオブジェクトやMidiManagerオブジェクト、MidiDeviceオブジェクトを扱う必要がある)。

Androidのシステム登録と無関係にMIDIバイスを接続するのであれば、システムが実装しているUSB接続やBLE接続に相当するコードを自分で用意しなければならない。仮想MIDIバイスなら特に難しいことはないだろう。*1

機能

AMidiはJavaandroid.media.midiパッケージのAPIに相当する機能の全てを提供しているわけではない。AMidiの機能の範囲は、次のように、ほぼMIDIアプリケーション(クライアント)側の、入出力ポートにおけるMIDIメッセージの送受信に限定されている。

  • MIDIバイスをあらわす構造体AMidiDeviceの入出力ポートAMidiInputPortAMidiOutputPortを開閉する
  • ポートの数は取得できるが、ポート情報の詳細は一切取得できない
  • MIDIバイスの詳細情報は一切取得できない
  • 入力ポートAMidiOutputPortからMIDIメッセージを受け取る
  • 出力ポートAMidiInputPortMIDIメッセージを送る

AMidi.hで宣言されている型は以下の3つ(内容は未公開)、関数は以下の13件しかない。

型:

typedef struct AMidiDevice AMidiDevice
typedef struct AMidiInputPort AMidiInputPort
typedef struct AMidiOutputPort AMidiOutputPort

関数:

media_status_t AMIDI_API AMidiDevice_fromJava(
    JNIEnv *env, jobject midiDeviceObj, AMidiDevice **outDevicePtrPtr)
media_status_t AMIDI_API AMidiDevice_release(
    const AMidiDevice *midiDevice)
int32_t AMIDI_API AMidiDevice_getType(
    const AMidiDevice *device)
ssize_t AMIDI_API AMidiDevice_getNumInputPorts(
    const AMidiDevice *device)
ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(
    const AMidiDevice *device)
media_status_t AMIDI_API AMidiOutputPort_open(
    const AMidiDevice *device, int32_t portNumber, AMidiOutputPort **outOutputPortPtr)
void AMIDI_API AMidiOutputPort_close(
    const AMidiOutputPort *outputPort)
ssize_t AMIDI_API AMidiOutputPort_receive(
    const AMidiOutputPort *outputPort, int32_t *opcodePtr, uint8_t *buffer, size_t maxBytes,
    size_t* numBytesReceivedPtr, int64_t *outTimestampPtr)
media_status_t AMIDI_API AMidiInputPort_open(
    const AMidiDevice *device, int32_t portNumber, AMidiInputPort **outInputPortPtr)
ssize_t AMIDI_API AMidiInputPort_send(
    const AMidiInputPort *inputPort, const uint8_t *buffer, size_t numBytes)
ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(
    const AMidiInputPort *inputPort, const uint8_t *buffer, size_t numBytes, int64_t timestamp)
media_status_t AMIDI_API AMidiInputPort_sendFlush(
    const AMidiInputPort *inputPort)
void AMIDI_API AMidiInputPort_close(
    const AMidiInputPort *inputPort)

バイス詳細情報やポート詳細情報など、文字列が関連してくるAPIについては、C/C++のコードでどのようなデータ表現を用いるのか、定まった解がなく、AMidi APIはこれらをうまいこと回避しているともいえる。

ひとつ重要なことを指摘しておく必要があろう。MidiDeviceインスタンスは、アプリケーション側であるMidiManagerを経由してのみ取得できるものなので、MidiDeviceServiceの実装でこれを取得することはできない。つまり、MidiDeviceServiceではAMidiは使用できないということである。後述するfluidsynth-midi-serviceでこれを利用できる可能性はゼロだ。

Javaコードとの棲み分け

AMidiは処理速度が重要になるMIDIメッセージの送受信に特化したネイティブライブラリであり、それ以外の機能についてはJavaAPIが使われることが想定される。 AMidiのエントリーポイントはAMidiDevice_fromJava()関数であり、AMidiを使うためには少なくともJava側でandroid.media.midi.MidiDeviceインスタンスを取得する必要がある。

そこから先は、MIDIアプリケーションの目的によって、利用できるAPIの向きが変わる。

  • (1) 外部MIDIバイスからのMIDIメッセージを受け取って処理するアプリケーションでは、MIDI入力ポートからのメッセージの受け取りをandroid.media.midi.MidiOutputPortからではなくAMidiOutputPort_receive()で受け取って処理するようにすると、Native MIDI APIによってMIDIメッセージの送受信が軽量化される可能性がある。(MIDI音源と接続したり仮想MIDI音源を操作するタイプのMIDIバイスサービス実装では、MidiDeviceインスタンスを取得できないので、この可能性は無い。)

  • (2) 外部MIDI音源MIDIメッセージを送るアプリケーションでは、MIDI出力ポートへのメッセージの送信をandroid.media.midi.MidiInputPortではなくAMidiInputPort_send()あるいはAMidiInputPort_sendWithTimestamp()で処理するようにすると、内部的に軽量化される可能性がある。(カスタムMIDIバイスと接続するMIDIバイスサービス実装では、MidiDeviceインスタンスを取得できないので、この可能性は無い。)

いずれの場合も、従来のJavaandroid.media.midiAPIでは、対象MIDIバイスとの接続にはAndroidのServiceの仕組みに基づいてメッセージがParcelにシリアライズされ、Parcelとして送受信され、Parcelから元のデータがデシリアライズされる必要があった。この部分がAMidiで最適化されている可能性はある。

この節で何度も「可能性がある」と留保付きの表現を用いているのは、ここはプレビュー時点でソース未公開のAndroid Qのフレームワーク実装の部分なので情報がないためである。一般的には異なるプロセス同士でメモリエリアを共有することはできないが、Parcelが内部的に使用しているbinderの仕組みでサービス側とメモリを直接やり取りしている可能性はある。Android Qで新たに追加された機能ということは、フレームワークのレベルで改良される必要があったということがうかがわれる。

Android 8.0から、binderのドライバーがrealtimeの優先度で動作できるようになっている。これがMidiDeviceServiceを経由するMIDIメッセージのやり取りに使用されている可能性もある、と筆者は想像している。

実用未遂: fluidsynth mdriverのAMidiサポート

筆者は(実のところさほど明確な目的もなく)Fluidsynthにパッチを送り続けているのだが、今回もFluidsynthにAMidiを応用するMIDIドライバーを作ってみた。自前のgithub fork上で公開しており、公式にもissueとして登録してあった。(ただ、前述のとおりMidiDeviceServiceで使える目処が立たなかったので、このissueは閉じてある。)

Fluidsynthは、その起源はLinux用のソフトウェア・シンセサイザーだが、クロスプラットフォームで動作するように、プラットフォーム固有の「ドライバー」の上に音声合成の共通コードが乗っかっているかたちになっている。ドライバーには、プラットフォームのオーディオAPIにアクセスするオーディオドライバー(以降ソースコードの名前に合わせてadriver)と、MIDI APIにアクセスしてそこから受け取ったMIDIメッセージをそのまま合成エンジンに高速に渡すことができるMIDIドライバー(以降mdriver)がある。mdriverは、fluidsynth(.exe)で内部的に利用できるオプション機能だ(使わなくてもよい)。

mdriverを使わない場合、合成エンジンに対してnote on, program change, control changeなどの機能を実装するC APIを呼び出すかたちになる。この場合、MIDIメッセージとして受信したバイト列をアプリケーション側が自前で解析して、対応するAPIを呼び出す必要がなる。

mdriverはnew_fluid_midi_driver()という関数で生成するが、このときコールバック関数としてhandle_midi_event_func_tを渡すことになる。この中で、別途生成したsynthエンジン(fluid_synth_t)で直接合成させてもよいし(fluidsynth(.exe)が行っているのはこれだ)、別の処理を挟んでもよい。Android上では、OpenSLESやOboeが有効になっていないsynthオブジェクトにMIDIドライバーが受け取ったMIDIメッセージを処理させても、あまり意味がない(MIDIメッセージのフィルタリングくらいはできる)が、幸いなことに最新のfluidsynth masterには筆者のOboe/OpenSLESドライバーが含まれているので、恩恵を受けることが出来る。

今回考えたのはNative MIDI APIを応用して、AMidiのmdriverを実装する、というものである。Android MIDI API経由でFluidsynthのMidiReceiverが受信したMIDIメッセージを、JNIマーシャリングコストなしでfluidsynthの合成エンジンが処理できるか試してみる、という感じである。しかしMidiDeviceServiceからAMidiへのエントリーポイントが全く存在しないため、実現可能性は無かった。

潜在的な需要: オーディオプラグイン機構のサポート

前述した話だが、Android 8.0ではリアルタイム優先度のbinderドライバーが利用可能になっている。これによって潜在的に大きな恩恵を受けることが出来るのがオーディオルーティングを伴うアプリケーションだ。オーディオ編集アプリケーションが、オーディオエフェクトをサービスとして実装するアプリケーションを、オーディオプラグインとして利用できたら、なかなかおもしろいことになるのではないかと筆者は想像している。(前節でも重ねて「可能性がある」と書いてきたのと同じで、まだソースが公開されていないので、ここは想定でしかない。)

Android 8.0未満の環境では、binderを経由してデータを渡して処理させたら、リアルタイム優先度を維持することが不可能だった。リアルタイム優先度を維持できないと、オーディオ処理に遅延が生じることになる。それを受け容れるというのも一つのあり方だが、レコーディング時やライブコーディング時など、限りなく「絶対に」に近いかたちで遅延を回避したい状況というのはあるだろう。

オーディオ? Native MIDI APIMIDIAPIではないのか? という疑問が湧いてくると思うが、音楽制作で一般的に使われるVSTAU (Audio Unit)、LADSPAなどオーディオプラグインの世界では、一般的にMIDIメッセージがプラグインの制御に使われている(もう少し正確にいえば、MIDIメッセージを変換して各オーディオプラグインのパラメーターを制御するようになっている。オーディオプラグインの制御範囲はMIDIの表現できる範囲に限定されない)。このメッセージング機構がリアルタイム優先度で行われるようになれば(もちろんオーディオ処理のほうがはるかに大きな制約になるわけだが)、オーディオホスト/プラグイン機構を実現するための壁がひとつ取り払われることになるのではないか、と思っている。

ついでに自分の予言者ごっこtweetも貼っておこう。

既存プラットフォームをサポートしつつamidiサポートを追加する

AMIDIサポートをネイティブコードで実装してするとなると、amidi.soをshared libraryとしてビルド時に参照することになる。これは、実行時にAndroid P以前のプラットフォームでは「使わない」ようにする必要がある。Javaのコードであれば、android.os.Build.VERSIONなどを使って条件分岐すれば足りるが、ネイティブの共有ライブラリの場合はそこまで単純ではないので注意が必要だ。

dlopen()で共有ライブラリがロードされるとき、デフォルトではそのライブラリが参照している共有ライブラリが連動してロードされる。Cの呼び出しを直接制御しているのであれば、dlopenの引数にRTLD_LAZYを指定すれば、シンボルの解決だけは先送りすることができるが、参照ライブラリのロード自体は即時に行われるので、Build.VERSIONの比較によって「実行されない」ようにコードを書いていても、JavaでいうところのUnsatisfiedLinkErrorが生じることは避けられない。

また、筆者のfluidsynthjnaのように、JNAeratorのような自動バインディング機構を活用していると、シンボル解決もロード時に全て行われる実装になるため、RTLD_LAZYにも効果がない。ダミーとなるライブラリを用意するとしても、シンボル自体が全て存在していなければならないことになる。

実のところこの問題には先例となるソリューションがある。GoogleはOboeでaaudioをサポートしているが、aaudioをサポートしたoboeがAndroid 27以前の端末でもUnsatisfiedLinkErrorを起こさずに実行できるのは、共有ライブラリとしてlibaaudio.soにリンクすることなく、dlopen()を使ってAAudioの関数を動的にロードしているためである。

https://github.com/google/oboe/blob/c278091a/src/aaudio/AAudioLoader.h#L67

古いプラットフォームでも動作するようにamidiを部分的にサポートするライブラリをビルドしようと思ったら、同様の仕組みを実現しなければならない。というわけで、自分で実装してみた。ただし一度も使っていないコードなので動作しない可能性が割とある。

https://github.com/atsushieno/fluidsynth-fork/blob/d92cbc5/src/drivers/fluid_android_amidi.c#L40

*1:たとえば、Google/music-synthesizer-for-AndroidにはMIDIメッセージを処理する機能があるが、Android MIDI APIとは無関係だ。

4月のmusic tooling hacks

4月というか平成も終わろうとしている今日この頃ですが、相変わらず時代をまたぐ無職生活エンジョイ勢です。相変わらず謎の方向性でコードを書いています。そんなわけでこのひと月くらい?のコーディング活動を雑にまとめます。

JUCE/VST3/Linuxの高い壁

楽曲制作環境の改善手順として、SMFベースのMIDIプレイヤーからオーディオプラグイン中心の音源ドライバーに移行しようと思っているのですが、2019年はLinuxデスクトップ開発者にとってたいへん難易度の高い年です(断言)。

昨年からSteinbergがVST2 SDKを廃止してダウンロードできない状態にしてしまい、VST3 SDKのみが入手可能な状態になったのですが、VST3 SDKGPLで入手できるものの、各種のネイティブVST*1プラグインDAW等のホストアプリケーションが追従していない状態です。VST2とVST3は根本的に別物に近いんですよね。

特にJUCEを使っているプロジェクトが、ホストもオーディオプラグインも全滅です。JUCEはLinux版VST3に対応しておらず、公式フォーラムに「おいおいLinux用VSTプラグインを作れると思って使ってきたけど詰んでるじゃん?」というスレッドが立っている状態です。 割と新鮮なUI設計で面白いhelioシーケンサーも、Waveformで使われているtracktion_engineも、Googleのmusic-synthesizer-for-AndroidベースのDX7互換FMシンセサイザーDexedも、サウンドフォントが使えるjuicysfpluginも動きません。*2

JUCEのプラグインならLADSPAでビルドすれば動くんじゃん?とか思うわけですが、dexedみたいにJUCE 5.2.1くらいのソースを取り込んでいるやつではProjucerでちょいちょいっとjuce_audio_processorsをいじってもVST2が無いだけでビルドエラーになるみたいな理不尽な状態なので、VST3でちゃんとビルドできるようになっていないとメンテナンス出来ない状態になるなあと思います。

そういうわけでたまにJUCEのソースを読みながら、Linux上でVST3を使えるようなブランチを作ろうとしているのですが、ちゃんと動くものが出来るまでには時間がかかりそうな気がしています。その間に本家で対応していてほしいけど、先のissueにsurprisingly difficultなどという供述があり…。JUCE、外部からのcontributionを受け付けない伽藍プロジェクトなので、今後こういうフレームワークに乗っかっていくことにはLinuxデスクトップユーザーとしては危機感があります。

オーディオプラグインフレームワークとしては、VSTの他にもLADSPA V2 (LV2) などがあって、各種ホストでこっちが標準でサポートされるようになってほしいところです。ちなみに今のところVST3をLinux上のDAWで使おうと思ったらBitwig StudioかArdour5訂正: 対応していると思っていたけどLXVSTのみでしたという選択肢しか無いと思います。Bitwig Studioえらい。

f:id:atsushieno:20190430211123p:plain

このスクショはJUCEのextraにあるAudioPluginHostなのですが、リストアップされているオーディオプラグインの中にVST3が含まれているのがミソです。juicysfpluginはguiが表示できなくて詰むのですが、mdaほげほげの類は音も出てGUIも表示され、パッチを作っていた自分がまずビックリでした…

fluidsynth-midi-service-j

github.com

先月までずっとfluidsynthのAndroidビルド用forkを本体にマージするための開発にかかっていたわけですが、ひと区切りついたので、Androidアプリケーション側の開発にシフトしました。まだ最初だけノイズが乗るみたいな問題があるのですが(ゴミのあるオーディオバッファが再生されている、だけでもなさそう…)、それなりに使える状態のアプリにはなってきたと思います。proof of conceptレベルでもちゃんとJava/Kotlinでアプリ開発するのはx年ぶり(思い出せない)なので*3なので、AACやらdata-bindingやらconstraint layoutやらをごく当たり前のように使ったり、DeployGateにも数年ぶりにうpしたりと、今らしいスタイルで開発できるのは新鮮です。これはこれで楽しい〜

先月の時点では単に和音を鳴らすだけのボタンがあるMainActivityしか無かったのですが、fluidsynth初期化設定をUIで調整できるようにして、Oboe/AAudioのLowLatencyモードやExclusiveモードも試せるようになったので、この辺に興味のある人にとってはちょっと楽しい感じです。

あと先月ノリでmanaged-midi (C#) から移植したKotilnのktmidiが、主にbyteのsigned/unsignedまわりとIntへの型変換を含めた比較で大爆死だったので、ちゃんと動くように全面的に手を加えて、SMFもちゃんと再生できるようになっています。KotlinにはexperimentalでunsignedTypesというのがあるのですが、仮にそれがkotlincまでシームレスに統合されるようになっても、Int定数との比較などでIntへの型変換などが必要になってくると、常にunsignedなのか否かを意識したコードを書かなければならず、KotlinでMIDIアプリケーションを安全に書くのは極めて難しいのではないかと思うようになりました(experimentalが外れてUByteの定数が書けるようになれば、この問題は少し緩和されることでしょう)。少なくとも、MMLコンパイラをKotlinに移植しようという気持ちはなくなりました。

あとアプリで実践的にfluidsynthを使うようになった関係で、ちゃんとSF3 (compressed soundfonts) をサポートするためにlibsndfileをビルドできるように本家fluidsynthに変更を加えたり、bitriseやローカルのdockerでビルドを確認できるようにしたり、といったこともやっていました。Android用fluidsynthがビルドできないという問い合わせ、地味によく来ていたので…(しかもなぜかメールが多い)。

あとこの方面の問い合わせに連動して北米方面のDAWの開発会社からうちで仕事しないか?みたいなのが来て、ktkr! とか思って前向きに返事したのですが、「よっしゃ連絡するわ」という返事を最後に通信途絶した()ので相変わらず無職です。(どうでもいい)

英語では他にもいろいろ書いたのですが(Android Native MIDI APIの話とか)、めんどくさくなったのでこっちでは省略します(何)。

soundfont-player-cs

github.com

MIDI中心からオーディオプラグイン中心の制作環境に移行するためには、スムーズにリズムパートを打ち込めるような環境と音源が必要です(前回のイベントで頒布した作品では、実はリズムパートが皆無に近いんです)。これまでもMMLで打ち込んできていて、ノートベロシティを踏まえた打ち込みは簡単だし、DAWでやると面倒くさい3連符もごく簡単に打ち込めるし、MMLでの打ち込みで続けたいのですが、手元にあるSC-8820にある音は限られていて、リズムパートを希望通りの音色で打ち込むのは無理ではないかと思うようになりました。

オーディオプラグインであればSerumとかEZDrummerとか、手元にあるだけでもOmnisphereとかBatteryとかあるわけですが、Linux環境で使えるものがほしいわけです。ドラムパートくらいならサウンドフォントでもいけるのではないか(加工系のプラグインならLADSPA等でいろいろあるし)と思ったのですが、サウンドフォント自体は大量に手に入る時代とはいえ、それぞれのサウンドフォントにどんな音があるのかを簡単に知る術がないぞ…と思ったわけです。

とりあえず、GUI上に手持ちのサウンドフォントにある音色(プリセット = プログラム / バンク)のリストをありったけ表示して、選ばれたやつを広大な鍵盤リストのようなものに表示して、どこでも叩けるようにしたら、音色の確認は簡単にできるようになるのではないかと思って、Xwt, Fluidsynth, NAudioを組み合わせてざっくり作りました。意外とこういうツールが無いんだよな…

f:id:atsushieno:20190430204056p:plain

やっている事自体は難しくないので、それこそ仕組みだけならWeb Audioとかでも作れなくはないのですが、サウンドフォントはファイルがでかいのでWebには向いていません。サウンドフォントに限らず、音楽系ツールは膨大なデータを伴うことが多いので、全般的にWebには向いていないですね。ローカルに各種リソースがあることが前提になると思います。

今回はたまたま C# で組みましたが、既にUIにかなり不満があって(特にListViewあたり)、これを解消するにはもう言語ごと他所に引っ越すしか無いかなと思っているのですが、なかなか自分の需要に合うものが少なそうです。とりあえずQML.NETあたりを試してみようかなとは思っています(これもnugetパッケージ版のLinuxビルドがおかしくて試せなかった)。

managed-midi: CoreMIDI on .NET Core, etc.

もうひとつ、managed-midiの話なのですが、ひとつ長い間どうにかしたいと思っていた問題が解決しました。.NET Frameworkや.NET Coreのプロジェクトでmanaged-midiを使うと、Macで使えるネイティブバックエンドが無いという問題です。

MIDIアクセスはプラットフォーム固有のAPIアクセスが必要になるのですが、Mac上のCoreMIDIを使うにはXamarin.Macに依存するしか無かったわけです。そうするとプロジェクトそのものがXamarin.MacのProjectTypeGuidを前提とするものになり、デスクトップ用のmonoを使ったり(.NET Frameworkが全体的にdeprecatedになりそうなので忘れられているかもしれませんが、.NET Coreの範囲を超えるデスクトップ用アプリを作ろうと思ったらmonoを使うことになるのです)、.NET Coreを使ったりということを考えると、Xamarin.Macは難しくなってきます。Xamarin.Macにはfull profileがあって、これはnet4x互換なのですが、いずれにしろわたしがLinux上でビルドするときに「無いとビルドエラーになる」ようなものでは困るわけです。

いろいろ考えた結果、もうXamarin.Macに含まれるCoreMIDI API相当の部分を自前実装しちゃったほうが早いだろう(これ自体はMonoMacで実現していたわけだし)、となって、今のmasterには実装が含まれています。IMidiAccess APIを実装する範囲の最小限のもので雑ですが。

これで自分のプロジェクトがちゃんと単一バイナリでもクロスプラットフォームで動作するといえる状態になったので、気が向いたらバイナリパッケージも作るかもしれません(managed-midi自体はずっとNuGetで配布しています)。

あと最近はWindows方面の開発者がUWP実装やwinmm実装のissueを報告してくれたりパッチを送ったりしてくれているので、managed-midiユーザーいたんだ…?みたいな気持ちでありがたくcontributionを受けています。自分が普段全然使っていない環境をケアしてくれる開発者がいるのはありがたいですね。

残っている課題?で多く来るのが「Unityで使いたい」なのですが、どうしようかな…Unity普段使っていないし、今さら.NET方面に時間使うのもったいないな…という感じで「やる気があったらcontributeしてくれ〜」みたいな感じで雑に返しているのが現状です。(モバイルまわりになると割と大変なので自分で作ってあげたほうがいいのかなーみたいに思ったりはしますが…) MIDIJackとかで対応してくれればそっちに誘導するんだけどなあ。

managed-midiの仮想ポート作成API

あと、仮想MIDIポートを作成するAPIを追加したので、これを関連プロジェクトであるところの仮想MIDIキーボードアプリxmmkでMIDI入力用ポートを追加して、Tracktion Waveformから接続して譜面に入力をペーストできるようにしました*4

実のところ送信内容をキー入力に限定する必要はなく、xmmkにはmugene MMLコンパイルしてMidiPlayerでバックグラウンド演奏できる機能も追加してあり、この内容を仮想入力ポートに送るだけでDAWMMLからペーストできるようになったともいえます*5。ピアノロールに打ち込めるのもまあ面白いのですが、一番「これは便利」と思っているのは、DAWから開いているオーディオプラグインで試しに音を出すとき、自分の好きなように入力UIを設計できることですね。物理MIDIキーボードと違って、ソフトウェアMIDIキーボードならいくらでも工夫の余地があるので…

ちなみにCoreMIDI APIでの実装はまだ何やら機能せず、WinMMには仮想ポートを定義する機能そのものが無いので対応しません。UWPは知らんけど多分無いんでしょう。

f:id:atsushieno:20190430211756p:plain

that's all

とりあえずこんなところでしょうか。本当はもっと音楽制作とかやって遊んでいるつもりだったのですが、ちょっと易きに流れてしまった感じで割と反省しています。来月はもちっと打ち込みに興じたいところです。(フラグ?)

*1:ユーザーとしてLinux上でVSTを動かすアプローチは2つあって、ひとつはLinux用のビルドを使う方式、もうひとつはwineを利用してWindows用のバイナルを動かす方式です。後者は商用のめんどくさいやつ(Kontaktとか)が全く使えないので、VST開発者にとっては前者のサポートが必須といえます。

*2:正確にはjuicysfpluginなどは古いJUCEを依存関係に含んでいるのか未だにビルドできるのですが、ライセンス的に配布できなくなっている状態です。

*3:ここでも何度か書いているかと思いますが、フレームワーク開発者がアプリを開発しない問題…

*4:特別にコードで実装しなくても、OSの機能としてMIDI Throughなどのポートにroutingかければいけたのですが

*5:個人的には楽曲の「土台作り」をDAW上でやりたくないので、ここに大きな可能性はあんまり見ていません

最近のOSS contribution活動

2月までずっと音楽制作…というしかないやつ…にかかりきりでだいぶフラストレーションがたまっていたのですが、それがひと区切りついたこともあって、3月は主にプログラムを書いて過ごしていました。長い間取り掛かっていたのが昨日終わったので、軽い気持ちでまとめていきます。

Xamarin.AndroidAndroid Q APIサポートを追加

github.com

(長大なcommitコメントはわたしではなくリーダーがまとめたものですが)

今月上旬にAndroid Qのfirst previewが出ました。わたしがXamarin.Androidチームにいた時の暗黙的な仕事のひとつが新APIへの対応だったのですが、わたしが抜けた後これに対応するメンバーがおらず、もともとcontract的な仕事として投げたいかも…実現できるかまだわからんけど…みたいに内々に言われたりしていました。ただわたしのほうは、まあそんな必要ないんじゃないかなー、だってこんなの1日2日あれば終わるでしょ…みたいな気持ちだったので、ちょうど暇になったこともあって手を出してみたら、案の定1日でほぼ終わってしまいました。(まあ経験があるから1日で済んだっていう話はありますが。)

そんなわけで「5月頃はもしかしたら受託で仕事しているかもしれないなー」などと言っていたのですがそれも発生する予定がなくなったので、仕事探し(?)が気楽にできるようになった感じです。

Fluidsynth for Android (OpenSLES/Oboe)

github.com

ここはてなも含め各所で何度か言及しているのですが、FluidsynthにAndroidのオーディオドライバー実装を追加したブランチを開発してメンテしていたやつが、ようやくupstream masterにマージされました。Fluidsynth、Android上で動くコードはあったのですが、何も音が出ないダミードライバーしか無くてほとんど意味がなかったのが、これでちゃんと音が出るようになりました。Fluidsynth 2.1の目玉機能のひとつとしてリリースされることになると思います。

VolcanoMobileなど独自にFluidsynthに手を加えてGoogle Play上で公開されているものもあるのですが、Androidドライバ部分のソースコードが非公開で(これLGPLには準拠していないってことなんですが、個別に許諾もらっているのかなぁ…)、ちゃんとOSSとして使えるコードが無かったので、これでまたひとつAndroidの音楽ソフトウェアの可能性を広げられたかな、と思っています。ちゃんとしたソフトシンセがほしい、という時に使ってやってください。

glibのビルドのために使っているcerberoが、Android NDKに変更が生じるたびに追従できなくなったり、手元のデスクトップ環境が変わるとビルドできなくなったりで、とにかくビルドが面倒だったやつです。仕事をやめてからようやく1週間単位で時間を割り当てられるようになって、それから整備して11月にpull requestを送って、でも今月までずっとCD制作で忙しくて先送りしていたやつのでした。

アプリケーションがproof of conceptであるfluidsynth-midi-serviceしかない状態で、実のところXamarin.AndroidLinuxビルドが死んでいて*1IDEも無い状態でXamarin.Androidを使い続けるのはデメリットしか無いので(あとAndroidの最先端に追従していきたいわたしとしては、いつまで経ってもXamarinにandroidxをサポートしていく姿勢が見えないのはリスクでしかない)、コレのMIDI基盤であるところのmanaged-midiひと通りKotlin化してPoCもKotlinでfluidsynth-midi-service-jとして作成しようと思っています。

あとfluidsynthと直接は関係ないところで、google/Oboeをshared libraryとしてビルドして使いたかったので、またone linerパッチを作って取り込んでもらいました。Oboeはカジュアルに変更を取り込んでくれるので支援しがいがありますね。*2

その他のコーディングとか

今月は2つ外向けにそれなりにでかいのがあったので外向けのやつがメインの話になってしまいましたが、自分のリポジトリだとmanaged-midiに設計思想メモを書いているのはそれなりに意味があるかなと思っています。MIDI playerをどう実装するかとか、割と試行錯誤感あるので。

他にもJUCEで遊んだりTracktionで遊んだりとかしているのですが、まだ特にアウトプットがあるわけでもないです。

いつもならこの時期はたぶん技術書を書く作業で死んでいたのですが、今回はそれがないので*3、たいへんゆとっています。雑に仕事を探したりとかするかもしれませんが、基本的には次の制作に向けて、開発環境を構築していくつもりです。(制作?開発?)

*1:すぐ上でxamarin-androidにPR作ったって話書いているのに…!

*2:同じgoogleでもmusic-synthesizer-for-androidなんかは完全にスルーなので…

*3:あと今回はイベントスタッフ業もやっていません。

幻想音楽祭サークル参加のKPT

https://dl2.pushbulletusercontent.com/mDQ1Wd6QAKKlr2JyX3u6mrsdw0XjPFml/20190302105736_p.jpg

幻想音楽祭の出展は無事終了しました。音楽サークルとして創作したのもイベント参加も初めて、他サークルの人々も一般参加者も知り合いがほぼ皆無、しかも単独サークルという99.9%アウェイな状況でしたが(!)、ブースでは多くの方に興味をもって音楽を聞いてもらえたり、展示していたMMLの解説に興味を持って話を聞いてもらえたりと、たいへん楽しく過ごすことが出来ました。来てくださった皆様ありがとうございました…!

「事前にPVを見てこれを買いに会場まで来ました」という方がいらっしゃって、これは「こんなことがあるのか…!」とちょっと信じられないくらい嬉しかったです。これを原動力として買ってくれた方の期待にまた応えられるような作品を作りたいと思いました。

10枚売れたときは「おおっ」ってなりました。売り子をお手伝いいただいた id:Colloid に「2桁行くって割といい数字ですよ」というコメントをいただいて、多分そうだろうな?とは思いつつ、事後になってどんなもんなんだろって思ってネットの海を探してみましたが、「2桁もいかなかった経験がある人は結構多いと思うので」という記述を見て、なるほどやはりそういうもんなんだな…と思いました。前述した今回のうちの状況を鑑みると、これはやっぱりけっこういい数字なのではないでしょうか…!

売上を支出と比較して考えると、今回の作品は用意していったCDが全部はけても赤字というラインだったので、打ち込みツールのプロモーション活動というか、この方向で開発活動を進めていくための足場作りであると割り切っています。もちろん、だからといって音楽制作の手を抜いたつもりはありません。(時間的に間に合わなかった作業はもちろんありますし、テクニックが身についていればアレもコレも出来た…みたいなことはもちろんあります。レベル上げが必要なやつですね…!)

KPT

同人音楽サークルは今回が初めてでしたが、M3などには何度も一般参加していますし、ネットの海にはいろいろな情報が流れています。いろいろ参考にしながらやったことがありますが、事前告知のエントリーですでに長々と書いた辺りはカットして、今回はいろいろKPTのかたちで振り返ってみたいと思います。

  • (K) ダウンロードコンテンツにMP3だけでなくMIDIソースとTracktionデータを含めた(「うわあ、こんなの入れちゃっていいんですか?」という話もされましたがw いいです。どんなものをどうやって作ったのかを広めるために作っていますし、これで「引き」がよくなりました)
  • (K) 制作過程を紹介したペラ紙とMML本の見本誌を用意した(これを読むために立ち止まってくれた方がたくさんいた)
  • (K) クロスフェードを作成してyoutubesoundcloudに登録した(どちらのアカウントも初利用)
  • (K) musicbrainzCDDBデータとMP3 song fingerprintingに基づくタイトル情報等を登録した
  • (P) ジャンルの最初にProgressiveと書いていたけど、最初の曲は3/4固定で、残りの曲もインパクトのある部分は後半に集中していて、試聴では不利な構成で、各曲1分ずつなどそれなりの時間をかけて視聴した人ほど買わない傾向があった(期待はずれだと思われた可能性が高い)
    • (T) 次回は譜面を用意してこようと思う(タブレットでも可)
  • (P) スケジューリングが思い通りにならなかった: これは事前の見通しの問題ですが、たとえば(1)アルバムジャケットを印刷依頼するためには、その時点で裏ジャケットに載せる曲名リストが確定していなければならない(最後の追い込みがうまくいったら1曲追加できる、みたいなことはできない)、(2)PVをアップロードするためには、その時点で作品がだいたい完成していなければならないが、告知用のPVが必要になった時点でまだ仕上がっていない、といった問題
  • (P) 参加サークルによるサンプル曲の募集に対応できなかった(何一つ完成していなかったので…!)
    • (T) なるべく1曲くらいはこういう用途に対応できるように音源を用意しておきたい
  • (P) ついたてが無かったのでPCの背中を使って紹介を掲示したのだけど、手作り感が目立ってしまった
    • (T) 次回はついたてを用意しておく or 組み立て台を調達する(隣のサークルさんが用意していて「いいな〜」ってなった)
  • (P) 名刺的なものを何も用意していなかったので、不在の時に「連絡先は…?」みたいなことになった(とはいえ週休七日だし名刺的なものを用意するには完全にサークル用の何かが必要)
    • (T) サークルと肩書をでっちあげる or 無職を失う

音楽は試聴するまで内容がわからないので、試聴しか伝達手段が無いと、じっくり聴く人でもない限りは、最初のせいぜい1分くらいで次の曲に進む人が多いです。その1分の間にインパクトのある内容(プログレであれば癖のある変拍子や音色選び)が無ければ、期待通りの内容とは思ってもらえません。ただ、曲の構成を「試聴に最適化されたかたちに」変えるというのは避けたいと思っています。全体的にどのような楽曲なのかは、楽譜になっていればある程度見えるはずです(特に変拍子など)。

実のところ、最初はSMFからmusescore経由で譜面を印刷して閲覧用に持っていくつもりだったのですが、最初の楽曲がすでに50ページを超える内容だったので、今回は見送ったのでした。結果的には「あったほうがよかった」と思っています。タブレットがこの目的にはちょうど良さそうですが、調達が必要な案件です。

今後の予定

実のところ、サークル出展自体が初めてだったこともあって、他のイベントに目を向ける余裕は全くありませんでした。M3春なども参加できる予定はありません。コミケはちょっとしんどそうなのでスルーすると思います。というわけでリアルイベントには、出られるとしても秋ですかね…(!) Apolloには気軽に出られそうなので多分参戦するんじゃないかと思っています。いずれにしても、じっくり制作に時間をかけられる体制を確立しておくことが必要ですね…!

今からでも入手したい

…という方のために、CDの無いダウンロード版としてのデータをboothから入手できるようにしました。もしかしたらCDもプラス500円+送料くらいで販売するかもしれません。

xamaritans.booth.pm

"幻想音楽祭" 出展のお知らせ (+ MMLによる音楽制作の実践)

gensouongaku.info

3/2に川崎市産業振興会館で行われる同人音楽の即売会「幻想音楽祭」に個人サークルとして出展します。オリジナル楽曲のアルバムCD・音楽データDLカード等を販売する予定です*1。サークル配置番号はA-4 "ginga" です。*2

ここを見ている人は大半が開発者だと思うので、ここで告知してもかなり「かぶらない」と思うのですが、個人的には半分くらいは作っているソフトウェアの展示とか、音楽の作り方をどうするかみたいな話に興味のある方に来てもらえればと思っています。イベント自体も初めてのようですし、ぜひ遊びに来てください。

TL;DR

目次

音楽作ってる? は? なぜに?

ここを読まれている人の大半はこんな反応になるかと思いますが(そうでもない?)*3、音楽ソフトウェアの技術同人誌とかではなく、同人音楽CDです。大昔にMIDI音源をいじっていたことを除くと完全に未知の分野なのですが、手探りで辛うじてやっています。

見せて

はい…(抵抗感) youtubesoundcloudにうpしてあります。

youtu.be

soundcloud.com

人知れずひっそり出すか、ずっと悩んでいたのですが、自作ツールのポートフォリオとするためには隠してやっても仕方ないよな…となって、表に出すことにしました。今後は何か作るとしても別名でやるかもしれん…

発端

昨年にもちらっとここで書いたのですが、11月にAudio Developers Conference 2018に参加して、参加者の何人かと雑談していた時にふと「おまいらも自分で音楽を作ったりするの?」と聞いてみたら、返答の大半がYESだったんですね。自分で音楽制作しているわけでもないのに音楽ソフトの開発やら仕事やらにシフトしてみたい、って言うことには、自分でも何か落ち着かないコンプレックスのような気持ちがあったわけです。

それで、一度くらいちゃんと音楽ソフトを使って制作とかやってみて、どういうものが必要で求められていたりするのか勉強してみようと思ったのでした。個人的にはM3とかボーマスとか、何度か行ったことがあるので、とりあえず一度何か同人音楽イベントに参加してみようと思って、それで11月末頃に今回のイベントが申込受付中であるのを発見しました。

「ファンタジー音楽」というのは何となくわかる気がするし*4、第1回だし、右も左もわからない雑魚サークルでも参加できそうな気もするし、参加できたらラッキー…くらいのノリで出来心で応募したら、あっさり通ってしまい、それならやるっきゃない…!と肚を括ったのが12月のことです。

制作環境の決定

参加が決まった時点での方向性はほぼゼロで、出せるコンテンツとしては20世紀の頃に作ったSMFとして作った楽曲がわずかにあるばかり…という状態でした。DAWなどを使ってみたほうがいいとは思っていましたが、経験ゼロのところからちゃんとイベントまでにCDを作るところまでもっていけるのか…!?という感じで、正直ゼロからDAWでやっていたら完成しなかったと思います(!?)

とりあえず、20世紀以来ほとんどやっていなかった「音楽を打ち込む」感覚を取り戻さないといけない…というところからやり直しました。とりあえず当時慣れ親しんでいた楽器であるRoland SC-8820を毎日持ち歩いて自作のソフトウェアMIDIキーボードxmmkで音を鳴らしてみたり、Tracktion Waveform9(面倒なので以降単にTracktionと書きます)を起動して標準添付のCollectiveで音色を探して4小節くらい和音を鳴らして終わり、みたいな感じで、たいへんに雑魚ったところからスタートしました。

ちなみにTracktionだったりCollectiveだったりするのは、わたしの普段使いのPCがLinuxなのでKontaktなどが一切使えなかったためです(Kompleteを9の頃に買っていて、「もっとWindowsUbuntuみたいに快適に動いてくれたらなあ…」などと言い続けながら、今回Macで使うまで何年も眠らせていたのでした…)。

最初の頃は、Tracktionの操作に慣れる目的で、過去に打ち込んでいたMIDIファイルを、まずはコピー曲などからインポートして、VSTに置き換える練習から始めました。Tracktion、それなりの頻度で落ちるので、Bitwig Studio*5などに変えようかなあ…などとも考えたのですが、この一見風変わりだけどロジカルに出来ているUIがやっぱり好みだったので、今でも使っています。

この時点でわたしが「わかる」と言える音色はSC-8820までのGM/GS音源の音色くらいだったので、Collectiveで音色を選んでそのパラメーターを調整するか、Collectiveにない音(かなりあります…)については、juicysfpluginサウンドフォントFluidR3_GM.sf2でGMにフォールバックする感じでした*6。ただ、これは早々に行き詰まりそうだったので、SC-8820のフル音色を徹底的に活用してMMLMIDI楽曲としてまず完成させつつ、その後にWindowsなりMacなりでKontaktなどの音源版を作って移植しよう、とざっくり方針を決めました。

この頃は一方で、ADC2018で持ち帰ったソフトウェア(JUCEやらTracktionやら)の知見を学ぶために*7いろいろコードをいじっていたのですが、それらが「動作する」「Linux以外の」環境が比較用に無いと作業が行き詰まるというところまで来ていたので、仕方なく*8Mac mini環境を構築したのでした。たまたま自宅に10インチくらいの液晶モニターがあったため、以降このMac miniはまるごとモバイル環境になり、電源のある環境でちょいちょい使われています(!)。

Linux環境でMMLで作曲、その後MacのTracktionに移植してマスタリング、という方針がざっくり決まりました。これが年明けくらいの話です。

MML打ち込み環境のブラッシュアップ

ここで改めて言及するのも何ですが、MMLというのは20世紀に使われていたDTMにおける音楽データ制作のツールであって、2019年にしかもLinux環境で使うというのは半ば正気の沙汰ではありません。とはいえ、これを実現するための土台は用意してありました。具体的には、今までこれらを開発していました:

少し前にここ2ヶ月ばかりのコーディング活動について書いたのですが、これは完全にこの音楽制作のためにやっていたことでした(単独のブログエントリとして見ると謎すぎましたね)。音楽制作と言いつつ、それなりの時間がプログラムの改良に当てられています。

2018年末にはVGM作曲家の古代祐三さんが使っているMMLコンパイラツールであるmucom88が公開されましたが、あれも主に自作環境の公開という雰囲気なので、わたしがここで公開しているのと(完成度とポピュラリティは違えど)似たようなノリだと思っています。

制作の勉強

この辺までの作業はツール開発者の視点で楽しく出来ることだったのですが、作曲ばかりは本当に自分のメンタルや創造性とのたたかい…になる側面が強くて、今でも進捗は圧倒的に悪いです。とはいえ、本当はそれなりにフレージングなどのテクニックがあれば進められることなので、勉強次第でどうにか出来るはず(それが足りない)という側面のほうが大きいことでしょう。まあ人間いちどに出来ることには限りがある…!

いくつか参考にした本などを挙げます。

nextpublishing.jp

まず手元にあった「ボカロでDTM入門」(Vocaloidは今回全く使っていませんが)。このエントリを書くために探して改訂版が出ていることに気付きましたが、うちにあったのは初版でした。アルバム(と呼ぶしかなさそうなのでそう呼びます)のテーマ作りみたいなことを考える時に参考になる部分がありました。

今回の制作にあたって、イラストレーターにカバーイラストをお願いするときにコンセプトをでっち上げる必要があったり、曲想が全く浮かばない日々が大半だったので(実のところ今でもそうで日々血涙を流しています)、何とかしないと…みたいな感じだったりで、解決の道筋になりそうなものを探し求めていた(いる)のです。ワタクシどう考えても情緒の豊かな人種でもクリエイティブな人種でもないので…

管弦楽法

管弦楽法

11月頃に、Tracktionでクラシックの管弦楽曲を打ち込もうとしていたことがありました。音楽制作については、職業DTMerの人にいろいろ教えてもらうことがあるのですが、この時に移調譜の読み方などを教わりながら「ピストンの『管弦楽法』を読むといい」と言われ、これを入手して読んでいました。

作業の過程で必要な部分だけつまみ食いで読んでいるのですが、その後KontaktでSession Stringsなどを使って音色を調整する時に、奏法などの未知のキーワードをこの本で調べたりしています。また、GM音源を使っているときにはもやっとしか知らなかった管弦楽器の編成についても、このあたりを知ることで一歩踏み込んで打ち込めるようになったと思います。

www.gcmstyle.com

VSTプラグインをひたすら紹介してくれる同人誌です。コミケ同人音楽イベントでも入手できると思います。CollectiveどころかKompleteを使っていても「あの音色が足りない…」みたいな場面がちょいちょい出てくるので、これを参考にしていろいろな音源を知ることが出来ました。

制作作業

…さて、ファンタジー系音楽、たぶんゲームとかそっち系映画のサントラ的な音楽が出来上がればいいんだろうと考えました。馴染みはあるジャンルなのですが、過去にそういう作品を作ったことは無かったようです(そもそも完成した楽曲と言えるようなものを作ったことが無かった…)。このジャンルならこういうのが好きなので100年後くらいには作れるようになりたいですね…

kai-you.net

今回は過去に打ち込んでいたものを何曲か使い回しているのですが、ファンタジー系というので、何かひとつ大仰なやつをゼロから作ってみたい…!と思って、ある時ちょっと20秒くらい浮かんだフレーズから作り始めました(1曲目)。最終的にはそこまでの大編成にはなっていないはずですが、オーケストラのスタンダードな音色でそれっぽい楽曲が打ち込めた気がします。まあほんの3分くらいなのですが…! もっとも、MIDIファイル上ではトランペットやトロンボーンが(知見が無くて)音域を逸脱したところまで演奏しているのを、Tracktion上ではSession Hornsにフォローさせている…みたいなところはちょいちょいあります。*10

昔打ち込んでいた自作曲を引っ張ってきたものは、当時プログレに傾倒していた(今でもか…?)こともあって、今回の収録作品には妙な変拍子が多いです(極端なのだと23/8とかあります…)。一方で楽曲の作り込みの甘いところが多いので(テクニック不足の問題…!)、まあ多分こういうちぐはぐなのはありがちですよね…

あとは…不気味な雰囲気の曲も作ってみたのですが(デモ2曲目)、これは全音音階とかを見よう見まねで使ってみました。音楽理論どころかコードも身についていないのはちょっとコンプレックスがあるので、そのうちどうにかしたいですね。

作業は全てgithubのプライベートリポジトリ上で記録されています。gitでこまめにcommit/pushしているので、何ならブランチも作ったりしているので、思いついたアレンジを気軽に試すことが出来ますし、特にMMLは意味のあるdiffが取れるので完全に便利です。Tracktionのほうは、前回のエントリでも書きましたが、tracktioneditファイルはXMLなのでこれも差分が見えます(あのエントリの内容もこの制作作業の一環として行われたわけです)。もっとも、一番知りたいのはオーディオプラグインのパラメーター設定などであって、これはオーディオプラグイン依存のバイナリなので、ろくに比較できませんしdiffで内容を追う時にたいへん邪魔です。

デジタルデータのダウンロードカードによる配布をメインにする予定なのですが、ここには技術同人誌を作成していたときと同じノウハウが使われることになるでしょう。

MMLおよびTracktionデータの公開(予定)

今回のアルバムのソースとなる打ち込みデータは公開する予定です。運良く(?)わたしが使っているSC-8820をお持ちの方は、MIDI版の楽曲をそのままフルで再現できることになります。MMLだけ公開としてコンパイラを使ってほしい…と言ってもいいのですが、多分SMFも公開すると思います。MMLコンパイラのサンプルとなるので、楽曲はMITライセンスで公開されることになるでしょう。

公開する理由はシンプルで、MMLコンパイラの実用例であることを示し、それによってコンパイラの価値を高めるためです。あと、MMLを公開することには、ソースから知見を吸い取って再利用してほしい、かつてJASRACが「MIDI潰し」を行う以前にあった「古き良きMIDI文化」、ジットレインが「生成的インターネット」と呼んだものを、少しでも取り戻せるように…という思いもあります。

同様に、Tracktionで打ち込んだデータも公開するつもりなのですが、こちらはオーディオプラグインを多用しているので、同じプラグインを全部持っている人にしか再現できないものになるでしょう。このプロプラエタリに染まったデータをどのようなライセンスで公開するかは未定です。Tracktion自体は7まで無料で公開されています。

いずれの版も、「自作MMLコンパイラでここまで出来る」ことを示す、ポートフォリオ的な意味合いがあるので、単独でも聴く価値のある…というと大袈裟なので「破綻していない」くらいの…楽曲を作るというのは重要な目的のひとつです。(という文を書いている現時点で全楽曲が完成しているわけではないので、実のところ今も焦っているわけですが…)

next steps

この自作MMLコンパイラは、少なくとも対象をMIDIとして、21世紀にふさわしい利便性*11クロスプラットフォームで実現するという目的を実現しつつあると思っています。一方で、音楽制作のメインストリームであるオーディオプラグインなどを活用した楽曲制作の置き換えまでには至っていません。この辺りは完全に別の世界として棲み分けがあるというのが現状でしょう。

DAW全盛期と言うのが相応しそうな現代ですが、ソフトウェアが古臭くなっていく一方で、Web MIDI APIやWeb Audio APIの登場、それらを踏まえたWeb Audio Modulesのような新しい技術*12の登場を考えると、DAWのように「個人ではとても作れない」巨大なソフトウェアを因数分解して、部品ごとに再利用し、打ち込みスタイルも多様化していく時代が来ても良いのではないかと思っています。MMLはその手法のひとつに過ぎず、またMMLの適用先としてDAWと同等の音楽を制作できるオーディオプラグインだらけの世界があっても良いと思っています。

制作過程のところでも言及しましたが、テキスト表現はgitなどVCSとの相性が抜群に良いです。思いついたアレンジをコメントアウトして残しておくのも簡単ですし、加工もそれなりに柔軟にできると思います(まだまだDAWのほうが得意な場面もたくさんありますが)。

そしてオーディオプラグインを利用した音楽制作のシーンは、もっとオープンに行われてほしいし、短期的にはわたしが日々使っているLinuxデスクトップ環境や新しいChromeOSのような環境でもシームレスに行えるようになってほしいと思っています。VST3はLinuxでもビルドできますし、JUCEならLADSPAもいけるし、オーディオプラグインのベンダーにもどんどん参入してもらいたいですね。

この音楽制作が終わったら余裕ができるので、こういう方向性で新しい仕事を探そうかなと思っています。*13

*1:サークル案内にあるように「作り方」みたいな本を出すつもりだったのですが、これはナシかもしれないです…少なくとも電子版になるでしょう(__ あとCDはジャケット印刷が間に合わないかもしれない…

*2:近いジャンルで名前がかぶったのですが、はてなに来る前のアカウント名であってgingaレーベルではないです

*3:最近rebuildfmでこっそり話していたんですよね…

*4:サークルの募集要項を見ると分かるのですが、定義は主観的でもやっとしています

*5:Linux環境で動作するDAWの選択肢は「ある」けどそれなりに限られるのです

*6:サウンドフォントについては、以前からfluidsynthにパッチを送っている程度にはいじっていて使い方がわかるのです。偏った知識…!

*7:特にLinux版JUCEで全くサポートされていないVST3を動かせるように

*8:わたしはPC所有者が自由にソフトウェアを動かすことの出来ないOSを作る会社がきらいなんですよね…

*9:とは言うものの、Mac環境のサポートはまだ新しい方向性で開発が始まったばかりです

*10:逆に知見が無くてもこの辺を使うと何とかなるのか…!という感じでした

*11:まだテキスト音楽サクラに及ばないところがちょいちょいあるのですが

*12:WAMs自体はC++などを前提とした従来のスタイルに見えるので、新しいと言ってしまうと実は個人的には抵抗感がありますが…

*13:とはいえ、私の知る限りそんな仕事は皆無ですし、まだ週休七日でもやれることはたくさんあるので、今のままでも良いのですが。

Tracktion/Waveformに変拍子の含まれるMIDIファイルをインポートして加工する

わたしが最近ひいき目に(?)しているDAWとして、Tracktion社のWaveformを使っているのですが、今日はこのTracktionまわりのhackを紹介します。*1

他所で作ったMIDIファイルをTracktionに取り込む時*2、たまにTracktionの挙動が不審で、予期しない謎の音楽が生成されることがあります。最近わたしが経験したものでは、METAイベントのテンポと拍子設定が入り乱れる音楽を取り込むと、テンポがめちゃくちゃになる(しかも説明が困難なかたちで)というものでした。

f:id:atsushieno:20190212155715p:plain
bogus imports of tempo and time signature

MIDIなんて使ってるやつそんなおらんやろ?と思われそうな気もしますが、Tracktionをはじめ大抵のDAWMIDIトラックやらインストゥルメンタルトラックやら呼ばれるものは、そして各種オーディオプラグインは、内部的にMIDIメッセージのやり取りで成り立っているので、この辺の問題のインパクトはそれなりにあります。この問題の場合は、テンポと拍子の扱いが「根本的に何かおかしい」可能性があります。

それはそれでせっかくgithubで公開されているのでissueとして登録したのですが、Tracktionを使った作業自体は継続したいわけです。なのでTracktionにはバグレポートしつつ、Tracktionが正常に動作するようなSMFを作って取り込もうと考えました。

バグの原因の探し方

バグの原因については先のgithub issueでちょくちょく追求した結果をコメントしているのですが、まずこの問題はtracktion_engineにも含まれるロジック部分にあるだろうと考えました。DAW上ではエディットを開いている画面でMIDIファイルの取り込みを指示する場面を追いかけます*3

f:id:atsushieno:20190212151120p:plain
Import an audio or MIDI file

(ちなみにこのスクショはWaveform9で撮ったものなのですが、Waveform10ではこの周辺のUIが微妙に変わっているので、そのつもりで読み進めてください。)

"Import an audio or MIDI file" という項目なのですが、これはOSS化されていないGUIのリソースなのでソースコードには含まれていません。しかし取り込みを続行するとダイアログが出現します。

f:id:atsushieno:20190212151834p:plain
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などを使えるツールでいくらでも加工できる…と言いたいところなのですが、ここにはひとつ罠があって、*.tracktioneditXML 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なので手作業で補正できるということを覚えておくと、他の問題があった場合にもたぶん役に立つと思います。

*1:会社名がTracktionで現行のDAW製品名がWaveformなのだけど、しばらく前まではTracktionという製品で、GPLで公開されているエンジンもtracktion_engineなので、以降もTracktionと書きます。

*2:ここで何度か書いているから気付いた人もいると思いますが、私の場合は自作のMMLコンパイラで打ち込んだものを取り込んでいます

*3:ちなみにプロジェクト選択画面(メインウィンドウのProjectsタブ)でインポートするとMIDIトラックデータは何も取り込まれないという謎挙動になるので、こっちは使いません

*4:全体的にJUCEモジュールなので実際のソースコードに辿り着くまでにヘッダファイルの海を泳がなければならない場面がちょいちょいありますが…

*5:先日書いたMIDIプレイヤーのマーカージャンプの話が良い例です

*6:すぐ後で言及しますが、JUCEのXMLまわりの実装が古臭いので使いたくないという気持ちもあり、XMLをいじるだけなら.NETで自前でやったほうがマシだと判断しました

MIDIプレイヤーにおけるマーカージャンプの実装

これは個人的にまとめている開発メモから「公開したほうが役に立ちそうだな」という感じのエントリを取り出してきたものなので、書いた当時は微妙に現状とは違うのだけど、とりあえず補足は後で追加する。


DAWで便利な機能のひとつにマーカーがあるのだけど(もっともあまり自分が活用できているとは言い難い)、mugene/xmdspではあまり真面目に向き合ってこなかった。しかし曲が長くなってくると、とりあえず早送りで飛ばしてから再生というのも非効率なので、そもそもマーカーでジャンプできれば楽ではないかと気が付いた。

これは主にxmdspの機能となるはずなのだけど、実際にはxmdsp, mugene, managed-midiの全てに手を入れることになった。

まず、MMLからマーカーを吐き出せるようにする必要がある。これはすでにMARKERという命令で実現していたのだけど、mugeneはメタテキストの処理にバグがあって、3バイトのメタテキストを他のMIDIメッセージと同様に扱うというしょうもない問題があった(1文字のメタテキストをもつSMFを生成したことがなかったので気付かなかった)。これを修正したらマーカーが出るようになった。

次は、SMF中のどこにマーカーがあるのか、SMFから取得できるような仕組みが必要になる。これはmanaged-midiのMidiMusicクラスに機能を追加して対応した。

さて、曲中のマーカーを拾うことはできたが、その位置は全て先頭からのtickでしかわからない。ユーザーが直感的にマーカーを選んでジャンプするには、これが秒単位になっている必要がある。このため、指定されたtickからテンポの変更などを考慮して時間位置を取得する実装が必要になった。これは今までトータル演奏時間を取得するために存在していた処理を再利用して実装した。

これだけやって、ようやくxmdspのUI上にマーカー一覧を表示することができた。

https://twitter.com/atsushieno/status/1087763243467104257

ここまでは序の口だ。この指定された位置にジャンプする機能をMidiPlayerに追加しなければならない。これは割と大仕事だ。というのも…

(1) MIDIにおけるseek処理は、単に処理するMIDIイベントのポインタを置き換えれば済むという問題ではない。MIDIトラック中のメッセージには、コントロールチェンジやプログラムチェンジが存在しており、これらを単純に無視して指定された位置からノートオンを続行すると、意図しない音色やコントロールの設定で音が出ることになる。これでは不十分だ。MIDIの場合は、基本的に先頭からseek先までに含まれるノート以外の命令も処理しなければならない。

理想をいえば、この間でも「後で上書きされる命令」は飛ばすべきなのだが、とりあえずは愚直に全部処理することにした。ひとつには、MIDI出力ポートとして抽象的に指定されているものの中には、受信したメッセージを分析したり蓄積したりフィルターしたりするものがあるかもしれず、それらは「間引き」されたメッセージリストを想定していないかもしれないのである。

間にどのような処理が挟まっているかわからないということを考えると、ひとことでseek処理と言っても、実はその実装アプローチはさまざまなのだ。そう考えると、ここには汎用的なシーク処理のインターフェースが必要になるのだけど、どのようなインターフェースなら既存のMidiPlayerのAPIにどう組み込めるのか、検討が必要になるので、とりあえずまだ公開インターフェースを用意できる段階ではないという結論に至った。

(2) 状態遷移を大きく乱す機能なので注意が必要になる…ということで、PauseやStopなどの命令をあーでもないこーでもない、と組み合わせながら取り組んだのだけど、最終的には余計なメソッドを呼び出さずにシンプルにMIDIメッセージ処理のポインタを移動するのみになった。現時点で既に実装に問題があって、seek呼び出し後に「現在の演奏時間」の値が壊れているのだけど、ここは少し腰を据えて取り組まないと解決しなさそうだ。とりあえずseek機能は今切羽詰まっている打ち込み作業の改善のためにほしいので、これは後回しとなった。

…というわけで、seekの実装はそれなりに大変だったのである。MidiPlayerJSなど、seek/jumpを実装しているライブラリでは、この辺はあんまし考慮されていないということもわかった。多分ardourやtracktion_engineの実装はもう少しまともなのだろうけど、さすがにソースを追っかけている時間がない。興味が出たら後で見る。


…というのが当時の現状で、seekの後の演奏時間はその後ちゃんと直った

マーカージャンプで曲中の任意の場所にすぐ移動できるようになってから、#conditional で条件コンパイルする機会は激減した。これの何が良いかというと、#conditionalでブロックをスキップしても、CCやPITCHやらは処理されないので、条件コンパイルすると曲がおかしくなってかえって原因究明に時間を取られる(のでMMLは慎重に書いていた)という本末転倒な状況から脱却できる。#conditionalのセマンティクスを変更するというアイディアもあるのだが、煩雑になりそうなのとマーカージャンプで十分に作業効率が上がったのとで、だいぶ優先度が低い。