25/05/2019 -
The Cycling Software Development Kit is a library that allows, once added among the packages,
to program a new object for Max. I have read the official documentation and have done some tests;
this article is the summary of what I concluded.

The idea is to create an object in C language that filters a white noise (or even an alternative noise) around a specific note, obtaining a sound as in tune as possible.

Starting from the mathematical model of a low pass filter, I have defined the simplest formulation that allowed to isolate a fairly narrow band of frequencies and with a fairly clean cut: it is of a low pass filter with an important resonance on the cutoff frequency, which I will obtain through the following formula:

where

The object will be called

Here's how I wrote the

I have included the libraries necessary for the functioning of an audio object, and I have defined the object class, calling it

After the definition of the temporary variables that are needed in the execution of the function, values ar loaded (in these newly created spaces) from the parameter set of the object, and a value called

To implement its operation, I used the temporary variables defined at the beginning of the function, and I structured a

To make this key step easier to understand, I have summarized the operation in a diagram.

The

The cutoff frequency is calculated starting from the midi-note, following the simple formula

and its data is stored in the

A

and the code with which I implement it is very simple

Some routine operations are performed, in order to allocate the memory necessary for the operation of the object, and the inlets (in our case 2) and the outlets are defined (in our case 1).

Some parametric values (such as

The software works, and once compiled it has successfully generated the

To test this, I made a simple patch where the noise produced by a

The result is a metallic sound and definitely not suitable for real use, but I am satisfied with how much I got because, after all, that's what I expected. This is what the spectroscope shows during notefilter operation

The complete

download c file

The idea is to create an object in C language that filters a white noise (or even an alternative noise) around a specific note, obtaining a sound as in tune as possible.

Starting from the mathematical model of a low pass filter, I have defined the simplest formulation that allowed to isolate a fairly narrow band of frequencies and with a fairly clean cut: it is of a low pass filter with an important resonance on the cutoff frequency, which I will obtain through the following formula:

*s*,*a1*and*a2*are coefficients to be calculated each time, based on even at the required cut-off frequency.The object will be called

*notefilter~*, and the advice to start writing its behavior in C is to clone the folder of one of the audio object examples (*Max/Packages/max-sdk/source/audio*) and clear the*.c*file, renaming appropriately all files it contains. In this way you quickly obtain a project already structured with libraries in their place. Personally I worked with Max 8, using Visual Studio in its free version. If you are using (as it is likely to be) 64-bit Max, I remind you to select that mode in the Visual Studio drop-down menu!Here's how I wrote the

*notefilter.c*file, step by step.I have included the libraries necessary for the functioning of an audio object, and I have defined the object class, calling it

*notefilter_class*
#include "ext.h"

#include "ext_obex.h"

#include "z_dsp.h"

#include <math.h>

void *notefilter_class;

specificandone poi i parametri, il cui primo (#include "ext_obex.h"

#include "z_dsp.h"

#include <math.h>

void *notefilter_class;

*t_pxobject l_obj*) รจ indispensabile per la corretta definizione dell'oggetto
typedef struct _notefilter

{

t_pxobject l_obj;

double freq_cut; // cutoff frequency

double l_a1; // coefficient 1

double l_a2; // coefficient 2

double l_a1p; // previous coefficient 1

double l_a2p; // previous coefficient 2

double l_ym1; // previous output sample

double l_ym2; // previous output sample

double l_fqterm; // frequency coefficient

double l_resterm; // coefficient of resonance

double l_2pidsr; // 2 pi

short l_fcon; // signal related to the frequency input

} t_notefilter;

Finally I have declared all the headers of the functions that I am going to implement, whether they are
actually methods of the object class or not.
{

t_pxobject l_obj;

double freq_cut; // cutoff frequency

double l_a1; // coefficient 1

double l_a2; // coefficient 2

double l_a1p; // previous coefficient 1

double l_a2p; // previous coefficient 2

double l_ym1; // previous output sample

double l_ym2; // previous output sample

double l_fqterm; // frequency coefficient

double l_resterm; // coefficient of resonance

double l_2pidsr; // 2 pi

short l_fcon; // signal related to the frequency input

} t_notefilter;

void notefilter_dsp64(t_notefilter *x, t_object *dsp64, short *count, double samplerate, long maxvectorsize, long flags);

void notefilter_perform64(t_notefilter *x, t_object *dsp64, double **ins, long numins, double **outs, long numouts, long sampleframes, long flags, void *userparam);

void notefilter_int(t_notefilter *x, int f);

void notefilter_calc(t_notefilter *x);

void *notefilter_new(double freq, double reso);

At this point the implementation of the actual functions begins, the first of which is called
void notefilter_perform64(t_notefilter *x, t_object *dsp64, double **ins, long numins, double **outs, long numouts, long sampleframes, long flags, void *userparam);

void notefilter_int(t_notefilter *x, int f);

void notefilter_calc(t_notefilter *x);

void *notefilter_new(double freq, double reso);

*ext_main*and takes care of physically creating the object through*class_new*, specifying the name, the methods of construction and destruction and the size (and other parameters that do not come used because unnecessary or obsolete). The same function also takes care of adding the methods to the class: the functions that until now are independent become methods via*class_addmethod*, which also requires you to pass a name to recognize that method within the function.*class_register*finally allows you to register the class defined in the*ClassBox*of Max.
void ext_main(void *r)

{

t_class *c;

c = class_new("notefilter~",(method)notefilter_new, (method)dsp_free,

sizeof(t_notefilter), 0L, A_DEFFLOAT, A_DEFFLOAT, 0);

class_addmethod(c, (method)notefilter_dsp64, "dsp64", A_CANT, 0);

class_addmethod(c, (method)notefilter_int, "int", A_LONG, 0);

class_dspinit(c);

class_register(CLASS_BOX, c);

notefilter_class = c;

return 0;

}

The next function, {

t_class *c;

c = class_new("notefilter~",(method)notefilter_new, (method)dsp_free,

sizeof(t_notefilter), 0L, A_DEFFLOAT, A_DEFFLOAT, 0);

class_addmethod(c, (method)notefilter_dsp64, "dsp64", A_CANT, 0);

class_addmethod(c, (method)notefilter_int, "int", A_LONG, 0);

class_dspinit(c);

class_register(CLASS_BOX, c);

notefilter_class = c;

return 0;

}

*notefilter_dsp64*, allows you to store the previous coefficients in special variables, and to store the input value provided at that instant; this step is added to the*dsp chain*, the list that specifies the signal processing functions defined by the object for its signals.
void notefilter_dsp64(t_notefilter *x, t_object *dsp64, short *count, double samplerate, long maxvectorsize, long flags)

{

x->l_2pidsr = (2.0 * PI) / samplerate;

notefilter_calc(x);

x->l_a1p = x->l_a1;

x->l_a2p = x->l_a2;

x->l_fcon = count[1];

dsp_add64(dsp64, (t_object *)x, (t_perfroutine64)notefilter_perform64, 0, NULL);

}

The core of the program lies in the next function, {

x->l_2pidsr = (2.0 * PI) / samplerate;

notefilter_calc(x);

x->l_a1p = x->l_a1;

x->l_a2p = x->l_a2;

x->l_fcon = count[1];

dsp_add64(dsp64, (t_object *)x, (t_perfroutine64)notefilter_perform64, 0, NULL);

}

*notefilter_perform64*, which requires perhaps a slightly more detailed analysis.After the definition of the temporary variables that are needed in the execution of the function, values ar loaded (in these newly created spaces) from the parameter set of the object, and a value called

*scale*, given by the sum of the two coefficients increased of one is defined.
void notefilter_perform64(t_notefilter *x, t_object *dsp64, double **ins, long numins, double **outs, long numouts, long sampleframes, long flags, void *userparam)
{

t_double *in = ins[0];

t_double *out = outs[0];

t_double freq = x->freq_taglio;

double a1 = x->l_a1;

double a2 = x->l_a2;

double ym1 = x->l_ym1;

double ym2 = x->l_ym2;

double val, scale, temp, resterm;

scale = 1.0 + a1 + a2;

The software now goes through all the available samples, processing the quantized value
according to the mathematical formulation I have chosen. t_double *in = ins[0];

t_double *out = outs[0];

t_double freq = x->freq_taglio;

double a1 = x->l_a1;

double a2 = x->l_a2;

double ym1 = x->l_ym1;

double ym2 = x->l_ym2;

double val, scale, temp, resterm;

scale = 1.0 + a1 + a2;

To implement its operation, I used the temporary variables defined at the beginning of the function, and I structured a

*while*loop to loop through the samples.To make this key step easier to understand, I have summarized the operation in a diagram.

while (sampleframes--) {

val = *in++;

temp = ym1;

ym1 = scale * val - a1 * ym1 - a2 * ym2;

ym2 = temp;

*out++ = ym1;

}

x->l_ym1 = ym1;

x->l_ym2 = ym2;

}

In Max's objects there can be different inputs, which accept data of different types.
In this case, the first input will be reserved for the signal to be filtered (noise, if you want
use val = *in++;

temp = ym1;

ym1 = scale * val - a1 * ym1 - a2 * ym2;

ym2 = temp;

*out++ = ym1;

}

x->l_ym1 = ym1;

x->l_ym2 = ym2;

}

