diff --git a/src/main.md b/src/main.md index d9e9caf..55c7828 100644 --- a/src/main.md +++ b/src/main.md @@ -1,7 +1,7 @@ ---- -abstract: | - This paper proposes $\lambda_{mmm}$, a call-by-value, simply typed lambda calculus-based intermediate representation for a music programming language that handles synchronous signal processing and introduces a virtual machine and instruction set to execute $\lambda_{mmm}$. Digital signal processing is represented by a syntax that incorporates the internal states of delay and feedback into the lambda calculus. $\lambda_{mmm}$ extends the lambda calculus, allowing users to construct generative signal processing graphs and execute them with consistent semantics. However, a challenge arises when handling higher-order functions because users must determine whether execution occurs within the global environment or during DSP execution. This issue can potentially be resolved through multi-stage computation. ---- +## Abstract + +This paper proposes $\lambda_{mmm}$, a call-by-value, simply typed lambda calculus-based intermediate representation for a music programming language that handles synchronous signal processing and introduces a virtual machine and instruction set to execute $\lambda_{mmm}$. Digital signal processing is represented by a syntax that incorporates the internal states of delay and feedback into the lambda calculus. $\lambda_{mmm}$ extends the lambda calculus, allowing users to construct generative signal processing graphs and execute them with consistent semantics. However, a challenge arises when handling higher-order functions because users must determine whether execution occurs within the global environment or during DSP execution. This issue can potentially be resolved through multi-stage computation. + # Introduction {#sec:intro} diff --git a/src/main_cite.md b/src/main_cite.md index 1c164de..7d8894b 100644 --- a/src/main_cite.md +++ b/src/main_cite.md @@ -1,4 +1,8 @@ -# Introduction {#sec:intro} +## Abstract + +This paper proposes $\lambda_{mmm}$, a call-by-value, simply typed lambda calculus-based intermediate representation for a music programming language that handles synchronous signal processing and introduces a virtual machine and instruction set to execute $\lambda_{mmm}$. Digital signal processing is represented by a syntax that incorporates the internal states of delay and feedback into the lambda calculus. $\lambda_{mmm}$ extends the lambda calculus, allowing users to construct generative signal processing graphs and execute them with consistent semantics. However, a challenge arises when handling higher-order functions because users must determine whether execution occurs within the global environment or during DSP execution. This issue can potentially be resolved through multi-stage computation. + +## Introduction {#sec:intro} Many programming languages have been developed for sound and music; however, only a few possess strongly formalized semantics. A language that is both rigorously formalized and practical is Faust \[1\]; it combines blocks with inputs and outputs with five primitive operations: parallel, sequential, split, merge, and recursive connection. Almost any type of signal processing can be written in Faust by providing basic arithmetic, conditionals, and delays as primitive blocks. In a later extension, a macro based on a term rewriting system was introduced that allowed users to parameterize blocks with an arbitrary number of inputs and outputs \[2\]. @@ -14,13 +18,13 @@ In addition, Kronos \[4\] and W-calculus \[5\] are examples of lambda calculus-b W-calculus includes feedback as a primitive operation, along with the ability to access the value of a variable from the past (i.e., delay). W-calculus restricts systems to those that can represent linear-time-invariant processes, such as filters and reverberators, and defines more formal semantics, aiming for automatic proofs of linearity and the identity of graph topologies. -Previously, the author designed the music programming language *mimium* \[6\]. By incorporating basic operations such as delay and feedback into the lambda calculus, signal processing can be concisely expressed while maintaining a syntax similar to that of general-purpose programming languages. Notably, *mimium*'s syntax was designed to resemble the Rust programming language. +Previously, the author designed the music programming language _mimium_ \[6\]. By incorporating basic operations such as delay and feedback into the lambda calculus, signal processing can be concisely expressed while maintaining a syntax similar to that of general-purpose programming languages. Notably, _mimium_'s syntax was designed to resemble the Rust programming language. -An earlier issue with *mimium* was its inability to compile code that contained combinations of recursive or higher-order functions with stateful functions involving delay or feedback because the compiler could not determine the data size of the internal state used in signal processing. +An earlier issue with _mimium_ was its inability to compile code that contained combinations of recursive or higher-order functions with stateful functions involving delay or feedback because the compiler could not determine the data size of the internal state used in signal processing. -In this paper, I propose the syntax and semantics of $\lambda_{mmm}$, an extended call-by-value simply typed lambda calculus, as a computational model intended to serve as an intermediate representation for *mimium*[^1]. In addition, I propose a virtual machine and its instruction set, based on Lua's VM, to execute this computational model in practice. Finally, I discuss both the challenges and potential of the current  model, one of which is that users must differentiate whether a calculation occurs in a global context or during actual signal processing; the other is that runtime interoperability with other programming languages could be easier than in existing DSP languages. +In this paper, I propose the syntax and semantics of $\lambda_{mmm}$, an extended call-by-value simply typed lambda calculus, as a computational model intended to serve as an intermediate representation for _mimium_[^1]. In addition, I propose a virtual machine and its instruction set, based on Lua's VM, to execute this computational model in practice. Finally, I discuss both the challenges and potential of the current  model, one of which is that users must differentiate whether a calculation occurs in a global context or during actual signal processing; the other is that runtime interoperability with other programming languages could be easier than in existing DSP languages. -# Syntax {#sec:syntax} +## Syntax {#sec:syntax}
@@ -44,7 +48,8 @@ $$ %%|& \quad \pi_n e \quad n\in \mathbb{N},\; n>0 \quad & [project]\\ %%|& \quad \langle e \rangle \quad & [code] \\ %%|& \quad \textasciitilde e \quad & [escape] -\end{aligned}$$ +\end{aligned} +$$ $$ \begin{aligned} @@ -53,7 +58,7 @@ e \; ::=& \ x &x \in {v_p} \ & [value]\\ |& \ let\; x = e_1\; in\; e_2 & & [let]\\ |& \ fix \; x.e & & [fixpoint]\\ |& \ e_1 \; e_2 & & [app]\\ - |& \ if\; (e_c)\; e_t\; else\; e_e \; & & [if] \\ + |& \ if\; (e_c)\; e_t\; else\; e_e \; & & [if] \\ |& \ delay\; n \; e_1 \; e_2 &n \in \mathbb{N}\ & [delay]\\ |& \ feed \; x.e & & [feed]\\ |& ... & & \\ @@ -76,11 +81,11 @@ The types and terms of $\lambda_{mmm}$ are presented in Figure [1](#fig:syntax_ Two terms are introduced in addition to the standard simply typed lambda calculus: $delay\ n\ e_1\ e_2$, which refers to the previous value of $e_1$ by $e_2$ samples (with a maximum delay of $n$ to limit memory usage to a finite size), and $feed\ x.e$, an abstraction that allows the user to refer to the result of evaluating $e$ from one time unit earlier as $x$ during the evaluation of $e$ itself. -## Syntactic Sugar of the Feedback Expression in *mimium* {#sec:mimium} +### Syntactic Sugar of the Feedback Expression in _mimium_ {#sec:mimium} -The programming language *mimium*, developed by the author, includes a keyword $self$ that can be used in function definitions to refer to the previous return value of the function. An example of a simple one-pole filter function, which mixes the input and last output signals such that the sum of the input and feedback gains is 1, is shown in Listing [\[lst:onepole\]](#lst:onepole){reference-type="ref" reference="lst:onepole"}. This code can be expressed in  as illustrated in Figure [2](#fig:onepole){reference-type="ref" reference="fig:onepole"}. +The programming language _mimium_, developed by the author, includes a keyword $self$ that can be used in function definitions to refer to the previous return value of the function. An example of a simple one-pole filter function, which mixes the input and last output signals such that the sum of the input and feedback gains is 1, is shown in Listing [\[lst:onepole\]](#lst:onepole){reference-type="ref" reference="lst:onepole"}. This code can be expressed in  as illustrated in Figure [2](#fig:onepole){reference-type="ref" reference="fig:onepole"}. -``` rust +```rust fn onepole(x,g){ x*(1.0-g) + self*g } @@ -99,11 +104,12 @@ $$
Equivalent expression to Listing 2 in $\lambda_{mmm}$ +
-## Typing Rules {#sec:typing} +### Typing Rules {#sec:typing}
@@ -119,7 +125,6 @@ $$ \end{gathered} $$ -
Figure 3. Excerpt of the typing rules for $\lambda_{mmm}$.
@@ -134,7 +139,8 @@ In W-calculus, which directly inspired the design of $\lambda_{mmm}$, the functi In $\lambda_{mmm}$, the problem of memory allocation for closures is delegated to runtime implementation (see Section [4](#sec:vm){reference-type="ref" reference="sec:vm"}), which allows the use of higher-order functions. However, $feed$ abstraction does not permit function types to be either input or output. Allowing function types in the $feed$ abstraction enables the definition of functions whose behavior could change over time. While this is theoretically interesting, there are no practical examples in real-world signal processing, and such a feature would likely further complicate the implementation. -# Semantics {#sec:semantics} +## Semantics {#sec:semantics} +
$$ @@ -159,6 +165,7 @@ $$ \frac{E^n \vdash e_1 \Downarrow cls(\lambda x_c.e_c, E^n_c) E^n \vdash e_2 \Downarrow v_2\ E^n_c,\ x_c \mapsto v_2 \vdash e_c \Downarrow v }{E^n \vdash\ e_1\ e_2 \Downarrow v }\ &\textrm{[E-APP]} \end{gathered} + $$
@@ -167,7 +174,7 @@ An excerpt of the operational semantics for $\lambda_{mmm}$ is shown in Figure Naturally, if we attempt to execute these semantics directly, we would need to recalculate from time 0 to the current time for every sample, saving all the variable environments at each step. However, in practice, a virtual machine is defined to account for the internal memory space used by $delay$ and $feed$, and  terms are compiled into instructions for this machine before execution. -# VM Model and Instruction Set {#sec:vm} +## VM Model and Instruction Set {#sec:vm} The virtual machine (VM) model and its instruction set for running $\lambda_{mmm}$ are based on Lua version 5 VM \[7\]. @@ -175,11 +182,11 @@ A key challenge when executing a computational model based on lambda calculus is Conversely, the runtime performance can be improved using a process called closure conversion (or lambda lifting). This process analyzes all the outer variables referenced by the inner function and transforms the inner function by adding arguments; thus, the outer variables can be referred to explicitly. However, the implementation of this transformation in the compiler is relatively complex. -The Lua VM adopts a middle-ground approach between these two methods by adding the VM instructions `GETUPVALUE` and `SETUPVALUE`, which allow the outer variables to be dynamically referenced at runtime. The implementation of the compiler and VM using *upvalues* is simpler than full closure conversion while still avoiding significant performance degradation. In this approach, the outer variables are accessed via the call stack rather than the heap memory unless the closure escapes the context of the original function \[8\]. +The Lua VM adopts a middle-ground approach between these two methods by adding the VM instructions `GETUPVALUE` and `SETUPVALUE`, which allow the outer variables to be dynamically referenced at runtime. The implementation of the compiler and VM using _upvalues_ is simpler than full closure conversion while still avoiding significant performance degradation. In this approach, the outer variables are accessed via the call stack rather than the heap memory unless the closure escapes the context of the original function \[8\]. -In addition, *upvalues* facilitate interoperability with other programming languages. Lua can be easily embedded through its C API, and when implementing external libraries in C, programmers can access the upvalues of the Lua runtime, not just the stack values available via the C API. +In addition, _upvalues_ facilitate interoperability with other programming languages. Lua can be easily embedded through its C API, and when implementing external libraries in C, programmers can access the upvalues of the Lua runtime, not just the stack values available via the C API. -## Instruction Set {#sec:instruction} +### Instruction Set {#sec:instruction} The VM instructions for $\lambda_{mmm}$ differ from those for the Lua VM in the following aspects: @@ -224,10 +231,10 @@ SUBF A B C R(A) := R(B) as float - R(C) as float MULF A B C R(A) := R(B) as float * R(C) as float DIVF A B C R(A) := R(B) as float / R(C) as float ADDF A B C R(A) := R(B) as int + R(C) as in -...Other basic arithmetics continues for each primitive types... +...Other basic arithmetics continues for each primitive types... ``` -## Overview of the VM Structure {#sec:vmstructure} +### Overview of the VM Structure {#sec:vmstructure} The overall structure of the virtual machine program, and instantiated closures for $\lambda_{mmm}$ is depicted in Figure [\[fig:vmstructure\]](#fig:vmstructure){reference-type="ref" reference="fig:vmstructure"}. In addition to the usual call stack, the VM has a dedicated storage area (a flat array) to manage the internal state data for feedback and delay. @@ -247,9 +254,9 @@ In 's VM, since the paradigm is call-by-value and there is no reassignment expre ![image](lambdammm_vm_structure.pdf) -## Compilation to the VM Instructions +### Compilation to the VM Instructions -``` {#lst:bytecodes_onepole float="" floatplacement="H" label="lst:bytecodes_onepole" caption="\\it Compiled VM instructions of one-pole filter example in Listing \\ref{lst:onepole}"} +```{#lst:bytecodes_onepole float="" floatplacement="H" label="lst:bytecodes_onepole" caption="\it Compiled VM instructions of one-pole filter example in Listing \ref{lst:onepole}"} CONSTANTS:[1.0] fn onepole(x,g) state_size:1 MOVECONST 2 0 // load 1.0 @@ -258,9 +265,9 @@ fn onepole(x,g) state_size:1 MOVE 3 0 // load x MULF 2 2 3 // x * (1.0-g) GETSTATE 3 // load self - MOVE 4 1 // load g + MOVE 4 1 // load g MULF 3 3 4 // self * g - ADDF 2 2 3 // compute result + ADDF 2 2 3 // compute result GETSTATE 3 // prepare return value SETSTATE 2 // store to self RETURN 3 1 @@ -276,20 +283,20 @@ After each reference to `self` through the `GETSTATE` instruction or after calli The state data can be stored as a flat array by representing the internal state as a relative position within the state storage, thereby simplifying compiler implementation; this avoids the need to generate a tree structure from the root, which was required in the previous implementation of mimium. This approach is similar to how upvalues simplify the compiler implementation by treating free variables as relative positions on the call stack. -``` {#lst:fbdelay .Rust float="" floatplacement="H" label="lst:fbdelay" language="Rust" caption="\\it Example code that combines self and delay without closure call."} +```rust{#lst:fbdelay .Rust float="" floatplacement="H" label="lst:fbdelay" language="Rust" caption="\it Example code that combines self and delay without closure call."} fn fbdelay(x,fb,dtime){ - x + delay(1000,self,dtime)*fb - } - fn twodelay(x,dtime){ - fbdelay(x,dtime,0.7) - +fbdelay(x,dtime*2,0.8) - } - fn dsp(x){ - twodelay(x,400)+twodelay(x,800) - } + x + delay(1000,self,dtime)*fb +} +fn twodelay(x,dtime){ + fbdelay(x,dtime,0.7) + +fbdelay(x,dtime*2,0.8) +} +fn dsp(x){ + twodelay(x,400)+twodelay(x,800) +} ``` -``` {#lst:bytecodes_fbdelay float="" floatplacement="H" label="lst:bytecodes_fbdelay" caption="\\it Compiled VM instructions of feedback delay example in Listing \\ref{lst:fbdelay}"} +```{#lst:bytecodes_fbdelay float="" floatplacement="H" label="lst:bytecodes_fbdelay" caption="\it Compiled VM instructions of feedback delay example in Listing \ref{lst:fbdelay}"} CONSTANTS:[0.7,2,0.8,400,800,0,1] fn fbdelay(x,fb,dtime) state_size:1004 MOVE 3 0 //load x @@ -303,7 +310,7 @@ CONSTANTS:[0.7,2,0.8,400,800,0,1] GETSTATE 4 //prepare result SETSTATE 3 //store to self RETURN 4 1 //return previous self - + fn twodelay(x,dtime) state_size:2008 MOVECONST 2 5 //load "fbdelay" prototype MOVE 3 0 @@ -320,12 +327,12 @@ CONSTANTS:[0.7,2,0.8,400,800,0,1] ADDF 3 3 4 SHIFTSTATE -1004 RETURN 3 1 - + fn dsp (x) MOVECONST 1 6 //load "twodelay" prototype MOVE 2 0 MOVECONST 3 3 //load 400 - CALL 1 2 1 + CALL 1 2 1 SHIFTSTATE 2008 MOVECONST 2 6 //load "twodelay" prototype MOVE 2 3 @@ -337,21 +344,21 @@ CONSTANTS:[0.7,2,0.8,400,800,0,1] RETURN 1 1 ``` -![[]{#fig:fbdelay_spos label="fig:fbdelay_spos"}*Image of how the state position moves while executing `twodelay` function in Listing [\[lst:bytecodes_fbdelay\]](#lst:bytecodes_fbdelay){reference-type="ref" reference="lst:bytecodes_fbdelay"}.*](fbdelay_spos.pdf){#fig:fbdelay_spos width="0.7\\hsize"} +![[]{#fig:fbdelay_spos label="fig:fbdelay_spos"}*Image of how the state position moves while executing `twodelay` function in Listing [[lst:bytecodes_fbdelay]](#lst:bytecodes_fbdelay){reference-type="ref" reference="lst:bytecodes_fbdelay"}.*](fbdelay_spos.pdf){#fig:fbdelay_spos width="0.7\\hsize"} Listing [\[lst:filterbank_good\]](#lst:filterbank_good){reference-type="ref" reference="lst:filterbank_good"} shows an example of a higher-order function `filterbank`, which takes another function `filter`---accepting an input and a frequency as arguments---duplicates `n` instances of `filter` and adds them together[^3]. The previous mimium compiler was unable to compile code that took a function with an internal state as an argument because the entire tree of internal states had to be statically determined at compile time. However, the VM in  can handle this dynamically. Listing [\[lst:bytecode_filterbank\]](#lst:bytecode_filterbank){reference-type="ref" reference="lst:bytecode_filterbank"} shows the translated VM instructions for this code. Recursive calls on the first line of `filterbank`, as well as calls to functions passed as arguments or obtained through upvalues (like `filter`), are executed using the `CALLCLS` instruction rather than the `CALL` instruction. The `GETSTATE` and `SETSTATE` instructions are not used in this function because the internal state storage is switched dynamically when the `CALLCLS` instruction is interpreted. -``` {#lst:filterbank_good .Rust float="" floatplacement="H" label="lst:filterbank_good" language="Rust" caption="\\it Example code that duplicates filter parametrically using a recursive function and closure."} +```rust{#lst:filterbank_good .Rust float="" floatplacement="H" label="lst:filterbank_good" language="Rust" caption="\it Example code that duplicates filter parametrically using a recursive function and closure."} fn bandpass(x,freq){ //... } fn filterbank(n,filter){ let next = filterbank(n-1,filter) if (n>0){ - |x,freq| filter(x,freq+n*100) + |x,freq| filter(x,freq+n*100) + next(x,freq) }else{ |x,freq| 0 @@ -363,13 +370,13 @@ fn dsp(){ } ``` -``` {#lst:bytecode_filterbank float="" floatplacement="H" label="lst:bytecode_filterbank" caption="\\it Compiled VM instructions filterbank example in Listing \\ref{lst:filterbank_good}"} +```rust{#lst:bytecode_filterbank float="" floatplacement="H" label="lst:bytecode_filterbank" caption="\it Compiled VM instructions filterbank example in Listing \ref{lst:filterbank_good}"} CONSTANTS[100,1,0,2] fn inner_then(x,freq) //upvalue:[local(4),local(3),local(2),local(1)] GETUPVALUE 3 2 //load filter - MOVE 4 0 - MOVE 5 1 + MOVE 4 0 + MOVE 5 1 GETUPVALUE 6 1 //load n ADDD 5 5 6 MOVECONST 6 0 @@ -381,18 +388,18 @@ fn inner_then(x,freq) CALLCLS 4 2 1 //call next ADDF 3 3 4 RETURN 3 1 - + fn inner_else(x,freq) MOVECONST 2 2 RETURN 2 1 - + fn filterbank(n,filter) MOVECONST 2 1 //load itself MOVE 3 0 //load n MOVECONST 4 1 //load 1 SUBF 3 3 4 MOVECONST 4 2 //load inner_then - CALLCLS 2 2 1 //recursive call + CALLCLS 2 2 1 //recursive call MOVE 3 0 MOVECONST 4 2 //load 0 SUBF 3 3 4 @@ -406,7 +413,7 @@ fn filterbank(n,filter) RETURN 3 1 ``` -# Discussion {#sec:discussion} +## Discussion {#sec:discussion} As demonstrated in the example of the filterbank, in , a signal graph can be parametrically generated during the evaluation of the global context, whereas Faust uses a term-rewriting macro and Kronos employs type-level computation, as shown in Table [1](#tab:comparison){reference-type="ref" reference="tab:comparison"}. @@ -414,15 +421,19 @@ The ability to describe both the generation of parametric signal processing and However, there is a drawback: unified semantics can cause  to deviate from the behavior typically expected in standard lambda calculus. +
|   | Parametric Signal Graph | Actual DSP | - |---------------- |-------------------------| ------------------| - Faust | Term Rewriting Macro | BDA | - Kronos |Type-level Computation |Value Evaluation| - mimium |Global Context Execution| `dsp` Function Execution| + |----------- |-------------------------| ------------------| + |Faust | Term Rewriting Macro | BDA | + |Kronos |Type-level Computation |Value Evaluation| + |mimium |Global Context Execution| `dsp` Function Execution| - : *Comparison of the way of signal graph generation and actual signal processing between Faust, Kronos and $\lambda_{mmm}$. +
+omparison of the way of signal graph generation and actual signal processing between Faust, Kronos and $\lambda_{mmm}$. +
+
-## Different Behaviour Depending on the Location of Let Binding {#sec:letbinding} +### Different Behaviour Depending on the Location of Let Binding {#sec:letbinding} By using functions with internal states that change over time in mimium, there is counterintuitive behavior when higher-order functions are used compared to general functional programming languages. @@ -434,13 +445,15 @@ However, in mimium, there are two distinct stages of evaluation. 0: The code is Although the code contains no destructive assignments, the recursive execution of the `filterbank` function occurs only once in Listing [\[lst:filterbank_good\]](#lst:filterbank_good){reference-type="ref" reference="lst:filterbank_good"} during the global environment evaluation. Conversely, in Listing [\[lst:filterbank_bad\]](#lst:filterbank_bad){reference-type="ref" reference="lst:filterbank_bad"}, the recursive function is executed, and a closure is generated each time the `dsp` function runs on every sample. Because the internal state of the closure is initialized at the time of closure allocation, in the example of Listing [\[lst:filterbank_bad\]](#lst:filterbank_bad){reference-type="ref" reference="lst:filterbank_bad"}, the internal state of the closure is reset at each time step, following the evaluation of `filterbank`. -``` {#lst:filterbank_bad .Rust float="" floatplacement="H" label="lst:filterbank_bad" language="Rust" caption="\\it Wrong example of the code that duplicate filter parametrically."} +
+ +```rust{#lst:filterbank_bad .Rust float="" floatplacement="H" label="lst:filterbank_bad" language="Rust" caption="Wrong example of the code that duplicate filter parametrically."} fn filterbank(n,filter){ if (n>0){ - |x,freq| filter(x,freq+n*100) + |x,freq| filter(x,freq+n*100) + filterbank(n-1,filter)(x,freq) }else{ - |x,freq| 0 + |x,freq| 0 } } fn dsp(){ @@ -448,7 +461,13 @@ fn dsp(){ } ``` -``` {#lst:filterbank_multi .Rust float="" floatplacement="H" label="lst:filterbank_multi" language="Rust" caption="\\it Example of filterbank function using multi-stage computation in a future specification of mimium."} +
+Wrong example of the code that duplicate filter parametrically. +
+
+ +
+```rust{#lst:filterbank_multi .Rust float="" floatplacement="H" label="lst:filterbank_multi" language="Rust" caption="Example of filterbank function using multi-stage computation in a future specification of mimium."} fn filterbank(n,filter){ .< if (n>0){ |x,freq| filter(x,freq+n*100) @@ -461,6 +480,10 @@ fn dsp(){ ~filterbank(3,bandpass)(x,1000) } ``` +
+Example of filterbank function using multi-stage computation in a future specification of mimium. +
+
This implies that major compiler optimization techniques, such as constant folding and function inlining, cannot be directly applied to mimium. These optimizations must be performed after global context evaluation and before the evaluation of the `dsp` function. @@ -468,13 +491,13 @@ To address this issue, it is necessary to introduce a distinction in the type sy The `filterbank` function is evaluated in stage 0 while embedding itself with `~`. In contrast to Faust and Kronos, this multi-stage computation code retains the same semantics for both the generation of the signal processing graph and the execution of signal processing. -## A Possibility of the Foreign Stateful Function Call +### A Possibility of the Foreign Stateful Function Call The closure data structure in  combines functions with the internal states, as shown in Figure 3. The fact that `filterbank` samples do not require special handling for internal states means that external signal processors (Unit Generators: UGens), such as oscillators and filters written in C or C++, can be called from mimium, just like normal closure calls. Additionally, it is possible to parameterize, duplicate, and combine external UGens[^4]. This capability is difficult to implement in Faust and similar languages but is easily achievable in the  paradigm. However, mimium currently uses sample-by-sample processing and cannot handle buffer-by-buffer value passing. Because most native unit generators process data on a buffer-by-buffer basis, there are few practical cases where external UGens are currently used. Nonetheless, in , only $feed$ terms require sample-by-sample processing. Therefore, it is possible to differentiate the functions that can process only one sample at a time from those that can process concurrently at the type level. As the multi-rate specification is being considered in Faust \[11\], it may be possible to facilitate buffer-based processing between an external Unit Generator by having the compiler automatically determine the parts that can be processed buffer-by-buffer. -# Conclusion {#sec:conclusion} +## Conclusion {#sec:conclusion} This paper proposed $\lambda_{mmm}$, an intermediate representation for programming languages for music and signal processing, along with a virtual machine and an instruction set to execute it. $\lambda_{mmm}$ enables the description of generative signal graphs and their contents within a unified syntax and semantics. However, users are responsible for ensuring that their code does not create escapable closures during the iterative execution of a DSP, which can be challenging for novice users to grasp. @@ -482,41 +505,36 @@ In this paper, the translation of  terms into VM instructions was illustrated b I hope that this research will contribute to more general representations of music and sound on digital computers and foster deeper connections between the theory of languages for music and the broader field of programming language theory. -# Acknowledgments +## Acknowledgments This study was supported by JSPS KAKENHI (Grant No. JP19K21615). I would also like to thank the many anonymous reviewers. ## References -[\[1\] ]{.csl-left-margin}[Y. Orlarey, D. Fober, and S. Letz, "Syntactical and semantical aspects of Faust," *Soft Computing*, vol. 8, no. 9, pp. 623--632, 2004, doi: [10.1007/s00500-004-0388-1](https://doi.org/10.1007/s00500-004-0388-1).]{.csl-right-inline} +[\[1\] ]{.csl-left-margin}[Y. Orlarey, D. Fober, and S. Letz, "Syntactical and semantical aspects of Faust," _Soft Computing_, vol. 8, no. 9, pp. 623--632, 2004, doi: [10.1007/s00500-004-0388-1](https://doi.org/10.1007/s00500-004-0388-1).]{.csl-right-inline} -[\[2\] ]{.csl-left-margin}[A. Gräf, "Term Rewriting Extension for the Faust Programming Language," in *International Linux Audio Conference*, 2010. Available: [https://hal.archives-ouvertes.fr/hal-03162973 https://hal.archives-ouvertes.fr/hal-03162973/document](https://hal.archives-ouvertes.fr/hal-03162973 https://hal.archives-ouvertes.fr/hal-03162973/document)]{.csl-right-inline} +[\[2\] ]{.csl-left-margin}[A. Gräf, "Term Rewriting Extension for the Faust Programming Language," in _International Linux Audio Conference_, 2010. Available: [https://hal.archives-ouvertes.fr/hal-03162973 https://hal.archives-ouvertes.fr/hal-03162973/document](https://hal.archives-ouvertes.fr/hal-03162973 https://hal.archives-ouvertes.fr/hal-03162973/document)]{.csl-right-inline} [\[3\] ]{.csl-left-margin}[B. R. Gaster, N. Renney, and T. Mitchell, "OUTSIDE THE BLOCK SYNDICATE: TRANSLATING FAUST'S ALGEBRA OF BLOCKS TO THE ARROWS FRAMEWORK," in *Proceedings of the 1st International Faust Conference*, Mainz,Germany, 2018.]{.csl-right-inline} -[\[4\] ]{.csl-left-margin}[V. Norilo, "Kronos: A Declarative Metaprogramming Language for Digital Signal Processing," *Computer Music Journal*, vol. 39, no. 4, pp. 30--48, 2015, doi: [10.1162/COMJ_a_00330](https://doi.org/10.1162/COMJ_a_00330).]{.csl-right-inline} +[\[4\] ]{.csl-left-margin}[V. Norilo, "Kronos: A Declarative Metaprogramming Language for Digital Signal Processing," _Computer Music Journal_, vol. 39, no. 4, pp. 30--48, 2015, doi: [10.1162/COMJ_a_00330](https://doi.org/10.1162/COMJ_a_00330).]{.csl-right-inline} -[\[5\] ]{.csl-left-margin}[E. J. G. Arias, P. Jouvelot, S. Ribstein, and D. Desblancs, "The [W-calculus]{.nocase}: A Synchronous Framework for the Verified Modelling of Digital Signal Processing Algorithms," in *Proceedings of the 9th ACM SIGPLAN international workshop on functional art, music, modelling, and design*, New York, NY, USA: Association for Computing Machinery, 2021, pp. 35--46. doi: [10.1145/3471872.3472970](https://doi.org/10.1145/3471872.3472970).]{.csl-right-inline} +[\[5\] ]{.csl-left-margin}[E. J. G. Arias, P. Jouvelot, S. Ribstein, and D. Desblancs, "The [W-calculus]{.nocase}: A Synchronous Framework for the Verified Modelling of Digital Signal Processing Algorithms," in _Proceedings of the 9th ACM SIGPLAN international workshop on functional art, music, modelling, and design_, New York, NY, USA: Association for Computing Machinery, 2021, pp. 35--46. doi: [10.1145/3471872.3472970](https://doi.org/10.1145/3471872.3472970).]{.csl-right-inline} -[\[6\] ]{.csl-left-margin}[T. Matsuura and K. Jo, "Mimium: A self-extensible programming language for sound and music," in *Proceedings of the 9th ACM SIGPLAN International Workshop on Functional Art, Music, Modelling, and Design*, in FARM 2021. New York, NY, USA: Association for Computing Machinery, Aug. 2021, pp. 1--12. doi: [10.1145/3471872.3472969](https://doi.org/10.1145/3471872.3472969).]{.csl-right-inline} +[\[6\] ]{.csl-left-margin}[T. Matsuura and K. Jo, "Mimium: A self-extensible programming language for sound and music," in _Proceedings of the 9th ACM SIGPLAN International Workshop on Functional Art, Music, Modelling, and Design_, in FARM 2021. New York, NY, USA: Association for Computing Machinery, Aug. 2021, pp. 1--12. doi: [10.1145/3471872.3472969](https://doi.org/10.1145/3471872.3472969).]{.csl-right-inline} -[\[7\] ]{.csl-left-margin}[R. Ierusalimschy, L. H. de Figueiredo, and W. Celes, "The Implementation of Lua 5.0," *JUCS - Journal of Universal Computer Science*, vol. 11, no. 7, pp. 1159--1176, Jul. 2005, doi: [10.3217/jucs-011-07-1159](https://doi.org/10.3217/jucs-011-07-1159).]{.csl-right-inline} +[\[7\] ]{.csl-left-margin}[R. Ierusalimschy, L. H. de Figueiredo, and W. Celes, "The Implementation of Lua 5.0," _JUCS - Journal of Universal Computer Science_, vol. 11, no. 7, pp. 1159--1176, Jul. 2005, doi: [10.3217/jucs-011-07-1159](https://doi.org/10.3217/jucs-011-07-1159).]{.csl-right-inline} [\[8\] ]{.csl-left-margin}[R. Nystrom, *Crafting Interpreters*. Daryaganj Delhi: Genever Benning, 2021.]{.csl-right-inline} -[\[9\] ]{.csl-left-margin}[W. Taha and T. Sheard, "Multi-Stage Programming with Explicit Annotations," *SIGPLAN Notices (ACM Special Interest Group on Programming Languages)*, vol. 32, no. 12, pp. 203--214, Dec. 1997, doi: [10.1145/258994.259019](https://doi.org/10.1145/258994.259019).]{.csl-right-inline} +[\[9\] ]{.csl-left-margin}[W. Taha and T. Sheard, "Multi-Stage Programming with Explicit Annotations," _SIGPLAN Notices (ACM Special Interest Group on Programming Languages)_, vol. 32, no. 12, pp. 203--214, Dec. 1997, doi: [10.1145/258994.259019](https://doi.org/10.1145/258994.259019).]{.csl-right-inline} -[\[10\] ]{.csl-left-margin}[O. Kiselyov, "The Design and Implementation of BER MetaOCaml," in *[Proceedings of the 12th International Symposium on Functional and Logic Programming]{.nocase}*, M. Codish and E. Sumii, Eds., Cham: Springer International Publishing, 2014, pp. 86--102. doi: [10.1007/978-3-319-07151-0_6](https://doi.org/10.1007/978-3-319-07151-0_6).]{.csl-right-inline} +[\[10\] ]{.csl-left-margin}[O. Kiselyov, "The Design and Implementation of BER MetaOCaml," in _[Proceedings of the 12th International Symposium on Functional and Logic Programming]{.nocase}_, M. Codish and E. Sumii, Eds., Cham: Springer International Publishing, 2014, pp. 86--102. doi: [10.1007/978-3-319-07151-0_6](https://doi.org/10.1007/978-3-319-07151-0_6).]{.csl-right-inline} [\[11\] ]{.csl-left-margin}[P. Jouvelot and Y. Orlarey, "Dependent vector types for data structuring in multirate Faust," *Computer Languages, Systems & Structures*, vol. 37, no. 3, pp. 113--131, 2011.]{.csl-right-inline} -## Footnotes - [^1]: The newer version of mimium compiler and VM based on the model presented in this paper is on the GitHub. - [^2]: In the actual implementation, instructions such as `MOVE` include an additional operand to specify the word size of values, particularly for handling aggregate types like tuples. - [^3]: In the previous specification of mimium \[6\], the syntax for the variable binding and destructive assignment was the same (`x = a`). However, in the current syntax, variable binding uses the `let` keyword. - [^4]: In fact, in the actual implementation of mimium, an interoperation between VM and audio driver is realized by passing and calling Rust's closure.