Bus や Group を使った非リアルタイム録音

経緯

私は SC 歴 3 ヶ月程度の者です。
ライブコーディング用途ではなく、「DAW のように曲を作ってファイルに保存したい」という目的で SC を使用しています。
チュートリアルを終えて試しに 1 曲作ろうとした時、 Bus や Group を用いた実践的な NRT ( Non-Real Time )録音についての情報がなさすぎて途方に暮れました。
幸い、 Discord に参加してほとんどの部分を助けていただき、ひとまず望んでいた結果を得ることができました。
私はまだまだ初心者であり、大半の関数に対して「なにが分からないのか分からない」「なぜか動いているからオッケー」という状態です。熟練者の皆様からするとおかしな点ばかりかもしれません。ですが実践的な NRT 録音に関する日本語情報を少しでも増やすため、恥を忍んで投稿します。

コード

(
// SynthDef を配列に入れておくことで、 NRT 用の Score に複数のシンセを登録できる。
~synth = [
	// この配列内に、曲の SynthDef を定義する。
	SynthDef(\foo, {
		| freq = 440, gate = 1, out = 0 |
		var signal = Saw.ar(freq: freq, mul: Env.adsr(0.01, 0.01, 0.8, 0.3).kr(doneAction: Done.freeSelf, gate: gate));
		signal = Pan2.ar(in: signal);
		Out.ar(out, signal);
	}),

	SynthDef(\effects1, {
		| in, out = 0, gate = 1, tempo = 1 |
		var env = Env.asr(0.0001, releaseTime: 0.1).kr(gate: gate, doneAction: Done.freeSelf);

		var signal;

		signal = In.ar(bus: in, numChannels: 2);

		// ここにエフェクトを書く。
		// (このエフェクトは適用した箇所のみ適用される)
		signal = LPF.ar(in: signal);

		Out.ar(bus: out, channelsArray: signal * env);
	}),

	SynthDef(\masterEffects, {
		| in, out = 0, gate = 1, tempo = 1 |
		var env = Env.asr(0.0001, releaseTime: 0.1).kr(gate: gate, doneAction: Done.freeSelf);

		var signal;

		signal = In.ar(bus: in, numChannels: 2);

		// ここにエフェクトを書く。
		// (このエフェクトは曲のはじめから終わりまで適用される)
		signal = FreeVerb2.ar(in: signal[0], in2: signal[1]);

		Out.ar(bus: out, channelsArray: signal * env);
	}),
];

~synth.do({|s| s.add;});
)

// ↑このブロックを実行してから、
// ↓このブロックを実行する。

(
// この structure 変数に曲の楽譜を書く。
// 特別なエフェクトをかけない場合、「out」は「\bus_master」に設定する。
// 特別なエフェクトをかける場合、「out」を当該エフェクト用の Bus に設定する。
var structure = Pseq([
	Pbind(*[
		instrument: \foo,
		tempo: 120/60,
		dur: Pseq([1,1,1,1]),
		out: Pkey(\bus_master),
		group: Pkey(\group_sources),
	]),
	Pbind(*[
		instrument: \foo,
		tempo: 180/60,
		dur: Pseq([1,1,1,1]),
		out: Pkey(\bus_effect1),
		group: Pkey(\group_sources),
	]),
	Pbind(*[
		instrument: \foo,
		tempo: 150/60,
		dur: Pseq([1,1,1,1]),
		out: Pkey(\bus_master),
		group: Pkey(\group_sources),
	]),
	Pbind(*[
		instrument: \foo,
		tempo: 60/60,
		dur: Pseq([1,1,1,1]),
		out: Pkey(\bus_effect1),
		group: Pkey(\group_sources),
	]),
]);

// Pproto を使うと Bus や Group をパターン内に閉じ込めることができる(らしい)。
var song = Pproto(
	makeFunction: {
		// ここで使用する Bus や Group を定義。
		// 各 Pattern 内において Pkey で呼び出される。
		// 定義順に注意。

		~bus_effect1 = (type: \audioBus, channels: 2).yield;
		~bus_master = (type: \audioBus, channels: 2).yield;

		~group_master = (type: \group).yield;
		~group_effects = (type: \group).yield;
		~group_sources = (type: \group).yield;
	},
	pattern: Ppar([
		// 「\bus_master」に流れている音に曲全体のエフェクトをかけ、 Bus 0 に流す。
		// 「dur」は、「structure」で書いた曲の tempo と dur に依存する。
		Pbind(*[
			instrument: \masterEffects,
			in: Pkey(\bus_master),
			out: 0,
			dur: Pseq([20], 1),
			group: Pkey(\group_master),
		]),
		// 局所的なエフェクト用の Pattern 。
		// エフェクト用のシンセ自体は曲全体を通して開いている。
		// この場合、「\bus_effect1」に流れてきた音すべてにエフェクトをかけて「\bus_master」に流している。
		// 「dur」は、「structure」で書いた曲の tempo と dur に依存する。
		Pbind(*[
			instrument: \effects1,
			in: Pkey(\bus_effect1),
			out: Pkey(\bus_master),
			dur: Pseq([20], 1),
			group: Pkey(\group_effects),
		]),
		structure
	])
);

var recordNRT = {
	| duration = 30, filePath = "", format = "FLAC" |

	var score = song.asScore(duration: duration, timeOffset: 0.001);
	
	// 配列に入れておいたシンセをここで全て Score に追加
	~synth.do({|s| score.add([0.0, [\d_recv, s.asBytes]]); });
	
	score.sort;

	score.recordNRT(
		outputFilePath: filePath.standardizePath,
		headerFormat: format,
		duration: duration,
		options: ServerOptions.new
		.numOutputBusChannels_(2)
		.memSize_(10240)
	);
};

// この行↓と最後の行をコメントアウトで切り替えれば、
// 「制作中の試聴」と「最終的なファイル出力」を切り替えることができる。
song.play;

// recordNRT.value(duration: 30, filePath: "ここにファイルパス.flac");
)

参考資料

公式ヘルプ内にある NRT 合成の資料
https://doc.sccode.org/Guides/Non-Realtime-Synthesis.html


SuperCollider の GitHub リポジトリに置かれている、録音についての資料
(ただし、「不幸にも、一番重要な非リアルタイム録音に関しては書きかけです」とあり、 NRT に関してはほぼ記載なし)

「いいね!」 2