ResidentMIDIKeyboard: Android用の常駐型MIDIキーボード

ずっとAndroid用のコードを書いているくせにAndroidアプリをリリースすることが全く無かったのですが、ちょっとオーディオプラグインプロジェクトの負担を減らすために(!?)、compose-audio-controlsの応用で常駐型MIDIキーボードアプリを作ったので、ひさしぶりにGoogle Play Storeアップロード体験してみるかと思ってリリースしてみました。といってもまだOpen Testingです。一応数日様子を見て、変更なく落ち着いたら正式リリースするつもりです(そうしない理由は特に無い)。

play.google.com

ソースも公開しています。

github.com

機能

Use anywhere: システムアラートウィンドウとして動作するので、「どこからでも」使用できます(compose-audio-controlsのDiatonicMidiKeyboardで実装した機能をSystem Alert Windowで使っているだけです)。

connect to MIDI OUT, get connected as MIDI IN: Android MIDI APIでデバイスとして認識されるMIDI OUTデバイスに接続して、MIDIメッセージを送りつけることができるMIDIキーボードです。MIDI OUTデバイスが無ければ何もできません。何もなければソフトウェアMIDIバイスをインストールしましょう。

あと、いったんリリースしてみて、このアプリはもしかして「MIDI入力デバイス」としてMIDIキーボード(ハードウェア)の代わりに使えたほうが便利なんじゃないか(DAWの上にオーバーレイ表示して意味があるのはそのDAWから入力デバイスとして接続したときだろう)、と考えたので、そうなるように機能追加しました。具体的には、このアプリはMidiDeviceServiceとしても動作するようになっています(MIDI OUTのほうはクライアントとして繋ぐだけなので「デバイスである」必要はない)。Android MIDI APIちゃんとサポートしているDAWなど任意のアプリであれば、MIDI INデバイスとして接続できます。

MIDI 2.0: MIDI 1.0でもMIDI 2.0でもメッセージを送りつけることができます。Android MIDI APIで利用できる任意のMIDI出力デバイスに接続します。MIDI2対応のデバイスは実質AAP MidiDeviceServiceしかないと思います。

AAPのプラグインはすべてちょっと設定するだけでMidiDeviceServiceとして動作するので、AAPとして動作するOB-XdやOdin2やVital(oid)やSurge-XT、sfizz、(aap-)fluidsynth、MDA-LV2の各種シンセといった音源が全部このキーボードから接続して動作確認できます(ただしパラメーターもstateも全て初期状態です)。Android MIDI APIに準拠していれば何でも良いですが、MIDI 2.0モードに対応しているのは現状AAP Instrumentsのみです。まあそれでもper-note pitchbendに対応しているわけではないです。あとaap-juceはどうもMIDI2モードのときにpitchbendの変換処理がおかしいっぽいので、この辺は要修正です(!)。

supported MIDI messages: キーボード コンポーネントからnote on, note off, pitchbend(横方向ドラッグ), per-note pitchbend(縦方向ドラッグ)、key pressure = PAF (デバイスポインタープレッシャー) をサポートしています。per-note pitchbendはMIDI 2.0モードを有効にする必要があります。ちなみにper-note pitchbendを受け取って音を変更できるデバイス/ソフトウェアをわたしは何一つ知りません。

最新版でコントロールパラメーターもknob経由で送信できるようになっています。CC、RPN、NRPN、Per-Note Controllers (Assignable/Registered)、Program Changeなどのほか、キーボードと役割がかぶるPAFやPer-Note Pitchbendなども送信できます。RPN/NRPNはMSB/LSB/DTE(MSB)の3つがセットです(DTE LSBまではちょっと画面に収まらなくなりそうなのであきらめました)。ものによっては値を連続的に送ってほしくないやつがあると思うので、無効化スイッチも付けました。スイッチを有効にしたときにも送られるので実用上これでいいでしょう。

Embedded UI: システムアラートウィンドウだけでなく、SurfaceControlViewHostを使ったリモートUIとしても使えます。ライブラリとして参照することなく、自分のアプリにこのMIDIキーボードを組み込めることになります。ただ実装してみたものの、Binderレベルでのプロトコルの互換性が保証されているわけでもないですし、ちゃんとバージョン管理できるaarを使ったほうがずっと安全ですね。このアプリがインストールされているときだけ、アドインとして呼び出してoptionalに使うべきものです。

補足

実装してみて初めて気づいたのですが、Androidで動作するDAWの大半が、実はAndroid MIDI APIをサポートしていないようです。Audio Evolution Mobile、Caustic3、Cubasis LE 3、FL Studio Mobile、n-Track Studioなどを試しましたが、USB MIDIやBLE MIDIを個別にサポートしている程度で、みんなMidiManagerからデバイスリストを取得していないのではないか…?という感じです。何でじゃろ…C++で実装していてそっちのほうがやりやすかったとか…?(そんなわけ無い気がする) ちなみにJUCEベースのZenBeatsやHelio Workstationは、JUCEのレベルでMIDI INとMIDI OUTがあべこべになっているので、確認する意味もないです。

動作確認にはMIDI IN接続を選択できるDreamhound StudiosのMIDI Keyboardを使いました。MIDIキーボードをMIDIキーボードに接続…(!)

mastodon.cloud

そんなわけで「これで打ち込み作業がやりやすくなったのでは…?」から「なんか便利に使える機会がないな…?」となった機能ですが、これがあればアプリ側にMIDIキーボードになるUIが無くても(あるいは「まともに使える」UIが無くても)困らなくなります(!?)

あと今回初めてGoogle Play Developer ConsoleでアップロードできるaabをGitHub Actionsでビルドするようなワークフローを組んでみたのですが(そもそも以前にGoogle Play Developer Consoleにアクセスしていたときはaabという概念そのものが無かった)、APK/AAB署名をやってくれるactionもあるし、Play Storeが要求するversionCodeを自動的にGitHub Actionsのbuild numberで置き換えてくれるプラグインもあるし、楽にできるようになっているな…となりました。気が向いたらGoogle Play Developer Consoleへの自動アップロードも組み込もうと思います。

今回はJetpack Composeを使ってキーボードを実装してSystem Alert WindowやSurfaceControlViewHostに載せてみたわけですが、こういう使い方をしている人はあまりいないので色々追加で調べることになりました。この辺の話はまたの機会に…

ちなみにresidentというのはMS-DOSの常駐型 = TSRのresidentから取ってます(terminateはしない)。たぶん現代的にも妥当なネーミングなんじゃなかろうか…

MIDI Clip File specificationについて

先月MIDI 2.0 June 2023 UpdatesがリリースされてすぐUMP仕様についてのアップデートをまとめましたが、今回は仕様に新しく追加されたSMF2とも呼ばれているMIDI Clip File specificationについて説明します。

想定していた通り、今回の内容は前回と比べたら短いものですし、内容もそもそも先月仕様書が出た直後にgistでまとめてMastodonで公開した内容のとおりなので、まあ見逃していた人向けという感じです。

SMF2 = MIDI Clip File (finalized) + MIDI Container File (unpublished, draft)

MIDI 2.0仕様は、単一の仕様ドキュメントではなく、複数の仕様がまとまって規定されているものです。仕様書のグループはいくつか存在しており、今回追加されたMIDI Clip File仕様は、その中でもMIDI 2.0 Core Specification Collectionと呼ばれる6件の仕様書のひとつに含まれているので、MIDI 2.0の中心的な仕様の1つと考えてよいでしょう。

MIDI Clip Fileは、拡張子.midi2が使われるべきである(should)とされ、midi.orgを中心にSMF2と呼ばれていますが、MIDI Clip Fileにはあくまで単一トラックのUMPシーケンスしか含まれないので、Format 0のSMFの代替にしかなりません。楽曲をエクスポートする場合のSMFの一般的なフォーマットは複数トラックを含めるFormat 1であり、MMAではこのFormat 1に相当するMIDI Container Fileが実現するまでは、SMFの代替は存在しないことになります。

