2024-11-04 08:04:35 +00:00
|
|
|
|
---
|
|
|
|
|
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のデータとして受け取れるようにすれば当面は十分
|
|
|
|
|
|
2024-11-12 06:22:04 +00:00
|
|
|
|
が、最終的にはMIDIエフェクト(MIDI信号自体のディレイやクォンタイズ、スロットリングとか)を[[Functional Reactive Programming|FRP]]っぽく書けると嬉しい
|
2024-11-04 08:04:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2024-11-12 06:22:04 +00:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### FRPっぽい感じで考えてみる
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
課題:4ポリのMIDIボイスアロケーターを考える
|
|
|
|
|
|
|
|
|
|
アロケーターを実行すると4つのCell(Behavior)が返ってくる
|
|
|
|
|
いや、ストリームを返すべき?
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
fn alloc_midi_voice_4(channel)->[Stream<Note>;4]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
状態のフィードバックはSodiumでは次の2つを持って実行できる
|
|
|
|
|
|
|
|
|
|
- hold: 任意のStreamが発火した時に最後の値をキープしたCellを作る
|
|
|
|
|
- snapshot:任意のStreamが発火したとき、Cellをサンプリングして別のStreamとして発火する
|
|
|
|
|
|
|
|
|
|
状態の保持として必要なのは、`Busymap([Option<Key>;4])`みたいなやつ。
|
|
|
|
|
|
2024-11-04 08:04:35 +00:00
|
|
|
|
|
2024-11-12 06:22:04 +00:00
|
|
|
|
```rust
|
|
|
|
|
fn bind_midi_event()->Stream<Byte>
|
|
|
|
|
fn bind_note_event()->Stream<(Channel,Note)>{
|
|
|
|
|
bind_midi_event
|
|
|
|
|
|> filter(|byte| byte |> is_note_event )
|
|
|
|
|
|> map(|byte| (byte[0],Note(byte[1],byte[2]) ) )
|
|
|
|
|
}
|
|
|
|
|
fn overwrite_arr(idx,v){
|
|
|
|
|
|arr| {
|
|
|
|
|
let mut narr = arr;
|
|
|
|
|
narr[idx] = v
|
|
|
|
|
narr
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn get_4_voice(note){
|
|
|
|
|
let (last_stream,last_busymap) = self;
|
|
|
|
|
if last_busymap |> is_busy{
|
|
|
|
|
(last_stream,last_busymap)
|
|
|
|
|
}else{
|
|
|
|
|
match note{
|
|
|
|
|
Note::On(pitch,vel)=>{
|
|
|
|
|
let voice_idx = last_busymap|>find_available_voice;
|
|
|
|
|
let new_busymap = last_busymap |> overwritearr(voice_idx,pitch);
|
|
|
|
|
let new_str = last_stream |> overwritearr(idx,(pitch,vel))
|
|
|
|
|
}
|
|
|
|
|
Note::Off=>{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn alloc_midi_voice_4(channel){
|
|
|
|
|
let busymap = Cell<_>([None,None,None,None]);
|
|
|
|
|
|
|
|
|
|
bind_note_event
|
|
|
|
|
|> filter(|(c,_note)| c==channel)
|
2024-11-12 07:59:44 +00:00
|
|
|
|
|> merge(busymap |> snapshot, |(note,busycell)| {
|
|
|
|
|
busycell.hold
|
|
|
|
|
} )
|
2024-11-04 08:04:35 +00:00
|
|
|
|
|
2024-11-12 06:22:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
```
|