<<home  <<previous  next>>

spectfilter~ object






After prototyping a parametric Fourier filter in a Max/MSP patch, I developed C code for that process, which can be found at the bottom of this page. Combined with the FFT lib I had written earlier, it is plugged into one single Max/MSP external object for further testing, demonstration and musical employment. The object description is followed by links where you can download the object or a demo application.


spectfilterobject


The most significant improvement in the single-object implementation is a user-controllable FFT size in runtime. Since the resolution/latency trade-off demands a choice of FFT sizes, according to programme material, this really was a requirement for the new object. In addition to the multiband filter as described on the previous page, the object has an extra single band filter spectrum, in serial connection. It's function is to confine the multiband output to a specific region of the spectrum, if so desired.

In it's simplest form, a patch using the spectfilter~ object would look like this:


simplepatch



Round the spectfilter~ object I figured a test/demo patch, where the object is submitted to intense manipulation by modulators. Next to audio output, the object provides filter spectrum index and y-value, which in the demo patch are employed for meticulous visual inspection.



spectfilterscreen



The test input signal is pink noise, but mic/line signals are of course also enabled, because they were the reason for doing spectfilter~ in the first place. It turns out that the object can fluently reflect the weirdest of modulations, extending it's musical versatility beyond static filter jobs.

In the demo patch, the filter spectrum is dynamically stored into a named buffer~ object which is viewed via a waveform~ object. This way, the action of the filter can be scrutinized in realtime, much more detailed than with a scope~ object. Settings are available to zoom in on the lower spectrum range, where things are more critical than higher up. The lowest octave interval in a discrete spectrum comprises only one FFT bin, one sample, while the highest octave has half the total spectrum width. Below, the lowest 1/16 portion of a multiband filter spectrum is represented, for a 2048 point FFT. The bars represent individual FFT bin values:


spectrumdetail


Because of the relatively artefact-free resynthesis routine, such narrow filterbands are not directly problematic, soundwise. The blocky shapes could make you suspicious, but overlap and proper windowing of FFT frames will make a smooth filter curve out of this, no matter how discontinuous it may look. The real hassle is, that filterbands comprising just one FFT bin or even less, can no longer correctly represent logarithmic bandwidth and distribution.

On the page Fourier Filter, I had made the assessment that logarithmically distributed separate filterbands can be computed with sufficient accuracy around bin 30 and higher. From my animated filter spectrum I can now see that this is indeed the case. My desire to make filterbands extremely narrow complicates things further. The figure below does not even show the narrowest filterband setting in spectfilter~, but even here it is clear that bands below bin 30 are totally beyond control, due to lack of resolution:


spectrumdetail2


The figure also shows the frequency at bin 30: 646 Hz. (I set the cursor in the spectrum representation to request the frequency for a bin). So, everything below that frequency can not properly follow the logarithmic scheme. For smaller FFT sizes, things are worse, while for larger FFT sizes it's better.

From these figures, you would say that the spectfilter~ object is pretty useless in musical practice, uh? I had hoped for a method to refine a spectrum's resolution by upsampling. Then I met with the Stretchable but Indivisible Dirichlet Kernel that is the discrete Fourier transform of a pure sinusoid. Upsampling refines the analysis, but can not help with sharper filtering. This is the relentless reality of spectral filtering. Details on this topic are on the page Windowing & Frequency Domain Filtering.

Despite this ever-present bottleneck at the low frequencies, spectral filtering has it's attractions. Specially when you can quickly choose an appropriate FFT size for the filter job at hand. Below is an example with two filter lobes, done with 512 point FFT (that is 11 milliseconds at sampling rate 44K1):


spectrumdetail3


If you want to chop your input signal into a couple of dozen peaks, set a long FFT. Though resizing the FFT can even be done while audio is on, increasing the size (thus increasing latency) will introduce a short moment of silence, so you still have to choose an appropriate moment for the switch. The filter spectrum shown below, done with FFT size 8192, could not so easily be applied with time domain filters, certainly not at the price of just a couple percent CPU time:


spectrumdetail4


Such filter settings effectuate specific musical effects. With noisy input, the filter becomes an instrument in it's own right. And since the filterspectrum is so easily recomputed, to the point of continuous modulation, spectfilter~ could be exploited in electronic music composition indeed. That is not what I made it for, but the lines between sound processing and synthesis are very thin here.


circular spectrum

In the spectfilter~ object, the filter spectrum is handled as a periodical phenomenon. This enables a continuous shift of filter peaks towards the low end or high end of the spectrum without ever reaching a limit. The peaks just wrap around. The effect is reminiscent of the famous Shepard tones, but with spectfilter~ the sound can be pure or diffuse, depending on filter settings.

Listen to pink noise filtered with a descending multiband filter (quicktime player)


