(
// Generate a terrain table
~terrain = Buffer.alloc(s, 1024, 1);
~terrain.sine1([1, 0.5, 0.3, 0.2, 0.1], true);

SynthDef("fm_pulsar_terrain", {
	arg freq=440, gate=1, amp=0.2, att=0.01, dec=0.05, susl=0.6, rel=1.0, curve=(-4),
	    cf=2, mf=2, kel=0.5, gain=0.1, outBus=0;
	var e, vel_, carfreq, modfreq, mInd, overlap, d, t, tblPos, a;

	vel_ = kel.clip(0,1.0).linlin(0, 1, 2, 0.7);

	e = EnvGen.kr(
		Env.adsr(att * vel_, dec * vel_, susl, rel, curve: curve),
		gate: gate,
		doneAction:2
	) * kel;

	carfreq = freq * cf + LFNoise1.kr(3).range(0,50);
	modfreq = carfreq * mf + LFNoise1.kr(3).range(0,100);

	t = Dust.ar(freq);
	mInd = e.pow(3.3);
	overlap = 1.1 - e.pow(0.5);
	d = overlap / freq;
	tblPos = modfreq.linlin(0, 8000, 0, 1).wrap(0, 1);

	a = Mix([
		SinOsc.ar(freq),
		GrainBuf.ar(1, t, d, ~terrain, tblPos, carfreq, 0, 2)
	]);

	a = LeakDC.ar(a * e);
	a = FreeVerb.ar(a, 0.33, 1);

	Out.ar(outBus, a.dup * gain);
}).add;
)


(
~notes = Array.newClear(128);

MIDIdef.noteOn(\noteOnTerrain, { |vel, nn, chan, src|
	if (chan == 0) {
		var freq = nn.midicps;
		var amp = vel.linexp(1,127,0.01,0.3);
		var p = ~ccVals;  // current cc values

		~notes[nn] = Synth(\fm_pulsar_terrain, [
			\freq, freq,
			\amp, amp,
			\att,   p[0].linlin(0,1, 0.001, 0.3),
			\dec,   p[1].linlin(0,1, 0.001, 0.3),

			\susl,  p[2].linlin(0,1, 0.1, 1.0),
			\rel,   p[3].linlin(0,1, 0.1, 8.0),
			\cf,    p[4].linlin(0,1, 0.5, 5.0),
			\mf,    p[5].linlin(0,1, 0.5, 5.0),

			\curve, p[6].linlin(0,1, -8, 8),
			\gain,  p[7].linlin(0,1, 0.01, 0.5),
			\kel,   p[8].linlin(0,1, 0.1, 1.0)
		]);
	}
});

MIDIdef.noteOff(\noteOffTerrain, { |vel, nn, chan|
	if (chan == 0 and: { ~notes[nn].notNil }) {
		~notes[nn].set(\gate, 0);
		~notes[nn] = nil;
	}
});
)



(
var paramName, adjustedValue;

// Init array to store CC values
~ccVals = Array.fill(16, 0.5);  // defaults to 0.5 (midpoint)

// Assigning names to the parameters
~paramNames = [
	"Attack", "Decay", "Sustain Level", "Release",
	"Carrier Frequency", "Modulation Frequency", "Envelope Curve"
	"Gain", "Kelvin", "Unused", "Unused", "Unused",
	"Unused", "Unused", "Unused", "Unused"
];

MIDIdef.cc(\ccTerrainKnobs, { |val, num, chan, src|
	if (chan == 0 and: { num < 16 }) {
		// Map CC value (0–127) to the range [0.0, 1.0]
		~ccVals[num] = val.linlin(0, 127, 0.0, 1.0);

		// Print the parameter name and the adjusted value
		paramName = ~paramNames[num];
		adjustedValue = ~ccVals[num].round(2); // Rounded for readability
		("CC " ++ num ++ " (" ++ paramName ++ ") = " ++ val ++ " → " ++ adjustedValue).postln;
	}
});
)

