Cepstrum Quefrency analysis and liftering


Fourier analysis of the shape of a signal's log-power spectrum. Because this transform applies to magnitudes and not the phases (i.e. it ignores half of the data), the cepstral Buffer is half the size of the FFT Buffer.


Forward transform (from spectral domain to cepstral domain):

Cepstrum(cepbuf, fftchain) 

cepbuf - a Buffer of half the size as the original FFT Buffer.

fftchain - a standard FFT chain as produced by the FFT UGen or various PV_ UGens.

The output is a cepstral "chain" which can be treated in many regards like an FFT chain: you can apply PV_ transforms, for example.

"Reverse" transform (from cepstral domain back to spectral domain):

ICepstrum(cepchain, fftbuf)

cepchain - a cepstral chain, such as that produced by Cepstrum or any PV_ UGens that have operated on a cepstrum.

fftbuf - The FFT buffer, typically the one that was the original source used for the forward transform.

The output is a standard FFT chain.


Very loosely speaking the cepstrum is an "FFT of an FFT". There are various forms of cepstral analysis - this UGen applies what is sometimes called the "power cepstrum": a Fourier analysis of the spectral log-magnitudes. (The log is important because that's part of what helps separate source-and-filter components in this procedure.) The reverse, ICepstrum, performs IFFT and exponentiates the result, to derive new magnitudes to be stored back into the fftbuf (phases are left untouched; they are essentially meaningless).


See also: MFCC, FFT


Example


s.boot;


~fftbuf = Buffer.alloc(s, 2048);

~cepbuf = Buffer.alloc(s, 1024);


(

x = {

var son, chain, cepsch;

// You might like to try uncommenting these different source signals:

son = WhiteNoise.ar;

//son = Impulse.ar(150);

//son = SinOsc.ar(440);

//son = SinOsc.ar([150, 1450, 7203, 12010]).mean;

// Or these filters:

son = MoogFF.ar(son, 5350) * 5;

//son = son + DelayN.ar(son, 0.003, 0.003);

chain = FFT(~fftbuf, son, wintype: 1);

cepsch = Cepstrum(~cepbuf, chain);

// PV_BrickWall can act as a low-pass filter, or here, as a wol-pass lifter...

// ...in practical terms, produces a smoothed version of the spectrum

cepsch = PV_BrickWall(cepsch, -0.95);

ICepstrum(cepsch, ~fftbuf);

// We'll stop the unit after 1 second, the results are visible quickly enough

Line.kr(1,0,1, doneAction: 2);

son * 0.1

}.play;

)


// A convenience function to plot magnitudes:

~plotmags = {|buf, lbl| buf.loadToFloatArray(action:{|data| {data[0,2..]  .max(0.000001)  .log.normalize  .plot(lbl)}.defer})};


~plotmags.(~cepbuf, "cepstrum");

~plotmags.(~fftbuf, "liftered (smoothed) spectrum");


[~fftbuf, ~cepbuf].do(_.free);


In the above example, the cepstrum plot itself may be difficult to interpret (although you can clearly see what the PV_BrickWall unit has done), but the liftered spectrum should look reasonably smooth and show the main peaks and troughs of the spectrum (try running the example without ICepstrum to see the unsmoothed version).


Note: the cepstral analysis here includes the DC bin and ignores the Nyquist bin, so as to ensure that the resulting number of bins is a power of two.