[obsidian] vault backup: 2025-12-19 18:28:34[
All checks were successful
Build / build (push) Successful in 8m36s

This commit is contained in:
2025-12-19 18:28:34 +09:00
parent 34f1574108
commit cf3805dc1a
2 changed files with 8 additions and 29 deletions

View File

@@ -285,31 +285,9 @@ fn osc(freq){
## 議論
### 他の言語とのコンパイル時計算のパラダイム比較
Reachのシステムと比較したときに、再帰によるパラメトリックなDSPコード生成に対応したままライブコーディング機能を実現できていること、またその実現にテキストエディタ固有の機能に依存せず、単にソースコード同士の比較によって構造比較を行えている点がメリットと言える。
| | Faust | Kronos | mimium |
| ---------------- | ------- | ------- | ------------------ |
| パラメトリックな信号ルーティング | 項書換えマクロ | 型レベル計算 | ステージ0の計算/グローバル環境評価 |
| 実際の信号処理 | BDA | 値レベルの評価 | ステージ1の計算 |
多段階計算の不足している部分
マクロとしては、型安全な代わりに、型システムの範囲を超えたメタ操作ができない。型システムが充実しない限り、Kronosのようなタプルの要素数をパラメトリックに扱うようなことはできない。代わりに配列を操作する。これはこれで体系がシンプルでいいかも。
現状、ツリーウォーク型のインタプリタでステージ0を評価し、VMのバイトコードに変換してからステージ1を評価している。これは、リアルタイム実行パフォーマンスの維持しつつ多段階計算の体系を導入するための苦肉の策である。せっかくマクロと実際の計算を同じ意味論で実行できるのに、結局2つの処理系を実装している。
### ライブ状態更新のエッジケース
差分処理を実行している間に内部状態が更新されてはいけない。なので、新しいソースコードのパースや内部状態構造導出、VMコード生成、木の比較までは非同期で行えるが、コピー中はオーディオ処理全てを一度中断しなくてはならない。
構造が大きくなった時にドロップアウトしないか。木のサイズでざっくりベンチを取りたい。バッファサイズの参考にできるはず
最長共通部分列の仕様上、`osc1()+osc2()`から`osc2()+osc1()`への変更のような、明らかに問題のない入れ替えでもどちらか片方しか引き継がれない。また、どちらが引き継がれるかはLCSのバックトラックの戦略に依存する。ツリーのードの子の種類として、順序の関係ない集合、ある集合とを区別するようにすれば実現できるかも。
関連して、引き継いではいけないはずのデータを引き継ぐ可能性がある。
エッジケースとして最長共通部分列の仕様上、`osc1()+osc2()`から`osc2()+osc1()`への変更のような、明らかに問題のない入れ替えでもどちらか片方しか引き継がれないという問題がある。また、どちらが引き継がれるかはLCSのバックトラックの戦略に依存する。関連して、引き継いではいけないはずのデータを引き継ぐ可能性がある。
```rust
fn phasor1(freq){ //0 ~ samplerate/freq
@@ -342,12 +320,13 @@ fn dsp(){//dspはFncall[Fncall[FnCall[Feed]],Fncall[Feed]]で変化なし
}
```
上のサンプルでは、はじめlfoを使って周波数をモジュレーションしている状態から、周波数は固定にして音量をモジュレーションする処理へと切り替えた例である。myfreq()とmyamp()はそれぞれどちらもosc関数を1度だけ呼び出すため、dsp関数の内部状態ツリーの構成は共通しており、再コンパイル時にデータが引き継がれる。
この時、myampにはmyfreqの最後の位相が引き継がれることになるが、今回実装しているphasor1はselfに保存される値が0~samplerate/freq、例えば1000Hzなら0~48の値のレンジを取り、これがmyampの中で使われているphasor2のselfのその値の本来のレンジは0~1であるべきにも関わらず引き継がれてしまう。
上のサンプルでは、はじめlfoを使って周波数をモジュレーションしている状態から、周波数は固定にして音量をモジュレーションする処理へと切り替えた例である。myfreq()とmyamp()はそれぞれどちらもosc関数を1度だけ呼び出すため、dsp関数の内部状態ツリーの構成は共通しており、再コンパイル時にデータが引き継がれる。この時、myampにはmyfreqの最後の位相が引き継がれることになるが、今回実装しているphasor1はselfに保存される値が0~samplerate/freq、例えば1000Hzなら0~48の値のレンジを取り、これがmyampの中で使われているphasor2のselfのその値の本来のレンジは0~1であるべきにも関わらず引き継がれてしまう。
ただ、結局phasor2を実行したときには0~1のレンジに丸まるので大きな問題にはならない。ある関数がある範囲に収まることが保証されているということは、仮にそこで使われているselfに不正な値が差し込まれたとしても、その関数が計算し終わったときには元の範囲に収まる可能性が高いからだ。また関連性のないデータが差し込まれたとしても、結局は聴感上違和感がない程度に留まる可能性もある。
ただ、実際には結局phasor2を実行したときには0~1のレンジに丸まるので大きな問題にはならない。ある関数がある範囲に収まることが保証されているということは、仮にそこで使われているselfに不正な値が差し込まれたとしても、その関数が計算し終わったときには元の範囲に収まる可能性が高いからだ。似たようなケースで関連性のないデータがコピーされたとしても、結局は聴感上違和感がない程度に留まる可能性もある。
さらに、実際にはコードの規模が大きくなっていくにつれて、関数呼び出しの深さが深くなっていくため、偶然関数呼び出しの構造が一致する可能性は小さくなっていくことと、実際のライブコーディングにおいては
現実的なユースケースを考えれば、2つの点においてこれらの問題点は気にならなくなる。
1つは、コードの規模が大きくなっていくにつれて、関数呼び出しの深さが深くなっていくため、偶然関数呼び出しの構造が一致する可能性は小さくなっていくことである。2つ目は、実際のライブコーディングにおいては複雑な構造変化を一度に行うことは少なく、あったとしてもそれは精々一箇所へのコード挿入、削除、置き換えといったアクションの連続として捉えられる。一度に大規模のコード変更をコミットせずに、個々の変更毎にセーブと更新を行えば、本システムでも問題なく対応できる。