CLASS:: Squiz
summary:: Wave squeezer. Maybe a kind of pitch shifter.
categories:: UGens>Filters>Nonlinear
related:: Classes/WaveLoss

DESCRIPTION::
A simplistic pitch-raising algorithm. It's not meant to sound natural; its sound is reminiscent of some weird mixture of filter, ring-modulator and pitch-shifter, depending on the input.
The algorithm works by cutting the signal into fragments (delimited by upwards-going zero-crossings) and squeezing those fragments in the time domain (i.e. simply playing them back faster than they came in), leaving silences imbetween. 
All the parameters apart from memlen can be modulated.

This UGen is dedicated to Suburban Base Records. (It doesn't sound like them, but was half-inspired by them.)


CLASSMETHODS::

METHOD:: ar
argument:: in

argument:: pitchratio
the ratio by which pitch will be raised (e.g. pitchratio of 2 will raise by one octave). Only upwards pitch-shifts are possible so a value below 1 will have no effect.
argument:: zcperchunk
indicates how many positive-going zero-crossings are used to delimit a chunk. (Only positive integer values make sense.)
argument:: memlen
the amount (in seconds) of memory the unit allocates internally to remember each fragment, i.e. the longest a given fragment can become. The default should be fine for most signals. Raising it higher will use more real-time memory and probably won't sound very different (especially if zcperchunk is low).

METHOD:: kr
same as .ar
argument:: in
argument:: pitchratio
argument:: zcperchunk
argument:: memlen
argument:: mul
argument:: add


EXAMPLES::

In both these examples, moving the mouse left/right varies pitchratio, while moving it up/down varies  zcperchunk. If you watch the scope it illustrates fairly well what's happening.

code::
s = Server.internal;
s.boot;
// On a simple sine wave
x = {Squiz.ar(SinOsc.ar, MouseX.kr(1, 10, 1), zcperchunk: MouseY.kr(1, 10), mul:0.1).dup}.play(s);
x.free;
// Scope is helpful for visualising what's going on
x = {Squiz.ar(SinOsc.ar, MouseX.kr(1, 10, 1), zcperchunk: MouseY.kr(1, 10), mul:0.1).dup}.scope;
x.free;

// On a sample of some sort - choose one...
b = Buffer.read(s,"sounds/a11wlk01.wav");
b = Buffer.read(s,"sounds/amenfast.wav");
(
x = {
	var sig;
	sig = PlayBuf.ar(1, b.bufnum, BufRateScale.kr(b.bufnum) * 0.5, startPos: 92898, loop: 1);
	Squiz.ar(sig, MouseX.kr(1, 100, 1), zcperchunk: MouseY.kr(1, 10), mul:0.5).dup
}.play(s);
)
x.free;
b.free;

// Let's make it a bit more complicated. By analysing the signal we can change the Squiz sound algorithmically.
s.boot;
b = Buffer.read(s,"sounds/amenfast.wav");
c = Buffer.alloc(s, 512);
(
x = {
	var sig, chain, centroid, hicent, locent, fraccent, fraccentl, heaviness, heaviness2, kick2trig, kick2;
	sig = PlayBuf.ar(1, b.bufnum, BufRateScale.kr(b.bufnum) * 0.5, /*startPos: 92898, */loop: 1);
	// kick2 to emphasise the main downbeats
	kick2trig = Impulse.kr(BufDur.kr(b.bufnum).reciprocal * 4);
	kick2 = SinOsc.ar(50 + EnvGen.ar(Env.perc(0.001, 0.01, 1000, -10), kick2trig));
	kick2 = EnvGen.ar(Env.perc, kick2trig) * kick2 * 0.3;
	chain = FFT(c.bufnum, sig);
	//centroid = FFTCentroid.kr(chain).log.max(0.000001);
	centroid = FFTPercentile.kr(chain, 0.9);//.log.max(0.000001);
	hicent =     Amplitude.kr(centroid, 0, 10);
	locent = 10000 - Amplitude.kr(10000 - centroid, 0, 10);
	fraccent = ((centroid - locent) / (hicent - locent)).min(1).max(0);
	fraccentl = Latch.kr(fraccent, Onsets.kr(chain));
	Out.kr(0, fraccentl);
	// fraccent.poll;
	//heaviness = MouseX.kr(0,1);
	//heaviness2 = MouseY.kr(0,1);
	heaviness  = LFPar.kr(0.016, 0, 0.5, 0.5);
	heaviness2 = LFPar.kr(0.016, 1, 0.5, 0.5);
	// we don't go really mental until we've got going
	# heaviness, heaviness2 = [heaviness, heaviness2] * Line.kr(0.1, 1, 100);
	kick2 + Squiz.ar(sig, 
		(   fraccentl  * LFNoise2.kr(0.1).exprange(1, 100) * heaviness) + 
		((1-fraccentl) * LFNoise2.kr(0.1).exprange(1, 100) * heaviness2) +
		0.8, 
		zcperchunk: LFNoise2.kr(0.1).range(1, 10), mul:0.5).dup
	//sig
}.play(s);
)
//s.scope5
x.free;
b.free;
::

