diff --git a/content/多段階計算と増分関数型リアクティブプログラミングによる信号処理のライブコーディング.md b/content/多段階計算と増分関数型リアクティブプログラミングによる信号処理のライブコーディング.md index b65e9b94..55c39a78 100644 --- a/content/多段階計算と増分関数型リアクティブプログラミングによる信号処理のライブコーディング.md +++ b/content/多段階計算と増分関数型リアクティブプログラミングによる信号処理のライブコーディング.md @@ -20,7 +20,7 @@ date: 2025-09-25 11:42 既存の信号処理をターゲットにした音楽プログラミング言語における問題の一つとして、コードの変更時に信号処理の内部状態がリセットされる問題がある。ディレイやフィルターは、内部状態(メモリ)への継続的な書き込みと読み込みを行うことで処理を実現しているが、その内部状態のインスタンスはコードのコンパイル後、信号処理を実際に始める前に0埋めで初期化されることが一般的である。 -Max(MSP)[@Max]やPureData[@puckette_pure_1997]、SuperCollider[@McCartney2002]のJITLibにおける信号処理のように、信号処理のインスタンスのグラフ構成自体を実行中に変更できるような仕組みの場合、内部状態はキープされる。TidaiCycles[@McLean2014]やSonic Pi[@Aaron2013]のようなSuperColliderのクライアントとして実装される言語も同様である一方、信号処理を使った表現の幅はSuperColliderのプリミティブとして用意されたUnit Generatorの組み合わせに留まることになる。 +SuperCollider[@McCartney2002]のJITLibにおける信号処理のように、信号処理のインスタンスのグラフ構成自体を実行中に変更できるような仕組みの場合、内部状態はキープされる。TidaiCycles[@McLean2014]やSonic Pi[@Aaron2013]のようなSuperColliderのクライアントとして実装される言語も同様である一方、信号処理を使った表現の幅はSuperColliderのプリミティブとして用意されたUnit Generatorの組み合わせに留まることになる。 Faust[@Orlarey2009]やMaxのGen、のように、サンプル単位レベルでの信号処理の記述ができるプログラミング言語の場合は、コードを一度低レベルな命令(FaustであればLLVM IRなど)に変換し、そのコードをインスタンス化してから実行するために、インスタンス化のタイミングで毎回内部状態はリセットされる。 @@ -30,13 +30,11 @@ Faust[@Orlarey2009]やMaxのGen、のように、サンプル単位レベルで こうした課題に対し、Reachは関数型のUnit Generatorを組み合わせて信号処理を記述する言語で、ソースコードの変更差分を解析して信号処理の内部状態を可能な限り保持する仕組み:**Incremental Functional Reactive Programming**(以下本稿ではIcFRPと呼ぶ)を提案している[@reach_incremental_2013]。この仕組みは、SuperColliderのJITLibのようなシステムと比べるとユーザーが現在の信号処理インスタンスに対して削除や追加などの命令を行うのではなく、常にその時のソースコードに望む信号処理を書けば必要な状態の更新はランタイム側が自動で担ってくれるという点で、ユーザーの演奏中の思考モデルが大きく異なると言える。 -ただ、Reachによる実装としては、ソースコードの単なるテキスト差分の解析では、複数の変更のパターンの可能性を絞り込めないため、テキストエディタEmacsの拡張機能として、切り取りや貼り付けといった操作の履歴を取得することで実装している。 +ただ、Reachによる実装としては、ソースコードの単なるテキスト差分の解析では、複数の変更のパターンの可能性を絞り込めないため、各項に隠れたラベルを紐づけた上で、テキストエディタEmacsの拡張機能として、切り取りや貼り付けといった操作の履歴を取得することで実装されている。 本稿では、筆者が開発してきた関数型音楽プログラミング言語mimiumに、IcFRPの考え方を応用しつつ、単純なテキスト比較のみで動作するライブコーディングシステムを提案する。 -以下、本論文はmimiumのこれまでの言語設計の簡単な説明と、導入される2種類の機能拡張について順番に説明する。 - -その後、本ライブコーディングシステムの他のシステムと比較した特徴および問題点を議論する。 +以下、本論文はmimiumのこれまでの言語設計の簡単な説明と、導入される2種類の機能拡張について順番に説明する。その後、本ライブコーディングシステムの他のシステムと比較した特徴および問題点を議論する。 ## mimium and lambda-mmm @@ -45,11 +43,11 @@ mimiumは、Rustに近いシンタックスを持った関数型の音楽信号 mimiumはコードを専用のVMバイトコードへコンパイルし実行する。実行モデルは、一般的なレジスタマシンの命令セットに、内部状態操作用の操作が加わったものとなる。ディレイやフィードバックで用いられる内部状態は、状態ストレージという1次元の配列領域と単一の読み出し位置ポインタを組み合わせたデータ領域に保存される。 -コンパイラは、状態ストレージの読み出し位置ポインタを相対的に前後させる命令を適切に出力することで、VM実行時にはストレージの一部分をフィードバックの状態変数やディレイ用のリングバッファとして解釈しデータを読み書きする。 +コンパイラは、状態ストレージの読み出し位置ポインタを相対的に前後させる命令を適切に出力することで、VM実行時にはストレージの特定領域をフィードバックの状態変数やディレイ用のリングバッファとして解釈しデータを読み書きする。 -過去のmimiumでは高階関数などを使うことによって任意の数のフィルタバンクのような、パラメトリックなプロセッサを生成することもできたが、こうしたプロセッサは状態ストレージのレイアウトとメモリサイズを決定できなかった。そのため、グローバルな関数の呼び出しとクロージャ(実行時に高階関数から生成される関数)の呼び出しは区別され、クロージャのインスタンスに個別の状態ストレージを生成し、クロージャ呼び出し時に使用する状態ストレージそのものを切り替えることで対応していた。 +過去のmimiumでは高階関数などを使うことによって任意の数のフィルタバンクのような、パラメトリックなプロセッサを生成することもできたが、こうしたプロセッサは状態ストレージのレイアウトとメモリサイズをコンパイル時に決定できなかった。そのため、グローバルな関数の呼び出しとクロージャ(実行時に高階関数から生成される関数)の呼び出しは区別され、クロージャのインスタンスに個別の状態ストレージを生成し、クロージャ呼び出し時に使用する状態ストレージそのものを切り替えることで対応していた。 -今回提案するライブコーディング機能は、2つの機能によって実現される。 +今回提案するライブコーディング機能は、2つの機能追加によって実現される。 1つは、状態ストレージのレイアウトをコンパイル時に可能な限り確定させるために、多段階計算という型安全なマクロの体系を言語に導入することである。 @@ -59,7 +57,7 @@ mimiumはコードを専用のVMバイトコードへコンパイルし実行す ## 多段階計算 -多段階計算は、型付きラムダ計算に対して、計算のステージを複数段階に分割する明示的なシンタックスを導入するものである。Lisp系言語のquote/splice機能[@lisp]のように、部分的に計算したコード片を埋め込むようなものを想定しているが、不正な値が埋め込まれないことを型システムとして保証することが特徴である(逆に、通常マクロに期待される型システムの範囲を超えたメタ操作を行うことは許されない)。実用的な例では、Scala 3でのマクロや、関数型組版処理エンジンSaTysFi[@suwa2024]のように、言語内DSLを型安全にライブラリとして実装することを想定しているものがある。mimiumにおいては、多段階計算はコンパイル時に行う計算(=シグナルグラフのルーティングの決定)と、ランタイム時に行う計算(=実際のオーディオ処理)を区別するのに用いている。 +多段階計算は、型付きラムダ計算に対して、計算のステージを複数段階に分割する明示的なシンタックスを導入するものである。Lisp系言語のquote/splice機能[@lisp]のように、部分的に計算したコード片を埋め込むようなものを想定しているが、不正な値が埋め込まれないことを型システムとして保証することが特徴である(逆に、通常マクロに期待される型システムの範囲を超えたメタ操作を行うことは許されない)。実用的な例では、Scala 3でのマクロや、関数型組版処理エンジンSaTysFi[@suwa2024]のように、言語内DSLを型安全にライブラリとして実装することを想定しているものがある。mimiumにおいては、多段階計算はコンパイル時に行う計算(=シグナルグラフのルーティングの決定)と、ランタイム時に行う計算(=実際のオーディオ処理)を明示的に区別するのに用いている。 ### シンタックスの拡張 図nにmimiumの内部表現Lambda-mmmに多段階計算の体系を加えた新しい内部表現 $\lambda_{Mmmm}$(Multi-stage version of $\lambda_{mmm}$)のシンタックスを定義する。 @@ -128,20 +126,21 @@ mimiumでは、多段階計算の体系を直感的に扱えるように2つの ### 多段階計算によるメタ操作の実例 ```rust -fn cascade(n,gen){ +... +fn osc(){ + ... +} +fn additive(n,gen){ let g = gen() - if (n>0){ - let c = cascade(n - 1.0 ,gen) - let multiplier = 1.0-(1.0/(n*2.0)) - |rate| { rate + g(rate/2.0)* 0.5 * rate * multiplier |> c } + if (n>1){ + let c = additive(n - 1.0 ,gen) + |rate| c(rate) + g(rate)/n }else{ |rate| g(rate) } } -fn osc(){ - ... -} -let myosc = cascade(20, | | osc); + +let myosc = additive(5, | | osc); fn dsp(){ let f = 200 let r = f |> myosc @@ -150,38 +149,45 @@ fn dsp(){ ``` ```rust +#stage(main) +let PI = 3.14159265359 +fn phasor_shift (freq,phase_shift){ + (self + freq/samplerate + phase_shift)%1.0 +} +fn sinwave = (freq,phase){ + phasor_shift(freq,phase)*2.0*PI |> sin +} +fn osc = (freq){ + sinwave(freq,0.0) * 0.5 +} #stage(macro) -fn cascade(n,gen){ - if (n>0.0){ - let multiplier = 1.0-(1.0/(n*3)) |> lift_f - `|rate| rate + ($gen)(rate/3)* rate* $multiplier - |> $cascade(n - 1.0 ,gen) +fn additive(n,gen:`(float)->float)->`(float)->float{ + if (n>1){ + let multiplier = lift_f(n) + let next = additive(n - 1.0 ,gen) + `|rate|{ ($gen)(rate*$multiplier) / $multiplier + ($next)(rate)} }else{ `|rate| ($gen)(rate) } } #stage(main) -fn osc(){ - ... -} fn dsp(){ - let f = 200 - let r = f |> cascade!(20,`osc) + let f = 1000 + let r = f |> additive!(10,`osc) |> Probe!("test") (r,r) } ``` ```rust -`{ +`{ // after desugar ${ - let cascade = |n,gen|{ - if (n>0.0){ - let multiplier = 1.0-(1.0/(n*3)) |> lift_f - `{|rate| rate + ($gen)(rate/3)* 0.5 * rate* $multiplier - |> $cascade(n - 1.0 ,gen) } + let additive = |n,gen|{ + if (n>1){ + let divider = lift_f(n) + `|rate| ($gen)(rate) / $divider + $additive(n - 1.0 ,gen) }else{ - `{|rate| ($gen)(rate)} + `|rate| ($gen)(rate) } } `{ @@ -190,7 +196,7 @@ fn dsp(){ } let dsp = | |{ let f = 200 - let r = f |> $cascade(20,`osc) + let r = f |> $additive(5,`osc) (r,r) } }