MIDI Container Fileは、Sequence Project, Notation File, Other Data Files (audio, video, sequencer specific data…といった内容が含まれたzipアーカイブのようなファイルになると考えられますが、MIDI 2.0 WGの代表が「1年以内には完成しないだろう」と書いているので、まだ当てに出来ない仕様と考えたほうがよいでしょう。

この方面では、MMAはほぼ同様の目的でXMF File Formatというコンテナ・フォーマットを規定して、ほぼどのDAWでも採用されなかったという実績を残しているので、MIDI Container Fileが説得力のあるフォーマットとして幅広く使われるかどうかも、正直判断できないところです。

デルタタイム指定

UMPがJune 2023 Updatesで大きく変わったのは、DCS (Delta Clockstamps)というクロック単位の時間データが利用可能になったことです。UMP updatesの解説でも書きましたが、DCTPQ (Delta Clockstamp Ticks Per Quarter Note)とテンポがあって初めて秒単位の時刻に変換できます。この時間単位が無いと、音楽的な譜面の表現は現実的に不可能であり、SMF1にもSMF HeaderにこのDCTPQに相当する数値の指定がありました。

MIDI Clip Fileは、ファイルの先頭にあるSMF2CLIPという8バイトの識別子を除いて、全てUMPであり、Clip File Headerの最初のごく一部のUMPを除いて、全てにDelta Clockstampsが指定されることになります。これはSMF1におけるMIDIイベントの先頭にデルタタイム指定が7-bit encoded lengthとして付いていたのと同じです。

MIDI Clip Fileの構造

Clip Configuration HeaderとClip Sequence Dataの区別

MIDI Clip Fileは、Clip Configuration HeaderとClip Sequence Dataの2部で構成されています。先頭のSMF2CLIPを除いて全てがUMPなので、ヘッダー部分とシーケンス本体の部分の境目は曖昧であるようにも見えますが、UMPに新しく追加されたStart of Clipメッセージによって識別できます。このメッセージ「から」Clip Sequence Dataが始まると言えるかというと、その直前には前述のとおりDCSが付いていてこのDCSも単独で有効なUMPなので、定義上の説明としてはより明確な区別が求められるでしょう(「MIDI 2.0検定」みたいなやつを想定したコメント)。

Clip Configuration Header

Clip Configuration Header部分には以下のものが含まれ得ると想定されています:

  • Clip Configuration: MIDI 2.0 Profile Configurationを、MIDI-CIのSet Profiles Onのトランザクションとして記述します(複数)。これらにDCSは付きません
  • DCTPQ: ここでDCSのマスタークロックが指定されることになります。このDCTPQにはクロック値が0のDCSが付加されなければならず、またこれ以降のUMPには前述の通りDCSが必ず付いて回ることになります。DCSの値が0であることは求められていません(その値に待機の意味はなく、大抵は0になると思われますが)。
  • Flex DataのSet Tempoは1度だけ出現してもよい(may)ことになっています
  • Flex DataのSet Time SignatureはSet Tempoの後に1度だけ出現してもよい(may)ことになっています

なお、MIDI Clip Fileに含まれるSet Profile Onメッセージは、MIDI-CI仕様としては応答を想定するメッセージですが、MIDI Clip Fileはそれ自体が応答を処理することがあり得ないので、そのメッセージの応答の処理はシーケンサー次第です。また、指定したProfilesが全て有効になるとは限りません。Profileには複雑なものもあって、そこまで対応するかどうかもシーケンサー次第です(と仕様書で規定されています)。

それと、Set Profile OnのMIDI-CIメッセージで設定すべき各項目については仕様書をみてください(destination device IDはチャンネル、グループ、機能ブロックを適切に指定する必要があり、sender MUIDとdestination MUIDはBroadcastにする、など)。

Clip Configuration Headerにはその他のUMPメッセージが含まれることがあり得ますが、Property ExchangeについてはMIDI Container Fileに含まれるべきものであり、MIDI Clip Fileに含めてはならない(shall)と規定されています。

Clip Sequence Body

シーケンス本体側について説明することはあまりありません(SMF1でもそうでした)。いくつか注意事項があるので書いておきます。

  • Clipの先頭にはSet TempoとSet Time Signatureがあるべき(should)とされています。
  • 先頭以外でのSet TempoはMIDI Clockが出現しうるタイミングでのみ出現すべきとされています。これは要するにSysEx UMPパケットのシーケンスの途中などに含まれるべきではない、ということでしょう。
  • Set Time Signatureは小節線のあるところでのみ出現できる(shall)とされています。
  • 残りのUMPはProperty Exchange以外であれば何でも可能とされていますが、MIDI-CIなどの双方向メッセージングが求められるものは無視されるかもしれません
  • クリップの最後にはEnd of Clipを置きます。SMF1におけるEnd of Trackに相当すると考えてよいでしょう。

6月の開発記録 (2023)

2019年以来東京と台北にしか出現していなかったのですが、今月は3年半ぶりに昨日まで他県に出かけていました。完全にあぶねーなこりゃ…ってなったので、まだしばらく旅行とかしなくてもいいかな…まあそんなわけで今月は(今度こそ)少な目です。

ktmidiのMIDI 2.0 June 2023 Updatesサポート

6/12にMIDI 2.0 updatesがようやく出たので、とりあえずktmidiのエコシステム(ktmidi/mugene-ng/kmmk)でUMPサポートをアップデートしました。UMP仕様そのもののアップデートについては↓に詳しくまとめてあります。

atsushieno.hatenablog.com

ktmidiではUMP Stream messageやFlex Dataメッセージを生成できるようになり、mugene-ngではMIDI 2.0対応の音楽ファイルフォーマットを変更して、各トラックはMIDI Clip File specification準拠のデータを生成するようになりました。複数トラックを統合する音楽ファイル自体はSMF Container Fileと呼ばれている秘密の仕様がまだ公開されていないので対応していません。mugene-ngのFlex Data対応作業をやっていて思いましたが、MIDI Clip FileはMarkerイベントが存在しないままではSMF format 0の代替としても使い物にならないですね。MarkerはContainer Fileのほうでカバーされるべき機能なので、SMFの代替は「実質的にまだ出ていない」とわたしは捉えています。

今回のMIDI 2.0アップデートはUMPだけのアップデートではないのですが、とりあえずMIDI-CIは現状ほぼ自分のプロジェクトと無関係なので(特にSet New Protocolが廃止された結果ますます関係なくなった)、その辺のアップデートはまだです。Android用のMIDI-CI Discovery Serviceくらい自作してもよいのですが、Android audio teamから出てくるだろうし、特に自分がやりたいことではないので様子見です。

あとは新しいフォーマットに対応したMidi2Playerのdogfoodingに使えるアプリケーションをビルドしたいところで、managed-midiの応用として存在していたxmdspの置き換えになるやつを作りたいところなのですが、Jetpack Compose 1.4のMultiplatformがJDKサポートまわりでリグレッションを起こしていて、SwingのAPI にしか無いファイルダイアログを出せなくなっているので、Compose Multiplatformが正常化するまでは様子見です。

compose-audio-controls

先月Knobを作った話を書きましたが、続いてKeyboardも作ったので、とりあえずこれでまとめておくかと思ってリポジトリを作って公開しました。

github.com

Material Design準拠のキーボードウィジェットはとにかく「表示できるキーを多くしたい」と「48dpの範囲でタッチが混乱しない」のせめぎ合いになるわけですが、この実装では「タッチUIの場合はポイント位置と中心座標に最も近い鍵を選ぶ」「それ以外のポインティングデバイスでは正確にポイント位置の下にある鍵を選ぶ」というアプローチで実用性を担保しています。

ドラッグ操作に対しては、ピッチベンドを送れるようになっていますが、この種のキーボードコントロールに求められる典型的な動作はシンプルなノート切り替えなので、デフォルトは単なるノート切り替えにして、この2つの動作モードを切替可能にしています。マルチタッチでドラッグ操作が可能なので、ノート別ピッチベンドも送信可能です。そのためにMIDI 2.0モードのスイッチもdemo appでは追加してあります。とはいえノート別ピッチベンドに対応するプラグインがまだ無いわけですが。

ピッチベンドに割り当てているのはdemo appでしかないので、他のコントロールに割り当てることも可能です(アプリ次第)。現時点では、縦方向ドラッグ、横方向ドラッグ、プレッシャーの3つがこのコントロールで受信可能です。demo appでは縦方向がPolyphonic Aftertouchに割り当てられていますが、本来ならプレッシャーに割り当てられていたほうがいいやつでしょう(縦方向はExpressionとかがよさそうだけど初期値とかリセットとかどうするか悩ましい)。

ちなみに、MIDI 2.0切り替えスイッチはUMP仕様に新しく追加されたStream Configuration Requestを(現在のプロトコルを無視してUMPで)一方的に送りつけてそれ以降は勝手にUMPを送りつけるスタイルです。受信するMIDIバイスがAAPのMidiDeviceServiceならこれで効きます(どちらもmainブランチのみ)。以前はMIDI-CIのSet New Protocolメッセージを一応MIDI 1.0のSysExとして送り付けていたわけで(返信は無視)、MIDI1を受け付けているところにUMPを送りつけるのは以前よりさらに乱暴な挙動なのですが(!)、little endianな環境なら(大抵のAndroidバイスはそう)12バイトの0が送りつけられることは仕様上無いだろうし、big endianな環境でもF0で始まるのはSysExで、ステータスコード5に相当するSysex Vendor IDは操業停止したPassport Designs社なので、99.999%無害と言って良いでしょう(!)

compose-audio-controlsはKMPで作られていますが、主にAAPのGUIのために存在しているので、AndroidでタッチUIとして使えることを主目的にしていて、デスクトップの仮想MIDIキーボードの実装であることを主目的としているkmmkとはだいぶ設計思想が違います。たとえばこういう用途を想定しています:

AAP native default compose UI

JUCEホストアプリの上でJetpack ComposeのプラグインUIが表示できていることがわかるような画面にしましたが、AAPのnative in-plugin-process GUIの今のデフォルト実装はこうなっています(あくまでデフォルトUIでしかないので、Jetpack Composeで自作UIを構築して表示させることも可能です)。先月、SurfaceControlViewHostでネイティブUIを表示できるとアピールするならdogfoodingが必要だという話を書いたのですが、それを実践した感じです。先月のsshotはin-host-processでも可能なWebViewでしたが、今月のはin-plugin-processのComposeViewです。

正直Jetpack Composeはスクロール周りのサポートが煩雑でデメリットのほうが大きそうだなあという印象ですが、その辺はSurfaceControlViewHost上でJetpack Composeを動かす知見が溜まってきたらどうにかなるかもしれません。

UIまわりは、サイズ設定を考えないとな…というところで止まっています(その頃にMIDI 2.0 updatesが来たので頭のチャンネルを切り替えてしまった)。

AAP: 動的パラメーターサポートの改善、列挙値サポート、aap-juceのデスクトップビルド不要化

Composeベースのnative default GUI実装がざっくり出来たので、パラメーターリストで「優先度」の設定を「プロパティとして」取得できるようにparameters拡張のAPIに手を加えたのですが、その過程で、パラメーターリストを動的に生成するプラグインのプロパティを取得できるネイティブAPIを、あくまでKotlinのAPI経由でしかパラメーターを取得しないホストでは呼び出していないことに気が付き、動的なパラメーターリストもIPC経由できちんと取得するように作り替えました。

それから、パラメーターリストをそもそも動的に生成していればaap_metadata.xmlに何も記述されていなくても良くなるはず…というのを思い出して、まずaap-juceでこれを実現しました。先月作ったaap-juce-adlplug-aeでは、現在<parameters>コメントアウトされています

パラメーターリストが無くても良い、ということになると、JUCEプラグインからaap_metadata.xmlを自動生成する必要はほぼなくなります。メタデータの自動生成には「AAPを組み込んだJUCEプラグインのデスクトップビルド」が必要で、これがJUCEプラグイン移植の難易度をかなり上げていたのですが、このステップが不要になったことで、移植の可能性が高まったと思います。たとえばSurge-XTはこの段階で挫折していたのですが、手作業でaap_metadata.xmlを作成して動かせるようになりました。

github.com

aap-lv2でのサポートはまだです。aap-import-lv2-metadataはデスクトップ版のプラグインをlilvでロードできてしまえばいいので、移植の妨げになったことは現状一度もなく、実装を移植するモチベーションがあんまし高くないところです…(!) まあ、やったほうがいいですけど。今月はAIDA-XをAAPに移植したのですが、こういうのもパラメーター情報を含めなくても済むようになりますし。

github.com

あと関連して列挙値サポートもparameters拡張のAPIに追加してあります(構造が複雑なのでプロパティでは解決できなかったやつです)。パラメーター情報に列挙値リストが含まれている場合は、GUIでも表示を変えるようになりました。オーディオプラグインの場合、列挙値は基本的に「数値に対応する文字列ラベル」になり、数値はfloatのままであることが多いので、native UIのほうはknobのままで(スペース的に厳しい)、値ラベル文字列だけ列挙値のものになります。列挙値はaap_metadata.xmlにも記述可能で、aap-metadata-generatorやaap-import-lv2-metadataによる自動生成もサポート済です。

今回デスクトップビルドを省略できるようにしたことで、aap-juceの難易度はだいぶ下がったと思いますが、もうちょっとがんばってCMakeLists.txtに数行追加するだけで対応完了できるようなところまで持っていきたいところですね。clap-juce-extensionsなんかは実現できているので、あの辺を見習いたいところです。

MIDI 2.0 UMP June 2023 updatesについて

半年前から「アップデートがある」と言われていたMIDI 2.0ですが、6/12にようやく最終版が確定して公開されました。MIDI 2.0という名称は変わりませんが、UMP(MIDIメッセージのフォーマット)の仕様への大掛かりな機能追加に加え、SMF2と呼ばれるMIDI Clip Fileフォーマット仕様の追加、MIDIバイスの初期化まわりのプロトコルに対する非互換変更も加えられています。

概要はmidi.orgの記事に書かれているので、そちらを参考にすると良いでしょう。ひとつ注意点として、このページはブログ記事っぽい作りですが、実際には仕様のアップデートがあるたびに更新されているので、古い内容が今回の更新で消えたことになりますし、次に変更があったらまた消える可能性があるので、今後参照するときはarchive.orgのページなどを確認したほうが良いかもしれません。

www.midi.org

ここを見ている人は知っているかもしれませんが、わたしはMIDI 2.0 UMPをサポートするCのライブラリ(cmidi2)とKotlinのMIDI 2.0ライブラリ/エコシステム(ktmidimugene-ngaugene-ngkmmk)を公開しているので、現在はこれらのライブラリを今回のアップデート(以降June 2023 updates)に追随させる作業に取り組んでいます。本当は今ktmidi 新しいMIDI Clip Fileの再生機能などを実装してからこのエントリをまとめるつもりだったのですが、まだ標準化されていないファイルフォーマットの楽曲構成に沿った変更を大きく加えなければならないことが見えてきたので、先にこちらをまとめることにしました。

このまとめは2部作で、今回はMIDI 2.0 UMPのアップデートについてのまとめになります。MIDI Clip Fileフォーマットに関するまとめが続く予定です(本編を公開する時点で未着手)。

長くなると思うので、このエントリをまとめる前に変更点を箇条書した自分用メモも共有します。

UMP-2023-06-Updates.md · GitHub

MIDI 2.0 UMPガイドブック」の内容に対する影響

わたしがM3や技術書典で「オーディオプラグイン研究所」名義で発行しているMIDI 2.0 UMPガイドブックは、当然ながらこの変更が加えられる前に書かれたものなので、今回のアップデートは反映されていません。とりあえずは本書のテクニカルサポートとして、このエントリでJune 2023 updatesの内容を詳しく説明していきます。同書には改訂版を発行するタイミングで反映するでしょう(改訂版発行をお約束する意図ではありません。基本的にはこのエントリでまとめてしまえば情報としては完結するはず…)

同書の内容として重要な変更が2点あります:

  • 2.6 「UMPとプロトコル」で言及している「プロトコルネゴシエーション」がまるごと廃止されました。今後はUMPに新しく追加されたプロトコル選択の仕組みを使います。
  • UMPのユーティリティ メッセージ(No-op、JR Clock、JR Timestamp)からグループをあらわすフィールドが消失しました。もともと意味があったフィールドとはいえないので、この変更による影響はほぼないでしょう。

その他は「追加機能」ばかりなので、同書の内容は引き続き意味のあるものとして読むことができます。

xamaritans.booth.pm

(紙版は先日の技術書典14でほぼ完売してしまったので、現在は電子版のみ取り扱っています。)

SMF2をサポートする新しいメッセージ種別の追加

UMP June 2023 updatesの主目的のひとつは、SMF2ことMIDI Clip Fileフォーマットの実現に必要なメッセージの追加です。これはほぼ「SMF仕様にあってMIDI 1.0仕様にないもの」と同等です。ただし、SMFのメタイベントにはMIDI Clip Fileのコンセプトに馴染まない部分があり、UMP June 2023 updatesには反映されていない重要なフィールドも存在します。

「SMFにあってMIDI 1.0になかった情報」を列挙します。

  • MIDIイベント間のデルタタイム(時間差分)とその計算基準値: デルタタイムは時/分/秒ではなく、「4分音符1つをxxxカウントとした場合(xxxは設定次第、480とか960とか192といった数値)のティック数」のように表現されます。これは、音符の長さを時/分/秒で表現するとテンポが変わるたびにこの数値が変わることになって、音楽の正しい解析と再生が妨げられるためです。UMPには時/分/秒に基づくJR Timestampしかありません。
  • メタイベント: これにはさらにいくつかのカテゴリがあるので、分けて説明します:
    • テンポ: これは演奏に直接影響します
    • 拍子(Time Signature)やコード情報(Key Signature)、マーカー(楽曲中の時間位置に紐付けられた目印)など、演奏に影響するともいえないが楽曲中で何度も指定される補助情報
    • 楽曲タイトル・作曲者等の情報などのメタ情報で、一般的には一度しか出現しないもの
    • End of Track: 楽曲ファイルのデータの区切りとなるステータスコード

SMFのメタイベントは、MIDI 1.0メッセージとしては「システムリアルタイム」メッセージに割り当てられていた0xFFを識別子として使用していました。そのため、MIDIメッセージングのための仕組み(たとえばMIDIバイスアクセスAPI)とは排他的な存在だったといえます。2020年版のUMP仕様はデバイス間のメッセージングに用いられるパケットのフォーマットを規定するために策定されたので、この中にSMFのメタイベントのようなものが含まれることはありませんでした。

Delta ClockstampsとDCTPQ

UMP June 2023 updatesには、このデルタタイムを表現する新しいメッセージDelta Clockstamps(DCS)と、このデルタタイムの基準値となるDelta Clockstamp Ticks Per Quarter note 略してDCTPQと呼ばれるメッセージがユーティリティメッセージ(message type = 0)に追加されました。ステータスコードはそれぞれ0304です。

DCTPQ and DC DCTPQに指定すべき数値は、SMF headerにおける"division"フィールドが「正の値であった場合」と同様です。MIDI 1.0の世界では、一般的には「3でも4でも5でも割り切れる数値」が用いられていたようです。480を指定すれば32分音符と48分音符が表現できます。960を指定すれば64分音符までいけます。筆者はMIDI以前の時代から192を使って64分音符まで表現できるようにしていました(255までしか使えなかった時代の名残…!)。

ちなみに、SMF headerのdivisionが「負の値であった場合」は、デルタタイムはSMPTEに基づいて計算されることになっていましたが、これはJR Timestampでカバーできるともいえるでしょう(JR Timestampにはたかだか2秒強しか格納できないので、時間に応じたパケット分割が必要になってしまうのが面倒ではありますが)。

DCの値もSMFにおける(7ビットエンコーディングする前の)デルタタイムの数値と同等と考えて良いです。JR TimestampやDCTPQと異なり20ビットあるので注意してください。6/13時点での仕様書にもこの辺を混同したサンプルパケット図が含まれていたりします(報告済)。

ちなみにMIDI 2.0 UMPガイドブックの姉妹書ともいえる「MIDI 2.0エコシステム構築術」の 3.3.2 「パケットから音楽データ構造まで」の節には、ktmidiに含まれるMIDI 2.0用楽曲データにおいて、筆者のJR Timestampを「乗っ取って」このDelta Clockstamp相当の機能を実現するやり方が書かれています。

Flex Dataメッセージ (0xD)

新しい可変長データのメッセージ種別

SMFメタイベントの多くは可変長テキストを指定するものです。メタイベントの識別子テキスト(01h)、著作権表記(02h)、トラック名(03h)、楽器名(04h)、歌詞(05h)、マーカー(06h)、キュー(07h)が該当します。

UMPには既に可変長パケットとしてSysEx7(メッセージ種別0x3)、SysEx8とMixed Data Set(メッセージ種別0x5)があり、メタイベントは特に0x5とは相性が良さそうにも見えますが、UMP June 2023 updatesでは新しく0xDというメッセージ種別コードでFlex Dataメッセージというコンセプトを新しく導入しました。結果的に、0x5にサブコードを追加するよりもスッキリとしたメッセージ体系になっています。Flex Dataは以下の図のような128ビットのパケットになります。

Flex Data message format

6/18追記: 図にchannelフィールドが含まれていなかったのを修正しました

Flex Dataには固定長のバイナリメッセージも存在しますが、Flex Dataの可変長データは(少なくともJune 2023 updatesでは)全てテキストデータです。テキストメッセージの大きな特徴のひとつはnull-terminatedである、すなわち\0が出現したらそれはデータの終端を意味するといえる、ということです。Flex Dataには、SysEx7/SysEx8/MDSのようなサイズ情報のフィールドが存在しませんFlex Dataの処理系は、\0が出現するまで、パケット分割番号などを気にすることなく文字列をバッファリング(あるいはキャパシティの許す限り格納)するだけでよいことになります。

(実はこれは本当にそう言えるのか疑わしいところがあるのですが、それは後で説明します。)

実際、SysEx8やMDSのパケット分割と統合はUMP処理の最も面倒な部分であり、「パケットが順番通りに到達しなかったら…」のようなことを考慮しなければならないことになります。UMPにデバイスのROMアップデートバイナリなどを送信する場合は、MDSのような並列送信を可能にする仕組みにも意義がありますが、Flex Dataのテキストメッセージはせいぜい歌詞が送信される程度なので、長大なデータ送信の最適化まで考慮する必要はないわけです。

Flex Dataメッセージの共通フィールド

Flex DataのformatはSysEx7/SysEx8/MDSではステータスバイトに割り当てられていた情報と同じで、0〜3でパケット分割の状態を示します(0 = 単一パケット、1 = 開始パケット、2 = 続行パケット、3 = 終端パケット)。

addressは他のメッセージには無い概念ですが、0のときはそのメッセージはチャンネルに送られ、1のときはグループ全体に送られる、と規定されています。SMFメタイベントはもともとチャンネルを宛先とするメッセージではなかったのですが、歌詞などは特定のチャンネル(パート)に対して指定すべきものであった(複数の歌唱パートがあれば複数の歌詞指定が必要になり得る)といえるでしょう。UMPではメインヴォーカルとバックコーラスに別々の歌詞を指定できることになります。

status bankは、その次に来るstatusフィールドのカテゴリーを示します:

  • 0: setup and performance: テンポや拍子など、音楽的な意味を持ち、演奏中に何度も指定値が変わり得るメッセージです。formatは(June 2023 updatesの時点では)どれも0です
  • 1: metadata text: 演奏情報ではなく楽曲のメタデータとなるテキスト
  • 2: performance text: 歌詞など、演奏中に消費される情報となるテキストです

(SMFのメタイベントの分類でほぼ同じ説明を書いていますが、同一ではありません)

Flex Dataメッセージの5バイト目〜16バイト目の意味は、status別に規定されます。以降、各論的に説明していきます。

テンポ指定 (bank = 0, status = 0)

status bank = 0, status = 0のFlex Dataはテンポ指定です。データ部の最初の32ビットでテンポを10ナノ秒単位で指定します。これはSMFにおけるテンポ指定の100倍の精度です。SMFでは1マイクロ秒単位で、500,000がBPM = 120のときの4分音符の長さとして使われていました。

もちろん、どれだけ高い解像度でテンポを指定しても、処理系がその通りに再生してくれるとは限りません。

拍子 (Time Signature) 指定 (bank = 0, status = 1)

status bank = 0, status = 1のFlex DataはTime Signature指定です。SMFのメタイベントで指定していたものと同じです。ちなみに(SMFも同じですが)分子と分母を素朴に指定するのではなく、分母には「2のn乗」のnを指定するかたちになり、「そういう枠にはまらない音楽」については0を指定します。つまり2の累乗しか実現できませんし、0が出現することがあり得ます。ついうっかり単純に分母として割り算すると(そんな事態があればですが)0除算エラーになります。

分子、分母、"number of 1/32 notes" を1バイトずつ指定します。その最後のやつは意味があるのか?と思われそうですが、UMP仕様の説明ぶりからして、SMF仕様の策定時にすでに何らかのデバイスとの後方互換性目的のためだけに存在していたようです:

The Number of 1/32 Notes field expresses the number of 1/32 notes in 24 MIDI Clocks. This is copied from SMF 1, where it is stated:
    
    "This was added because there are already multiple programs which allow the user to specify that what MIDI thinks of as a quarter-note (24 clocks) is to be notated as, or related to in terms of, something else."

メトロノーム (bank = 0, status = 2)

UMPにはメトロノームを指定するメッセージが追加されています。シーケンサーが送ってきたり、受信デバイス上のメトロノームを設定したりというユースケースが想定されています。SMF2に含まれることはないでしょう。パラメーターの説明は省略しますが、primary divisionに加えてsubdivisionで裏拍のパターンも指定できます。

調号指定 (Key Signature) (bank = 0, status = 5) とコード名指定 (bank = 0, status =6)

UMP Flex Dataにはkey signatureを指定するメッセージが追加されています。DAWではコードトラックのような概念が一般的に存在していますが、SMFにはそのような概念がありませんでした。

コード名のほうは、オルタードや分数コード等も表現できることもあって、だいぶ情報量が多くなっていますが、16バイトに収まっています。フィールドの詳細な説明は割愛します。

ルート音などを指定するフィールドで注意すべきこととして、A, B, C, D, E, F, Gが1, 2, 3, 4, 5, 6, 7に割り当てられています。0〜6ではないですし、C, D, E, F, G, A, Bではありません

テキストメッセージ

Flex Dataの残りはテキストメッセージです。status bankの説明で言及したとおり、曲中に変化しないメタデータテキストと、演奏中に何度も指定される演奏テキストがあります。

(1) status bank = 1: メタデータテキスト

status 内容
00 未知のメタデータテキスト
01 プロジェクト名
02 曲名
03 MIDIクリップ名
04 著作権表記
05 作曲者名
06 作詞者名
07 編曲者名
08 発行者? 出版社名? (publisher name)
09 主要演奏者名
10 並奏者名
11 録音/実演日
12 録音/実演場所

(2) status bank = 2: 演奏テキスト

status 内容
01 未知の演奏テキスト
02 歌詞
03 歌詞の言語
04 ルビ
05 ルビの言語

特筆すべきテキスト種別は無い…といいたいところですが、いくつか気をつけるべき点があります。まずメタデータテキストの種類によっては、複数回指定されるものがあり得るということです。著作権表記や作曲者名、編曲者名などは複数行出現し得ます。

次に、歌詞とそのルビの文字列には、実は\0が含まれることがあり得ます。「テキストが\0で終わるというのはウソ」という冒頭の伏線はここで回収します。歌詞の中に出現する\0はmelisma = メリスマを表します。

musescore.org

ひとつのシラブルに複数のノートがスラーで繋がっているような場合が考えられるでしょう。

UMP仕様ではこの\0で表現されるmelismaはformat = 2こと継続パケットでのみ出現しうる、となっています。継続パケットで続きが存在することが既知であれば、その中で\0が出現してもメリスマであると断定できますが、format = 1のときは\0は出現できないことになっています。

筆者はこの仕様には疑問の余地があると考えています(これも報告済)。format = 1のときに出現する\0はむしろformat = 2のときと同じでmelisma以外はあり得ず、意味が曖昧になるのはformat = 0とformat = 3の場合です。これらのformatの場合でも、歌詞がmelisma終わることは考えられないので、最後の非\0コードまでは有効な歌詞、それ以降は空白データ、それ以前の\0はmelisma、と判断することは可能でしょう。

歌詞の中で改行が出現する場合の扱いもやや特殊です。改行はCRのみです。そして続いてLFが出現した場合、改段として扱われます。

SMFにあってFlex Dataメッセージにないもの

Flex Dataの説明もこれで最後になりますが、以下のものはSMFにあってFlex Dataにありません。

  • インストゥルメント名
  • マーカー
  • キューポイント

キューポイントは「(ここから)ソロ」みたいな意図でテキストを残すものとして規定されていますが、DAW等で使われているとも考えにくく、シンプルに削除されたのでしょう。

MIDI Clip Fileで使われることを前提として考えると、インストゥルメント名の位置付けは割と不透明です。「トラック名」が楽器を表すのが一般的ともいえますし、ひとつのトラックが複数の楽器を演奏する可能性もあって特に時間で変化する可能性もあると考えると、メタデータテキストなのか演奏テキストなのかも自明ではありません。扱いに困ったので削除、という見方もできるでしょう。

マーカーも筆者はSMFでそれなりに活用していたのですが(たとえばSMFプレイヤーにマーカーを選択してジャンプする機能を実装して打ち込み作業を楽にしていました)、これはMIDI Clip Fileではないほうのフォーマット(後述)で代替されるべき情報であると考えられます。追記: midi.orgの関連スレ

UMP Streamメッセージ (0xF)とプロトコル確立の変更

Protocol Negotiationの廃止とUMP Endpointの誕生

MIDI 2.0はMIDI 1.0システムの上で構築できるようにプロトコルが設計されていて、2020年当初のMIDI 2.0システムでは、MIDI 1.0からMIDI 2.0への切り替えはMIDI 1.0でも表現できるUniversal SysExに基づくMIDI-CIのレベルで、Protocol Negotiationメッセージによって行われていました。June 2023 updatesでは、MIDI-CIからこのProtocol Negotiationが消失し、その機能はUMPに新しく追加された「UMP Endpointに接続調整する」仕組みによって置き換えられました。

「なぜ?」というのは仕様書には全く書かれていないのですが、このようなドラスティックな仕様の変更を引き起こす理由はいくつか考えられます。

  • 後述するFunction Blockという概念が新しく追加され、これがMIDI-CIを伴わないUMPのレベルで完結できるため、プロトコル設定のメッセージングもUMPのレベルで完結するようにして見通しを良くした可能性はあります。
  • MIDI-CIに基づく接続が確立されていないとMIDI 1.0と2.0の切り替えが行えないというのは、実のところ不必要な制約でした。筆者はMIDI 2.0をサポートしているプロジェクトを複数持っていますが、そのいずれもMIDI-CIに基づくProtocol Negotiationをまともに実装していません(そもそもMIDI 1.0接続のAPIを前提にしていて、一方的に旧仕様におけるSet New Protocolメッセージを一方的に送り付け、返信は受信しない、といった動作)。MIDI 1.0/2.0プロトコルの切り替えが意味を成すのはMIDI 2.0をサポートするデバイスのみなので、UMPで切り替えを指示できればそれで十分なのです。

Function Blocksのコンセプト

UMP June 2023 updatesでは、新しくFunction Blocksというコンセプトが導入されました。この"function"は関数ではなく機能と捉えるのが適切でしょう。function blockはMIDI 2.0 UMPにおける「グループ」を複数まとめてひとつのメッセージングの送受信対象として扱おうというものです。

UMP June 2023 updatesの仕様書には、「MIDI出力に送信するキーボードに1つ、トーンジェネレーターに2つ、DAWのトランスポートコントロールに1つ」の4つのグループを1つのFunction Blockとしてまとめる例が挙げられています。これが「ひとつの(仮想的な)シンセプラグイン」を表していると考えると、比較的すっきり理解できるでしょう。

Function Blocksがどのように確立されるか、既存の(あるいは廃止された)MIDI-CIのどの概念に対応するものなのか、といった議論は、MIDI 1.0ポートのサポートなども含めてそれなりのボリュームのある変更となっており、UMPの「パケット」に関する変更点を解説しようという今回のまとめの趣旨からはやや外れるので(そういう話はMIDI-CI updatesをまとめることがあればそこでまとめられるべき)、今回はFunction BlocksをサポートするUMP Streamメッセージの説明に登場する、グループをさらにまとめる論理単位である、という以上は踏み込まずに説明します。

UMP Streamメッセージ (0xF)

以上のように、UMPには新たにMIDI-CIでやっていたようなメッセージを新たに追加する必要が出てきました。MIDI-CIはUniversal SysExで実現していたのですから、これらの新しいメッセージもUMPとして表現する必要は必ずしもないのですが、UMP Endpointに関するメッセージもFunction Blocksに関するメッセージも、特定のチャンネルに結びつけて処理すべきものでもなく、SysEx7/SysEx8ではない独自のメッセージ種別を新たに定義したほうが、仕様として見通しが良いのは確かでしょう。

そういうわけで、かどうかは(このまとめはMMAやAMEIの関係者ではない筆者が書いているだけなので)わかりませんが、June 2023 updatesでは新たに0xFというメッセージ種別が規定されました。

UMP Stream message format パケット分割フォーマットはいつも通りの0〜3(完結・開始・継続・終了)です。あとはステータスコードしかないので、内容としては非常にスッキリしています。

UMP Streamメッセージに規定されているのは次のものです。

status メッセージ
0x00 エンドポイント探索リクエス
0x01 エンドポイント情報の報告
0x02 バイス識別子の報告
0x03 エンドポイント名の報告
0x04 プロダクトインスタンスIDの報告
0x05 ストリーム設定リクエス
0x06 ストリーム設定の報告
0x10 Function Block探索リクエス
0x11 Function Blockの報告
0x12 Function Block名の報告
0x20 Start of Clip
0x21 End of Clip

最後の2つ以外は、MIDI-CIで規定されていたようなリクエストとレスポンスのメッセージを規定したものとして読めます。前述した通り、これらのメッセージについて説明するためには、そもそもMIDI-CIなどがどのようなメッセージのやり取りをするものなのかを説明しなければならないので、この内容の詳細な説明は割愛します。MIDI-CI仕様にひと通り目を通してからこの部分を読むと理解が早まるでしょう。

Start of ClipとEnd of Clip

UMP Streamメッセージの最後の2つ、Start of ClipとEnd of Clipは、ここに出現するとやや唐突な印象がありますが、何も難しいことはなく、End of ClipはSMF仕様に含まれていたEnd of Trackに相当するものだと考えればよいでしょう。Start of ClipはSMFには無かった概念ですが、新しいMIDI Clip File (SMF2) 仕様においては、ヘッダーブロックと演奏ブロックを区分する目印として不可欠の存在となります。

MIDI 2.0メッセージの追加

ここまでの変更に比べるとだいぶささやかではありますが、MIDI 2.0のチャンネルボイスメッセージにもいくつか追加項目があります。

  • Controller 84 (Portamento) のデータ部分はノート番号なのですが、この「値」は32ビットでどのように表現されるのが正しいのか自明ではない、という議論があったようです。32ビットの先頭7ビットが使われるということが明記されました。(一貫性のあるMIDI 1.0 to 2.0 Translationを実装することを考えれば、これはほぼ自明だったともいえます)
  • CC 126とCC 127のOmni Mode On/Offについても、同様の明確化が図られました。
  • Registered Controller (MIDI 1.0におけるRPN)にはPitch Bend Sensitivityが定義されているのですが、Per-Note Pitchbendについてはどうなっているのか?という問題が2年前から提起されていたようです。今回新たにPer-Note用のRPN #00/07 としてSensitivity of Per-Note Pitch Bendが追加されました。先頭7ビットが100セント(半音)単位での整数部、残り23ビットが小数部です。

MIDI Clip File編に続く(たぶん)

UMP仕様のアップデートに関する解説は、(だいぶ端折りましたが)これでひと通り終了です。本当はここにMIDI Clip Fileのセクションを追加して終わりにしたかったのですが、ここまでですでに10000字位になっていて、いくらなんでも長すぎるので、改めて書こうと思います。ただ、そんなに長い仕様でもないので、まとめても今回の1/10くらいで終わるショボい内容になりそうなふいんきもあります。

先に要点だけ書いておくと、MIDI Clip FileはSMF2を名乗って拡張子も*.midi2と規定するくせに、SMFの代替になるわけではなりませんMIDI Container Fileという仕様が別途策定作業中にあって、MMAの企業メンバー以外には非公開です。

5月の開発記録 (2023)

5月はGoogle I/Oがあったり(リモートでひと晩見てただけ)、ADC-xがあったり(リモートでひと晩見てただけ)、技術書典14があったり(サークル出展して当日スタッフやってただけ)、割とタイムゾーンを越える必要があって、完全に疲労困憊していました。そんなわけでアウトプットも少なめです…いやそうでもねえかなこれ…

オープンなアプリケーションストアを改善する? PackageInstallerとそのアップデート

5月の上旬にTechBoosterで技術書典14向けの新刊を出すという話が出ていたので、Android 14向けにアップデートが含まれていて、サードパーティアプリケーションストアの文脈でそれなりに時流に乗ったトピックであろうPackageInstallerの話を書きました。が、その新刊自体がポシャったのでzennで供養しました(他の著者もやってたのでまねっこ)。

zenn.dev

この記事では完全に余談扱いのandroid-ci-package-installerですが、zennの別記事にも書いた通り、PackageInstallerベースの実装は(書いてはあったものの)パッケージインストールまでまともに進むことがなく、ずっとIntentベースのインストーラーになっていました。今回この記事の調べ物に合わせて、これを何とかして動くようにしました。

ついでにこの記事でも書いた事前承認のAPIもサポートしたので、Android 14以降のtargetであれば、事前承認さえあればインストール処理が完全にサービスベースになって、GitHubからのダウンロードが完了したときにAndroidシステムから承認UIが「飛び出てくる」こともなく「ちゃんと使える」ようになります。

ADLplug-AEとaap-juce-adlplug-ae

OPL3/OPN2エミュレーターのオーディオプラグイン版であるところのADLplugにはCMakeのビルドスクリプトが存在しているのですが、JUCE6で新しく導入されたCMakeモジュールではなく開発者の独自ビルドスクリプトで何もかもが構築されている(!)ので、すごいんだけどJUCE CMakeモジュールとしての応用は何ひとつ効かないという問題がありました。aap-juceを使ったビルドもProjucerで行っていました。

Projucerサポートもだんだん無理が出てきて、0.7.6ではついにビルドが通らなくなってしまったので(もはや原因を覚えていない)、独自にJUCE公式スタイルのCMakeサポートに置き換えたものをADLplug-AEという名前で新しく作ることにしました。ADLplugは実質もうメンテされていないので(そもそも最近jpcimaが何をしているのか、霊圧を感じない…)、独自版として自分の名義で開発してもいいだろう、みたいな判断です。

github.com

従来のADLplugと異なり、JUCE7に移行して、モーダルダイアログの類も全部禁止して再構築してあるので、Android版のビルドもシームレスに行えます(ADLplugにも自分のパッチを当てていたので、自分としては何の変化も無いに等しいのですが)。ついでにclap-juce-extensionsも組み込んだので、CLAP版もビルドできます。LV2サポートは従来のADLplugにもありましたが、JUCE公式のAtom portとLV2 Patchを使ったアプローチが2023年のLV2の王道スタイルなので、ADLplug-AE版を使ったほうがおすすめです。

これに基づいてaap-juce-adlplugは引退してaap-juce-adlplug-aeに置き換えられました。

相変わらずADLplugとOPNplugのどちらかしかビルドできないのはまだイマイチなのですが、手が空いてやる気が出たら複数プラグインをビルド出来るような仕組みを考えます…

aap-juce-ddsp

今月はNeuralNoteが話題になって、そういえば最近NNを活用する類のプラグインは何も移植してないな…となって、雑にmagenta/DDSP-VSTをAAP化しました。

github.com

シンプルなJUCEプラグインなら小一時間あれば移植できるようになってきたのですが、DDSPにはLinuxでちゃんとビルドできない問題があったり、tensorflowliteのビルドでちょっとハマったりして、1日くらいかかっていました。ただまあさすがにAndroidでは重いですね(とはいってもパフォーマンス分析までやっていないので、いつもみたいにJUCEの問題かもしれません)。実用性は疑問符がつくところだと思います。

NeuralNoteも気が向いたら移植しようと思いますが、すでにBYODで移植が実証できているRTNeuralだけでなく、onnxruntimeも使われているので、それ自体はAndroidで動作済としてもC++プロジェクトで使えるかわからないので、それなりに気合いが必要そうな印象です。

rtmidi-javacpp

5月はktmidiをいじる機運だと思っていたのですが、ktmidiの応用(ktmidi, mugene-ng, kmmk, augene-ng)をLinux以外の環境でいじるにはktmidiのRtMidiAccessを使えるようにしなければならず、先月書いた「rtmidi-jnaがApple Siliconでビルドできない問題」に向き合わざるを得なくなりました。

実際にはずっと向き合ってはいたのですが、(a) JNAeratorを何とかしてM1で動かすアプローチ (b) PanamaをAndroidでも使えるような代替を作るアプローチ (c) JavaCPPをrtmidiに適用するアプローチ、のどれも中途半端に試して成果が出ていなかった、というのが実態です。どれもそれなりに問題があります:

  • JNAeratorは仮にdawrin-arm64でビルドできるようになったとしても、そもそも延命して使い続けるのが妥当なのか? JNAeratorはBridJのためにあった側面が強く、Panamaとjextract(機能的にはこっちがJNAeratorに相当)の正式版が出たら完全にリタイアするのではないか? といった疑問が拭えない
  • Panama自体は当然ながらまだ正式版が出ておらずAPIも変更されうるし、あと根本的な問題として現行のjextractには単独でネイティブライブラリのロードが可能な仕組みがなさそう
  • JavaCPPはJNAやPanamaのようなJNI glue不要の仕組みではないので、ビルドしたバインディングライブラリにはネイティブコードが含まれ、各プラットフォーム/ABI用にビルドされなければならない。

いろいろ問題があるとはいえ、やってみればなんとかなるかもしれないと思って、とりあえず一番無難そうなJavaCPP版を真面目に取り組んで見ることにしました。さまざまな部分でハマりましたが、一応MIDIポートの列挙くらいはできています。

github.com

ただ、現状これをMaven Centralでパッケージ配布するところまでは進めていません。「無理そう」という印象があります。これは「darwin-arm64版をGitHub Actionsでビルドできない」のと「darwin-arm64ビルドを用意しなければならない」の2つが組み合わさって生じる問題なのですが、長くなりそうなので(先にコレを書き終わりたい)気が向いたら別の機会にまとめます。

Logic Pro for iPadの登場でオーディオプラグイン/DAW開発は変わるのか?

という記事をAppleの発表直後(実物が出る2週間くらい前)に書きました。

zenn.dev

2022年にはCLAPがオーディオプラグイン開発の世界を席巻しましたが、今年はAUv3化が最大の波になりそうな気がします。AUv3の発表は2015年で、すでに8年が経過しており、VST3並みに「無視されてきた」フォーマットといっていいと思いますが、Logic Pro for iPadが大々的にフィーチャーしてきたことで、AUプラグインのv3対応が始まる可能性は高いと思います。すでにBeatScholarなど、23日のLogic Pro for iPadのリリースに合わせて出してきたところもあります。

Compact ViewがプラグインUIとして求められるようになったら、プラグイン開発にだいぶインパクトがあるなと思っています。iOS AUv3のadoptionが進めば、macOS上でのデフォルトプラグインフォーマットもAUv3にシフトしてくる可能性が高いです。そして各社DAWがAUv3をサポートしようとすると必然的に「Androidではオーディオプラグインをサポートしないの?」っていう話が上がってくるはずなので(たとえば今月ではないけどこんな感じ)、AAPの開発をそれなりの段階まで進めないとなあ、と思っています。

exploring SurfaceControlViewHost for AAP GUI vNext

AAP GUI統合のアプローチとして、主にWeb UIを使ったプロセス境界越えとSystem Alert Windowがあるという話を、4月まではずっとしていて、ここはAndroidフレームワーク側も改善の余地があるんじゃないの?みたいな話を書いていましたが、ここにきてSurfaceControlViewHostという新しいAPIを発見したので、最近はそのアプローチでAUv3相当のプラグインUIを実現できるのではないかと考えています。

SurfaceControlViewHostはAndroid Rで新しく追加されたAPIで、「(主に他の)アプリケーション上のSurfaceViewをBinder経由で操作する」仕組みです。現状このAPIOSSの世界ではWear OSでしか使われていません。使い方は以下の記事が詳しくまとめてくれています。というかこの記事しか情報源がないし、自分もたまたま目にしなければこのクラスの存在自体知ることがなかったし、この記事でも「Googleが何も詳細をアナウンスしていない」と書いていたり…

commonsware.com

CommonsWareは最近わたしが新しいAndroid APIについて調べているとだいたい引っかかるサイトで、その手の情報はここから見始めるといいんじゃないかと思います。

描画先としてSurfaceViewが選ばれるだけで描画元は「Display IDだけあれば描画できる」ものであれば何でもよく、UIインタラクションも適宜伝播されるという感じなので、試しにComposeViewを転送するサンプルを何も考えずに書いてみましたが、全然できませんでした。いろいろ試行錯誤して出来るようになったので、サンプルコードを新しく適当に作ったリポジトリに公開してあります。

github.com

Composeが使えるくらいならSystem Alert Windowを置き換える存在として十分に役に立つだろうと考えて、AAPにもサポートを追加してみましたが、現状では(SystemAlertWindowと同様に)単にWebViewを使ってdogfoodingいるだけで、GUIイベントのプラグイン本体とのインタラクションも十分に行えていない状態です。in-process UIになるのでプラグイン実装のコストは格段に下がるはずですし、もうちょっと有用なデフォルトUIをComposeで用意したいところです。

aapinstrumentsample w/ native UI

Compose Knob Control

SurfaceControl UIを正式に推奨アプローチにするなら、まずネイティブUIとしてのちゃんとしたdogfoodingが必要だと思っているのですが、現状AndroidのネイティブUIとして動作する有用なコードが存在せず、WebViewで作ったUIのほうがまともなので、とりあえずComposeで作った雑なUIでいいから動くものがほしいかな…となっています。

しかしこれは待っていても出てこない類のものなので、とりあえずKnobから作るか…と思って自作することにしました。Jetpack Compose Knob などでぐぐるとそれっぽいyoutube動画が出てくるのですが、実装アプローチも操作方法もコレジャナイ感が強いというか、われわれがKnobコントロールに求めるのはKnobGalleryのフリーKnob素材がそのまま使えることです。超縦長(たまに横長?)の画像の、もっとも値に近い矩形を表示するという原始的なアプローチですが、コードなしにビューを実装できるという点でこれ以上移植性の高いknobは無いでしょう。AAPのWeb UIで使っているのもwebaudio-knobでKnobGalleryにある画像ですし(作ったのは自分ですが)。

そういうわけでざっくり作りました。近日中にWaveform Viewなんかも移して独立したリポジトリにまとめようと思います。

ComposeKnob sshots

ちなみに矩形と書きましたが正方形しか(まだ?)サポートしていません(正方形でない画像はvalidなのか、validだとしたらどうやって適切な1要素を切り出せるのか、何もわからない)。

関連して knob or slider? みたいなことも考えたのですが、現状以下のような理由でモバイルにはknobのほうが合っているかな?と思っています:

  • sliderのほうがでかい(横か縦に伸びる): モバイルUIでスペースをとるのは基本的に好ましくない
  • 視認性はsliderのほうがわずかに良いが、knobでもいったん指を左右に「どける」ことでなんとかなる(それで値が「見えれば」十分)
  • sliderは本質的に指の位置と表示位置が一致しないといけないが、knobはドラッグ操作の開始点として存在さえすれば十分で、動きに合わせた値の変更幅も調整できる
    • sliderもknobもホールド操作などをトリガーとして拡大スライダーをオーバーレイ表示するようなアプローチがあり得る(ただ、ホールド位置がスクリーンの左端や右端などだとそこから動かせる範囲が限られるし、縦スクロールバーにしても上端・下端で同じことになる)

現状、Compose Knobは上下ドラッグでのみ値を変更するので(デスクトップでは一般的な挙動だし、モバイルでもいちいち2本指で回転とかしたくないわけです)、スクリーンの上下端に置くと変更範囲が限られるわけですが、2本目の指を使って反転操作や微細操作(webaudio-knobのshift+マウス操作)に対応する必要はあるかもしれません。

操作中だけ表示するツールチップラベル表示もデフォルト実装があって(常時表示も可能、実装カスタマイズもComposableとして可能)、Knobの最小サイズとして50.dpを初期値に割り当てていて(推奨しないけどより小さくできる)、これはDPの160dpi = 1inchという定義から約0.8cmとなり、細い指であれば値ラベルを別途用意しなくても見えるとは思いますが、指で完全にノブが隠れてしまったとしても、指を左右に水平にどければ何も値を変えずに見えるようになります。1本指のときに左右に操作を割り当てると指を「どける」操作ができなくなるので、これは避けたいですね。

来月の予定

6月はひさびさに何も締切やイベントがないので、AAP GUIサポートなど仕掛りの作業をのびのびと進めたいと思っています。5月にはMIDI 2.0のアップデートが出てこなかったので、ktmidiに手を加える必要が生じるとしたら6月になりそうです(さらに順延されていなければの話ですが)。

技術書典14 サークル参加情報

告知が直前になってしまいましたが、5/21の技術書典14にサークル「オーディオプラグイン研究所」としてオフライン参加予定です。配置番号は「え-01」です。エノ列です。

オフラインサークル参加は自分でもびっくりしたことに2018年の技術書典5以来、なんと5年ぶりなんですね。当時は音楽技術サークルではなくXamarinサークルだったので(MMLコンパイラ本があったのですが、当時はC#アプリケーションだったのでまあギリ一貫してる…?)、完全に別物としての参加です。

オーディオプラグイン研究所としては(前身のcircle gingaも含め)もう5年分の成果があるわけで、発行している同人誌もけっこうな数になってきました。オフラインの技術書典に参加するのはある意味「初めて」で、これまでは主にM3で頒布してきたので、改めてオーディオプラグイン研究所としてこれまでどのようなストーリーラインで刊行物を発行してきたのか、ひと通り紹介していきたいと思います。この順番だと新刊紹介が一番最後になってしまうので、新刊にだけ興味がある方は最後のほうまで飛ばしてください。

MMLコンパイラmugeneによる音楽制作ガイド

MMLコンパイラmugeneによる音楽制作ガイド

この本を書いたのは技術書典4の頃だったのですが、Xamarinチームに在籍していた頃も趣味でmanaged-midiとかmugeneという自作MML to MIDIコンパイラを作っていたこともあって、ずっと「~(.NETは飽きたので)~音楽系ソフトに主軸を移したい…!!」という思いが高じてついカジュアルに書いてしまったやつです。MMLコンパイラとしては割りと柔軟にマクロを定義して自由にMIDIバイナリを出力できたので、それなりにおもちゃ以上の存在ではあったと思います。「こういう本があるべき」みたいな高い意識はなく、単に同人誌として書きたかったことを書いたものです(!)

mugeneのrepo自体は今はarchivedで、mugene-ngとしてKotlinで作り直されていて、新たにMIDI 2.0もサポートしており、Kotlin/JSを活用してnpm化もして、言語ランタイム不要なvscode-language-mugeneという拡張機能に組み込めています。

mugene fantasy suite (CD)

mugene fantasy suite (CD)

2018年にXamarinを卒業してからも、しばらくの間はmanaged-midiやmugene MML compilerなどC#で書いてきたプロジェクトをしばらく維持していましたが、上記のMMLコンパイラmugeneを音楽制作にちゃんと使ったことは無く、そのことに忸怩たる思いがあったので、2019年3月に幻想音楽祭という比較的小規模なイベントで音楽サークルとして参加し、そのためにdogfoodingを兼ねて(!)制作に使うことにしました。詳しくは↓に書いています

https://atsushieno.hatenablog.com/entry/2019/02/22/181235

今回はそのCDをグッズとして販売します。まあ音楽作品としてはアレですがMMLの実例として…!

DAWシーケンサーエンジンを支える技術 [第2版]

DAW・シーケンサーエンジンを支える技術 [第2版]

上記CDは、音楽の打ち込み部分はLinuxデスクトップとMIDI音源として大昔に使っていたRoland SC-8820を使ってMMLだけで完結させたものの、最終的にはMac上でTracktion Waveform (DAW)とKontaktOzone等のオーディオプラグインを使って素人マスタリングしたものでした。

音楽ソフトに主軸を移していこうというのに、いつまでも古臭いMIDIの知識で生き延びられるはずはないし、ちゃんとオーディオ処理と向き合っていこう…という気持ちになったものの、当然ながらC#でまともなオーディオアプリケーションが開発できるはずは無く、modern C++を勉強していこうという初心者の気持ちになって、2019年から本格的にJUCEを使ったソフトウェアの開発に入門していきました。今思に開発していて今回の新刊の題材であるAudio Plugins For Android(当時はandroid-audio-plugin-framework)もこの頃に雑に始めたものです。

CD制作でTracktion Waveformという割とマニアックなDAWを選んだのには理由があって、2018年にこのDAWGUI以外の部分がTracktion EngineというOSSで公開されたのです。TracktionはJUCEのメンバーが開発してきたDAWで、商用DAWとして本格的な実用性があって(それなりに安定もしていて)エンジンがOSSで公開されているというのであれば、いざとなれば音楽プレイヤーをOSSで提供できる楽曲データをDAWでも打ち込める…! というアドバンテージがあったわけです。

Waveformの楽曲データは単なるXMLで、これは自分としてはntracktive, kotracktiveといったTracktion用の解析ライブラリをC#やKotlinでも簡単に作れるものでした。Tracktionの楽曲フォーマットを調べることで、MIDIでは出来なかったDAWの各種機能が、割と幅広くわかるようになりました。そういったオーディオプラグイン用のプレイヤーやエディターみたいな「シーケンサーエンジン」の設計については、ほとんど情報がなく(そもそもVSTホスト開発の情報などが希少なわけです)、そういうのをざっくり把握できる本があるべきだなと思って書いたのがこの本です。

LV2オーディオプラグイン開発者ガイド

LV2オーディオプラグイン開発者ガイド

自分のメインのデスクトップは2011年にWindowsから乗り換えて以来Ubuntuなのですが、この方面に飛び込んでから2021年頃までは「Linux DTMがしんどい」と感じることが多い期間でした。Steinbergが2018年にVST2 SDKの公開を停止し、VST2のプラグインがビルドできなくなっていく中、当時ROLI傘下にあったJUCE 5.xではVST3プラグインLinuxでビルドできず(この状況はPACE買収後のJUCE6で改善しています)、Linuxで利用できるプラグインはLV2だけでした。多くのJUCEプラグインが非公式のLV2 forkでビルドされていました。

LV2のプラグインにも面白いものがあり、特に当時は「いつまでもFluidsynthでサウンドフォントというわけにはいかない…OSSKontaktに代わるものは何を使えばいいのか…」となってSFZを発見し、その最先端実装であるsfizzにちょいちょい関わるようになり、android-audio-plugin-framework(当時)でも使えるようにしたい…となっていわゆるLV2 SDKAndroidに移植するようになったり…と、少しずつLV2に詳しくなっていきました。日本でもLV2プラグインを開発している人が何人かいるのでとても最先端ではありませんが、例によってホスト側の視点に立つことが多く、たぶんそれなりにユニークな知見が溜まっていました。技術書典9とM3が近づいてきたので、一度この辺の経験をもとにLV2開発本を書いてみよう、となってこの本ができました。

開発以前にそもそも「使う」知見も必要じゃないか…となって、結果的に「LV2とはなんぞや」から入るような本になったのですが、同人誌らしいユニークなやつになったと思います。執筆当時はまだでしたが、2022年にはLV2がJUCE7でも正式にサポートされ、Reaperでも使えるようになって、一気に利用場面が広がっています。

この頃うつぼかずらさんのVST3プラグイン開発本も出て、JUCE Japan本と合わせてプラグインの開発にとっかかるには概ね困らなくなったと思います。(まだAUが無いし、AUv2とAUv3はだいぶ違うという話もある)

MIDI 2.0 UMPガイドブック

MIDI 2.0 UMPガイドブック

2020年。オーディオプラグイン研究所で一番ポピュラーなところを狙った一冊です。

謎の勢いでLV2本を書いたものの、冷静になると「さすがにこの本は売れないだろうな…」とは考えざるを得ませんでした。もう少しキャッチーなトピックの音楽技術ネタは無いものか…そういえばMIDI 2.0の正式版が出ていたな…ちょっと調べてみるか…という感じで、雑に始まったものでした。最初のMML本は3日で書いたので、1週間もあればカジュアルな本が1冊くらい書けるだろうと高をくくっていました(実際1週間くらいで書いています)。

MIDI-CIはしばらく前から存在していたので、新しく登場したUMPくらいがいいだろうと思って調べ始めてみると、これが割とモダンなDAWとオーディオプラグインのイベント入力にも十分に耐えうるデータフォーマットで、これはもっと普及してもいいと思うようになりました。もしMIDI 1.0について知っていれば差分は難しくないので、単なる解説書程度なら簡単に書けると思います。ただそれだけだと「仕様書とほぼ同じ内容」になってしまうので(それでも日本語資料は皆無なので価値はあるといえばありますが)、仕様書では読み取ることができない「なぜこうなっているのか」の部分を自分なりに解釈して解説しています。

MIDI 2.0エコシステム構築術

MIDI 2.0エコシステム構築術

MIDI 2.0本は当初の目論見どおり()順調に売れてよかったのですが、MIDI 2.0を採用したプロダクトはなかなか登場しませんでした(今でもほとんど無いと言わざるを得ないでしょう)。MIDIのように相互運用性が課題の技術では、鶏と卵の問題に陥りがちです。

でも…この場合の鶏と卵って一体何のことを言うんだろう?という考えが浮かびました。われわれは既にMIDI音源を使ってDTMをやっていないわけです。われわれが使っているのはオーディオプラグインであり、ソフトウェアシンセサイザーです。であれば、プラグインMIDI 2.0をサポートするようになって、DAWMIDI 2.0をサポートするようになれば、ハードウェアは後回しでもいいのでは? と考えるようになりました。

この頃、ついに重い腰を上げて、C#のmanaged-midiをKotlinにktmidiという新しいライブラリとして移植していたのですが、せっかくUMPにも詳しくなったのだからMIDI 2.0サポートを追加しちゃえ、となって、UMPサポートを追加していました。managed-midiにはMIDIプレイヤーのAPIもあったので、MIDI 2.0用のMIDIプレイヤーなども独自に作り込んでいました。MIDI 1.0プレイヤーは当然SMFをターゲットにしていたのですが、MIDI 2.0にはそもそもSMF相当のものが無いので(今でもありません。仕様はMMAで開発中ステータス)、そういうものを作る必要があるよね…といった考えを持つようになりました。

また2019年に作り始めたAndroid用のプラグインフレームワークでも、MIDI 1.0のメッセージでイベント入力を受け付けるようにしていたのですが、「MIDI 2.0も受け付けられるようにしたらカコイイんじゃね?」などと考えるようになって、実際に実装していました。MIDI 2.0をサポートするプラグインAPIは、当時最先端だったと思います。

こんな感じで、MIDI 2.0を音楽制作の世界で採用するための下地を整備していこう、という「オピニオン」がいろいろ蓄積されていたので、それを一度アウトプットしておこうという気持ちで書かれたのがこの本です。技術書としては「エッセイ」に近いかもしれませんが、AppleAUでUMPをサポートし、JUCEもMIDI2サポートを次のターゲットにしていて、ここに書いてきたことがどんどん実現しつつあります。

MML to DAW via MIDI 2.0: 次世代MMLコンパイラ開発研究

MML to DAW via MIDI 2.0: 次世代MMLコンパイラ開発研究

2021年刊行。上記の薄い本ではMIDI 2.0採用に関する「主張」を書いたわけですが、やはりここでも「主張したソリューションを自分で実践すべきでは…?」という思いが残り続けました。

この頃、managed-midiからktmidiへの移行が過渡期にあって、MMLコンパイラが生成したSMFからTracktion Engineの楽曲データを生成するaugeneというツールを、ktmidiでも移植してaugene-ngというツールにしていました。そのとき、「ktmidiはMIDI 2.0をサポートしているんだから、MMLMIDI 2.0対応にできるし、augene-ngはMIDI 2.0の32ビットCCやNRPNからオーディオプラグイン操作もできる次世代MMLコンパイラにできるのではないか?」という発想が浮かんできて、そのまま実装されることになりました。

MIDI 1.0時代のaugeneは、2019年にロンドンで行われたAudio Developers Conferenceで行われたLT大会で紹介してきたこともある程度には古いのですが、この時点でもJUCE AudioPluginHostで設定したプラグインのルーティング情報をプラグイン設定として扱う実装を作っていたものの、打ち込みに実戦投入はしていませんでした。MIDI 1.0の範囲でしかできないことをやってもな…という気持ちがあったのですが、今回は32ビットパラメーターを直接操作できるというので、MMLサンプルもめでたく完成しました。この本はこの具体的な成果を踏まえて「MIDI 2.0を技術的に組み込んだ打ち込みはできるんだ(MMLで出来たことはDAWでも出来るでしょ…!)」という話を書いたものです。この後、Appleも実際にAUMIDI 2.0を組み込んで、Logic ProもMIDI 2.0をサポートしてきたので、すっかり追いつかれてしまいました(!)

ちなみに、サンプル制作は、前回制作したCDの楽曲でも出来なくはなかったのですが、「OSSなんだしパブリックCIでMP3レンダリングまで再現できるようにしたい」というこだわりが生じて、それを実現させること自体がひとつのプロジェクトとなった感じでした。結果的には何とかなりました。これは本書の余談でこの本には書かれていません。

CLAPオーディオプラグイン開発者ガイド

CLAPオーディオプラグイン開発者ガイド

2022年にはCLAPという新顔が登場して、オーディオプラグイン開発の世界が大きく揺れた1年でした。それまでVSTAU、あと圧倒的に事例は少なくなるけどLinuxのLV2、といった感じで大勢が決まっていたところに、新しい汎用プラグインフォーマットのAPIが登場して、既存フォーマットの多くの問題点を解決したモダンなAPIだというので、大いに注目を集めました。

新しいオーディオプラグインフォーマットを作るというのは勇気のいる仕事です。「すでにプラグインフォーマットがたくさんあるのに、これ以上新しく対応したくない」という声が出てくるのは必定です。当然ながらそういう反応も出てきましたが(そして今でもCLAPは苦戦しているといえば苦戦しているので、そういう反応が多くを占めているともいえます)、割と歓迎されて将来を期待されていた(いる)ようです。LV2ともろに競合するところもあって、CLAPコミュニティには「LV2はいらなくなった」などと書いているユーザーもいて、あまり治安が良くない側面もあるのですが、オーディオ開発界隈あるあるかな…とは思っています(そう思うようになった)。

Android用のプラグインフォーマットAPIで独自の戦いを邁進している自分も、当然ながら大きな関心をもち、オンライン勉強会も開催して、いろいろ調べたりしました。この本はそれらの成果のひとつの集大成です。CLAPの本ではありますが、前述のLV2本を書いてあったこともあって、この本は「他のプラグインフォーマットとはどう違うのか」という部分を全面的に押し出して書いています。

Linux DTMガイドブック

Linux DTMガイドブック

CLAP本は技術書典13とM3 2022秋に向けて書いたものですが、書き上げた後で「CLAPプラグインの本なんて売れな(ry」とまた思うようになりました。もう少しキャッチーなトピックの本を追加しよう…と思ったものの、特にそんなネタは思い浮かばず、「そういえばLinux用の音源に関して自分のスタンスでいろいろOSSプラグインを紹介したやつがほしかった…」と思っていたのを思い出し、~「プラグイン紹介本」ならスクショだらけですぐにページ数稼げると思って~この本が生まれました。

Linuxオーディオは(他のプラットフォームも多かれ少なかれそうだとは思いますが)Jackの排他制御をきちんと扱えるようになったりALSAやPulseAudio、最近ではPipeWireなども知悉していないと扱えない…みたいな敷居の高い世界観になりがちですが、そうではない、カジュアルにDTMを始めたい勢がWindowsMacでも使っている人がいるような音源をLinuxでも使える…といった世界観を作り出したいと思って書いたものです。

Audio Plugins For Androidの設計と実装

Audio Plugins For Androidの設計と実装

ここまででたまに言及してきましたが、わたしの最近の主な開発プロジェクトは、Audio Plugins For Android (AAP) というAndroid用のプラグインフォーマットのエコシステムを作ることになっています。オーディオプラグインの仕組みは、デスクトップだけでなくiOSにすら存在するのに、自由に他のアプリケーションと連携できるはずのAndroidにはその仕組が存在せず、DAWは自分専用のプラグインをユーザーに使わせることしかできません。自由な音楽制作ができない、たいへんよろしくない事態だと思っています。

AAPはこの状況を打開するために開発されています。ここまでの書籍でも言及してきたような現代的な機能も含め、オーディオプラグインに求められる機能を実現できるよう、MIDI 2.0ネイティブでモダンな設計を目指し、JUCEアプリなどマルチフォーマットでプラグインをビルドできるプラグインを数多く移植しています(とはいえ、まだまだ開発中で色々足りていないのですが)。

プラグインフォーマットがAndroidに存在しないのは、デスクトップとはアーキテクチャが全く異なるというところに根本的な原因があるのですが、Androidで全く出来ないというわけではなく、できるところまではそれなりにiOSのAUv3に近づけられます。何がモバイルプラットフォームにおけるオーディオプラグインを実現困難にするのか、このAAPというプロジェクトがそれらの障害をどうやって乗り越えたか、またオーディオプラグインフォーマットを設計するとはどういうことなのか、といった思想的な話など、AAPの開発に際して書いておくべきことを(公式ドキュメントとは別に)いろいろまとめあげたものです。

続きはオフライン開場で(!?)

当日の頒布品は以上の10点です…いくらなんでも多くない!? 当日スタッフが見本誌チェックするの(やります)、ちょう大変そう…すまんの…

オフライン開場には、今あるAudio Plugins For Androidのデモ環境も用意して持っていく予定です。まだ大したことができるわけではないのですが、もしこの方面に興味のある方がいたらぜひ試してみてください。

今回は当日スタッフとしても動いていて、また1人で店番をやる予定なので、もしかしたら不在になっているかもしれませんが、いる時間のほうがずっと多いはずなので、もし不在でも懲りずにまた見に来てやってください。

4月の開発記録 (2023)

4月は終わっただなんて、またまたそんな嘘を…(時候の挨拶)

4月は(予告通り)あんまし開発に時間を割いていない感じで、AAPの宣伝活動強化月間みたいになっていました。リリース作業も行っていたので、生産期から収穫期に移ったような感じです。

「(AAP) の設計と実装」の執筆

予定通り、昨日のうちに(ギリギリでしたが)boothで販売を始めました。

xamaritans.booth.pm

来月は技術書典14があるので、それまでにもしかしたらアップデートがあるかもしれません。無いかもしれません。実は技術書典オンラインのほうはCLAP本もアップデートが半年弱くらい反映されない状態なので(イベントが終わった途端全くレビューしないフェーズに入るとは思ってなかった)、AAP本も今後は同じように著者から自由にアップデートできない可能性が高いです。そういうわけで、基本はアップデートの体制が整っているboothでの購入をおすすめします。技術書典オンラインにそれを期待するのは無理かなと。

AAPの設計と実装の執筆作業は、設計に関する思索を巡らせる契機になることも多く、今月は特に「AAPは仕組みとしてはAUv3に近づけてもいい気がする」とか「AAPの仕組みをAPIから独立させて実装アプローチとしても規定したいが、具体的に何が考えられるか」とか(関連issue)、「拡張APIとして規定するとして、実質的に『必須』の拡張があるならそれは任意とは言えないので、明確に準拠プロファイルとして規定すべき」、みたいなことを考えました。

執筆したことの半分くらいは前提知識を埋めるためのものだったのですが、残りの部分は設計の取捨選択とか設計の理由とかに関するもので、これは公式ドキュメント…とはならないまでも公式参考資料として、英訳して使い回したいと思っています。LV2 Bookが近いコンセプトでしょうか。AAPの宣伝本なんだから無償でいいんじゃないの、等も考えましたが、内容的には公式ドキュメントに載せるべき情報とは異質な技術読み物としての側面が強いので、今のところは普通に(?)趣味の技術書として読まれてほしいです。

AAP 0.7.6 Released

本当は5月でいいかなとか、GUIサポートの問題がいろいろ修正されてからでもいいと思っていたのですが、2月初頭にリリースしてから2ヶ月以上経って、これ以上変更を溜め込んだら周辺プロジェクトが追従するときに追いきれなくなると思って、早目にやっておくことにしました。

リリースノートはここにまとめてあるのですが、今回もずいぶん更新が多かったです。

github.com

前回0.7.5の時に「これはずいぶんでかいリリースになったな」と思いましたが、0.7.6もそれと同程度以上のインパクトがあったと思います。個人的には、AAPのクオリティ面での修正が多く含まれていて、long-standing issueも多く閉じることが出来て、いい傾向だったと思います。クオリティというのは、実装の安定度もそうですが、仕様も着実にブラッシュアップされています。(今までが雑だっただけかもしれませんが…!)

aap-juce improvements

最近のaap-juceのホスティングまわりの改善は効果が著しく、最近はhelio-workstationの移植の上で初回起動時に出てくるサンプル楽曲のInstrumentを全部mda-lv2のプラグインで置き換えてもちゃんと再生できるレベルまで来ています。これは先月書いたJUCE_DONT_AUTO_OPEN_MIDI_DEVICES_ON_MOBILEに加え、「JUCE on AndroidアプリがデフォルトでOboeを使うようになっていない問題」と、次のJUCEのリリースには含まれているであろう「Oboeオーディオループで毎回calloc()が呼ばれている問題」のバグフィックスを個別パッチとして取り込んだためです。

「JUCEでもそんなレベルのバグがあるんだ」って思われるかもしれませんが、やっぱりリアルタイムオーディオ処理って簡単ではないし、フレームワーク開発者はアプリケーションのレベルまでdogfoodingする機会がなかなか無いのと、Androidサポートの優先度が高いプロジェクトではなさそうなこともあって、ユーザー(この場合は開発者)が発見する機会が少なからずあると思います。JUCEチームは小さくてAndroidの問題にきちんと取り組める程度までスケールしないので、この状況を打破するにはJUCEと戦える競争相手が必要そうな気がします。といっても商用製品としてのJUCEは適切な価格設定だと思うので(v5くらいの頃なら「価格の割に価値が大きい」くらいは書いていたと思う)、収益のために競争相手を作るよりは、「プラットフォームネイティブでいいものを作る」みたいな「競争」のほうが現実的かなと思います。

rtmidi-jnaのFFIをどうにかしたい問題に取り組む(進行形?)

NAMM 2023のタイミングに合わせてMIDI 2.0のバージョンアップが出ると見込んでいたのが5月以降までずれ込んで(見込みが妥当だったかは別としてNAMMのMIDI2関連セッションでもそれくらいと言及されています)、それだったらktmidiというかkmmkをもうちょっとブラッシュアップしてPer-Note pitchbendくらい扱えるようにしておきたいなあとか思っています。

ただ、現状でktmidiがrtmidi-jnaで利用しているJNAeratorでブロックされている側面が割とあって(たとえばSiliconで動作させるのに必要なネイティブビルドが無かったり)、さすがにFFI機構でJNAeratorに依存している部分を置き換えたいなあと思っています。手軽に対応するならJavaCPPあたりが正解だと思いますが(ただ過去に一度失敗していて理由を思い出せない)、最近はPanamaもだいぶ使えるようになってきているようだしなあ…と思って、いろいろ調べたりしている感じです。

Panamaは割と面白いですね。BridJをいじってた頃、あるいはそれ以前に自分でdyncallなどを使ってJNAの代替を作ろうとしていた頃を思い出します。Panamaは、libffiでのfallback実装(があります)を越えてやっている部分はBridJに近そうだし(BridJはdyncallを使っていたけどPanamaはJavaでmanaged solution的に実装している)、そこにMemorySegmentとMemoryLayoutのコンセプトを持ち込んでBridJが扱えなかったstruct return by valueの問題にも対応できています。array return by valueだけまだ怪しそう。JNRもその辺はBridJより新規性に乏しい設計でした。この辺の話は、(Mastodonにも書きましたが)JVM language summit '19のセッション動画が参考になります。

www.youtube.com

まあrtmidiバインディングをJNAからPanamaにしてしまうとKotlin/JVMでの動作要件が厳しくなりすぎてしまうので、そうはならないような選択肢に落ち着くと思います。JavaCPPにするか、バインディングを生成済みのやつに切り替えるか…

5月の予定

そんなわけで、AAPの作業よりktmidiの作業をメインにしている可能性がそれなりにあります。5月は技術書典14もあるので、そっち方面で原稿を書いてるかもしれませんし、MIDI2のアップデートも出てくるかもしれないし、Google I/Oで何か出てくるかもしれないし、思いのほかAAPに「戻ってくる」まで時間がかかるかもしれません。