diff --git a/src/main.tex b/src/main.tex index e3b9d63..66dca3b 100755 --- a/src/main.tex +++ b/src/main.tex @@ -19,7 +19,7 @@ \author{松浦 知也}{Matsuura Tomoya}{TOMOYA}[me@matsuuratomoya.com] \begin{abstract} -本発表では筆者の開発する音楽のためのプログラミング言語mimiumの理論的基盤について、音楽向け言語の歴史的文脈に沿って解説する。mimiumは、リアルタイム信号処理を想定した音楽用のDSLだが、既存の多くの言語とは異なり、Unit Generatorのような特定の音楽表現に基づくプリミティブを用意しない。代わりに、値呼びの単純型付きラムダ計算に遅延とフィードバックという2要素をプリミティブとして追加した中間表現を定義することで、その言語上でほとんどの信号処理アルゴリズムを関数のパイプとして表現できる。また、Luaを参考にしたVMを設計したことで、内部状態を持つ信号処理関数の高階関数を用いた複製や、ホスト環境の埋めこみを容易に可能にしている。音楽というドメインに特化しながらも、汎用性を失わない言語の意義について議論する。 +本発表では筆者の開発する音楽のためのプログラミング言語mimiumの理論的基盤について,音楽向け言語の歴史的文脈に沿って解説する.mimiumは,リアルタイム信号処理を想定した音楽用のDSLだが,既存の多くの言語とは異なり,Unit Generatorのような特定の音楽表現に基づくプリミティブを用意しない.代わりに,値呼びの単純型付きラムダ計算に遅延とフィードバックという2要素をプリミティブとして追加した中間表現を定義することで,その言語上でほとんどの信号処理アルゴリズムを関数のパイプとして表現できる.また,Luaを参考にしたVMを設計したことで,内部状態を持つ信号処理関数の高階関数を用いた複製や,ホスト環境の埋めこみを容易に可能にしている.音楽というドメインに特化しながらも,汎用性を失わない言語の意義について議論する. \end{abstract} \begin{jkeyword} @@ -31,39 +31,38 @@ % Body %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{はじめに-音楽プログラミング言語の分業化} -音楽のためのプログラミング言語・環境は、Cycling'74 MaxやPure Data、CSound、SuperCollider、ChucKといった言語を代表として様々なものが開発されてきている\cite{roads2001,Lazzarini2013,Nishino2016,Dannenberg2018}。こうした言語の多くは歴史を遡ると1950年代にベル研究所でマックス・マシューズらが開発したMUSICシリーズに遡ることができる\cite{mathews1963,park2009}。MUSICシリーズはパルス符号変調という今日のコンピューター上のでの音声処理のすべての基礎でもある理論、すなわち音圧波形を時間・音圧の2次元で離散化・量子化した数列として表すことで、計算によって任意の波形を生成するという考えに基づいて、計算機で音を出した最初の事例である。 +音楽のためのプログラミング言語・環境は,Cycling'74 MaxやPure Data,CSound,SuperCollider,ChucKといった言語を代表として様々なものが開発されてきている\cite{roads2001,Lazzarini2013,Nishino2016,Dannenberg2018}.こうした言語の多くは歴史を遡ると1950年代にベル研究所でマックス・マシューズらが開発したMUSICシリーズに遡ることができる\cite{mathews1963,park2009}.MUSICシリーズはパルス符号変調という今日のコンピューター上のでの音声処理のすべての基礎でもある理論,すなわち音圧波形を時間・音圧の2次元で離散化・量子化した数列として表すことで,計算によって任意の波形を生成するという考えに基づいて,計算機で音を出した最初の事例である. -CやC++といった汎用プログラミング言語で音声合成を行う場合もこのPCMの理論に基づいてプログラミングを行うことになるが、これら低レイヤーを対象にした言語はメモリ管理や並行処理といったハードウェアに近い処理への理解を要求されるため、音楽の記述を行う場合にはより抽象化されたライブラリやドメイン固有言語を利用するのが普通である。 -% todo MUSICがUGENパラダイムを導入したという接続を入れる -そうしたライブラリや言語では、MUSIC IIIで導入されたUnit Generator(UGen)と呼ばれる、オシレーターやフィルターといった基礎的な処理単位を、モジュラーシンセサイザーの様に組み合わせていくことで処理を行う考え方に基づく。\footnote{モジュラーシンセサイザーとUnit Generatorは実際には同時期に現れたコンセプトではある(\cite[p20]{park2009})が、その後の音楽プログラミング言語は積極的にビジュアル的なメタファーを物理的なシンセサイザーから取り入れてきている。} +CやC++といった汎用プログラミング言語で音声合成を行う場合もこのPCMの理論に基づいてプログラミングを行うことになるが,これら低レイヤーを対象にした言語はメモリ管理や並行処理といったハードウェアに近い処理への理解を要求されるため,音楽の記述を行う場合にはより抽象化されたライブラリやドメイン固有言語を利用するのが普通である. -2000年代以降では、Sonic Pi\cite{Aaron2013}やTidalCycles\cite{McLean2014}に代表される、メロディやリズムパターンをソースコードから生成し、リアルタイムでソースコードを書き換えることで演奏を行うライブコーディングのための言語や環境が開発されてきた。こうした言語の多くは信号処理をSuperColliderなどのUGenベースの言語に任せ、それらの言語ランタイムへ命令を送信をするクライアントとして実装されている。そして、多くの言語でUGen自体の実装にはC++などの低級言語が用いられ、動的ロードライブラリの形で提供される。 +そうしたライブラリや言語では,MUSIC IIIで導入されたUnit Generator(UGen)と呼ばれる,オシレーターやフィルターといった基礎的な処理単位を,モジュラーシンセサイザーの様に組み合わせていくことで処理を行う考え方に基づく.\footnote{モジュラーシンセサイザーとUnit Generatorは実際には同時期に現れたコンセプトではある(\cite[p20]{park2009})が,その後の音楽プログラミング言語は積極的にビジュアル的なメタファーを物理的なシンセサイザーから取り入れてきている.} -%--[p42] Nishino2016にしたい +2000年代以降では,Sonic Pi\cite{Aaron2013}やTidalCycles\cite{McLean2014}に代表される,メロディやリズムパターンをソースコードから生成し,リアルタイムでソースコードを書き換えることで演奏を行うライブコーディングのための言語や環境が開発されてきた.こうした言語の多くは信号処理をSuperColliderなどのUGenベースの言語に任せ,それらの言語ランタイムへ命令を送信をするクライアントとして実装されている.そして,多くの言語でUGen自体の実装にはC++などの低級言語が用いられ,動的ロードライブラリの形で提供される. -このように音楽のためのプログラミング言語は、表現可能な範囲をユーザーや実装者の関心のある領域ごとに細分化する形で発展してきている。その一方で、NishinoとNakatsuによるサーベイで指摘されている(\cite[p42]{Nishino2016})ように、近年のコンピューター音楽実践ではマイクロサウンド\footnote{音声波形を細かく切り刻んで再構成するような音声合成手法\cite{roads2004}。シンセサイザーのカテゴリとして、グラニュラー合成と呼ばれる物の基礎的な考え方でもある。}や、サンプル単位精度でのイベント処理のような高度な処理が求められる中で、Unit Generatorというコンセプトを基礎に置くこと自体の妥当性が問われてもいる。 -ユーザーおよび開発者の利便性に限った点に絞ったとしても、UGenパラダイムにはいくつかの問題点が指摘できる。まず1つはコードの再利用性である。多くの言語では、実際にはほとんど同じような内容であるにも関わらず、それぞれが基本的なUGenを実装し直しており、言語間での相互運用性に欠けている。2つ目は、UGenがプリミティブなデータ型として提供されており、非線系オシレーターの用に既存のUGenの組み合わせでは表現できない新たなUGenの定義がその言語上では難しいことである。3つ目に、UGen自体のパラメトリックな接続の抽象化(例えば、オシレーターを任意の本数複製したり、フィルターをカスケードしたり)することが難しい点である。これは言い換えると、多くの言語では、UGenを引数として受け取って新たなUGenを定義するような関数を定義できないということである。 +このように音楽のためのプログラミング言語は,表現可能な範囲をユーザーや実装者の関心のある領域ごとに細分化する形で発展してきている.その一方で,NishinoとNakatsuによるサーベイで指摘されている(\cite[p42]{Nishino2016})ように,近年のコンピューター音楽実践ではマイクロサウンド\footnote{音声波形を細かく切り刻んで再構成するような音声合成手法\cite{roads2004}.シンセサイザーのカテゴリとして,グラニュラー合成と呼ばれる物の基礎的な考え方でもある.}や,サンプル単位精度でのイベント処理のような高度な処理が求められる中で,Unit Generatorというコンセプトを基礎に置くこと自体の妥当性が問われてもいる. + +ユーザーおよび開発者の利便性に限った点に絞ったとしても,UGenパラダイムにはいくつかの問題点が指摘できる.まず1つはコードの再利用性である.多くの言語では,実際にはほとんど同じような内容であるにも関わらず,それぞれが基本的なUGenを実装し直しており,言語間での相互運用性に欠けている.2つ目は,UGenがプリミティブなデータ型として提供されており,非線系オシレーターの用に既存のUGenの組み合わせでは表現できない新たなUGenの定義がその言語上では難しいことである.3つ目に,UGen自体のパラメトリックな接続の抽象化(例えば,オシレーターを任意の本数複製したり,フィルターをカスケードしたり)することが難しい点である.これは言い換えると,多くの言語では,UGenを引数として受け取って新たなUGenを定義するような関数を定義できないということである. \subsection{関数型プログラミング言語に影響を受けた信号処理言語} -Unit Generatorコンセプトに基づく言語の問題点は、既存の(信号処理を表現対象に含む)音楽プログラミング言語の多くが実装に依存しており、言語の意味論を形式化したものがほとんど無いことにも起因する。 +Unit Generatorコンセプトに基づく言語の問題点は,既存の(信号処理を表現対象に含む)音楽プログラミング言語の多くが実装に依存しており,言語の意味論を形式化したものがほとんど無いことにも起因する. -この観点で、Faust\cite{Orlarey2004}は実用的に使われている音楽プログラミング言語の中でも例外的に強く形式化された言語である。Faustは、入出力を持つブロックを、並列、直列、分岐、合流、再帰の5つの合成子を用いて組み合わせていくブロックダイアグラム代数(Block Diagram Algebra-BDA)という体系を基礎に置く。バイパス、カット、基本的な算術演算、条件分岐、遅延をプリミティブなブロックとして提供することで、複雑な信号処理グラフを記述することができる。また後の拡張では、項書き換え系に基づくマクロを導入することで、任意の数の入出力を持つブロックの抽象化を可能にした\cite{graf2010}。 +この観点で,Faust\cite{Orlarey2004}は実用的に使われている音楽プログラミング言語の中でも例外的に強く形式化された言語である.Faustは,入出力を持つブロックを,並列,直列,分岐,合流,再帰の5つの合成子を用いて組み合わせていくブロックダイアグラム代数(Block Diagram Algebra-BDA)という体系を基礎に置く.バイパス,カット,基本的な算術演算,条件分岐,遅延をプリミティブなブロックとして提供することで,複雑な信号処理グラフを記述することができる.また後の拡張では,項書き換え系に基づくマクロを導入することで,任意の数の入出力を持つブロックの抽象化を可能にした\cite{graf2010}. -この形式化を通じた強力な抽象化能力により、FaustはC、C++、Rust、LLVM IRなどの様々なバックエンドにコンパイルでき、あらかじめ用意されたアーキテクチャファイルと呼ばれるボイラープレートコードと組み合わせることで、MaxやSuperColliderのUGenとしてエクスポートしたり、WebAssemblyを利用してWebブラウザ上で実行することができる。その一方、Faustの理論的基盤であるBDAは、一般的なプログラミング言語との理論的・実用的な互換性に欠ける。Faustでは外部のC関数を呼び出すことは可能だが、ポインタや参照を扱えないのでホストの内部状態をFaust側から制御することは難しい。 +この形式化を通じた強力な抽象化能力により,FaustはC,C++,Rust,LLVM IRなどの様々なバックエンドにコンパイルでき,あらかじめ用意されたアーキテクチャファイルと呼ばれるボイラープレートコードと組み合わせることで,MaxやSuperColliderのUGenとしてエクスポートしたり,WebAssemblyを利用してWebブラウザ上で実行することができる.その一方,Faustの理論的基盤であるBDAは,一般的なプログラミング言語との理論的・実用的な互換性に欠ける.Faustでは外部のC関数を呼び出すことは可能だが,ポインタや参照を扱えないのでホストの内部状態をFaust側から制御することは難しい. -また、Faustのマクロは、パターンマッチに基づいてBDAを生成する独立した項書き換え系である。そのため、パターンマッチングのための数値引数は暗黙のうちに整数であることが要求され、BDA上では整数と実数の方の区分けは存在しないにもかかわらず、コンパイル時エラーを起こすことある。この暗黙の型付けルールは、初学者のユーザーにとっては直感的なものとは言い難い。 +また,Faustのマクロは,パターンマッチに基づいてBDAを生成する独立した項書き換え系である.そのため,パターンマッチングのための数値引数は暗黙のうちに整数であることが要求され,BDA上では整数と実数の方の区分けは存在しないにもかかわらず,コンパイル時エラーを起こすことある.この暗黙の型付けルールは,初学者のユーザーにとっては直感的なものとは言い難い. -ラムダ計算のような、より汎用的な計算モデルに基づく信号処理の計算モデルを提案することは、さまざまな汎用言語との相互運用を促進しうるほか、既存の最適化手法の流用や、コンパイラやランタイムの実装を容易にする可能性をもつ\footnote{これまで、BDAはモナドの高位抽象化であるアローとしてラムダ計算ベースの汎用関数型言語に変換可能なことが証明されてはいる[3]。ただ、汎用関数型言語の内部DSLとして信号処理を範疇に入れた音楽プログラミング言語を実装するには、多くの言語では動的なメモリ割り当て・解放のタイミングがユーザー側で制御できないなど、ハードリアルタイム処理に適さないという問題が残る。}。 +ラムダ計算のような,より汎用的な計算モデルに基づく信号処理の計算モデルを提案することは,さまざまな汎用言語との相互運用を促進しうるほか,既存の最適化手法の流用や,コンパイラやランタイムの実装を容易にする可能性をもつ\footnote{これまで,BDAはモナドの高位抽象化であるアローとしてラムダ計算ベースの汎用関数型言語に変換可能なことが証明されてはいる\cite{gaster2018}.ただ,汎用関数型言語の内部DSLとして信号処理を範疇に入れた音楽プログラミング言語を実装するには,多くの言語では動的なメモリ割り当て・解放のタイミングがユーザー側で制御できないなど,ハードリアルタイム処理に適さないという問題が残る.}. -Kronos\cite{norilo2015}とW-calculus\cite{arias2021}は、それぞれFaustに影響を受けたラムダ計算ベースの抽象化の例である。Kronosは理論的基盤としてSystem-$F\omega$ というラムダ計算のバリエーションに基づいており、型を入力として受け取り、新しい型を返す関数を定義することができる。Kronosでは、型レベルの計算が信号処理グラフの生成に対応し、値の計算が実際の処理に対応する。Kronosにおいては遅延だけが唯一の特殊なプリミティブ演算であり、フィードバックを伴うルーティングは型計算における再帰関数の適用として表現される。ただし、意味論の厳密な形式化はなされていない。 +Kronos\cite{norilo2015}とW-calculus\cite{arias2021}は,それぞれFaustに影響を受けたラムダ計算ベースの抽象化の例である.Kronosは理論的基盤としてSystem-$F\omega$ というラムダ計算のバリエーションに基づいており,型を入力として受け取り,新しい型を返す関数を定義することができる.Kronosでは,型レベルの計算が信号処理グラフの生成に対応し,値の計算が実際の処理に対応する.Kronosにおいては遅延だけが唯一の特殊なプリミティブ演算であり,フィードバックを伴うルーティングは型計算における再帰関数の適用として表現される.ただし,意味論の厳密な形式化はなされていない. -W-calculusは、変数の過去の値にアクセスする機能(すなわち遅延)とともに、プリミティブ操作としてフィードバックを含む。W-calculusでは、定理証明支援システムCoqを用いた厳密な意味論の定義がなされている。他方で、W-calculusは記述する対象をフィルタやリバーブのような線形時不変システムに限定している。その代わりに、書かれたシステムではシステムの線形性が保証されるほか、異なる表記のシステム間の同一性を証明することなどが可能になる。またもう1つの制限として、W-calculusでは高階関数の使用は許されていない。 +W-calculusは,変数の過去の値にアクセスする機能(すなわち遅延)とともに,プリミティブ操作としてフィードバックを含む.W-calculusでは,定理証明支援システムCoqを用いた厳密な意味論の定義がなされている.他方で,W-calculusは記述する対象をフィルタやリバーブのような線形時不変システムに限定している.その代わりに,書かれたシステムではシステムの線形性が保証されるほか,異なる表記のシステム間の同一性を証明することなどが可能になる.またもう1つの制限として,W-calculusでは高階関数の使用は許されていない. -筆者が開発している音楽のためのプログラミング言語mimium(\emph{MInimal-Musical-medIUM:ミミウム})\cite{Matsuura2021}では、ラムダ計算に遅延とフィードバックを基本構文として組み込むことで、汎用プログラミング言語と同様の構文を維持しながら、信号処理を簡潔に表現することができる。ただ、mimiumのこれまでの問題として、意味論が厳密に定義できていないために、遅延やフィードバックを含む内部状態を伴う関数と、再帰関数や高階関数の組み合わせを含むコードをコンパイルできないという点があった。 +筆者が開発している音楽のためのプログラミング言語mimium(\emph{MInimal-Musical-medIUM:ミミウム})\cite{Matsuura2021}では,ラムダ計算に遅延とフィードバックを基本構文として組み込むことで,汎用プログラミング言語と同様の構文を維持しながら,信号処理を簡潔に表現することができる.ただ,mimiumのこれまでの問題として,意味論が厳密に定義できていないために,遅延やフィードバックを含む内部状態を伴う関数と,再帰関数や高階関数の組み合わせを含むコードをコンパイルできないという点があった. -本稿では、W-calculusの2つの制限(非線形システムと高階関数)を緩和する形で設計された、mimiumの内部表現となる計算モデル$\lambda_{mmm}$について解説する\footnote{本稿の内容は、2024年11月に行われたInternational Faust Conference 2024での筆者の発表\cite{matsuura2024}を、対象読者として日本語話者かつ言語設計コミュニティを想定し大幅に改稿したものである。また現在のmimiumの開発リポジトリは{\url{https://github.com/tomoyanonymous/mimium-rs}}で公開されている。}。以降の節では、まず第2節で$\lambda_{mmm}$のシンタックスと型付け規則、ナイーブな操作的意味論を簡単に紹介する。この操作的意味論は現実的に直接リアルタイム実行することが難しいため、実際のmimiumの実行環境では、$\lambda_{mmm}$を実際に実行するための仮想機械(VM)とその命令セットを持つ。第3節ではこのVMおよび命令セットの設計について概説する。第4節では、現状の$\lambda_{mmm}$を下敷きにしたmimiumの設計の今後の課題および展望を3つの視点で議論する。1つ目は、計算がグローバル環境の評価時に発生するのか、実際の信号処理中に発生するのかをユーザーが区別しなければならないという問題である。2つ目は、if式による部分的な内部状態更新を許すかどうかが信号処理の表現できる範囲およびユーザー体験に大きな影響を与えるという点である。3つ目は、$\lambda_{mmm}$の中間表現を取り入れることで外部関数の呼び出しが容易になるという点である。 +本稿では,W-calculusの2つの制限(非線形システムと高階関数)を緩和する形で設計された,mimiumの内部表現となる計算モデル$\lambda_{mmm}$について解説する\footnote{本稿の内容は,2024年11月に行われたInternational Faust Conference 2024での筆者の発表\cite{matsuura2024}を,対象読者として日本語話者かつ言語設計コミュニティを想定し大幅に改稿したものである.また現在のmimiumの開発リポジトリは{\url{https://github.com/tomoyanonymous/mimium-rs}}で公開されている.}.以降の節では,まず第2節で$\lambda_{mmm}$のシンタックスと型付け規則,ナイーブな操作的意味論を簡単に紹介する.この操作的意味論は現実的に直接リアルタイム実行することが難しいため,実際のmimiumの実行環境では,$\lambda_{mmm}$を実際に実行するための仮想機械(VM)とその命令セットを持つ.第3節ではこのVMおよび命令セットの設計について概説する.第4節では,現状の$\lambda_{mmm}$を下敷きにしたmimiumの設計の今後の課題および展望を3つの視点で議論する.1つ目は,計算がグローバル環境の評価時に発生するのか,実際の信号処理中に発生するのかをユーザーが区別しなければならないという問題である.2つ目は,if式による部分的な内部状態更新を許すかどうかが信号処理の表現できる範囲およびユーザー体験に大きな影響を与えるという点である.3つ目は,$\lambda_{mmm}$の中間表現を取り入れることで外部関数の呼び出しが容易になるという点である. \section{mimiumの内部表現$\lambda_{mmm}$の仕様} @@ -72,14 +71,14 @@ W-calculusは、変数の過去の値にアクセスする機能(すなわち % 行けたら評価環境の定義をきちんとする -図\ref{fig:syntax_v}に$\lambda_{mmm}$の型と項の定義を示す。実際のmimiumの型システムではタプルのような合成型も使用されているが、本稿では、議論を単純化するため合成型は割愛し、適宜必要な部分で合成型を加えるときの実装方針について注釈を加えることにする。 +図\ref{fig:syntax_v}に$\lambda_{mmm}$の型と項の定義を示す.実際のmimiumの型システムではタプルのような合成型も使用されているが,本稿では,議論を単純化するため合成型は割愛し,適宜必要な部分で合成型を加えるときの実装方針について注釈を加えることにする. -標準的な値呼び単純型付きラムダ計算に加えて、$e_1$の$e_2$サンプル分過去の値を参照する $delay\ n\ e_1\ e_2$ と、$e$ そのものの評価中に1単位時刻前の $e$ の評価結果を $x$ として参照できる抽象化 $feed x\ x.e$ の2つの項が導入されている。$delay$では使用するメモリを有限に制限するため、最大遅延として$n$をリテラルな値として明示する必要がある。 +標準的な値呼び単純型付きラムダ計算に加えて,$e_1$の$e_2$サンプル分過去の値を参照する $delay\ n\ e_1\ e_2$ と,$e$ そのものの評価中に1単位時刻前の $e$ の評価結果を $x$ として参照できる抽象化 $feed x\ x.e$ の2つの項が導入されている.$delay$では使用するメモリを有限に制限するため,最大遅延として$n$をリテラルな値として明示する必要がある. \subsection{mimiumにおけるfeed項の糖衣構文} \label{sec:mimium} -mimiumには、関数定義の中でその関数の直前の時刻の返り値を参照するためのキーワード\texttt{self}がある。入力ゲインとフィードバックゲインの和が1になるように入力信号と最後の出力信号を混合する、単極フィルタ(積分器)を表す関数の例を図\ref{fig:onepole-code}に示す。このコードは $\lambda_{mmm}$ では図\ref{fig:onepole}のように表現できる。 +mimiumには,関数定義の中でその関数の直前の時刻の返り値を参照するためのキーワード\texttt{self}がある.入力ゲインとフィードバックゲインの和が1になるように入力信号と最後の出力信号を混合する,単極フィルタ(積分器)を表す関数の例を図\ref{fig:onepole-code}に示す.このコードは $\lambda_{mmm}$ では図\ref{fig:onepole}のように表現できる. \begin{figure}[ht] \centering @@ -88,7 +87,7 @@ fn onepole(x,g){ x*(1.0-g) + self*g } \end{verbatim} -\caption{\label{fig:onepole-code}{mimiumにおける単極フィルタの実装のサンプル。}} +\caption{\label{fig:onepole-code}{mimiumにおける単極フィルタの実装のサンプル.}} \end{figure} \begin{figure}[ht] @@ -99,7 +98,7 @@ let\ & onepole = \\ & \ \lambda x. \lambda g.\ feed\ y.\ x *(1.0 - g) + y * g \ in\ ... \end{aligned} \end{equation*} -\caption{\label{fig:onepole}{$\lambda_{mmm}$における図\ref{fig:onepole-code}と等価な表現。}} +\caption{\label{fig:onepole}{$\lambda_{mmm}$における図\ref{fig:onepole-code}と等価な表現.}} \end{figure} \subsection{型付け規則} @@ -107,75 +106,75 @@ let\ & onepole = \\ \input{typing.tex} -典型的な単純型付きラムダ計算に追加される $\lambda_{mmm}$ の型付け規則を図\ref{fig:typing}に示す。プリミティブ型には、ほとんどの信号処理で使われる実数型と、ディレイの添字などに使われる自然数型がある。$\lambda_{mmm}$ の設計に直接影響を与えたW-calculusでは、関数型は実数のタプルを取り、実数のタプルを返すことしかできない。これはすなわち高階関数の定義を制限していることを意味する。高階関数は実行時にクロージャのような動的メモリ割り当てに依存するデータ構造を必要とするので、この制約は信号処理言語として合理的でな一方で、ラムダ計算の汎用性を失わせてしまってもいる。 +典型的な単純型付きラムダ計算に追加される $\lambda_{mmm}$ の型付け規則を図\ref{fig:typing}に示す.プリミティブ型には,ほとんどの信号処理で使われる実数型と,ディレイの添字などに使われる自然数型がある.$\lambda_{mmm}$ の設計に直接影響を与えたW-calculusでは,関数型は実数のタプルを取り,実数のタプルを返すことしかできない.これはすなわち高階関数の定義を制限していることを意味する.高階関数は実行時にクロージャのような動的メモリ割り当てに依存するデータ構造を必要とするので,この制約は信号処理言語として合理的でな一方で,ラムダ計算の汎用性を失わせてしまってもいる. -$\lambda_{mmm}$ では、クロージャのメモリ割り当ての問題はランタイム側の設計と実装に先送りすることで(第\ref{sec:vm}節を参照)、高階関数の使用を許している。しかし、$feed$の抽象化では、関数型を含まない型\footnote{タプルを含む合成型を扱う場合、単純な関数型のみならずメンバに関数型を1つも含まない型を意味する。}のみを許す。$feed$抽象で関数型を許可するということは、例えば1サンプル前の関数から現在の時刻に使用する関数を合成する、といった記述を可能にすることになる。しかしこれを可能にすると、クロージャによる動的メモリ割り当ての問題に加えて、時間経過ごとに新しいクロージャが過去のクロージャを参照し続けることで使用するメモリサイズが肥大化する空間漏洩(space leak)という更なる問題が発生する\footnote{空間漏洩は関数型リアクティブプログラミング(FRP)という、時変値の伝播を関数合成で記述するパラダイムの初期の実装の問題点として現れた\cite{krishnaswami2013}。近年の実用的なFRPライブラリや言語ではこの問題の解決に様々なアプローチで取り組んでいるため、今後の時間変化する関数型の値の活用の可能性はありえる。}。このため、現在mimiumでは専ら実装を簡易化するという理由で関数型を含む型を$feed$の項では取れないようにしている。 +$\lambda_{mmm}$ では,クロージャのメモリ割り当ての問題はランタイム側の設計と実装に先送りすることで(第\ref{sec:vm}節を参照),高階関数の使用を許している.しかし,$feed$の抽象化では,関数型を含まない型\footnote{タプルを含む合成型を扱う場合,単純な関数型のみならずメンバに関数型を1つも含まない型を意味する.}のみを許す.$feed$抽象で関数型を許可するということは,例えば1サンプル前の関数から現在の時刻に使用する関数を合成する,といった記述を可能にすることになる.しかしこれを可能にすると,クロージャによる動的メモリ割り当ての問題に加えて,時間経過ごとに新しいクロージャが過去のクロージャを参照し続けることで使用するメモリサイズが肥大化する空間漏洩(space leak)という更なる問題が発生する\footnote{空間漏洩は関数型リアクティブプログラミング(FRP)という,時変値の伝播を関数合成で記述するパラダイムの初期の実装の問題点として現れた\cite{krishnaswami2013}.近年の実用的なFRPライブラリや言語ではこの問題の解決に様々なアプローチで取り組んでいるため,今後の時間変化する関数型の値の活用の可能性はありえる.}.このため,現在mimiumでは専ら実装を簡易化するという理由で関数型を含む型を$feed$の項では取れないようにしている. \subsection{ナイーブな操作的意味論} \label{sec:semantics} \input{semantics.tex} -図\ref{fig:semantics}に $\lambda_{mmm}$ のビッグステップ(評価の中間過程を考えず、最終的な評価結果のみを考える)操作的意味論の抜粋を示す。この意味論では各時刻にごとに評価環境が用意され、現在時刻が $n$ における、$t$ サンプル過去の評価環境を $E^{n-t}$ と表記する。0より小さい時刻の評価環境が参照された場合は、どの項もその型のデフォルト値(数値型の場合は0)として評価される。 +図\ref{fig:semantics}に $\lambda_{mmm}$ のビッグステップ(評価の中間過程を考えず,最終的な評価結果のみを考える)操作的意味論の抜粋を示す.この意味論では各時刻にごとに評価環境が用意され,現在時刻が $n$ における,$t$ サンプル過去の評価環境を $E^{n-t}$ と表記する.0より小さい時刻の評価環境が参照された場合は,どの項もその型のデフォルト値(数値型の場合は0)として評価される. -この意味論で$\lambda_{mmm}$ を実際の計算機上で評価しようとすると、各サンプルごとに時刻0から現在時刻までを再計算し、各ステップですべての変数環境を保存する必要がある。しかし実際には、 $delay$ と $feed$ が使用する内部メモリ空間を考慮した仮想マシンが定義され、項は実行前にこのマシン用の命令にコンパイルされる。 +この意味論で$\lambda_{mmm}$ を実際の計算機上で評価しようとすると,各サンプルごとに時刻0から現在時刻までを再計算し,各ステップですべての変数環境を保存する必要がある.しかし実際には, $delay$ と $feed$ が使用する内部メモリ空間を考慮した仮想マシンが定義され,項は実行前にこのマシン用の命令にコンパイルされる. \section{VMおよび命令セットの設計} \label{sec:vm} -$\lambda_{mmm}$ を実行するための仮想マシン(VM)モデルとその命令セットは、 Luaバージョン5のVM\cite{ierusalimschy2005}に基づく。 +$\lambda_{mmm}$ を実行するための仮想マシン(VM)モデルとその命令セットは, Luaバージョン5のVM\cite{ierusalimschy2005}に基づく. -ラムダ計算をベースとした計算モデルを実行する際の重要な課題は、クロージャと呼ばれるデータ構造を扱うことである。クロージャは、入れ子になった関数を定義した場所での変数環境をキャプチャし、外部関数のコンテキストにある変数を参照できるようにする。例えばクロージャを内側の関数の定義とそこで使われる変数と値の辞書との対として定義すれば、コンパイラ(もしくはインタプリタ)の実装は簡単だが、実行時の性能は制限される。 +ラムダ計算をベースとした計算モデルを実行する際の重要な課題は,クロージャと呼ばれるデータ構造を扱うことである.クロージャは,入れ子になった関数を定義した場所での変数環境をキャプチャし,外部関数のコンテキストにある変数を参照できるようにする.例えばクロージャを内側の関数の定義とそこで使われる変数と値の辞書との対として定義すれば,コンパイラ(もしくはインタプリタ)の実装は簡単だが,実行時の性能は制限される. -逆に、クロージャ変換(ラムダ・リフティング)と呼ばれる、内部関数が参照するすべての外部変数を解析し、内部関数の隠し引数として追加するような処理を使えば、実行時のパフォーマンスを向上できる。しかし、コンパイラの実装においてこの変換は比較的複雑になる。 +逆に,クロージャ変換(ラムダ・リフティング)と呼ばれる,内部関数が参照するすべての外部変数を解析し,内部関数の隠し引数として追加するような処理を使えば,実行時のパフォーマンスを向上できる.しかし,コンパイラの実装においてこの変換は比較的複雑になる. -LuaのVMは、2つの方法の中間的なアプローチである上位値(\textit{upvalue})という概念を採用しており、命令 \texttt{GETUPVALUE} と \texttt{SETUPVALUE} を追加することで、実行時に外部変数を動的に参照できるようになっている。上位値を使ったコンパイラとVMの実装は、クロージャの完全な変換よりも単純でありながら、大幅な性能低下を避けることができる。このアプローチでは、クロージャが元の関数の文脈からエスケープしない限り、上位値はヒープメモリではなくコールスタックへの間接的参照で読み取られる\cite{nystrom2021}。さらに、上位値の利用は他のプログラミング言語との相互運用性を促進する。例えばLua用の外部ライブラリをC言語で実装する場合、プログラマはC API経由でランタイムのコールスタック上の値だけでなく、Luaランタイムの上位値にアクセスすることもできる。 +LuaのVMは,2つの方法の中間的なアプローチである上位値(\textit{upvalue})という概念を採用しており,命令 \texttt{GETUPVALUE} と \texttt{SETUPVALUE} を追加することで,実行時に外部変数を動的に参照できるようになっている.上位値を使ったコンパイラとVMの実装は,クロージャの完全な変換よりも単純でありながら,大幅な性能低下を避けることができる.このアプローチでは,クロージャが元の関数の文脈からエスケープしない限り,上位値はヒープメモリではなくコールスタックへの間接的参照で読み取られる\cite{nystrom2021}.さらに,上位値の利用は他のプログラミング言語との相互運用性を促進する.例えばLua用の外部ライブラリをC言語で実装する場合,プログラマはC API経由でランタイムのコールスタック上の値だけでなく,Luaランタイムの上位値にアクセスすることもできる. \subsection{命令セット} \label{sec:instruction} -LuaのVM命令と $\lambda_{mmm}$ のVM命令は以下の点で異なる。 +LuaのVM命令と $\lambda_{mmm}$ のVM命令は以下の点で異なる. \begin{enumerate} -\item mimiumはLuaと異なり静的型付け言語であるため、基本的な算術演算の命令が型ごとに用意されている。 -\item 静的型付けと状態付き高階関数を管理するために、呼び出し操作が通常の関数呼び出しとクロージャ呼び出しに分割されている(詳細は\ref{sec:vmstructure}を参照)。 -\item if文のような条件式にはLua VMでは専用の \texttt{TEST} 命令が採用されているが、mimiumでは\texttt{JMP} と \texttt{JMPIFNEG} の2つの命令を組み合わせて実装されている。 -\item forループに関連する命令、オブジェクト指向プログラミングで使用される\texttt{SELF}命令、変数へのメタデータ参照に関連する\texttt{TABLE}命令は不要なため、mimiumでは省略している。 -\item リスト、タプル、配列のようなデータ構造の実装は、本稿で示す $\lambda_{mmm}$ の記述の範囲外であるため除外している\footnote{実際のコンパイラでは、タプルのようなデータ型は、型に応じてアドレスのオフセットを事前に計算し、単純な型に対する命令として分解されているため、ランタイム上でタプル用の特別な命令は持っていない。}。 +\item mimiumはLuaと異なり静的型付け言語であるため,基本的な算術演算の命令が型ごとに用意されている. +\item 静的型付けと状態付き高階関数を管理するために,呼び出し操作が通常の関数呼び出しとクロージャ呼び出しに分割されている(詳細は\ref{sec:vmstructure}を参照). +\item if文のような条件式にはLua VMでは専用の \texttt{TEST} 命令が採用されているが,mimiumでは\texttt{JMP} と \texttt{JMPIFNEG} の2つの命令を組み合わせて実装されている. +\item forループに関連する命令,オブジェクト指向プログラミングで使用される\texttt{SELF}命令,変数へのメタデータ参照に関連する\texttt{TABLE}命令は不要なため,mimiumでは省略している. +\item リスト,タプル,配列のようなデータ構造の実装は,本稿で示す $\lambda_{mmm}$ の記述の範囲外であるため除外している\footnote{実際のコンパイラでは,タプルのようなデータ型は,型に応じてアドレスのオフセットを事前に計算し,単純な型に対する命令として分解されているため,ランタイム上でタプル用の特別な命令は持っていない.}. \end{enumerate} -$\lambda_{mmm}$ のVMは、Lua VM(バージョン5以降)と同様にレジスタマシンとして動作する。ただし、命令内でのレジスタ番号は物理レジスタを指すわけではなく、単に、VM実行中のベースポインタに対するコールスタック上のオフセットを指す。またほとんどの命令の最初のオペランドは、演算結果が格納されるレジスタ番号を指定する。 +$\lambda_{mmm}$ のVMは,Lua VM(バージョン5以降)と同様にレジスタマシンとして動作する.ただし,命令内でのレジスタ番号は物理レジスタを指すわけではなく,単に,VM実行中のベースポインタに対するコールスタック上のオフセットを指す.またほとんどの命令の最初のオペランドは,演算結果が格納されるレジスタ番号を指定する. \input{instructions.tex} -命令の一覧を図\ref{fig:instruction}に示す(基本的な算術演算などは省略した)。命令の表記は、Lua VMのドキュメントに概説されている形式(\cite[p.13]{ierusalimschy2005})に従った。各命令は32ビットの整数として表現される\footnote{命令の種類に8ビットを割り当てているのは後の拡張のためでもあるが、実装の際Rustのタグ付き共用体(\texttt{enum})として実装するのが簡単なためという理由も大きい。}。図\ref{fig:instruction}の擬似コードでは左から順に8ビットずつ、命令の種類、最大3つのオペランドのリストが並ぶ。3つのオペランドがそれぞれ符号なし8ビット整数として使用される場合、\texttt{A B C}のように表現される。オペランドが符号付きの整数として使用される場合は、接頭辞 \texttt{s} が付く。複数のオペランドフィールドを組み合わせて16ビット、24ビットの値にする場合、接尾辞 \texttt{x}、\texttt{xx} が付加される。例えば、\texttt{B} と \texttt{C} を結合して符号付き 16 ビット値として扱う場合、\texttt{sBx} と表現される。 +命令の一覧を図\ref{fig:instruction}に示す(基本的な算術演算などは省略した).命令の表記は,Lua VMのドキュメントに概説されている形式(\cite[p.13]{ierusalimschy2005})に従った.各命令は32ビットの整数として表現される\footnote{命令の種類に8ビットを割り当てているのは後の拡張のためでもあるが,実装の際Rustのタグ付き共用体(\texttt{enum})として実装するのが簡単なためという理由も大きい.}.図\ref{fig:instruction}の擬似コードでは左から順に8ビットずつ,命令の種類,最大3つのオペランドのリストが並ぶ.3つのオペランドがそれぞれ符号なし8ビット整数として使用される場合,\texttt{A B C}のように表現される.オペランドが符号付きの整数として使用される場合は,接頭辞 \texttt{s} が付く.複数のオペランドフィールドを組み合わせて16ビット,24ビットの値にする場合,接尾辞 \texttt{x},\texttt{xx} が付加される.例えば,\texttt{B} と \texttt{C} を結合して符号付き 16 ビット値として扱う場合,\texttt{sBx} と表現される. -擬似コードでは、\texttt{R(A)} はコールスタック上の現在の関数のベースポインタ + \texttt{A}の位置にあるデータを示す。\texttt{K(A)}はコンパイル済みプログラムの静的変数セクションの \texttt{A} 番目のエントリを指し、\texttt{U(A)}は現在の関数の \texttt{A} 番目のupvalueにアクセスする。 +擬似コードでは,\texttt{R(A)} はコールスタック上の現在の関数のベースポインタ + \texttt{A}の位置にあるデータを示す.\texttt{K(A)}はコンパイル済みプログラムの静的変数セクションの \texttt{A} 番目のエントリを指し,\texttt{U(A)}は現在の関数の \texttt{A} 番目のupvalueにアクセスする. -Luaのupvalue演算に加えて、$delay$ と $feed$ 式のコンパイルを処理するために、 \texttt{GETSTATE}、\texttt{SETSTATE}、\texttt{SHIFTSTATE}、\texttt{DELAY}の4つの新しい演算が導入されている。 +Luaのupvalue演算に加えて,$delay$ と $feed$ 式のコンパイルを処理するために, \texttt{GETSTATE},\texttt{SETSTATE},\texttt{SHIFTSTATE},\texttt{DELAY}の4つの新しい演算が導入されている. \subsection{VMの構造} \label{sec:vmstructure} \begin{figure*}[t] \centerline{\includegraphics[width=\hsize]{lambdammm_vm_structure.pdf}} - \caption{\label{fig:vmstructure}{$\lambda_{mmm}$を実行するための仮想機械、プログラム、またインスタンス化されたクロージャの概要を表した図。}} + \caption{\label{fig:vmstructure}{$\lambda_{mmm}$を実行するための仮想機械,プログラム,またインスタンス化されたクロージャの概要を表した図.}} \end{figure*} -図\ref{fig:vmstructure}に、 $\lambda_{mmm}$ のVM、プログラム、および実行中にインスタンス化されたクロージャの関係を示す。通常のコールスタックに加えて、VMは、フィードバックと遅延のための内部状態データを管理する専用の記憶領域(単純な1次元の配列、以下内部状態ストレージ)および複数の状態ストレージを切り替えるための状態スタック(後述)を持つ。 +図\ref{fig:vmstructure}に, $\lambda_{mmm}$ のVM,プログラム,および実行中にインスタンス化されたクロージャの関係を示す.通常のコールスタックに加えて,VMは,フィードバックと遅延のための内部状態データを管理する専用の記憶領域(単純な1次元の配列,以下内部状態ストレージ)および複数の状態ストレージを切り替えるための状態スタック(後述)を持つ. -この内部状態ストレージには、\texttt{GETSTATE}命令と\texttt{SETSTATE}命令で内部ステートデータを取得する位置を示すポインタが付随している。これらの位置は、\texttt{SHIFTSTATE}命令を使用して前方または後方に移動される。状態記憶メモリ内の実際のデータレイアウトは、コンパイル時に \texttt{self}、\texttt{delay}、およびその他の状態関数を含む関数呼び出しを解析して静的に決定される。\texttt{DELAY}操作は2つの入力として\texttt{B}は入力される信号、\texttt{C}は遅延時間を受け取る。 +この内部状態ストレージには,\texttt{GETSTATE}命令と\texttt{SETSTATE}命令で内部ステートデータを取得する位置を示すポインタが付随している.これらの位置は,\texttt{SHIFTSTATE}命令を使用して前方または後方に移動される.状態記憶メモリ内の実際のデータレイアウトは,コンパイル時に \texttt{self},\texttt{delay},およびその他の状態関数を含む関数呼び出しを解析して静的に決定される.\texttt{DELAY}操作は2つの入力として\texttt{B}は入力される信号,\texttt{C}は遅延時間を受け取る. -しかし、高階関数--別の関数を引数として受け取ったり、1つの関数を返したりする関数--では、渡された関数の内部状態ストレージのメモリレイアウトはコンパイル時には不明である。そのため、インスタンス化された各クロージャには、VMインスタンスが保持するグローバルな内部状態ストレージとは別の内部状態ストレージが割り当てられる。また、VMはインスタンス化されたクロージャの内部状態ストレージのポインタを追跡するために、コールスタックとは別に状態ストレージポインタを記録するスタック(以下、\textbf{状態スタック}と呼ぶ)を使用する。\texttt{CALLCLS}操作が実行されるたびに、VMはクロージャの内部状態ストレージのポインタを状態スタックにプッシュする。クロージャの呼び出しが完了すると、VMは内部状態ストレージのポインタを状態スタックからポップする。 +しかし,高階関数--別の関数を引数として受け取ったり,1つの関数を返したりする関数--では,渡された関数の内部状態ストレージのメモリレイアウトはコンパイル時には不明である.そのため,インスタンス化された各クロージャには,VMインスタンスが保持するグローバルな内部状態ストレージとは別の内部状態ストレージが割り当てられる.また,VMはインスタンス化されたクロージャの内部状態ストレージのポインタを追跡するために,コールスタックとは別に状態ストレージポインタを記録するスタック(以下,\textbf{状態スタック}と呼ぶ)を使用する.\texttt{CALLCLS}操作が実行されるたびに,VMはクロージャの内部状態ストレージのポインタを状態スタックにプッシュする.クロージャの呼び出しが完了すると,VMは内部状態ストレージのポインタを状態スタックからポップする. -インスタンス化されたクロージャは、それ自身の上位値(upvalue)用の記憶領域も保持する。クロージャが親関数のコンテキストにいる(「オープンなクロージャ」とここでは呼ぶ)限り、その上位値は、現在のスタックのベースポインタから負のオフセットを保持する。このオフセットはコンパイル時に決定され、プログラム内の関数のプロトタイプに格納される。さらに、上位値はローカル変数だけでなく、親関数の上位値を参照することもある(この状況は少なくとも3つの関数が入れ子になっている場合に発生する)。つまり、値が親関数のローカルなスタック変数か、さらなる上位値かを示すタグと、それに対応するインデックス(スタックの負のオフセットまたは親関数の上位値インデックス)である。 +インスタンス化されたクロージャは,それ自身の上位値(upvalue)用の記憶領域も保持する.クロージャが親関数のコンテキストにいる(「オープンなクロージャ」とここでは呼ぶ)限り,その上位値は,現在のスタックのベースポインタから負のオフセットを保持する.このオフセットはコンパイル時に決定され,プログラム内の関数のプロトタイプに格納される.さらに,上位値はローカル変数だけでなく,親関数の上位値を参照することもある(この状況は少なくとも3つの関数が入れ子になっている場合に発生する).つまり,値が親関数のローカルなスタック変数か,さらなる上位値かを示すタグと,それに対応するインデックス(スタックの負のオフセットまたは親関数の上位値インデックス)である. -例えば、プログラム中のupvalueインデックスが\texttt{\[upvalue(1), local(3)\]}と指定されている場合を考える。この場合、\texttt{GETUPVALUE 6 1} という命令は、(\texttt{upvalue(1)}によって参照される)上位値一覧のインデックス \texttt{3} にある値を、ベースポインタに対して相対的に \texttt{R(-3)} から取得し、その結果を \texttt{R(6)} に格納することを示す。 +例えば,プログラム中のupvalueインデックスが\texttt{\[upvalue(1), local(3)\]}と指定されている場合を考える.この場合,\texttt{GETUPVALUE 6 1} という命令は,(\texttt{upvalue(1)}によって参照される)上位値一覧のインデックス \texttt{3} にある値を,ベースポインタに対して相対的に \texttt{R(-3)} から取得し,その結果を \texttt{R(6)} に格納することを示す. -クロージャが \texttt{RETURN} 命令によって元の関数コンテキストから抜け出すとき、事前に挿入された \texttt{CLOSE} 命令によってアクティブな上位値はスタックからヒープメモリに退避させられる。これらの上位値は、特にネストしたクロージャを含む場合には、複数の場所から参照される可能性がある。したがって、これらの上位値が使用されなくなった時にはガベージコレクションでメモリを解放する必要がある\footnote{現在のmimiumの実装では参照カウント方式でメモリを開放している。}。 +クロージャが \texttt{RETURN} 命令によって元の関数コンテキストから抜け出すとき,事前に挿入された \texttt{CLOSE} 命令によってアクティブな上位値はスタックからヒープメモリに退避させられる.これらの上位値は,特にネストしたクロージャを含む場合には,複数の場所から参照される可能性がある.したがって,これらの上位値が使用されなくなった時にはガベージコレクションでメモリを解放する必要がある\footnote{現在のmimiumの実装では参照カウント方式でメモリを開放している.}. -$\lambda_{mmm}$ は値呼びラムダ計算であり、再代入が存在しないので、 \texttt{SETUPVALUE}命令は省略される。再代入が許可されているような意味論を追加する場合、オープンなものも含めupvalueは共有メモリセルとして実装される必要がある\footnote{現在のmimiumの実装では、初期実装からの使用を引き継いで\texttt{let}で宣言された変数に対する再代入が許されているため、実際には\texttt{SETUPVALUE}も使用されているが、本稿では議論を単純化するため省略した。関数に値を渡す際に値はコピーされるため、関数の外側の状態を変更する方法はクロージャがキャプチャした変数への再代入のみとなっている。}。 +$\lambda_{mmm}$ は値呼びラムダ計算であり,再代入が存在しないので, \texttt{SETUPVALUE}命令は省略される.再代入が許可されているような意味論を追加する場合,オープンなものも含めupvalueは共有メモリセルとして実装される必要がある\footnote{現在のmimiumの実装では,初期実装からの使用を引き継いで\texttt{let}で宣言された変数に対する再代入が許されているため,実際には\texttt{SETUPVALUE}も使用されているが,本稿では議論を単純化するため省略した.関数に値を渡す際に値はコピーされるため,関数の外側の状態を変更する方法はクロージャがキャプチャした変数への再代入のみとなっている.}. \subsection{VM命令へのコンパイル例} @@ -197,12 +196,12 @@ fn onepole(x,g) state_size:1 SETSTATE 2 // store to self RETURN 3 1 \end{verbatim} -\caption{\label{fig:bytecode_onepole}{図\ref{fig:onepole-code}の単極フィルターのコードをVM命令にコンパイルした結果を擬似コードで表した例。}} +\caption{\label{fig:bytecode_onepole}{図\ref{fig:onepole-code}の単極フィルターのコードをVM命令にコンパイルした結果を擬似コードで表した例.}} \end{figure} -図\ref{fig:bytecode_onepole}は図\ref{fig:onepole-code}で示したmimiumでの単極フィルターのコードをVMの命令列にコンパイルした結果の例である。\texttt{self}が参照されると、\texttt{GETSTATE}命令で値を取得し、\texttt{SETSTATE}命令で戻り値を格納して内部状態を更新してから、\texttt{RETURN}命令で値を返す。この場合、実際の戻り値は2番目の \texttt{GETSTATE} 命令を使用して取得され、時刻0の時にはデフォルトの値(数値型なら0)を返すようになる。 +図\ref{fig:bytecode_onepole}は図\ref{fig:onepole-code}で示したmimiumでの単極フィルターのコードをVMの命令列にコンパイルした結果の例である.\texttt{self}が参照されると,\texttt{GETSTATE}命令で値を取得し,\texttt{SETSTATE}命令で戻り値を格納して内部状態を更新してから,\texttt{RETURN}命令で値を返す.この場合,実際の戻り値は2番目の \texttt{GETSTATE} 命令を使用して取得され,時刻0の時にはデフォルトの値(数値型なら0)を返すようになる. -例えば、$\lambda_{mmm}$ でサンプル単位の時刻カウンタを $feed x. x+1$ と書いたとき、時刻 = 0 のときの戻り値を 0 にするか 1 にするかは、コンパイラの設計次第である。もしコンパイラが時刻=0で1を返すような設計にする場合、2番目の\texttt{GETSTATE}命令は省略でき、\texttt{RETURN}命令の値は\texttt{R(2)}となる(これは厳密には図\ref{fig:semantics}のE-FEEDの意味論に反する)。 +例えば,$\lambda_{mmm}$ でサンプル単位の時刻カウンタを $feed x. x+1$ と書いたとき,時刻 = 0 のときの戻り値を 0 にするか 1 にするかは,コンパイラの設計次第である.もしコンパイラが時刻=0で1を返すような設計にする場合,2番目の\texttt{GETSTATE}命令は省略でき,\texttt{RETURN}命令の値は\texttt{R(2)}となる(これは厳密には図\ref{fig:semantics}のE-FEEDの意味論に反する). \begin{figure}[ht] \centering @@ -218,7 +217,7 @@ fn dsp(x){ twodelay(x,400)+twodelay(x,800) } \end{verbatim} -\caption{\label{fig:fbdelay_code}{mimiumにおける、\texttt{self}とtexttt{delay}を組み合わせたフィードバックディレイの関数を複数回使用するコードの例。}} +\caption{\label{fig:fbdelay_code}{mimiumにおける,\texttt{self}とtexttt{delay}を組み合わせたフィードバックディレイの関数を複数回使用するコードの例.}} \end{figure} \begin{figure}[ht] @@ -269,19 +268,19 @@ fn dsp (x) SHIFTSTATE -2008 RETURN 1 1 \end{verbatim} -\caption{\label{fig:bytecodes_fbdelay}{図\ref{fig:fbdelay_code}のフィードバックディレイの例をVM命令列にコンパイルした例。}} +\caption{\label{fig:bytecodes_fbdelay}{図\ref{fig:fbdelay_code}のフィードバックディレイの例をVM命令列にコンパイルした例.}} \end{figure} \begin{figure}[ht] \centerline{\includegraphics[width=0.7\hsize]{fbdelay_spos.pdf}} -\caption{\label{fig:fbdelay_spos}{\it 図\ref{fig:bytecodes_fbdelay}の\texttt{twodelay}関数の命令列を実行している間の、内部状態ストレージ上を移動する状態位置ポインタの移動過程を概念的に示した図。}} +\caption{\label{fig:fbdelay_spos}{\it 図\ref{fig:bytecodes_fbdelay}の\texttt{twodelay}関数の命令列を実行している間の,内部状態ストレージ上を移動する状態位置ポインタの移動過程を概念的に示した図.}} \end{figure} -図\ref{fig:fbdelay_code} と 図\ref{fig:bytecodes_fbdelay}に、より複雑なコードの例とそのコンパイル結果のVM命令列の例を示した。このコードでは\texttt{fbdelay}としてフィードバック付きのディレイ関数を定義して、別の関数\texttt{twodelay}では異なる引数を使う2つのフィードバック付きディレイを呼び出す。さらに、 \texttt{dsp} は \texttt{twodelay} 関数を2つ使う。 +図\ref{fig:fbdelay_code} と 図\ref{fig:bytecodes_fbdelay}に,より複雑なコードの例とそのコンパイル結果のVM命令列の例を示した.このコードでは\texttt{fbdelay}としてフィードバック付きのディレイ関数を定義して,別の関数\texttt{twodelay}では異なる引数を使う2つのフィードバック付きディレイを呼び出す.さらに, \texttt{dsp} は \texttt{twodelay} 関数を2つ使う. -この例ではVM命令の中で、\texttt{GETSTATE} 命令で \texttt{self} を参照した後、または別の内部状態付き関数を呼び出した後に、\texttt{SHIFTSTATE} 命令が挿入され、次の(非クロージャ)関数呼び出しに備えて内部状態の読み書き位置を移動している。関数が終了する前には、再び\texttt{SHIFTSTATE}命令を使用して、読み書き位置を現在の関数の実行開始時の状態にリセットしている。図\ref{fig:fbdelay_spos}は、\texttt{twodelay}関数の実行中に\texttt{SHIFTSTATE}命令によって内部状態読み書きポインタがどのように遷移するかを示している。\texttt{SHIFTSTATE}操作の引数はワードサイズ(符号付き24ビットの数値)であり、ディレイのためのワードサイズは読み取り位置インデックス、書き込み位置インデックス、リングバッファの長さの値の3つが付加されるため、最大遅延時間+3となる\footnote{符号付き24bit整数の最大値はサンプリングレート192kHzなら42秒程度である。場合によってはより大きな値を扱う必要があるかもしれないが、その場合は\texttt{SHIFTSTATE}命令を複数回に分割して実行することで対応ができる。}。 +この例ではVM命令の中で,\texttt{GETSTATE} 命令で \texttt{self} を参照した後,または別の内部状態付き関数を呼び出した後に,\texttt{SHIFTSTATE} 命令が挿入され,次の(非クロージャ)関数呼び出しに備えて内部状態の読み書き位置を移動している.関数が終了する前には,再び\texttt{SHIFTSTATE}命令を使用して,読み書き位置を現在の関数の実行開始時の状態にリセットしている.図\ref{fig:fbdelay_spos}は,\texttt{twodelay}関数の実行中に\texttt{SHIFTSTATE}命令によって内部状態読み書きポインタがどのように遷移するかを示している.\texttt{SHIFTSTATE}操作の引数はワードサイズ(符号付き24ビットの数値)であり,ディレイのためのワードサイズは読み取り位置インデックス,書き込み位置インデックス,リングバッファの長さの値の3つが付加されるため,最大遅延時間+3となる\footnote{符号付き24bit整数の最大値はサンプリングレート192kHzなら42秒程度である.場合によってはより大きな値を扱う必要があるかもしれないが,その場合は\texttt{SHIFTSTATE}命令を複数回に分割して実行することで対応ができる.}. -以前のmimiumの実装では、内部状態を関数の呼び出しに応じた木構造として生成していたが、本VMの命令セットでは内部状態の読み書き位置を相対的なオフセットとして表現することで、メモリレイアウトは単純な配列として表現可能になり、各関数はどの関数から呼び出されるのかについて知る必要がなくなる。これはLuaのVMがクロージャ変換で全ての自由変数の参照を静的に解決するのではなく、upvalueとしてコールスタック上の相対的な位置として扱い、部分的にランタイムに上位値の解決を任せることでコンパイラの実装を簡略化しているのと近しいアプローチと言える。 +以前のmimiumの実装では,内部状態を関数の呼び出しに応じた木構造として生成していたが,本VMの命令セットでは内部状態の読み書き位置を相対的なオフセットとして表現することで,メモリレイアウトは単純な配列として表現可能になり,各関数はどの関数から呼び出されるのかについて知る必要がなくなる.これはLuaのVMがクロージャ変換で全ての自由変数の参照を静的に解決するのではなく,upvalueとしてコールスタック上の相対的な位置として扱い,部分的にランタイムに上位値の解決を任せることでコンパイラの実装を簡略化しているのと近しいアプローチと言える. \begin{figure}[ht] \centering @@ -305,13 +304,13 @@ fn dsp(){ myfilter(x,1000) } \end{verbatim} -\caption{\label{fig:filterbank_good}{再帰関数とクロージャを使ってパラメトリックにフィルターを複製するコードの例。}} +\caption{\label{fig:filterbank_good}{再帰関数とクロージャを使ってパラメトリックにフィルターを複製するコードの例.}} \end{figure} -さらに別の例として図\ref{fig:filterbank_good}に高階関数\texttt{filterbank}を使うものを示す。この関数は、別の関数\texttt{filter}(入力と周波数を引数として受け取り結果の数値を返す)を引数として受け取り、\texttt{filter}のインスタンスを\texttt{n}個複製し、それらをの計算結果を足し合わせるような関数を返すものである\footnote{以前のmimiumの仕様\cite{Matsuura2021}では、変数束縛と破壊的代入の構文は同じ(\texttt{x = a})だった。しかし、新しいバージョンの構文では、変数の束縛に\texttt{let}キーワードを使用している。}。 +さらに別の例として図\ref{fig:filterbank_good}に高階関数\texttt{filterbank}を使うものを示す.この関数は,別の関数\texttt{filter}(入力と周波数を引数として受け取り結果の数値を返す)を引数として受け取り,\texttt{filter}のインスタンスを\texttt{n}個複製し,それらをの計算結果を足し合わせるような関数を返すものである\footnote{以前のmimiumの仕様\cite{Matsuura2021}では,変数束縛と破壊的代入の構文は同じ(\texttt{x = a})だった.しかし,新しいバージョンの構文では,変数の束縛に\texttt{let}キーワードを使用している.}. -以前のmimiumのコンパイラは、内部状態のツリー全体をコンパイル時に静的に決定する必要があったため、このような内部状態を引数に取る関数をコンパイルすることができなかった。しかし、$\lambda_{mmm}$ のVMではこれを動的に解決することができる。図\ref{fig:bytecode_filterbank}に、このコードをVM命令にコンパイルした例を示す。\texttt{filterbank}の1行目の再帰呼び出しや、引数として渡された関数や(\texttt{filter}のように)upvalueとして取得した関数の呼び出しは、\texttt{CALL}命令ではなく\texttt{CALLCLS}命令を使って実行される。ここで、\texttt{GETSTATE}命令や \texttt{SETSTATE} 命令はこの関数では使用されない。これは、\texttt{CALLCLS} 命令が実行される際に、使用する内部状態ストレージが動的に切り替わるためである。内部状態を変更する命令は引数として与えられた\verb|filter|を呼び出す際に実行される。 +以前のmimiumのコンパイラは,内部状態のツリー全体をコンパイル時に静的に決定する必要があったため,このような内部状態を引数に取る関数をコンパイルすることができなかった.しかし,$\lambda_{mmm}$ のVMではこれを動的に解決することができる.図\ref{fig:bytecode_filterbank}に,このコードをVM命令にコンパイルした例を示す.\texttt{filterbank}の1行目の再帰呼び出しや,引数として渡された関数や(\texttt{filter}のように)upvalueとして取得した関数の呼び出しは,\texttt{CALL}命令ではなく\texttt{CALLCLS}命令を使って実行される.ここで,\texttt{GETSTATE}命令や \texttt{SETSTATE} 命令はこの関数では使用されない.これは,\texttt{CALLCLS} 命令が実行される際に,使用する内部状態ストレージが動的に切り替わるためである.内部状態を変更する命令は引数として与えられた\verb|filter|を呼び出す際に実行される. \begin{figure}[ht] \centering @@ -360,7 +359,7 @@ fn filterbank(n,filter_factory) CLOSE 4 RETURN 4 1 \end{verbatim} -\caption{\label{fig:bytecode_filterbank}{図\ref{fig:filterbank_good}のフィルタバンクの例をVM命令列にコンパイルした例。}} +\caption{\label{fig:bytecode_filterbank}{図\ref{fig:filterbank_good}のフィルタバンクの例をVM命令列にコンパイルした例.}} \end{figure} \section{議論} @@ -368,11 +367,11 @@ fn filterbank(n,filter_factory) \input{comparison.tex} -\texttt{filterbank}の例で示したように、mimiumではグローバル環境の評価においてフィルタの複製などのパラメトリックな信号処理内容を高階関数として計算し、\texttt{dsp}の中で生成された関数を利用して実際の信号処理を評価している。 +\texttt{filterbank}の例で示したように,mimiumではグローバル環境の評価においてフィルタの複製などのパラメトリックな信号処理内容を高階関数として計算し,\texttt{dsp}の中で生成された関数を利用して実際の信号処理を評価している. -表\ref{tab:comparison}に示すように、既存の言語ではパラメトリックな信号処理内容の生成でFaustでは項書き換えマクロを、Kronosでは型レベルの計算を用い、その実行にはBDAや値レベルの計算を混在させているのに対して、mimiumではグローバル環境と実際の信号処理の実行に同じ値レベルの意味論を用いている。 +表\ref{tab:comparison}に示すように,既存の言語ではパラメトリックな信号処理内容の生成でFaustでは項書き換えマクロを,Kronosでは型レベルの計算を用い,その実行にはBDAや値レベルの計算を混在させているのに対して,mimiumではグローバル環境と実際の信号処理の実行に同じ値レベルの意味論を用いている. -この単一の意味論による体系の利点としては、初心者が言語の体系の理解を簡単にする、また他の汎用言語との実行時の相互運用性を高められる、といったことが考えられる。一方で、意味論が統一されていることによって、 $\lambda_{mmm}$ は普通のラムダ計算で期待される振る舞いから逸脱した挙動を見せる問題がある。 +この単一の意味論による体系の利点としては,初心者が言語の体系の理解を簡単にする,また他の汎用言語との実行時の相互運用性を高められる,といったことが考えられる.一方で,意味論が統一されていることによって, $\lambda_{mmm}$ は普通のラムダ計算で期待される振る舞いから逸脱した挙動を見せる問題がある. \subsection{Let束縛の位置の違いによる挙動の変化} \label{sec:letbinding} @@ -392,20 +391,20 @@ fn dsp(){ filterbank(3,bandpass)(x,1000) } \end{verbatim} -\caption{\label{fig:filterbank_bad}{図\ref{fig:filterbank_good}におけるパラメトリックなフィルター複製のコードの間違った例。}} +\caption{\label{fig:filterbank_bad}{図\ref{fig:filterbank_good}におけるパラメトリックなフィルター複製のコードの間違った例.}} \end{figure} -mimiumでは時間の経過とともに変化する内部状態を持つ関数を使用することで、一般的な関数型プログラミング言語と比較して、高階関数を使用した場合に直感に反する振る舞いを引き起こす。 +mimiumでは時間の経過とともに変化する内部状態を持つ関数を使用することで,一般的な関数型プログラミング言語と比較して,高階関数を使用した場合に直感に反する振る舞いを引き起こす. -図\ref{fig:filterbank_bad}に、図\ref{fig:filterbank_good}の\verb|filterbank|のコード例を少しだけ変更した、間違ったコードの例を示す。図\ref{fig:filterbank_bad}と図\ref{fig:filterbank_good}の主な違いは、\texttt{filterbank}関数内の再帰呼び出しを返却される関数の外側で一度\texttt{let}式で束縛しているか、内側で直接実行しているかである。同様に、\texttt{dsp}関数(mimiumのオーディオドライバから呼び出される)でも、\texttt{filterbank}関数が、グローバル環境でで一度\texttt{let}で束縛されているか、\texttt{dsp}内で直接評価されるかという違いがある。 +図\ref{fig:filterbank_bad}に,図\ref{fig:filterbank_good}の\verb|filterbank|のコード例を少しだけ変更した,間違ったコードの例を示す.図\ref{fig:filterbank_bad}と図\ref{fig:filterbank_good}の主な違いは,\texttt{filterbank}関数内の再帰呼び出しを返却される関数の外側で一度\texttt{let}式で束縛しているか,内側で直接実行しているかである.同様に,\texttt{dsp}関数(mimiumのオーディオドライバから呼び出される)でも,\texttt{filterbank}関数が,グローバル環境でで一度\texttt{let}で束縛されているか,\texttt{dsp}内で直接評価されるかという違いがある. -典型的な関数型プログラミング言語では、関数の破壊的代入を伴わない限り、図\ref{fig:filterbank_good}から図\ref{fig:filterbank_bad}への変換に見られるように、\texttt{let}で束縛された変数をその項にで置き換えた(ベータ簡約)としても、計算処理の内容は変化しない。 +典型的な関数型プログラミング言語では,関数の破壊的代入を伴わない限り,図\ref{fig:filterbank_good}から図\ref{fig:filterbank_bad}への変換に見られるように,\texttt{let}で束縛された変数をその項にで置き換えた(ベータ簡約)としても,計算処理の内容は変化しない. -しかしmimiumでは、評価の段階が グローバル環境の評価(信号処理グラフの具体化)および \texttt{dsp} 関数の繰り返しの実行(内部状態の暗黙の更新を伴う実際の信号処理)の2つに分かれている。 +しかしmimiumでは,評価の段階が グローバル環境の評価(信号処理グラフの具体化)および \texttt{dsp} 関数の繰り返しの実行(内部状態の暗黙の更新を伴う実際の信号処理)の2つに分かれている. -例に挙げたmimiumのコードには破壊的な代入は含まれていないが、図\ref{fig:filterbank_good}では、グローバル環境の評価中に\texttt{filterbank}関数の再帰的な実行が1回だけ発生する。逆に、図\ref{fig:filterbank_bad}では、\texttt{dsp}関数が毎サンプル実行されるたびに再帰関数が実行され、クロージャが生成される。クロージャの内部状態はクロージャのアロケーション時に初期化されるため、図\ref{fig:filterbank_bad}の例では、\texttt{filterbank}が評価された後クロージャの内部状態は毎時刻リセットされてしまうことになる。 +例に挙げたmimiumのコードには破壊的な代入は含まれていないが,図\ref{fig:filterbank_good}では,グローバル環境の評価中に\texttt{filterbank}関数の再帰的な実行が1回だけ発生する.逆に,図\ref{fig:filterbank_bad}では,\texttt{dsp}関数が毎サンプル実行されるたびに再帰関数が実行され,クロージャが生成される.クロージャの内部状態はクロージャのアロケーション時に初期化されるため,図\ref{fig:filterbank_bad}の例では,\texttt{filterbank}が評価された後クロージャの内部状態は毎時刻リセットされてしまうことになる. -この問題に対処する方法として、グローバル環境評価(ステージ0)と実際の信号処理(ステージ1)のどちらで項を使うべきかを示す区別を型システムに導入することが考えられる。これは多段階計算\cite{Taha1997}を用いて実現できる。コード\ref{fig:filterbank_multi}は、BER MetaOCamlの構文を使った\texttt{filterbank}のコード例である。\verb|..|は次のステージで使われるプログラムを生成し、\verb|~term|は前のステージで評価された項を埋め込むことを意味する\cite{kiselyov2014}。\texttt{filterbank}関数はステージ0で評価され、\texttt{dsp}関数の中で\verb|~|で評価結果を埋め込んでいる。FaustやKronosとは対照的に、この多段階計算の記述は信号処理グラフの生成と信号処理の実行の両方で統一された意味論を保持している。 +この問題に対処する方法として,グローバル環境評価(ステージ0)と実際の信号処理(ステージ1)のどちらで項を使うべきかを示す区別を型システムに導入することが考えられる.これは多段階計算\cite{Taha1997}を用いて実現できる.コード\ref{fig:filterbank_multi}は,BER MetaOCamlの構文を使った\texttt{filterbank}のコード例である.\verb|..|は次のステージで使われるプログラムを生成し,\verb|~term|は前のステージで評価された項を埋め込むことを意味する\cite{kiselyov2014}.\texttt{filterbank}関数はステージ0で評価され,\texttt{dsp}関数の中で\verb|~|で評価結果を埋め込んでいる.FaustやKronosとは対照的に,この多段階計算の記述は信号処理グラフの生成と信号処理の実行の両方で統一された意味論を保持している. \begin{figure}[ht] \centering @@ -424,16 +423,16 @@ fn dsp(){ ~filterbank(3,..)(x,1000) } \end{verbatim} -\caption{\label{fig:filterbank_multi}{mimiumの将来的な仕様における、多段階計算を使用した\texttt{filterbank}関数の例。}} +\caption{\label{fig:filterbank_multi}{mimiumの将来的な仕様における,多段階計算を使用した\texttt{filterbank}関数の例.}} \end{figure} \subsection{if式による部分的な内部状態更新の意味} -現在の意味論では、if式のthen節、else節の中で関数あるいはクロージャの呼び出しを行った場合、部分的に内部状態が更新されたりされなかったりする。つまり、先程と同様、内部状態を更新した結果を\verb|let|で束縛してからif式の評価中にその結果を利用するのと、if式の中で直接内部状態を更新するのとでは計算結果が異なる。この部分的な内部状態の更新は、例えば高速に条件をスイッチした時に、実質的なサンプリングレートが変化することになるため、意図しない処理結果を招く可能性がある。 +現在の意味論では,if式のthen節,else節の中で関数あるいはクロージャの呼び出しを行った場合,部分的に内部状態が更新されたりされなかったりする.つまり,先程と同様,内部状態を更新した結果を\verb|let|で束縛してからif式の評価中にその結果を利用するのと,if式の中で直接内部状態を更新するのとでは計算結果が異なる.この部分的な内部状態の更新は,例えば高速に条件をスイッチした時に,実質的なサンプリングレートが変化することになるため,意図しない処理結果を招く可能性がある. -Faustは条件分岐先の両方をあらかじめ評価してから条件に合わない分岐先の結果に0を掛けて足し合わせる、という意味論を持っているのでこのサンプリングレートの変化は起きない一方、条件分岐先をすべて評価するため最終的には使用されない信号処理も必ず評価されるため、計算リソースの無駄な使用が発生するという新たな問題が発生する。Faustでは近年、新たな言語プリミティブとして、部分的な内部状態更新を明示的に使用する\verb|ondemand|プリミティブの導入が検討されている\cite{yannorlarey2023}。これは、リソースの有効活用という先程の問題の解決に加えて、ある一定間隔でバッファした入力をまとめて計算するマルチレート(例えばFFT)処理というこれまでのFaustの仕様で難しかった処理を実現可能にもしている。 +Faustは条件分岐先の両方をあらかじめ評価してから条件に合わない分岐先の結果に0を掛けて足し合わせる,という意味論を持っているのでこのサンプリングレートの変化は起きない一方,条件分岐先をすべて評価するため最終的には使用されない信号処理も必ず評価されるため,計算リソースの無駄な使用が発生するという新たな問題が発生する.Faustでは近年,新たな言語プリミティブとして,部分的な内部状態更新を明示的に使用する\verb|ondemand|プリミティブの導入が検討されている\cite{yannorlarey2023}.これは,リソースの有効活用という先程の問題の解決に加えて,ある一定間隔でバッファした入力をまとめて計算するマルチレート(例えばFFT)処理というこれまでのFaustの仕様で難しかった処理を実現可能にもしている. -mimiumもこれに倣って、部分的な内部更新を伴うべき時とそうでない時を区別できるような構文を導入することで、ユーザーの混乱を少なくしつつ信号処理の適用範囲を広げることが期待できる。 +mimiumもこれに倣って,部分的な内部更新を伴うべき時とそうでない時を区別できるような構文を導入することで,ユーザーの混乱を少なくしつつ信号処理の適用範囲を広げることが期待できる. \subsection{外部言語で定義される状態付き関数の呼び出し} \label{sec:ffi} @@ -452,28 +451,28 @@ fn dsp(){ \caption{\label{fig:sampler}{mimiumでの外部関数\texttt{gen\_sampler\_mono}を使用したオーディオファイルの再生}} \end{figure} -クロージャのデータは、図\ref{fig:vmstructure}で示したように、関数と内部状態の組み合わせで表現されている。\verb|filterbank|の例で内部状態に対して特別な操作を必要としていないということは、mimiumからホスト言語として定義された発振器やフィルターなどのUGenを、通常の関数と同じように呼び出すことができることを意味する。また外部UGenのパラメトリックに複製・合成することも可能である。この機能はFaustでは実装が難しいが、 $\lambda_{mmm}$ の設計では簡単に実現できる。 +クロージャのデータは,図\ref{fig:vmstructure}で示したように,関数と内部状態の組み合わせで表現されている.\verb|filterbank|の例で内部状態に対して特別な操作を必要としていないということは,mimiumからホスト言語として定義された発振器やフィルターなどのUGenを,通常の関数と同じように呼び出すことができることを意味する.また外部UGenのパラメトリックに複製・合成することも可能である.この機能はFaustでは実装が難しいが, $\lambda_{mmm}$ の設計では簡単に実現できる. -実際、現在のmimiumでは、オーディオファイルのデコードなどmimium言語単体では現状難しい低レベルなバイト操作などを、Rustのライブラリ(Symphoniaクレート)を利用した外部モジュールとして実装している。図\ref{fig:sampler}はmimium側からの外部関数呼び出しの例である。\verb|gen_sampler_mono|関数は、ファイルパスを引数として実行すると新たな関数(クロージャのインスタンス)を返す。このインスタンスを数値型の引数を与えて呼び出すと、単に読み込んだオーディオデータの配列に引数をインデックスとして値を取得する。そのため、1サンプルごとに1増加する\verb|counter|関数と組み合わせると、オーディオファイルを1.0倍の速度で1度だけ再生することになる。 +実際,現在のmimiumでは,オーディオファイルのデコードなどmimium言語単体では現状難しい低レベルなバイト操作などを,Rustのライブラリ(Symphoniaクレート)を利用した外部モジュールとして実装している.図\ref{fig:sampler}はmimium側からの外部関数呼び出しの例である.\verb|gen_sampler_mono|関数は,ファイルパスを引数として実行すると新たな関数(クロージャのインスタンス)を返す.このインスタンスを数値型の引数を与えて呼び出すと,単に読み込んだオーディオデータの配列に引数をインデックスとして値を取得する.そのため,1サンプルごとに1増加する\verb|counter|関数と組み合わせると,オーディオファイルを1.0倍の速度で1度だけ再生することになる. -Rust側では関数名(\verb|gen_sampler_mono|)、その関数の型、Rustで実装された関数もしくはクロージャへの参照の3つ組をあらかじめ定義しておき、それをコンパイラとランタイムの初期化時にそれぞれ読み込ませることで呼び出すことが可能になる。実行用に、VMの命令には外部関数呼び出し用の\verb|CALLEXTFUN|が追加で実装されている。 +Rust側では関数名(\verb|gen_sampler_mono|),その関数の型,Rustで実装された関数もしくはクロージャへの参照の3つ組をあらかじめ定義しておき,それをコンパイラとランタイムの初期化時にそれぞれ読み込ませることで呼び出すことが可能になる.実行用に,VMの命令には外部関数呼び出し用の\verb|CALLEXTFUN|が追加で実装されている. -ただし、現在のところmimiumはサンプル単位の処理を基本にしており、バッファ単位の信号を扱うことができない。ほとんどのネイティブで実装されたUGenはバッファ単位でデータを処理するため、今のところ、既存の外部UGenがそのまま使用可能なケースは少ない。この点は先述した部分的な内部状態の更新によるマルチレート処理の導入と共に解決し得る。 +ただし,現在のところmimiumはサンプル単位の処理を基本にしており,バッファ単位の信号を扱うことができない.ほとんどのネイティブで実装されたUGenはバッファ単位でデータを処理するため,今のところ,既存の外部UGenがそのまま使用可能なケースは少ない.この点は先述した部分的な内部状態の更新によるマルチレート処理の導入と共に解決し得る. \section{結論} \label{sec:conclusion} -本稿では、音楽・信号処理用プログラミング言語の中間表現 $\lambda_{mmm}$ と、それを実行するための仮想マシン・命令セットを提案した。 $\lambda_{mmm}$ は、信号処理グラフの生成と、その実際の処理を統一された構文と意味論で記述可能にした。また信号処理のプロセスを内部状態を伴う関数の適用として表現することで、外部で定義されたUGenの利用を単なる高階関数の呼び出しとして可能にしているように、既存の汎用言語との相互運用性も高めている。 +本稿では,音楽・信号処理用プログラミング言語の中間表現 $\lambda_{mmm}$ と,それを実行するための仮想マシン・命令セットを提案した. $\lambda_{mmm}$ は,信号処理グラフの生成と,その実際の処理を統一された構文と意味論で記述可能にした.また信号処理のプロセスを内部状態を伴う関数の適用として表現することで,外部で定義されたUGenの利用を単なる高階関数の呼び出しとして可能にしているように,既存の汎用言語との相互運用性も高めている. -一方で、関数がグローバル環境で評価されるかDSPの繰り返し実行で評価されるかの判別や、if文による内部状態更新をどの位置で評価するかなどはユーザーの責任となり、初学者にはその区別が難しいという欠点がある。 +一方で,関数がグローバル環境で評価されるかDSPの繰り返し実行で評価されるかの判別や,if文による内部状態更新をどの位置で評価するかなどはユーザーの責任となり,初学者にはその区別が難しいという欠点がある. -また本論文では、mimiumでのコードの例とそれに対応するVM命令列擬似コードの例を示すことでコンパイル過程を説明したのみである。現在反映されてない合成型の形式化や、今後の多段階計算の導入と並行してコンパイル過程を表示的意味論として形式化を進める必要がある。 +また本論文では,mimiumでのコードの例とそれに対応するVM命令列擬似コードの例を示すことでコンパイル過程を説明したのみである.現在反映されてない合成型の形式化や,今後の多段階計算の導入と並行してコンパイル過程を表示的意味論として形式化を進める必要がある. -この研究が、コンピュータ上での音・音楽のより一般的な表現に貢献し、音楽のための言語理論とプログラミング言語理論のより広範な分野との間のより深いつながりを促進することを期待する。\\ +この研究が,コンピュータ上での音・音楽のより一般的な表現に貢献し,音楽のための言語理論とプログラミング言語理論のより広範な分野との間のより深いつながりを促進することを期待する.\\ \begin{acknowledgment} -mimiumの初期開発は、2019年度未踏IT人材発掘・育成事業の支援の元行われたほか、オープンソースソフトウェアとして様々な方からコントリビューションを受けている。また本研究は、日本学術振興会科研費若手研究『音楽と工学の相互批評的実践としての「音楽土木工学」の研究 』(23K12059)の助成を受けている。ここに感謝の意を表する。 +mimiumの初期開発は,2019年度未踏IT人材発掘・育成事業の支援の元行われたほか,オープンソースソフトウェアとして様々な方からコントリビューションを受けている.また本研究は,日本学術振興会科研費若手研究『音楽と工学の相互批評的実践としての「音楽土木工学」の研究 』(23K12059)の助成を受けている.ここに感謝の意を表する. \end{acknowledgment} % BibTeX を使用する場合 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%