converted comma and period

This commit is contained in:
2024-11-29 09:50:58 +00:00
parent 81afb843aa
commit cdcab4baa1

View File

@@ -19,7 +19,7 @@
\author{松浦 知也}{Matsuura Tomoya}{TOMOYA}[me@matsuuratomoya.com] \author{松浦 知也}{Matsuura Tomoya}{TOMOYA}[me@matsuuratomoya.com]
\begin{abstract} \begin{abstract}
本発表では筆者の開発する音楽のためのプログラミング言語mimiumの理論的基盤について音楽向け言語の歴史的文脈に沿って解説するmimiumはリアルタイム信号処理を想定した音楽用のDSLだが既存の多くの言語とは異なりUnit Generatorのような特定の音楽表現に基づくプリミティブを用意しない代わりに値呼びの単純型付きラムダ計算に遅延とフィードバックという2要素をプリミティブとして追加した中間表現を定義することでその言語上でほとんどの信号処理アルゴリズムを関数のパイプとして表現できるまたLuaを参考にしたVMを設計したことで内部状態を持つ信号処理関数の高階関数を用いた複製やホスト環境の埋めこみを容易に可能にしている音楽というドメインに特化しながらも汎用性を失わない言語の意義について議論する 本発表では筆者の開発する音楽のためのプログラミング言語mimiumの理論的基盤について音楽向け言語の歴史的文脈に沿って解説するmimiumはリアルタイム信号処理を想定した音楽用のDSLだが既存の多くの言語とは異なりUnit Generatorのような特定の音楽表現に基づくプリミティブを用意しない代わりに値呼びの単純型付きラムダ計算に遅延とフィードバックという2要素をプリミティブとして追加した中間表現を定義することでその言語上でほとんどの信号処理アルゴリズムを関数のパイプとして表現できるまたLuaを参考にしたVMを設計したことで内部状態を持つ信号処理関数の高階関数を用いた複製やホスト環境の埋めこみを容易に可能にしている音楽というドメインに特化しながらも汎用性を失わない言語の意義について議論する
\end{abstract} \end{abstract}
\begin{jkeyword} \begin{jkeyword}
@@ -31,39 +31,38 @@
% Body %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Body %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{はじめに-音楽プログラミング言語の分業化} \section{はじめに-音楽プログラミング言語の分業化}
音楽のためのプログラミング言語・環境はCycling'74 MaxやPure DataCSoundSuperColliderChucKといった言語を代表として様々なものが開発されてきている\cite{roads2001,Lazzarini2013,Nishino2016,Dannenberg2018}こうした言語の多くは歴史を遡ると1950年代にベル研究所でマックス・マシューズらが開発したMUSICシリーズに遡ることができる\cite{mathews1963,park2009}MUSICシリーズはパルス符号変調という今日のコンピューター上のでの音声処理のすべての基礎でもある理論すなわち音圧波形を時間・音圧の2次元で離散化・量子化した数列として表すことで計算によって任意の波形を生成するという考えに基づいて計算機で音を出した最初の事例である 音楽のためのプログラミング言語・環境はCycling'74 MaxやPure DataCSoundSuperColliderChucKといった言語を代表として様々なものが開発されてきている\cite{roads2001,Lazzarini2013,Nishino2016,Dannenberg2018}こうした言語の多くは歴史を遡ると1950年代にベル研究所でマックス・マシューズらが開発したMUSICシリーズに遡ることができる\cite{mathews1963,park2009}MUSICシリーズはパルス符号変調という今日のコンピューター上のでの音声処理のすべての基礎でもある理論すなわち音圧波形を時間・音圧の2次元で離散化・量子化した数列として表すことで計算によって任意の波形を生成するという考えに基づいて計算機で音を出した最初の事例である
CやC++といった汎用プログラミング言語で音声合成を行う場合もこのPCMの理論に基づいてプログラミングを行うことになるがこれら低レイヤーを対象にした言語はメモリ管理や並行処理といったハードウェアに近い処理への理解を要求されるため音楽の記述を行う場合にはより抽象化されたライブラリやドメイン固有言語を利用するのが普通である CやC++といった汎用プログラミング言語で音声合成を行う場合もこのPCMの理論に基づいてプログラミングを行うことになるがこれら低レイヤーを対象にした言語はメモリ管理や並行処理といったハードウェアに近い処理への理解を要求されるため音楽の記述を行う場合にはより抽象化されたライブラリやドメイン固有言語を利用するのが普通である
% todo MUSICがUGENパラダイムを導入したという接続を入れる
そうしたライブラリや言語では、MUSIC IIIで導入されたUnit Generator(UGen)と呼ばれる、オシレーターやフィルターといった基礎的な処理単位を、モジュラーシンセサイザーの様に組み合わせていくことで処理を行う考え方に基づく。\footnote{モジュラーシンセサイザーとUnit Generatorは実際には同時期に現れたコンセプトではある(\cite[p20]{park2009})が、その後の音楽プログラミング言語は積極的にビジュアル的なメタファーを物理的なシンセサイザーから取り入れてきている。}
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{関数型プログラミング言語に影響を受けた信号処理言語} \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はCC++RustLLVM IRなどの様々なバックエンドにコンパイルできあらかじめ用意されたアーキテクチャファイルと呼ばれるボイラープレートコードと組み合わせることでMaxやSuperColliderのUGenとしてエクスポートしたりWebAssemblyを利用してWebブラウザ上で実行することができるその一方Faustの理論的基盤であるBDAは一般的なプログラミング言語との理論的・実用的な互換性に欠けるFaustでは外部のC関数を呼び出すことは可能だがポインタや参照を扱えないのでホストの内部状態をFaust側から制御することは難しい この形式化を通じた強力な抽象化能力によりFaustはCC++RustLLVM 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}$の仕様} \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項の糖衣構文} \subsection{mimiumにおけるfeed項の糖衣構文}
\label{sec:mimium} \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] \begin{figure}[ht]
\centering \centering
@@ -88,7 +87,7 @@ fn onepole(x,g){
x*(1.0-g) + self*g x*(1.0-g) + self*g
} }
\end{verbatim} \end{verbatim}
\caption{\label{fig:onepole-code}{mimiumにおける単極フィルタの実装のサンプル}} \caption{\label{fig:onepole-code}{mimiumにおける単極フィルタの実装のサンプル}}
\end{figure} \end{figure}
\begin{figure}[ht] \begin{figure}[ht]
@@ -99,7 +98,7 @@ let\ & onepole = \\
& \ \lambda x. \lambda g.\ feed\ y.\ x *(1.0 - g) + y * g \ in\ ... & \ \lambda x. \lambda g.\ feed\ y.\ x *(1.0 - g) + y * g \ in\ ...
\end{aligned} \end{aligned}
\end{equation*} \end{equation*}
\caption{\label{fig:onepole}{$\lambda_{mmm}$における図\ref{fig:onepole-code}と等価な表現}} \caption{\label{fig:onepole}{$\lambda_{mmm}$における図\ref{fig:onepole-code}と等価な表現}}
\end{figure} \end{figure}
\subsection{型付け規則} \subsection{型付け規則}
@@ -107,75 +106,75 @@ let\ & onepole = \\
\input{typing.tex} \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{ナイーブな操作的意味論} \subsection{ナイーブな操作的意味論}
\label{sec:semantics} \label{sec:semantics}
\input{semantics.tex} \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および命令セットの設計} \section{VMおよび命令セットの設計}
\label{sec: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{命令セット} \subsection{命令セット}
\label{sec:instruction} \label{sec:instruction}
LuaのVM命令と $\lambda_{mmm}$ のVM命令は以下の点で異なる LuaのVM命令と $\lambda_{mmm}$ のVM命令は以下の点で異なる
\begin{enumerate} \begin{enumerate}
\item mimiumはLuaと異なり静的型付け言語であるため基本的な算術演算の命令が型ごとに用意されている \item mimiumはLuaと異なり静的型付け言語であるため基本的な算術演算の命令が型ごとに用意されている
\item 静的型付けと状態付き高階関数を管理するために呼び出し操作が通常の関数呼び出しとクロージャ呼び出しに分割されている(詳細は\ref{sec:vmstructure}を参照) \item 静的型付けと状態付き高階関数を管理するために呼び出し操作が通常の関数呼び出しとクロージャ呼び出しに分割されている(詳細は\ref{sec:vmstructure}を参照)
\item if文のような条件式にはLua VMでは専用の \texttt{TEST} 命令が採用されているがmimiumでは\texttt{JMP}\texttt{JMPIFNEG} の2つの命令を組み合わせて実装されている \item if文のような条件式にはLua VMでは専用の \texttt{TEST} 命令が採用されているがmimiumでは\texttt{JMP}\texttt{JMPIFNEG} の2つの命令を組み合わせて実装されている
\item forループに関連する命令オブジェクト指向プログラミングで使用される\texttt{SELF}命令変数へのメタデータ参照に関連する\texttt{TABLE}命令は不要なためmimiumでは省略している \item forループに関連する命令オブジェクト指向プログラミングで使用される\texttt{SELF}命令変数へのメタデータ参照に関連する\texttt{TABLE}命令は不要なためmimiumでは省略している
\item リストタプル配列のようなデータ構造の実装は本稿で示す $\lambda_{mmm}$ の記述の範囲外であるため除外している\footnote{実際のコンパイラではタプルのようなデータ型は型に応じてアドレスのオフセットを事前に計算し単純な型に対する命令として分解されているためランタイム上でタプル用の特別な命令は持っていない} \item リストタプル配列のようなデータ構造の実装は本稿で示す $\lambda_{mmm}$ の記述の範囲外であるため除外している\footnote{実際のコンパイラではタプルのようなデータ型は型に応じてアドレスのオフセットを事前に計算し単純な型に対する命令として分解されているためランタイム上でタプル用の特別な命令は持っていない}
\end{enumerate} \end{enumerate}
$\lambda_{mmm}$ のVMはLua VMバージョン5以降と同様にレジスタマシンとして動作するただし命令内でのレジスタ番号は物理レジスタを指すわけではなく単にVM実行中のベースポインタに対するコールスタック上のオフセットを指すまたほとんどの命令の最初のオペランドは演算結果が格納されるレジスタ番号を指定する $\lambda_{mmm}$ のVMはLua VMバージョン5以降と同様にレジスタマシンとして動作するただし命令内でのレジスタ番号は物理レジスタを指すわけではなく単にVM実行中のベースポインタに対するコールスタック上のオフセットを指すまたほとんどの命令の最初のオペランドは演算結果が格納されるレジスタ番号を指定する
\input{instructions.tex} \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の構造} \subsection{VMの構造}
\label{sec:vmstructure} \label{sec:vmstructure}
\begin{figure*}[t] \begin{figure*}[t]
\centerline{\includegraphics[width=\hsize]{lambdammm_vm_structure.pdf}} \centerline{\includegraphics[width=\hsize]{lambdammm_vm_structure.pdf}}
\caption{\label{fig:vmstructure}{$\lambda_{mmm}$を実行するための仮想機械プログラムまたインスタンス化されたクロージャの概要を表した図}} \caption{\label{fig:vmstructure}{$\lambda_{mmm}$を実行するための仮想機械プログラムまたインスタンス化されたクロージャの概要を表した図}}
\end{figure*} \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命令へのコンパイル例} \subsection{VM命令へのコンパイル例}
@@ -197,12 +196,12 @@ fn onepole(x,g) state_size:1
SETSTATE 2 // store to self SETSTATE 2 // store to self
RETURN 3 1 RETURN 3 1
\end{verbatim} \end{verbatim}
\caption{\label{fig:bytecode_onepole}{\ref{fig:onepole-code}の単極フィルターのコードをVM命令にコンパイルした結果を擬似コードで表した例}} \caption{\label{fig:bytecode_onepole}{\ref{fig:onepole-code}の単極フィルターのコードをVM命令にコンパイルした結果を擬似コードで表した例}}
\end{figure} \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] \begin{figure}[ht]
\centering \centering
@@ -218,7 +217,7 @@ fn dsp(x){
twodelay(x,400)+twodelay(x,800) twodelay(x,400)+twodelay(x,800)
} }
\end{verbatim} \end{verbatim}
\caption{\label{fig:fbdelay_code}{mimiumにおける\texttt{self}とtexttt{delay}を組み合わせたフィードバックディレイの関数を複数回使用するコードの例}} \caption{\label{fig:fbdelay_code}{mimiumにおける\texttt{self}とtexttt{delay}を組み合わせたフィードバックディレイの関数を複数回使用するコードの例}}
\end{figure} \end{figure}
\begin{figure}[ht] \begin{figure}[ht]
@@ -269,19 +268,19 @@ fn dsp (x)
SHIFTSTATE -2008 SHIFTSTATE -2008
RETURN 1 1 RETURN 1 1
\end{verbatim} \end{verbatim}
\caption{\label{fig:bytecodes_fbdelay}{\ref{fig:fbdelay_code}のフィードバックディレイの例をVM命令列にコンパイルした例}} \caption{\label{fig:bytecodes_fbdelay}{\ref{fig:fbdelay_code}のフィードバックディレイの例をVM命令列にコンパイルした例}}
\end{figure} \end{figure}
\begin{figure}[ht] \begin{figure}[ht]
\centerline{\includegraphics[width=0.7\hsize]{fbdelay_spos.pdf}} \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} \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] \begin{figure}[ht]
\centering \centering
@@ -305,13 +304,13 @@ fn dsp(){
myfilter(x,1000) myfilter(x,1000)
} }
\end{verbatim} \end{verbatim}
\caption{\label{fig:filterbank_good}{再帰関数とクロージャを使ってパラメトリックにフィルターを複製するコードの例}} \caption{\label{fig:filterbank_good}{再帰関数とクロージャを使ってパラメトリックにフィルターを複製するコードの例}}
\end{figure} \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] \begin{figure}[ht]
\centering \centering
@@ -360,7 +359,7 @@ fn filterbank(n,filter_factory)
CLOSE 4 CLOSE 4
RETURN 4 1 RETURN 4 1
\end{verbatim} \end{verbatim}
\caption{\label{fig:bytecode_filterbank}{\ref{fig:filterbank_good}のフィルタバンクの例をVM命令列にコンパイルした例}} \caption{\label{fig:bytecode_filterbank}{\ref{fig:filterbank_good}のフィルタバンクの例をVM命令列にコンパイルした例}}
\end{figure} \end{figure}
\section{議論} \section{議論}
@@ -368,11 +367,11 @@ fn filterbank(n,filter_factory)
\input{comparison.tex} \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束縛の位置の違いによる挙動の変化} \subsection{Let束縛の位置の違いによる挙動の変化}
\label{sec:letbinding} \label{sec:letbinding}
@@ -392,20 +391,20 @@ fn dsp(){
filterbank(3,bandpass)(x,1000) filterbank(3,bandpass)(x,1000)
} }
\end{verbatim} \end{verbatim}
\caption{\label{fig:filterbank_bad}{\ref{fig:filterbank_good}におけるパラメトリックなフィルター複製のコードの間違った例}} \caption{\label{fig:filterbank_bad}{\ref{fig:filterbank_good}におけるパラメトリックなフィルター複製のコードの間違った例}}
\end{figure} \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|.<term>.|は次のステージで使われるプログラムを生成し\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|.<term>.|は次のステージで使われるプログラムを生成し\verb|~term|は前のステージで評価された項を埋め込むことを意味する\cite{kiselyov2014}\texttt{filterbank}関数はステージ0で評価され\texttt{dsp}関数の中で\verb|~|で評価結果を埋め込んでいるFaustやKronosとは対照的にこの多段階計算の記述は信号処理グラフの生成と信号処理の実行の両方で統一された意味論を保持している
\begin{figure}[ht] \begin{figure}[ht]
\centering \centering
@@ -424,16 +423,16 @@ fn dsp(){
~filterbank(3,.<bandpass>.)(x,1000) ~filterbank(3,.<bandpass>.)(x,1000)
} }
\end{verbatim} \end{verbatim}
\caption{\label{fig:filterbank_multi}{mimiumの将来的な仕様における多段階計算を使用した\texttt{filterbank}関数の例}} \caption{\label{fig:filterbank_multi}{mimiumの将来的な仕様における多段階計算を使用した\texttt{filterbank}関数の例}}
\end{figure} \end{figure}
\subsection{if式による部分的な内部状態更新の意味} \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{外部言語で定義される状態付き関数の呼び出し} \subsection{外部言語で定義される状態付き関数の呼び出し}
\label{sec:ffi} \label{sec:ffi}
@@ -452,28 +451,28 @@ fn dsp(){
\caption{\label{fig:sampler}{mimiumでの外部関数\texttt{gen\_sampler\_mono}を使用したオーディオファイルの再生}} \caption{\label{fig:sampler}{mimiumでの外部関数\texttt{gen\_sampler\_mono}を使用したオーディオファイルの再生}}
\end{figure} \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{結論} \section{結論}
\label{sec:conclusion} \label{sec:conclusion}
本稿では音楽・信号処理用プログラミング言語の中間表現 $\lambda_{mmm}$それを実行するための仮想マシン・命令セットを提案した $\lambda_{mmm}$信号処理グラフの生成とその実際の処理を統一された構文と意味論で記述可能にしたまた信号処理のプロセスを内部状態を伴う関数の適用として表現することで外部で定義されたUGenの利用を単なる高階関数の呼び出しとして可能にしているように既存の汎用言語との相互運用性も高めている 本稿では音楽・信号処理用プログラミング言語の中間表現 $\lambda_{mmm}$それを実行するための仮想マシン・命令セットを提案した $\lambda_{mmm}$信号処理グラフの生成とその実際の処理を統一された構文と意味論で記述可能にしたまた信号処理のプロセスを内部状態を伴う関数の適用として表現することで外部で定義されたUGenの利用を単なる高階関数の呼び出しとして可能にしているように既存の汎用言語との相互運用性も高めている
一方で関数がグローバル環境で評価されるかDSPの繰り返し実行で評価されるかの判別やif文による内部状態更新をどの位置で評価するかなどはユーザーの責任となり初学者にはその区別が難しいという欠点がある 一方で関数がグローバル環境で評価されるかDSPの繰り返し実行で評価されるかの判別やif文による内部状態更新をどの位置で評価するかなどはユーザーの責任となり初学者にはその区別が難しいという欠点がある
また本論文ではmimiumでのコードの例とそれに対応するVM命令列擬似コードの例を示すことでコンパイル過程を説明したのみである現在反映されてない合成型の形式化や今後の多段階計算の導入と並行してコンパイル過程を表示的意味論として形式化を進める必要がある また本論文ではmimiumでのコードの例とそれに対応するVM命令列擬似コードの例を示すことでコンパイル過程を説明したのみである現在反映されてない合成型の形式化や今後の多段階計算の導入と並行してコンパイル過程を表示的意味論として形式化を進める必要がある
この研究がコンピュータ上での音・音楽のより一般的な表現に貢献し音楽のための言語理論とプログラミング言語理論のより広範な分野との間のより深いつながりを促進することを期待する\\ この研究がコンピュータ上での音・音楽のより一般的な表現に貢献し音楽のための言語理論とプログラミング言語理論のより広範な分野との間のより深いつながりを促進することを期待する\\
\begin{acknowledgment} \begin{acknowledgment}
mimiumの初期開発は2019年度未踏IT人材発掘・育成事業の支援の元行われたほかオープンソースソフトウェアとして様々な方からコントリビューションを受けているまた本研究は日本学術振興会科研費若手研究『音楽と工学の相互批評的実践としての「音楽土木工学」の研究 』23K12059の助成を受けているここに感謝の意を表する mimiumの初期開発は2019年度未踏IT人材発掘・育成事業の支援の元行われたほかオープンソースソフトウェアとして様々な方からコントリビューションを受けているまた本研究は日本学術振興会科研費若手研究『音楽と工学の相互批評的実践としての「音楽土木工学」の研究 』23K12059の助成を受けているここに感謝の意を表する
\end{acknowledgment} \end{acknowledgment}
% BibTeX を使用する場合 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % BibTeX を使用する場合 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%