(
// ========== Spectral SynthDef with PV_Control and expanded modulation ==========
SynthDef(\pv16knob, {
	arg bufnum, fftSize = 1024,
		amp = 0.3, smearAmt = 5, randRate = 6, binScrambleAmt = 0.5,
		pvRouting = 0, grainSize = 0.1,
		delTime = 0.3, delFeedback = 0.4, verbMix = 0.2,
		lfoFreq = 0.2, bitcrushAmt = 0.0, distAmt = 1.0,
		ringFreq = 200, freezeAmt = 0.5,
		transpose = 1.0, filterCutoff = 8000;

	var in, chain, dry, fx, delay, verb;
	var chain0, chain1, chain2, chain3, chain4, chain5;
	var lfo = SinOsc.kr(lfoFreq).range(0.1, 1.0);
	var controlBuf = LocalBuf(fftSize); // for PV_Control output

	// Audio input and bitcrushing
	in = PlayBuf.ar(1, bufnum, BufRateScale.kr(bufnum) * transpose, loop: 1);
	in = Decimator.ar(in, (1 - bitcrushAmt).linexp(0, 1, 44100, 1000), 8 - (bitcrushAmt * 7));

	// FFT and spectral freeze
	chain = FFT(LocalBuf(fftSize), in);
	chain = PV_MagFreeze(chain, freezeAmt);

	// Spectral processing chains
	chain0 = PV_BinScramble(PV_MagSmear(PV_RandComb(chain, 0.1, Impulse.kr(randRate)), smearAmt + lfo), binScrambleAmt, 0);
	chain1 = PV_BrickWall(chain, lfo.range(0.05, 0.2));
	chain2 = PV_BinShift(chain, lfo * 20, 0.5);
	chain3 = PV_BinWipe(chain, lfo);
	chain4 = PV_SpectralEnhance(chain, 0.8, 1.5, 0.2);

	// PV_Control dynamic bin suppression
	PV_Control.new(chain, controlBuf,
		thresh: 0.5,
		mulFactor: 0.8,
		limiter: 10,
		attackReleaseFrames: 50,
		sustainZeroFrames: 100,
		waitGoFrames: 50,
		tripCount: 2,
		tripBlockFrames: 500,
		highestBin: 400
	);
	chain5 = PV_MagMul(chain, controlBuf);

	// Select branch
	chain = SelectX.kr(pvRouting.clip(0, 5), [chain0, chain1, chain2, chain3, chain4, chain5]);
	dry = IFFT(chain);

	// Post-PV grain, distortion, filter
	dry = TGrains.ar(2, Impulse.ar(20), bufnum, 1, Phasor.ar(0, 1, 0, BufFrames.ir(bufnum)), grainSize, 0, 0.1, 1);
	dry = Ringz.ar(dry, ringFreq, 0.2);
	dry = (dry * distAmt).tanh;
	dry = LPF.ar(dry, filterCutoff);

	// Delay and reverb
	delay = CombC.ar(dry, 1, delTime, delFeedback);
	verb = FreeVerb.ar(dry, verbMix, 0.8, 0.3);

	// Output
	fx = Mix([dry, delay, verb]);
	Out.ar(0, fx.dup * amp);
}).add;
)
