見出し画像

動的なSynthDefの定義

これまで、SynthDefは事前に定義を済ませておくものと考えていた。つまりシンセの設計はSynthDefによって予め確定しており、引数で振る舞いを変えるという考え方だ。

以下のExample 1では、SynthDef(\perc)でシンセの設計を確定し、loopごとに音高(pch)、持続(dur)、定位(pan)、間隔(itv)、反復(rep)を決定しつつ、Synth(\perc)を演奏している。

// Example 1
(
SynthDef(\perc, {|pch, dur, pan|
	var frq, env, sig;
	
	frq = pch.midicps;
	env = Env.perc(0.01, dur, 0.2).ar(2);
	sig = SinOsc.ar(frq);
	sig = Pan2.ar(sig, pan, env);
	Out.ar(0, sig);
}).add;

Tdef(\perc_play, {
	loop {
		var pch = rrand(48, 72.0);
		var dur = exprand(0.01, 1);
		var pan = rrand(-1, 1.0);
		var itv = exprand(0.05, 0.2);
		var rep = rrand(1, 10);
		// dur = 0.5; itv = 1;	// See Ugens
		
		rep.do {
			s.bind { Synth(\perc, [pch:pch, dur:dur, pan:pan]) };
			itv.wait;
		};
	};
}).play;
)

ここで、SynthDef(\perc)を拡張して複数のエンベロープや波形を選べるようにしたいとする。まずSelectを使う方法が考えられるが、選択の有無にかかわらず第2引数(array)の中の全てのUGenが立ち上がるため、CPUに負荷がかかると思われる。

// Example 2
(
SynthDef(\perc, {|pch, dur, pan, esel, wsel|
	var frq, env, sig;
	
	frq = pch.midicps;
	
	env = Select.ar(esel, [
		Env.perc(dur, 0.01, 0.2).ar(2),
		Env.perc(0.01, dur, 0.2).ar(2)
	]);
	
	sig = Select.ar(wsel, [
		SinOsc.ar(frq),
		LFTri.ar(frq),
		LFSaw.ar(frq),
		LFPulse.ar(frq)
	]);
	
	sig = Pan2.ar(sig, pan, env);
	Out.ar(0, sig);
}).add;

Tdef(\perc_play, {
	loop {
		var pch = rrand(48, 72.0);
		var dur = exprand(0.01, 1);
		var pan = rrand(-1, 1.0);
		var esel = 2.rand;
		var wsel = 4.rand;
		var itv = exprand(0.05, 0.2);
		var rep = rrand(1, 10);
		// dur = 0.5; itv = 1;	// See Ugens
		
		rep.do {
			s.bind { Synth(\perc, [pch:pch, dur:dur, pan:pan, esel:esel, wsel:wsel]) };
			itv.wait;
		};
	};
}).play;
)

実行中のUGenの数はSCIDE右下のステータスバーの「~u」で確認できる。上の2例のTdef(\perc_play)で、コメントアウトした行をアンコメントすると、一音ごとのUGenの数が確認できるが、Example 1では6u、Example 2では12uと表示される。

さて、ここまではSynthDefを静的に使用していたが、動的な使用、つまり演奏の最中に都度、定義し直すということを考える。Example 3ではSynthDefがTdefの中に入っており、loopごとにSynthDef(\perc)が再定義される仕組みだ。音高等のパラメータは引数ではなく変数で直接与える。

// Example 3
(
Tdef(\perc_play, {
	loop {
		var pch = rrand(48, 72.0);
		var dur = exprand(0.01, 1);
		var pan = rrand(-1, 1.0);
		var esel = 2.rand;
		var wsel = 4.rand;
		var itv = exprand(0.05, 0.2);
		var rep = rrand(1, 10);
		// dur = 0.5; itv = 1;	// See Ugens
		
		SynthDef(\perc, {
			var frq, env, sig;
			
			frq = pch.midicps;
			
			env = Select.ar(esel, [
				Env.perc(dur, 0.01, 0.2).ar(2),
				Env.perc(0.01, dur, 0.2).ar(2)
			]);
			
			sig = Select.ar(wsel, [
				SinOsc.ar(frq),
				LFTri.ar(frq),
				LFSaw.ar(frq),
				LFPulse.ar(frq)
			]);
			
			sig = Pan2.ar(sig, pan, env);
			Out.ar(0, sig);
		}).add;
		
		rep.do{
			s.bind { Synth(\perc) };
			itv.wait;
		};
	};
}).play;
)

Example 2と同じくSynthDef(\perc)はSelectを使用しているため、第2引数の全てのUGenが立ち上がる。コメントアウト行をアンコメントするとステータスバーに10uと表示される。

Example 4ではSelectの代わりに条件分岐switchを用いている。この場合、SynthDef(\perc)には選択肢の中のいずれか一つのUGenだけが書き込まれる。コメントアウト行をアンコメントするとステータスバーに4uと表示される。

// Example 4
(
Tdef(\perc_play, {
	loop {
		var pch = rrand(48, 72.0);
		var dur = exprand(0.01, 1);
		var pan = rrand(-1, 1.0);
		var esel = 2.rand;
		var wsel = 4.rand;
		var itv = exprand(0.05, 0.2);
		var rep = rrand(1, 10);
		// dur = 0.5; itv = 1;	// See Ugens
		
		SynthDef(\perc, {
			var frq, env, sig;
			
			frq = pch.midicps;
			
			env =
			switch ( esel )
			{ 0 } {	Env.perc(dur, 0.01, 0.2).ar(2) }
			{ Env.perc(0.01, dur, 0.2).ar(2) };
			
			sig =
			switch ( wsel )
			{ 0 } { SinOsc.ar(frq) }
			{ 1 } { LFTri.ar(frq) }
			{ 2 } { LFSaw.ar(frq) }
			{ LFPulse.ar(frq) };
			
			sig = Pan2.ar(sig, pan, env);
			Out.ar(0, sig);
		}).add;
		
		rep.do{
			s.bind { Synth(\perc) };
			itv.wait;
		};
	};
}).play;
)

Tdef(\perc_play).stop;

ところで、Example 3,4ではUGenの数は記述の通りであり、Example 1,2はそれより2つ多い。

この記事が気に入ったらサポートをしてみませんか?