159 lines
3.9 KiB
Markdown
159 lines
3.9 KiB
Markdown
---
|
||
date: 2024-10-30 15:42
|
||
---
|
||
#mimium
|
||
|
||
## 要件
|
||
|
||
どこまでMIDIインプットをmimiumの世界の外側として捉えるか
|
||
|
||
|
||
### 先行例
|
||
|
||
[[SuperCollider]]や[[ChucK]]はコールバックを登録するようなイメージ
|
||
|
||
[Using MIDI | SuperCollider 3.12.2 Help](https://doc.sccode.org/Guides/UsingMIDI.html
|
||
|
||
```smalltalk
|
||
s.boot;
|
||
|
||
(
|
||
var notes, on, off;
|
||
|
||
MIDIClient.init;
|
||
MIDIIn.connectAll;
|
||
|
||
notes = Array.newClear(128); // array has one slot per possible MIDI note
|
||
|
||
on = MIDIFunc.noteOn({ |veloc, num, chan, src|
|
||
notes[num] = Synth(\default, [\freq, num.midicps,
|
||
\amp, veloc * 0.00315]);
|
||
});
|
||
|
||
off = MIDIFunc.noteOff({ |veloc, num, chan, src|
|
||
notes[num].release;
|
||
});
|
||
|
||
q = { on.free; off.free; };
|
||
)
|
||
|
||
// when done:
|
||
q.value;
|
||
```
|
||
|
||
まあこれはDSPアウトプットの合成が.playで暗黙的に行える(加算で合成されるという想定)だからできることかな、、、
|
||
|
||
ChuckもボイスごとにShredを生やす方向で対応してるからちょっと微妙だ
|
||
|
||
[Chuck - Input & Output](https://chuck.stanford.edu/doc/reference/io.html#MidiIn)
|
||
|
||
---
|
||
|
||
現実的には、[[Faust]]のように、ボイスアロケーターは外側で実装してしまい、非同期に更新されうるAtomicな値のセルをノートやccのデータとして受け取れるようにすれば当面は十分
|
||
|
||
が、最終的にはMIDIエフェクト(MIDI信号自体のディレイやクォンタイズ、スロットリングとか)を[[Functional Reactive Programming|FRP]]っぽく書けると嬉しい
|
||
|
||
|
||
---
|
||
|
||
Noteのバインドの記法
|
||
|
||
```rust
|
||
let cell:()->float = bind_midi_note_mono(channel)
|
||
|
||
let (note,vel) = cell(); //値の取り出し
|
||
|
||
```
|
||
|
||
```rust
|
||
//rust
|
||
fn bind_midi_note_mono(&mut self ,vm:&mut Machine)->ReturnCode{
|
||
let ch = Machine::get_as::<f64>(vm.get_stack(0));
|
||
let cell = Arc::new((AtomicF64::new(),AtomicF64::new() ));
|
||
self.add_midi_listener(ch,|note,vel|{
|
||
let (note_c,vel_c) = cell.clone();
|
||
note_c.write(note);
|
||
vel_c.write(vel);
|
||
});
|
||
let cls = |vm:&mut Machine|->ReturnCode{
|
||
let (note_c,vel_c) = cell.clone();
|
||
vm.set_stack(0,Machine::to_value(note_c));
|
||
vm.set_stack(1,Machine::to_value(vel_c));
|
||
2
|
||
}
|
||
vm::set_stack(0, wrap_rust_closure(cls))
|
||
1
|
||
}
|
||
|
||
```
|
||
|
||
あとは頑張って`add_midi_listener`相当の何かを作ろうって感じだな(関数型っぽくはないけども)
|
||
|
||
|
||
|
||
---
|
||
|
||
|
||
IOの順序保証とか考える
|
||
|
||
正格評価だとIOモナドとかはそもそも考える必要がない
|
||
|
||
[Algebraic Effects入門](https://v2.aintek.xyz/posts/introduction-to-algebraic-effects)
|
||
|
||
> `type 'a io = unit -> 'a`
|
||
> 純粋な値は以下のようなIOモナドにリフトできる。
|
||
> `let return x = fun () -> x`
|
||
> 計算はバインド演算子によってつなげることができる。
|
||
> `let (>>=) c1 c2 = fun () -> c2 (c1 ())`
|
||
|
||
mimiumだとバインド演算子ってこうか(ジェネリクスが必要だけども)
|
||
|
||
```rust
|
||
fn bind(f1,f2){
|
||
| | f1() |> f2
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### FRPっぽい感じで考えてみる
|
||
|
||
MIDIのバイトストリームをフィルターして、ノートオンオフのストリームを作る
|
||
|
||
最終的にはそれをHoldしてセルにして、セルをsampleして値にすれば良い
|
||
|
||
なんかJSには[[midiguchi]]というライブラリがあるらしい
|
||
|
||
[[Haskell]]には[[Reactive-Banana]]使った[[ReactiveBalsa]]もある
|
||
|
||
|
||
```rust
|
||
|
||
fn voice_mono(notes:Stream<Note>){
|
||
let cell = notes |> hold({pitch:60,velocity:0})
|
||
}
|
||
|
||
let midiinput = midi_open_port("from Max 1");
|
||
let notes = midiinput |> filter_map(_,is_byte_note)
|
||
let notes_cell = notes |> voice_mono //ここまでが今のbind_midi_note_monoに相当
|
||
|
||
fn dsp(){
|
||
let {pitch,velocity} = notes_cell |> sample
|
||
pitch |> midi2freq |> osc |> _ * velocty/127
|
||
}
|
||
|
||
```
|
||
|
||
[[Computation Expression]]使ってIOの値を`let!`でバインドしていく、とかの方がわかりやすいかも?
|
||
|
||
## MIDIディレイとかどうすんの
|
||
|
||
```rust
|
||
fn delay<T>(stream:Stream<T>,time:float){
|
||
stream@(now+float)
|
||
}
|
||
|
||
|
||
```
|
||
|