quartz-research-note/content/mimiumでMIDIインプットを実装.md
松浦 知也 Matsuura Tomoya 9ea4fec06c
All checks were successful
Build / build (push) Successful in 3m59s
[obsidian] vault backup: 2024-11-12 16:59:44[
2024-11-12 16:59:44 +09:00

4.4 KiB
Raw Permalink Blame History

date
2024-10-30 15:42

#mimium

要件

どこまでMIDIインプットをmimiumの世界の外側として捉えるか

先行例

SuperColliderChucKはコールバックを登録するようなイメージ

[Using MIDI | SuperCollider 3.12.2 Help](https://doc.sccode.org/Guides/UsingMIDI.html

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


現実的には、Faustのように、ボイスアロケーターは外側で実装してしまい、非同期に更新されうるAtomicな値のセルをートやccのデータとして受け取れるようにすれば当面は十分

が、最終的にはMIDIエフェクトMIDI信号自体のディレイやクォンタイズ、スロットリングとかFunctional Reactive Programmingっぽく書けると嬉しい


Noteのバインドの記法

let cell:()->float = bind_midi_note_mono(channel)

let (note,vel) = cell(); //値の取り出し

//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入門

 type 'a io = unit -> 'a 純粋な値は以下のようなIOモナドにリフトできる。  let return x = fun () -> x 計算はバインド演算子によってつなげることができる。  let (>>=) c1 c2 = fun () -> c2 (c1 ())

mimiumだとバインド演算子ってこうか(ジェネリクスが必要だけども)

fn bind(f1,f2){
 | | f1() |> f2
}

FRPっぽい感じで考えてみる

課題4ポリのMIDIボイスアロケーターを考える

アロケーターを実行すると4つのCell(Behavior)が返ってくる いや、ストリームを返すべき?

fn alloc_midi_voice_4(channel)->[Stream<Note>;4]

状態のフィードバックはSodiumでは次の2つを持って実行できる

  • hold: 任意のStreamが発火した時に最後の値をキープしたCellを作る
  • snapshot:任意のStreamが発火したとき、Cellをサンプリングして別のStreamとして発火する

状態の保持として必要なのは、Busymap([Option<Key>;4])みたいなやつ。

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)
	|> merge(busymap |> snapshot, |(note,busycell)| {
	busycell.hold
	} )

}