測定プロトコル
機体を測定する順序です。各段階は前の段階が書いたファイルを見て対象を決めるので、順序は便宜ではなく手法の一部です。順序を外して走らせた段階は、このアーカイブが記述できないものを測ることになります。
2 台の機体を比べる主張は、どちらもこの方法で測定してあることの上に成り立っています。違う方法で測定した機体も、それ単体としてなら記述できますが、比較には参加できません。
どの段階よりも前に
すべてのコマンドは、何かを書き込む前に MIDI 経路を検証します。自己診断は単独で先に走らせてください。そうすれば、失敗したときに「たまたま走らせていた段階の失敗」ではなく「経路の失敗」として読めます。
rye run soundings selftest --audio "<audio interface>"自己診断に通ったことが示すのは、ハーネスが機体を読めることだけです。その機体を安全に調べられることまでは示しません。すべての段階に共通して、制約が 2 つあります。
- 一括要求は 64 バイト以下に保つ。 これを超える要求で、機体が応答を完全に停止することが実測されています。復帰には電源の入れ直しが必要です。
- 応答すると分かっているアドレスを定期的に読む。 応答しなくなった機体は、アドレスが 1 つも存在しない空間として読めてしまいます。無応答を不在として扱う探索は、何も測らないまま正常終了します。
その走行がどの空間に向けられているか
3 バイトのアドレスは、それだけでは何も指しません。何を指すかを決めるのは、それが送られるモデル ID であり、機体は複数のモデル ID に応答しうるものです。 このアーカイブにある 1 台は、表示ブロックだけが、残りの部分が住んでいる空間の外にあります。モデル ID ごとに別のアドレス空間があり、2 つの空間に現れる同じ 3 バイトは、2 つの別のパラメータです。
rye run soundings --model-id 0x45 read "10 00 00"--port や --device-id と同じく、サブコマンドより前に置きます。ある段階が何をするかではなく、その走行が何に向けられているかを述べるものだからです。既定は GS です。
ある族に属する知識を必要としない段階だけが、GS 以外の値を受け取ります。 sweep、boundary、offsets、power-on、write-probe、window-probe、hold-probe、そして見るだけの 2 つのコマンドです。それ以外は拒み、その旨を述べます。送っているものが、ある族に固有のものだからです — リセットのメッセージ、効果の固定アドレス、バンクとプログラムの約束事。別の空間に送れば、それらのバイトがそこで意味する何かに対する書き込みになり、走行はその答えを測定として読み戻すことになります。
すべての記録は、装置 ID の隣に、問い合わせたモデル ID を持ちます。この欄が存在する前に書かれた記録は持ちませんが、それは不明ではありません。この旗が存在するまで、ここにあるどのコードも GS 以外のモデル ID を送れませんでした。
経路の証明は、探索しようとしている空間ではなく、調査用アドレスが応答すると分かっている空間で行います。何も応答しない空間は、その段階が報告すべき知見であって、自己診断が段階の開始を拒む理由ではありません。ですから selftest は、第 2 の空間を意図して証明するための --probe を取ります。
段階
1. 識別
rye run soundings identityIdentity Request に対して機体が返すものを記録します。返答は、背面の定格表示、測定経路、機体側の設定とともに、そのまま meta.json へ入ります。以降に測定するものはすべて、この記録が示す範囲についてのものになります。
2. アドレスマップ
rye run soundings sweep --out data/units/<unit-id>/sweep/whole-map.jsonどのアドレスが応答するかを機体に問い合わせます。このマップは文書が並べているものではなく機体が答えたもので、以降の段階はこれを見て対象を決めます。
スイープは各ブロックの下位バイトを、指定した値についてだけ読みます。ですからブロックの開始位置が、読んだ場所にあるときにだけ見つかります。ここで得られるのはブロックの一覧であって、アドレスの一覧ではありません。 後者として読むと、これを元にするすべての段階に穴が空きます。アドレスの一覧に変えるのは段階 3 です。
3. オフセットと、そこに現れる形状
rye run soundings offsets "40 10 00:128" ... --canary <addr> \
--resume --out data/units/<unit-id>/offsets/whole-map.jsonスイープが見つけたブロックについて、その全オフセットを 1 バイト読みで読み出します。領域の末尾から前へ進む読み方だと、最初に応答しないアドレスで止まってしまうので、無応答の向こう側にある、応答するアドレスの連なりが見えません。全オフセットを読むことだけが、それを見つけます。
応答したオフセットの集合が一致するブロックは、同じ形状です。アドレス空間は繰り返し構造を持ちます。この方法で最初に測った機体では、461 ブロックが 12 種類の形状に収まり、そのうち 1 つは 204 回現れました。これ以降のすべての段階は、形状ごとに 1 つの代表ブロックを対象にし、網羅率を形状に対する分数で報告します。全ブロックを測るのは同じものを繰り返し測ることで、そこから出る数字は「どれだけ分かったか」を何も語りません。
形状をまとめられるかどうかは、仮定せずに測定します。代表のほかに、同じ形状のブロックをもう 1 つ読み出して、一致を記録します。両者が食い違えば形状の切り方が間違っていたということなので、そのときは分けます。
読み出ししか送らないので、電源投入時の状態を取得するより前に置きます。 書き込みを行う段階の後ろに置くと、その取得の機会を失います。取得はやり直せません。
4. 以降の各段階が見張る集合
rye run soundings watch-set data/units/<unit-id> \
--out data/units/<unit-id>/watch-set/reachable.json
rye run soundings watch-set data/units/<unit-id> --keep-windows \
--out data/units/<unit-id>/watch-set/with-windows.json
rye run soundings watch-set data/units/<unit-id> --one-at-a-time \
--out data/units/<unit-id>/watch-set/one-at-a-time.json機体ではなく記録に問い合わせます。ここから先の段階はすべて --map を取り、その地図が、その段階の出すあらゆる否定的所見の範囲を決めます。どの地図を渡したかは、その記録が何を意味するかの一部です。
これは 2 種類の読みの合併であり、そうでなければなりません。どちらも他方を含まないからです。 領域読みは連続したアドレスに 1 つの返答を返し、単体で問うと何も答えないアドレスに届きます。単体読みは 1 アドレスを問い、どの領域も始まっていない場所から始まるアドレスに届きます——段階 3 が見つけるのはこれです。片方だけを渡すと、メッセージが「誰も見ていない場所」に着地でき、記録は「どこにも保存されない」と言います。この方法で最初に測った機体では、スイープの地図は応答するアドレスを 3,650 個取りこぼしており、5 つの段階がそれを狙って走った後で気づきました。
窓と判定されたブロックは外します。そこに着地した値は、窓が指している記憶領域に着地し、その記憶領域はすでに見張られているからです。--keep-windows は戻します——メッセージの行き先ではなく、各アドレスが保持していた値を捕捉する用途です。--one-at-a-time は単体読みが答えるものだけを、1 アドレス 1 領域で持ちます。単体読みへの返答は、問うたアドレスについて答えるか、何も答えないかのどちらかなので、バイトが 1 つ下のアドレスに載ることが原理的に起きません。
ここには測定は 1 つもなく、ファイル自身がそう述べます。元の記録が変われば作り直すもので、その元も名指しします。
5. 電源投入時の状態
rye run soundings power-on --map data/units/<unit-id>/watch-set/with-windows.json \
--unit-id <unit-id> --out data/units/<unit-id>/power-on/regions.json
rye run soundings power-on --map data/units/<unit-id>/watch-set/one-at-a-time.json \
--unit-id <unit-id> --out data/units/<unit-id>/power-on/one-at-a-time.json空間を 2 度読み出し、何も書き込みません。得られるのは、電源を入れた直後に機体が置かれている状態です。
1 回の電源再投入で、2 通りとも取ります。届くアドレスが違うからです。 領域読みは単体読みが答えないアドレスに届き、単体読みは短い返答が取り違えるアドレスを正しく取ります。ある機体は 32 バイトの要求に 30 バイトを返します——再現し、チェックサムも通ります。しかも省かれる 2 つは、同じブロックをオフセットごとに問うたときに無応答になる 2 つとは別です。そういう返答は要求したアドレス列に敷けないので、敷かずに拒否し、「短く答えた領域」として記録します。基準値は 2 つの捕捉の合併で、soundings complete もそう数えます。
書き込みを行う段階より前に走らせられる、最後の段階です。 段階 1 から 4 は読み出ししか送らないか、何も送りません。ですからその後に取得しても電源投入時の状態のままです。段階 6 以降は機体へ書き込むので、そこから先は次に電源を入れ直すまで取得できません。マップはスイープをやり直せば作れますが、この取得はやり直せません。
この実行は、電源を入れた直後の機体と、以前の実行が書き込んだ後の機体とを区別できません。どちらも読み出しに対して同じように答えるからです。ですから記録するのは「実行前に何をしたか」という測定者の申告であり、それを申告だと明示したうえで、この実行自身が言えること——自分では何も書いていないこと——を併記します。
2 回の読み出しが食い違ったアドレスは、決着させずに一覧として残します。どちらかに決めるには 3 度目の読み出しが必要で、しかも間違った決め方をしても後から見えません。そのバイトは、次にこの取得と比較されるリセットが書き換えたもの、として読めてしまいます。以降のリセット測定はすべて、このファイルと比較します。
6. 窓
rye run soundings window-probe --stores <addr> <addr> <candidate>...自分の値を保持しているブロックと、別のブロックの値を見せているだけのブロックを見分けます。アドレスが何を保持しているかを解釈する段階より前に走らせます。窓は、書き込んで読み戻す方法では本物の記憶領域と区別できないからです。書き込みが窓の行き先を決め、読み出しがそれに従うので、そのアドレスは与えたとおりの値を答えます。
窓と判定したブロックは、それを記述する記録の中で名指しし、以降の段階からは除外します。除外しないと、同じ記憶領域を、その素性が残らない名前で何度も測ることになります。
7. 受け付ける値
rye run soundings write-probe --map data/units/<unit-id>/watch-set/reachable.json \
--resume --out data/units/<unit-id>/write-probe/whole-map.json各バイトを単独で書いて読み戻し、そのアドレスが何を受け付け、何を丸め、何を拒否するかを記録して、元に戻します。領域は終わった順に書き出すので、中断した実行は最後に完了した領域から再開できます。
1 バイト読みに応答しないアドレスへは書き込みません。元に戻すべき値が存在しないからです。それらは skipped として記録します。そのうちどれが未定義で、どれが「もっと大きなブロックとしてしか読めない」ものなのかは、この段階では決着しません。
8. 独立した記憶領域
rye run soundings hold-probe --map data/units/<unit-id>/watch-set/reachable.json \
--out data/units/<unit-id>/hold-probe/whole-map.json隣り合うアドレスへ異なる値を与えてから、途中で 1 つも読まずに、まとめて全部を読み戻します。こうすると、1 つのセルに支えられた連続アドレスと、それぞれが別のセルである連続アドレスを分けられます。この順序そのものが手法です。書きと読みを分ければ、近くへ最後に書かれた値しか見せないアドレスはその 1 つの値を答え、独立したアドレスはそれぞれ自分の値を答えます。
この段階は、別のブロックを映しているブロックを見つけません。それは段階 5 が調べることです。
9. エイリアス
rye run soundings alias-scan --map data/units/<unit-id>/watch-set/reachable.json \
--kind cc --out data/units/<unit-id>/alias-scan/cc-ch1.jsonある系列のメッセージを 1 つずつ送り、その前後でアドレス空間を比べて、メッセージが届く先を特定します。系列ごとに 1 回ずつ走らせます。cc、nrpn、rpn、channel、drum-nrpn、そして SysEx による書き込みの address です。
マップ全域を監視してください。 --prefix の既定はそうなっています。1 つのブロックに限定した走査は、そこで得られる「見つからなかった」という所見のすべてを、そのブロックの中だけの話に閉じ込めます。「このメッセージはどこにも保存されない」という記録が、「見た範囲のどこにも」という意味に変わってしまいます。
--kind address は、先に走らせた走査が到達を確認したアドレスへ書き込み、その場所に 3 つめの入口があるかどうかを調べます。書き込み先は、その機体自身の記録から取ります。
rye run soundings alias-scan --map data/units/<unit-id>/watch-set/reachable.json \
--kind address --addresses-from data/units/<unit-id>/alias-scan/cc-ch1.json \
--out data/units/<unit-id>/alias-scan/sysex-ch1.json10. リセット
rye run soundings reset-probe --baseline data/units/<unit-id>/power-on/one-at-a-time.json \
--map data/units/<unit-id>/watch-set/reachable.json \
--write-probe data/units/<unit-id>/write-probe/whole-map.json \
--out data/units/<unit-id>/reset-probe/whole-map.json各リセットの前に状態を崩し、後で空間を読みます。こうすると、文書がリセットについて述べていることではなく、そのリセットが実際に戻すものを測定できます。目印とは「そのアドレスがいま保持していない値」で、ライトプローブがそのアドレスについて実測した受理値から選びます。これを「どんな値でも受け付けるアドレス」と読むと、範囲を持つバイトがまるごと外れます——そして範囲を持つバイトこそ、文書が機能を与えている側です。何でも受け取るバイトは、たいてい誰も定義していないバイトなので。1 値しか受け取らないと実測されたバイトには目印が存在しないので外します。保持している値を書いても何も崩れず、リセットが手を触れなかった場合に「戻した」と読めてしまうからです。
すべてのリセットは、同じリセットを先に送ってから走らせます。そうすることで、結果どうしを比べられます。先に送らないと、それぞれの結果を「直前のリセットがたまたま残した状態」を基準に読むことになります。
11. 音色とエフェクト
rye run soundings tone-map --out data/units/<unit-id>/tone-map/map-select-0.json
rye run soundings efx-map --out data/units/<unit-id>/efx-map/types.json各音色と各インサーションエフェクトを要求し、受け付けられたかどうかを読み戻します。走査の前に間引きを行ったマップは、その間引きを但し書きとして持ちます。--exhaustive は全バンクに 128 プログラムすべてを問い合わせるもので、但し書きの付かない唯一の形式です。
12. 再現性——音を比べる前に必ず
rye run soundings repeat --audio "<audio interface>" \
--out data/units/<unit-id>/repeat/program-0.json同じテスト音を 2 回鳴らし、2 つのテイクがどれだけ一致するかを測定します。この数値が、以降のあらゆる差分測定の検出下限になります。検出下限を下回る領域では、「差がない」と「差を見分ける解像度がない」は同じ読みになります。
検出下限は機体と測定経路の両方の性質なので、機体ごとに測定し、経路を変えたときは測り直します。
13. 音の変化
rye run soundings contrast --cc 91 --audio "<audio interface>" \
--out data/units/<unit-id>/contrast/cc-91.json1 つのパラメータの 2 つの設定を、再現性の検出下限を基準に比べます。こうすると、機体が値を保持しているパラメータと、それを通して実際に音が変わるパラメータを分けられます。値は保持しているのに音が変わらないパラメータは、実際に存在します。アドレス空間側の段階ではその区別がつきませんが、この段階ではつきます。
transfer、motion、decay はそれぞれ、アナログ経路、時間とともに変化するエフェクト、エフェクトの減衰を測定します。motion と decay はテイクを読むだけなので、機体の接続を必要としません。
14. ブロック全体
rye run soundings plan data/units/<unit-id>/write-probe/whole-map.json "40 11" \
--out plan-40-11.json
rye run soundings block plan-40-11.json <plain records> --gesture <gesture records> \
--balance <balance record> --out data/units/<unit-id>/block/40-11.jsonマニュアルが面白そうに見せたアドレスではなく、ブロック内の全アドレスについて「変えると音が変わるか」を調べます。2 パス構成です。まず打鍵しただけの音で調べ、次に、そこで差が聞こえなかったアドレスを、設定の後にメッセージを動かした状態で調べ直します。メッセージを受け取るかどうかだけを決めるパラメータは、打鍵しただけの音では受け取るものを持たないからです。
各アドレスを調べる値の組は、文書ではなくライトプローブから取ります。ブロックは、たまたま存在する記録の数ではなく、計画に対して数を突き合わせます。47 アドレスのブロックに 45 個の記録がある状態は、完全に答え終えた状態とまったく同じ見た目になるからです。
そのブロック自身が「聞いている」と言うチャンネルで調べること。 そのバイトはブロックの中にあり、機体が答えてくれます。別のチャンネルに出したテスト音は、そのアドレスが宛先としないパートに向けて鳴らすことになり、ブロックの全アドレスが「差は聞こえない」と答えます。
受け付ける範囲だけから選んだ値の組では、チャンネルのバイトを調べられません。 ライトプローブはそのアドレスが何を受け取るかを報告しますが、テスト音がどのチャンネルにいるかは知りません。組の両方の値が、鳴っているノートからパートを引き離してしまう場合、どちらの設定も無音になり、実行は測定を拒否します。しかもそれは「値の組の選び方が悪い」ではなく「収録の失敗」に見えます。そういうアドレスは、そのパート自身のチャンネル対 off で調べます。
「音が変わった」という判定の読み方
判定は 3 つの経路で立ちます。波形の形が変わった、レベルが変わった、あるいは片方の設定のテイクどうしが、もう片方に比べてはるかに一致しない。前の 2 つは音そのものを測っています。3 つ目はばらつきを測っていて、ばらつきはパラメータ以外のものからも生じます。
一致度だけを根拠にする判定は、安定している側が絶対値としても安定していることを求めます。 比べる相手はもう片方の設定ではなく、同じアドレスについて、打鍵しただけの音が示した数値そのものです。音を遮るパラメータは、遮っている側の設定を、打鍵しただけの音と同じように再現させます。一方、位相が自由に動いている状態から取った 2 回の測定は、どれだけ離れていても何も意味しません。
テスト音は、その判定が数に入る前に、自分自身の再現性を確立していなければなりません。 段階 11 が測るのは、1 つのテスト音についての検出下限です。複数のボイスを鳴らすテスト音は、トリガでは固定できない相対位相を持ちます。トリガのばらつきが、鳴っているノートの 1 周期より広い場合、位相はテイクごとに自由になり、レベルがそれに従って動きます。ある機体での実測では、打鍵しただけの音が 56〜62 dB で一致したのに対し、2 音のテスト音は 1〜66 dB に散らばり、その 84% が 10 dB より悪い値でした。このテスト音が報告したものは、二度目に調べたとき何一つ残っていませんでした。テイク数を倍にしても改善しませんでした。
ちょうど 1 つのテスト音だけが捉えた判定は、公開前に調べ直します。 ある 3 ブロック分の測定では、この種の判定がすべて再現しませんでした。最初のパスでいちばん健全に見えたものも含めてです。調べ直す手間は、存在しない結果を公開する代償に比べれば安上がりです。
同じパラメータを持つ 3 つのブロックは、3 回の試行です。 1 つのブロックにだけ現れて他に現れない判定は、パート間の差か(パラメータは普通そういう差を持ちません)、再現しなかった測定か、どちらかです。その区別には調べ直しが要ります。議論では決まりません。
すべての記録が持つもの
何も見つけなかった段階は、その実行が何かを見つけられたはずである場合にだけ、読む価値を持ちます。ですから各記録は、その実行が備えていた対照を明示します。
- 陽性対照。 最初のテスト音の前と、最後のテスト音の後に送ります。1 回しか送っていない対照は、その実行の残りについて何も語りません。
- 調べている系列のメッセージが実際に届いたかどうか。 陽性対照はコントロールチェンジなので、他の系列のメッセージが通ったことまでは示せません。これを欠く実行では、「見つからなかった」という所見はすべて、機体ではなく経路についてのものになります。
- 書き込んだブロックの外に着弾したときに、それを報告できたかどうか。 あるアドレスへ書いてそのアドレスしか動かなかった、という実行は、そこを映すものが他にない証拠になります。ただしそれは、他所に着弾していたら報告されたはずである場合に限ります。
- 何を元に戻し、何を対象から外したか。 実行がカバーするのをやめた範囲は、その実行が述べることすべてに掛かる但し書きです。
何時間もかかる段階を走らせる
ブロック全体を測る段階は何時間も走り、procedures/ のドライバから起動します。ここはバージョン管理の対象です。1 台の機体に固有のドライバは作業ノートなので対象外ですが、プロトコルの一部であるものはここに置きます。誰も持っていないスクリプトで測定された 2 台目は、同じやり方で測定されたことになりません。
./procedures/part-block.sh "40 11" 0 # ブロックと、それが聞いているチャンネル
./procedures/stop-sweep.sh # 停止し、止まったことを確かめる1 アドレスずつ、新しいプロセスとして走らせます。これは実装の都合ではありません。 無人の長時間実行が遭遇する失敗は 2 つあり、どちらもアドレス 1 つ分を丸ごと失わせ、うち一方はプロセスを即座に終了させます。ですから、新しいプロセスとして調べ直すことが、唯一の回復手段になります。
各段階は、前の段階自身の記録を見て対象を決めます。 ジェスチャのパスが調べる対象は、soundings block が「まだ開いている」と名指ししたものであり、手で書くのではなくファイルから読みます。二度書かれたリストは二度下された判断であり、両者はずれます。対象を決めるべき記録が存在しない場合、段階は起動を拒否します。空のリストと、読み込みに失敗したリストは、次のパスに同じもの——つまり何も——を渡すので、実行が「やることがなかったパス」として報告してしまうからです。
測定経路
測定経路はプロトコルの一部です。インタフェース、ゲイン、結線、収録方法は、機体をまたいで同一に保ちます。そうしないと、2 台の機体のレベル差を、2 つの経路の差と区別できません。
やむを得ず経路を変えた場合は、その違いを meta.json の measurement_chain に記録し、その機体を含むレベル比較には「確立していない」と付記します。
実機を使う段階は直列に走らせます。オーディオインタフェース 1 台、機体 1 台という条件では、手前の収録の最中に裏で別の実行が音を鳴らし、再現性が静かに崩れた数値だけが残ります。
この規則は、破られる前から書いてありました。つまり書き留めるだけでは足りないということなので、ハーネス側で強制します。機体を動かすコマンドは排他ロックを取り、2 つ目は起動を拒否されます。拒否することが要点です。 2 つ目の実行の側からは、何も異常に見えません。ノートは鳴り、テイクは録れ、記録は普通の顔をして出てきます。痕跡は 1 つ目の実行のテイクの中、もう一方のノートが、ノイズフロアを測るためのリードインに紛れ込んでいる場所にしか残りません。
実行を止めるとは、そのプロセスを全部止め、かつ止まったことを確かめることです。 ドライバは入れ子になっていて、それぞれが一度に 1 段階ずつ起動します。ですからプロセスツリーの頂点を落とすと、落とされた子が終了した瞬間に、親が次の段階を開始します。親を子より先に落とし、プロセスの一覧が空になるまで繰り返し、止まったと確かめられない停止は止まらなかったものとして扱います。取り残されたプロセスは待機しているのではありません。機体にノートを鳴らし続けています。
無人の長時間実行における 2 つの失敗は、アドレスについてのことではなく、その瞬間に機械が資源を渡さなかったことです。 要求した長さより短く返ってきた収録と、MIDI 層がクライアントの作成そのものを拒むことです。どちらもアドレス 1 つ分を丸ごと失わせ、後者はプロセスを即座に終了させます。ですからどちらも、新しいプロセスとして調べ直すことで再試行します。何回の再試行が必要だったかは記録します。再試行率が上がっていくことは、測定経路についての事実だからです。