2024-02-08 09:01:44 +00:00
|
|
|
|
---
|
2024-02-08 11:06:45 +00:00
|
|
|
|
date: "2023-09-20T17:12:17+0900"
|
2024-02-08 09:01:44 +00:00
|
|
|
|
---
|
2023-12-06 11:20:54 +00:00
|
|
|
|
#memo #mimium #programming-language
|
2023-08-28 13:32:09 +00:00
|
|
|
|
|
|
|
|
|
[[音楽プログラミング言語の形式化#mimium と 多段階計算]]
|
|
|
|
|
|
|
|
|
|
[[多段階計算]]を取り入れたい
|
|
|
|
|
|
2023-12-06 09:17:39 +00:00
|
|
|
|
とりあえず[[The w-calculus a synchronous framework for the verified modelling of digital signal processing algorithms|W Calculus]]を自然に拡張してみる。
|
2023-09-08 10:46:16 +00:00
|
|
|
|
$W$ Calculusとmimiumの形式は似ているが、主に2つの違いがある。
|
|
|
|
|
|
2024-06-05 08:39:28 +00:00
|
|
|
|
1. $W$ Calculus はLinear-Time Invariant なシステムを想定しているため、基本演算は項の加算と、項と定数の乗算しか使えない。
|
2023-09-08 10:46:16 +00:00
|
|
|
|
2. $W$ Calculusでは関数が数をとって数を返すものしかない。つまり、関数やfeedの項を取ったり返すような高階関数は想定されていない。
|
2023-08-28 13:32:09 +00:00
|
|
|
|
|
2023-09-08 10:46:16 +00:00
|
|
|
|
問題になるのは後者の方だ。
|
|
|
|
|
|
2023-09-08 12:47:38 +00:00
|
|
|
|
## 型
|
2023-08-28 14:19:07 +00:00
|
|
|
|
|
|
|
|
|
|
2023-08-28 13:32:09 +00:00
|
|
|
|
|
|
|
|
|
n以下の自然数$I_n$ (ディレイのbounded access用)
|
|
|
|
|
|
|
|
|
|
$$
|
|
|
|
|
\begin{align}
|
2023-08-28 14:19:07 +00:00
|
|
|
|
\tau ::=&\quad R_a \quad & a \in \mathbb{N}\\
|
2023-09-08 14:47:43 +00:00
|
|
|
|
|&\quad I_n \quad &n \in \mathbb{N} \\\
|
2023-09-08 12:47:38 +00:00
|
|
|
|
|&\quad \tau → \tau \quad &a,b \in \mathbb{N}\\
|
|
|
|
|
% |&\quad \langle \tau \rangle
|
2023-08-28 13:32:09 +00:00
|
|
|
|
\end{align}
|
|
|
|
|
$$
|
|
|
|
|
とりあえず1要素のタプルと普通のRは区別しないことにする
|
2023-08-28 14:19:07 +00:00
|
|
|
|
(そしてよく見るとこれは関数→関数のような高階関数を許してないんだな)
|
|
|
|
|
そうか高階関数を考えなければクロージャを考慮する必要もないものな
|
|
|
|
|
|
2023-12-13 15:36:29 +00:00
|
|
|
|
|
|
|
|
|
[[ブロックとサンプルの互換性]]をどうするかが問題?
|
|
|
|
|
|
2023-09-08 12:47:38 +00:00
|
|
|
|
## 値
|
|
|
|
|
|
|
|
|
|
一旦タプルについては考えないことにしよう
|
2023-08-28 13:32:09 +00:00
|
|
|
|
|
2023-12-13 15:36:29 +00:00
|
|
|
|
|
|
|
|
|
|
2023-08-28 13:32:09 +00:00
|
|
|
|
$$
|
|
|
|
|
\begin{align}
|
2023-09-08 12:47:38 +00:00
|
|
|
|
v \; ::= & \quad R \\
|
2023-09-08 14:47:43 +00:00
|
|
|
|
| & \quad \lambda x:\tau.e \quad & [lambda]\\
|
2023-08-28 14:19:07 +00:00
|
|
|
|
|& \quad feed \; x.e \quad & [feed] \\
|
2023-08-28 13:32:09 +00:00
|
|
|
|
\end{align}
|
2023-08-28 14:19:07 +00:00
|
|
|
|
$$
|
2023-09-08 12:47:38 +00:00
|
|
|
|
## 項
|
2023-08-28 14:19:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$$
|
|
|
|
|
\begin{align}
|
2023-09-08 12:47:38 +00:00
|
|
|
|
e \; ::=& \quad x \quad x \in \mathbb{V} \quad & [value]\\
|
2023-08-28 14:19:07 +00:00
|
|
|
|
|& \quad \lambda x.e \quad & [lambda]\\
|
2023-11-22 02:32:45 +00:00
|
|
|
|
|& \quad fix \; x.e \quad & [fixpoint]\\
|
2023-09-08 12:47:38 +00:00
|
|
|
|
|& \quad e \; e \quad & [app]\\
|
2023-11-22 02:32:45 +00:00
|
|
|
|
|& \quad feed \; x.e \quad & [feed] \\
|
|
|
|
|
|& \quad delay \; e \; e & [delay]\\
|
2023-09-08 12:47:38 +00:00
|
|
|
|
%%|& \quad (e_1,e_2) \quad & [product]\\
|
|
|
|
|
%%|& \quad \pi_n e \quad n\in \mathbb{N},\; n>0 \quad & [project]\\
|
|
|
|
|
%%|& \quad \langle e \rangle \quad & [code] \\
|
|
|
|
|
%%|& \quad \textasciitilde e \quad & [escape]
|
2023-08-28 14:19:07 +00:00
|
|
|
|
\end{align}
|
|
|
|
|
$$
|
|
|
|
|
|
2023-09-08 12:47:38 +00:00
|
|
|
|
基本演算(Intrinsic)は直感に任せる
|
2023-08-28 14:19:07 +00:00
|
|
|
|
|
2023-09-08 12:47:38 +00:00
|
|
|
|
本来はfixの中でfeedを使ったり、feedの中でfixを使うとエラーだが、結局シンタックスレベルでは排除できないので型でエラーとして弾くことにする…?
|
|
|
|
|
いや値レベルでの切り分けは不可能なので、こうする
|
2023-08-29 14:53:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-09-08 12:47:38 +00:00
|
|
|
|
## 実例
|
|
|
|
|
|
2023-09-08 15:26:30 +00:00
|
|
|
|
|
2023-09-08 12:47:38 +00:00
|
|
|
|
```rust
|
2023-09-08 13:47:39 +00:00
|
|
|
|
fn cascade(order:int,fb)->(float->float){
|
|
|
|
|
if(order>0){
|
|
|
|
|
|x|{
|
2023-09-20 08:12:17 +00:00
|
|
|
|
cascade(order-1)(x) *(1-fb) + self*fb
|
2023-09-08 13:47:39 +00:00
|
|
|
|
}
|
|
|
|
|
}else{
|
|
|
|
|
|x| x
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
2023-08-29 07:37:16 +00:00
|
|
|
|
|
2023-09-08 13:47:39 +00:00
|
|
|
|
|
|
|
|
|
ちょっとわかりやすさのために`self`を使わずfeedにしてみる
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
fn cascade(order:int,fb)->(float->float){
|
|
|
|
|
if(order>0){
|
|
|
|
|
|x|{
|
2023-09-20 08:12:17 +00:00
|
|
|
|
feed(y) { cascade(order-1)(x) *(1-fb) + y*fb }
|
2023-09-08 13:47:39 +00:00
|
|
|
|
}
|
|
|
|
|
}else{
|
|
|
|
|
|x| x
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
あー、今までは`fn(x)`でself使うものを`feed(self).lambda(x).e,`って感じに自動的に変換してたけど、変換するとしたら`lambda(x).feed(x).e`の方が良かったってことなんだな
|
|
|
|
|
|
|
|
|
|
これを`cascade(3,0.9)`とかで簡約してみるか
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
cascade(3,0.9)
|
|
|
|
|
|
|
|
|
|
|x|{
|
|
|
|
|
let res = cascade(2)(x);
|
|
|
|
|
feed(y) { res*0.1 + y*0.9 }
|
|
|
|
|
}
|
|
|
|
|
|x1|{
|
|
|
|
|
let res1 =|x2|{
|
|
|
|
|
let res2 = cascade(1)(x2);
|
|
|
|
|
feed(y2) { res2*0.1 + y2*0.9 }
|
|
|
|
|
}(x1);
|
|
|
|
|
feed(y1) { res1*0.1 + y1*0.9 }
|
|
|
|
|
}
|
|
|
|
|
|x1|{
|
|
|
|
|
let res1 =|x2|{
|
|
|
|
|
let res2 = |x3|{
|
|
|
|
|
let res3 = cascade(0)(x3);
|
|
|
|
|
feed(y3) { res3*0.1 + y3*0.9 }
|
|
|
|
|
}(x2);
|
|
|
|
|
feed(y2) { res2*0.1 + y2*0.9 }
|
|
|
|
|
}(x1);
|
|
|
|
|
feed(y1) { res1*0.1 + y1*0.9 }
|
|
|
|
|
}
|
|
|
|
|
|x1|{
|
|
|
|
|
let res1 =|x2|{
|
|
|
|
|
let res2 = |x3|{
|
|
|
|
|
let res3 = |x|{x}(x3);
|
|
|
|
|
feed(y3) { res3*0.1 + y3*0.9 }
|
|
|
|
|
}(x2);
|
|
|
|
|
feed(y2) { res2*0.1 + y2*0.9 }
|
|
|
|
|
}(x1);
|
|
|
|
|
feed(y1) { res1*0.1 + y1*0.9 }
|
|
|
|
|
}
|
|
|
|
|
|x1|{
|
|
|
|
|
let res1 =|x2|{
|
|
|
|
|
let res2 = |x3|{
|
|
|
|
|
let res3 = x3;
|
|
|
|
|
feed(y3) { res3*0.1 + y3*0.9 }
|
|
|
|
|
}(x2);
|
|
|
|
|
feed(y2) { res2*0.1 + y2*0.9 }
|
|
|
|
|
}(x1);
|
|
|
|
|
feed(y1) { res1*0.1 + y1*0.9 }
|
|
|
|
|
}
|
|
|
|
|
|x1|{
|
|
|
|
|
let res1 =|x2|{
|
|
|
|
|
let res2 = |x3|{
|
|
|
|
|
feed(y3) { x3*0.1 + y3*0.9 }
|
|
|
|
|
}(x2);
|
|
|
|
|
feed(y2) { res2*0.1 + y2*0.9 }
|
|
|
|
|
}(x1);
|
|
|
|
|
feed(y1) { res1*0.1 + y1*0.9 }
|
|
|
|
|
}
|
|
|
|
|
|x1|{
|
|
|
|
|
let res1 =|x2|{
|
|
|
|
|
let res2 = feed(y3) { x2*0.1 + y3*0.9 };
|
|
|
|
|
feed(y2) { res2*0.1 + y2*0.9 }
|
|
|
|
|
}(x1);
|
|
|
|
|
feed(y1) { res1*0.1 + y1*0.9 }
|
|
|
|
|
}
|
|
|
|
|
|x1|{
|
|
|
|
|
let res1 =|x2|{
|
|
|
|
|
feed(y2) { feed(y3) { x2*0.1 + y3*0.9 } *0.1 + y2*0.9 }
|
|
|
|
|
}(x1);
|
|
|
|
|
feed(y1) { res1 *0.1 + y1*0.9 }
|
|
|
|
|
}
|
|
|
|
|
|x1|{
|
|
|
|
|
let res1 = feed(y2) { feed(y3) { x1 *0.1 + y3*0.9 } *0.1 + y2*0.9 };
|
|
|
|
|
feed(y1) { res1 *0.1 + y1*0.9 }
|
|
|
|
|
}
|
|
|
|
|
|x1|{
|
|
|
|
|
feed(y1) {
|
|
|
|
|
feed(y2) {
|
|
|
|
|
feed(y3) { x1*0.1 + y3*0.9} *0.1 + y2*0.9 }*0.1 + y1*0.9 }
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
feedの項に対する加算とか乗算の計算は簡約がしづらいなあ
|
|
|
|
|
|
|
|
|
|
時間0の時のyは全て0として、
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
|x1,y1_ref,y2_ref,y3_ref| {
|
|
|
|
|
let y1 = *y1_ref;
|
|
|
|
|
let y1_next= {
|
|
|
|
|
let y2 = *y2_ref;
|
|
|
|
|
let y2_next = {
|
|
|
|
|
let y3 = *y3_ref;
|
|
|
|
|
let y3_next = x1*0.1 + y3*0.9;
|
|
|
|
|
*y3_ref = y3_next;
|
|
|
|
|
y3_next
|
|
|
|
|
}*0.1 + y2*0.9;
|
|
|
|
|
*y2_ref = y2_next;
|
|
|
|
|
y2_next }*0.1 + y1*0.9;
|
|
|
|
|
*y1_ref = y1_next;
|
|
|
|
|
y1_next
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2023-09-08 14:47:43 +00:00
|
|
|
|
やっぱdenotationalの方が定義しやすいかもなあ
|
|
|
|
|
|
|
|
|
|
ああでもfeedを無事に展開できるということは、feedの項に対して`Cell`を割り当てることそのものには間違いはないのか
|
|
|
|
|
|
|
|
|
|
ただ、例えばFeedの項の中に関数が残っちゃうような可能性もあるため、Feedのhistoryの中にLambdaの項が保存されるような状況が回避できない。
|
|
|
|
|
|
2023-09-08 15:26:30 +00:00
|
|
|
|
型付規則の中でfeed x.eのeがプリミティブというか、Boxedにならないものしか取れないようにすればいいのかね。そうするとValueはCopyトレイトを実装できて、feedの中に実際にはラムダが入ってたとしても、簡約後は必ずValueになっていると
|
2023-09-08 14:47:43 +00:00
|
|
|
|
|
|
|
|
|
## 型(改正版)
|
|
|
|
|
|
|
|
|
|
というわけで型の定義再訪
|
|
|
|
|
|
|
|
|
|
$$
|
|
|
|
|
\begin{align}
|
|
|
|
|
\tau_p ::=&\quad R_a \quad & a \in \mathbb{N}\\
|
|
|
|
|
|&\quad I_n \quad &n \in \mathbb{N} \\
|
|
|
|
|
\tau :: = &\quad \tau_p\\
|
|
|
|
|
|&\quad \tau → \tau \quad &a,b \in \mathbb{N}\\
|
|
|
|
|
% |&\quad \langle \tau \rangle
|
|
|
|
|
\end{align}
|
|
|
|
|
$$
|
|
|
|
|
|
2023-09-08 15:47:43 +00:00
|
|
|
|
でラムダ抽象とFeedの型付け規則こういう感じになると
|
2023-09-08 14:47:43 +00:00
|
|
|
|
|
|
|
|
|
$$
|
2023-11-22 02:32:45 +00:00
|
|
|
|
\frac{\Gamma, x:\tau^a \vdash e:\tau^b}{\Gamma \vdash \lambda x.e:\tau^a \to \tau^b }
|
|
|
|
|
$$
|
|
|
|
|
$$
|
2023-09-08 14:47:43 +00:00
|
|
|
|
\frac{\Gamma, x : \tau_p^a \vdash e: \tau_p^a }{\Gamma \vdash feed\ x.e:\tau_p^a}
|
|
|
|
|
$$
|
2023-09-08 15:26:30 +00:00
|
|
|
|
|
2023-09-08 15:47:43 +00:00
|
|
|
|
タプルとかレコードもできるけど、関数をタプルの要素にしたりはできない(できないでもないけど、「そういう型をとれるタプル」と「そういうのできないタプル」を分けて考える必要がある)、って感じでユーザーにはややこしいですねえ
|
|
|
|
|
|
|
|
|
|
## Feedのスコープと簡約の順番について
|
|
|
|
|
|
|
|
|
|
ところでさっきの`cascade`関数ってさ、わざわざ高階関数にせず一括でやれるんですかね、
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
fn cascade_f(order:int,fb,x)->float{
|
|
|
|
|
letrec cascade = if(order>0){
|
|
|
|
|
|x|{
|
|
|
|
|
cascade(N-1)(x) *(1-fb) + self*fb
|
|
|
|
|
}
|
|
|
|
|
}else{
|
|
|
|
|
|x| x
|
|
|
|
|
}
|
|
|
|
|
cascade(x)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let res = cascade_f(3,0.9,input)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
ともあれコピーキャプチャのクロージャでも問題はなさそうだけども、この状態だと`cascade`のfeedのコンテキストは毎サンプル終了しちゃうって感じなんだよね
|
|
|
|
|
|
2023-09-08 16:47:43 +00:00
|
|
|
|
|
2023-09-19 16:33:51 +00:00
|
|
|
|
## VMのインストラクションとデータ構造
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
type Ref = u8;
|
|
|
|
|
enum UpIndex{
|
|
|
|
|
Local(Reg),
|
|
|
|
|
UpValue(u64)
|
|
|
|
|
}
|
|
|
|
|
struct FuncProto{
|
|
|
|
|
instructons: Vec<Instruction>,
|
|
|
|
|
constants: Vec<RawVal>,
|
|
|
|
|
upvalue_idxs: Vec<UpIndex>,
|
|
|
|
|
feed_idx: Vec<u64>
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
この関数だけだとfeedidをどうつければいいかなあ
|
|
|
|
|
|
|
|
|
|
```rust
|
2023-09-20 06:12:21 +00:00
|
|
|
|
fn filterbank(N,input,lowestfreq, margin,filter){
|
2023-09-19 16:33:51 +00:00
|
|
|
|
if(N>0){
|
2023-09-20 06:12:21 +00:00
|
|
|
|
let freq = lowestfreq+N*margin;
|
|
|
|
|
return filter(input,freq)
|
|
|
|
|
+ filterbank(N-1,input,lowestfreq,margin,filter)
|
2023-09-19 16:33:51 +00:00
|
|
|
|
}else{
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn lowpass(input,fb){
|
|
|
|
|
input* (1-fb) + self * fb
|
|
|
|
|
}
|
2023-09-20 06:12:21 +00:00
|
|
|
|
let lowpass = |input,fb|{feed(y) {
|
|
|
|
|
input* (1-fb) + y * fb
|
|
|
|
|
}} }
|
|
|
|
|
let lowpass = |input,fb,ref_y|{
|
|
|
|
|
let res = input* (1-fb)+ deref(ref_y)*fb
|
|
|
|
|
ref_y := res
|
|
|
|
|
res
|
|
|
|
|
}
|
2023-09-19 16:33:51 +00:00
|
|
|
|
res = filterbank(3,input,100,2000,2.0,lowpass)
|
|
|
|
|
```
|
|
|
|
|
|
2023-09-19 22:46:15 +00:00
|
|
|
|
lowpassは最終的にlambda{feed{self}}的な感じになるが、それはfilterbankの中で呼ばれるまではわからない
|
2023-09-20 06:12:21 +00:00
|
|
|
|
lowpassのバイトコードはこんな感じか
|
|
|
|
|
```
|
|
|
|
|
lowpass: //stack 0:input, 1: fb
|
|
|
|
|
moveconst 2 0
|
|
|
|
|
sub 3 1 2
|
|
|
|
|
getfeed 4
|
|
|
|
|
mult 5 2 4
|
|
|
|
|
add 6 3 5
|
|
|
|
|
retfeed 6 1
|
2023-09-20 08:12:17 +00:00
|
|
|
|
const:
|
2023-09-20 06:12:21 +00:00
|
|
|
|
1_i64
|
2023-09-20 08:12:17 +00:00
|
|
|
|
upindexes:
|
2023-09-20 06:12:21 +00:00
|
|
|
|
//nothing
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
global_ftable:
|
|
|
|
|
lowpass
|
|
|
|
|
filterbank
|
|
|
|
|
|
|
|
|
|
filterbank: //stack 0:N,1:input,2:lfreq,3:margin,4:filter
|
|
|
|
|
moveconst 5 0
|
|
|
|
|
gt 6 0 5
|
|
|
|
|
jmpifneg 6 16
|
|
|
|
|
mult 6 0 3
|
|
|
|
|
add 7 2 0
|
|
|
|
|
move 6 6 7
|
|
|
|
|
move 7 4 // get filter
|
|
|
|
|
move 8 1
|
|
|
|
|
move 9 6
|
2023-09-20 08:12:17 +00:00
|
|
|
|
callcls 7 2 1 //result on stack 7
|
2023-09-20 06:12:21 +00:00
|
|
|
|
moveconst 8 1
|
|
|
|
|
sub 9 0 8
|
|
|
|
|
move 10 -1 // get recursive call
|
|
|
|
|
move 11 9 // prepare arguments...
|
|
|
|
|
move 12 1
|
|
|
|
|
move 13 2
|
|
|
|
|
move 14 3
|
2023-09-20 08:12:17 +00:00
|
|
|
|
closure 15 4 // hof requires all function should be closure
|
2023-09-20 06:12:21 +00:00
|
|
|
|
call 10 5 1 //recursive call, result on stack 10
|
|
|
|
|
add 0 7 10
|
|
|
|
|
jmp 1
|
|
|
|
|
moveconst 0 0
|
|
|
|
|
ret 0 1
|
2023-09-20 08:12:17 +00:00
|
|
|
|
const:
|
|
|
|
|
0_i64
|
|
|
|
|
-1_u64
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
feedが呼ばれた時にどのfeedidかを判別するのはランタイム側の役目
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
fn cascade_f(order:int,fb,x)->float{
|
|
|
|
|
letrec cascade = if(order>0){
|
|
|
|
|
|x|{
|
|
|
|
|
cascade(N-1)(x) *(1-fb) + self*fb
|
|
|
|
|
}
|
|
|
|
|
}else{
|
|
|
|
|
|x| x
|
|
|
|
|
}
|
|
|
|
|
cascade(x)
|
|
|
|
|
}
|
|
|
|
|
fn phasor(freq)->float{
|
|
|
|
|
1+self
|
|
|
|
|
}
|
|
|
|
|
fn doubleosc(freq)->freq{
|
|
|
|
|
phasor(freq)+phasor(freq+10)
|
|
|
|
|
}
|
|
|
|
|
let res = cascade_f(3,0.9,input)+doubleosc(440)
|
2023-09-20 06:12:21 +00:00
|
|
|
|
```
|
2023-09-19 16:33:51 +00:00
|
|
|
|
|
2024-06-08 13:50:17 +00:00
|
|
|
|
なんかこんな感じだとして、レキシカルに何番目の関数呼び出しか、
|
|
|
|
|
|
|
|
|
|
[[mimiumの中間表現を考える]]
|