mimium固有の機能

mimium固有の言語機能 #

selfによる信号フィードバックの表現 #

関数の中では、selfという特別なキーワードが使用できます。 selfは関数が最後に返した値を参照できる変数です。たとえば、以下のような関数を作ると呼び出される度incrementずつ増える値を返します。

fn counter(increment){
  self+increment
}

selfはオーディオエンジンが開始された時に0で初期化され、呼び出しコンテキストごとに別々の値が生成、管理されます。 たとえば以下の例ではcounter関数にそれぞれ異なるincrementを与えていますが、この場合内部的にselfのためのメモリは2つ分確保され、lchは毎サンプル0.01ずつ増えて1を越えるたび0にリセットされ、rchは毎サンプル0.05ずつ増えます。

fn dsp()->(float,float){
  let lch = counter(0.01)%1
  let rch = counter(0.05)%1
  (lch,rch)
}
Note

selfは現在常に0か、タプルであれば全てのメンバが0になるような形で初期化されます。この初期値を変更する方法は現在検討中です。またself自体が関数型や関数型をメンバに含むタプル型になる場合はコンパイルエラーになります。

@演算子によるスケジューリング #

void型の(返り値を持たない)関数に続けて@演算子、とさらに数値型の値を続けることで、関数の実行を遅らせることができます。(時間の単位はサンプルです。)

例えば次のコードではオシレーターの周波数を変更して、再帰的に自分自身を1秒後に呼び出すような関数updaterを定義しています。このパターンはTemporal Recursionと呼ばれるもので、Extemporeのような言語で使われているものです。

include("osc.mmm")
let freq = 100

fn updater(){
    freq = (freq + 1.0)%1000
    println(freq)
    updater@(now+1.0*samplerate)
}
updater@1.0
fn dsp(){
    sinwave(freq,0.0)
}

ただし、このスケジューリングの関数は破壊的代入との組み合わせによって効果を成す物なので、関数型のデータフローとの相性があまり良くありません。実際に使う際には、ライブラリのreactive.mmmにあるmetro関数のように、ステートフルな関数をサンプル単位ではなく遅い間隔で更新する高階関数と組み合わせて使うのが便利でしょう。

fn metro(interval,sig:()->float)->()->float{
    let v = 0.0
    letrec updater = | |{
      let s:float =sig();
    v = s
      let _ = updater@(now+interval);
    }
    let _ = updater@(now+1)
    | | {v}
}

metro関数を使うと、同じように一定間隔で周波数を更新する先程のコードは次のように書くことができます。

include("osc.mmm")
include("reactive.mmm")
fn counter(){
    (self+100)%1000
}
let myfreq:()->float = metro(1.0*samplerate ,counter);
fn dsp(){
    let r = sinwave(myfreq,0.0) * 0.5
    (r,r)
}
(c) mimium development community(2024)