*notefilter*as a synthesizer), while the second will receive an integer relative to the note midi around whose frequency you want to filter.The

*notefilter_int*function is in charge of defining the behavior of the object upon receiving that integer.The cutoff frequency is calculated starting from the midi-note, following the simple formula

and its data is stored in the

*cut_freq*parameter.A

*post*function (syntax analogous to the common*printf*function of C) allows you to print in the max console a message containing the note and frequency information in question. The function finally calls*notefilter_calc*, a method that performs the calculation of the coefficients, which I will discuss shortly.
void notefilter_int(t_notefilter *x, int f)

{

x->freq_taglio = (440.0 / 32.0) * pow(2.0,((f - 9.0) / 12.0));

post("nota n. %i - frequenza: %f",f, x->freq_taglio);

notefilter_calc(x);

}

The coefficients {

x->freq_taglio = (440.0 / 32.0) * pow(2.0,((f - 9.0) / 12.0));

post("nota n. %i - frequenza: %f",f, x->freq_taglio);

notefilter_calc(x);

}

*a1*and*a2*are fundamental for a correct filtering operation: they characterize the intensity of the resonance and report the cutoff frequency; their calculation takes place in the function*notefilter_calc*according to the following expressions:and the code with which I implement it is very simple

void notefilter_calc(t_notefilter *x)