To optimally exploit this unbounded filterband shifting, I had to find a method which can handle automated shifting and manipulation by hand simultaneously. Something like a turntable, where you can play around with speed and direction of the machine. Conveniently, Max/MSP has a dial object that can do full circles of 360 degrees, with circular mouse tracking. Ideal for this purpose. For Max fans, here is a patch and explanation:



wrapint


The essence is in diffentiating the output values of a sawtooth modulator wave (phasor~), and with these values, increment/decrement an integrator which is built around the graphical interface object. That integrator must wrap to zero after reaching the maximum parameter value, which is 1023 in my case. This can be realised by using a bitwise-and mask, but only when the parameter is of unsigned integer type and it's maximum value + 1 is a power of 2. Anti-clockwise wrapping is also possible, if a constant addition of maximum value + 1 is included in the loop.

The cyclic nature of frequency position applies to multiband and singleband filter in the spectfilter~ object. The other parameters controlling bandwidths and number of filterbands do not wrap around. Parameter values run from 0 till 1023 inclusive and are 'dimensionless', they do not mean anything physical. It is just one of many standard parameter ranges, and my personal favourite.

If you are on Intel Mac, you can play around with spectfilter~ yourself. The object is available, together with the demo patch in the form of spectfilter~.maxhelp. If you do not own Max/MSP, you can download spectfilter~demo.app, which has Max Runtime built in. Max 4.6 users could download both zip files, one for the object and the other for the demo, since spectfilter~.maxhelp was created in Max5 format.


downloads

spectfilter~object.zip, 68 KB, contains spectfilter~.mxo + spectfilter~.maxhelp version 1.0, for Max/MSP users on Intel Mac


spectfilter~app.zip, 6.4 MB, contains spectfilter~demo.app version 1.0, independent application for Intel Mac






Part of the spectfilter~ object is a C function that computes a filterspectrum with logarithmic shape and distribution of filter lobes, shown below. It is a refinement of the procedure outlined in the prototype patch on the previous page. The minimum bandwidth is no longer a function of actual FFT size, but a function of the maximum FFT size permitted by the object's buffers. This helps in keeping filter characteristics uniform across FFT sizes. The object incorporates FFT and window/overlap functions which are not shown here. Separate pages on these topics are in the FFT section.



#define LOGMINFFTSIZE 9                   // radix 2 logarithm of minimum FFT size used
#define LOGMAXFFTSIZE 13                  // radix 2 logarithm of maximum FFT size used
#define LOGOVERLAP 2                      // radix 2 logarithm of FFT overlap factor
#define PARRANGE 1024.                    // user parameter range / maximum value, float
#define LIMITEXPONENT -24.                // must be a float
#define PARFACTOR (1./PARRANGE)

void computespectrumbands(
    typfloat *spectrumbuffer,             // filter spectrum is stored for repeated use
    typfloat *log2table,                  // table with base 2 logarithms
    typfloat bandwidth,
    typfloat freqposition,
    typfloat bands,
    unsigned int spectrumsize)
{
    unsigned int n = spectrumsize - 1;
    typfloat basefactor = 1. / ((float)(LOGMAXFFTSIZE-1));              // fixed base factor
    typfloat twopi = asin(1.) * 4.;
    typfloat logcurvefactor, logcurve, freqoffset, logchirp, magnitude;
    typfloat powbandwidth, subliminal;
   
    bandwidth = clipfourierparameter(bandwidth) * PARFACTOR;            // clip and scale
    freqposition = clipfourierparameter(freqposition) * PARFACTOR;   
    bands = clipfourierparameter(bands) * PARFACTOR;               
   
    magnitude = 1. + bandwidth;                                  // compensation factor
    powbandwidth = pow((bandwidth * 3), 3);                      // change response curve      
    subliminal = pow(2, (LIMITEXPONENT / powbandwidth));               
    logcurvefactor = (((3. - basefactor) * bands) + basefactor) * twopi;
    freqoffset = (float)log2table[spectrumsize-1] * logcurvefactor;               
    freqposition = -(freqposition * twopi) - freqoffset;
       
    *spectrumbuffer++ = 0.;                                            // pass over DC bin
    log2table++;                                           
   
    while(n--)
    {
        logcurve = (*log2table++ * logcurvefactor) + freqposition;     // compute logarithms
        logchirp = (cos(logcurve) + 1.) * 0.5;                         // compute chirp
        if(logchirp > subliminal) logchirp = pow(logchirp, powbandwidth);       
        else logchirp = 0.;                                           
        logchirp *= 1.5;                                               // raise the peak(s)
        if (logchirp > 1.) logchirp = 1.;                              // clip peak(s)
        *spectrumbuffer++ = logchirp * magnitude;                      // apply compensation
    }
}
// end of computespectrumbands function definition