(
// Load buffers
Buffer.readChannel(s, "/home/lcoogan/snd/live/2025-04-26.Basic_City_Brewery/crawling-stems/lead.wav", channels:[0], bufnum: 0);
Buffer.readChannel(s, "/home/lcoogan/snd/live/2025-04-26.Basic_City_Brewery/crawling-stems/perc mel solo.wav", channels:[0], bufnum: 1);
Buffer.readChannel(s, "/home/lcoogan/snd/live/2025-04-26.Basic_City_Brewery/crawling-stems/bass.wav", channels:[0], bufnum: 2);
Buffer.readChannel(s, "/home/lcoogan/snd/live/2025-04-26.Basic_City_Brewery/crawling-stems/drums.wav", channels:[0], bufnum: 3);
Buffer.readChannel(s, "/home/lcoogan/snd/live/2025-04-26.Basic_City_Brewery/crawling-stems/break.wav", channels:[0], bufnum: 4);
)

(
// Granulator
Ndef(\granulator, { |bufnum=0, tFreq=20, overlap=2, rate=1, posStart=0.0,
	tFreqMF=0, tFreqMD=0, rateMF=0, rateMD=0,
	posRateMF=0, posRateMD=0, granDur=4|

	var samplePhasor, phasor, gran, env, bufrd;
	var bufFrames = BufFrames.ir(bufnum);
	var t;

	var tFreqMod = SinOsc.ar(tFreqMF, Rand(0.0, 2pi)) * tFreqMD;
	var rateMod = SinOsc.ar(rateMF, Rand(0.0, 2pi)) * rateMD;
	var posRateMod = SinOsc.ar(posRateMF, Rand(0.0, 2pi)) * posRateMD;

	var playTrig = Impulse.ar(\playTrFr.kr(0.5));

	tFreq = tFreq + tFreqMod.dup;
	rate = rate + rateMod.dup;

	t = Impulse.ar(tFreq);

	samplePhasor = EnvGen.ar(Env([0, 0, bufFrames], [0, bufFrames / SampleRate.ir], curve: 0), gate: playTrig);
	phasor = EnvGen.ar(Env([posStart, posStart, 1], [0, granDur], curve: 0), gate: playTrig);

	bufrd = BufRd.ar(1, bufnum, samplePhasor, interpolation: 4);
	bufrd = LeakDC.ar(bufrd);

	gran = GrainBufJ.ar(
		numChannels: 1,
		trigger: t,
		dur: tFreq.reciprocal * overlap,
		sndbuf: bufnum,
		rate: rate,
		pos: phasor + posRateMod.dup,
		interp: 2,
		pan: 0,
		envbufnum: -1,
		maxGrains: 512,
		loop: 0
	) * EnvGen.ar(Env.perc(0.001, granDur * \grainGate.kr(1), curve: \granCurve.kr(-4)), gate: playTrig);

	Mix([
		bufrd * \dry.kr(1.0),
		gran  * \wet.kr(0.5)
	])
});

Ndef(\granulator).addSpec(
	\overlap, [0.001, 40, \exp],
	\rate, [-2, 2],
	\posStart, [0.0, 1.0],
	\tFreq, \widefreq,
	\tFreqMF, [0.0,80],
	\tFreqMD, [0.0,20],
	\rateMF, [0.0,80],
	\rateMD, [0.0,2],
	\posRateMF, [0.0,80],
	\posRateMD, [0.0,2],
	\dry, [0.0,1],
	\wet, [0.0,1],
	\playTrFr, [0.0,1],
	\granDur, [0.0,8],
	\granCurve, [-10,10],
	\grainGate, [0,1],
	\bufnum, [0, 4, \lin, 1]
).edit;
)

(
// MIDI Mappings for Channel 2 (MIDI channels are zero-indexed)
MIDIdef.cc(\gran_bufnum, { |val, num, chan|
	if(chan == 1, { Ndef(\granulator).set(\bufnum, val.linlin(0,127,0,4).round(1)) });
}, ccnum: 1);

MIDIdef.cc(\gran_posStart, { |val, num, chan|
	if(chan == 1, { Ndef(\granulator).set(\posStart, val.linlin(0,127,0,1)) });
}, ccnum: 2);

MIDIdef.cc(\gran_tf, { |val, num, chan|
	if(chan == 1, { Ndef(\granulator).set(1\tFreq, val.linexp(0,127,0.1,100)) });
}, ccnum: 3);

MIDIdef.cc(\gran_overlap, { |val, num, chan|
	if(chan == 1, { Ndef(\granulator).set(\overlap, val.linexp(0,127,0.01,20)) });
}, ccnum: 4);

MIDIdef.cc(\gran_rate, { |val, num, chan|
	if(chan == 1, { Ndef(\granulator).set(\rate, val.linlin(0,127,-2,2)) });
}, ccnum: 5);

MIDIdef.cc(\gran_posRateMD, { |val, num, chan|
	if(chan == 1, { Ndef(\granulator).set(\posRateMD, val.linlin(0,127,0,2)) });
}, ccnum: 6);

MIDIdef.cc(\gran_dry, { |val, num, chan|
	if(chan == 1, { Ndef(\granulator).set(\dry, val.linlin(0,127,0,1)) });
}, ccnum: 7);

MIDIdef.cc(\gran_wet, { |val, num, chan|
	if(chan == 1, { Ndef(\granulator).set(\wet, val.linlin(0,127,0,1)) });
}, ccnum: 8);
)