{

double resterm;

resterm = exp(0.125) * 0.882497;

x->l_fqterm = cos(x->l_2pidsr * x->freq_taglio);

x->l_a1 = -2. * resterm * x->l_fqterm;

x->l_a2 = resterm * resterm;

x->l_resterm = resterm;

}

The last function of the software, which concludes the realization of the object, is {

double resterm;

resterm = exp(0.125) * 0.882497;

x->l_fqterm = cos(x->l_2pidsr * x->freq_taglio);

x->l_a1 = -2. * resterm * x->l_fqterm;

x->l_a2 = resterm * resterm;

x->l_resterm = resterm;

}

*notefilter_new*, what is called when creating the object.Some routine operations are performed, in order to allocate the memory necessary for the operation of the object, and the inlets (in our case 2) and the outlets are defined (in our case 1).

Some parametric values (such as

*2pidsr*) can be calculated here, since their value does not have to be recalculated during the execution: these operations, in fact, come performed only when the object is created.
void *notefilter_new(double val, double reso)

{

t_notefilter *x = object_alloc(notefilter_class);

dsp_setup((t_pxobject *)x,2);

x->freq_taglio = val;

x->l_2pidsr = (2. * PI) / sys_getsr();

notefilter_calc(x);

x->l_a1p = x->l_a1;

x->l_a2p = x->l_a2;

outlet_new((t_object *)x, "signal");

return (x);

}

{

t_notefilter *x = object_alloc(notefilter_class);

dsp_setup((t_pxobject *)x,2);

x->freq_taglio = val;

x->l_2pidsr = (2. * PI) / sys_getsr();

notefilter_calc(x);

x->l_a1p = x->l_a1;

x->l_a2p = x->l_a2;

outlet_new((t_object *)x, "signal");

return (x);

}

The software works, and once compiled it has successfully generated the

*notefilter~*object, which appears now among Max's objects, complete with auto-fill when you start typing its name.To test this, I made a simple patch where the noise produced by a

*noise~*object is filtered from*notefilter~*, which receives from a*kslider*the note around which to filter the signal. The output is viewed via the*spectroscope*and reproduced via the sound card via the object*ezdac*.The result is a metallic sound and definitely not suitable for real use, but I am satisfied with how much I got because, after all, that's what I expected. This is what the spectroscope shows during notefilter operation

The complete

*notefilter.c*file is available at the following link: