mus177/206 – updated fuzztrem, other JUCE details

Here is the updated fuzztrem plugin with 3 parameters and controls added ClassTest1126.

Sample code to parse MIDI – noteOff, noteOn and bendPitch are my own methods – you will need to write your own versions of these methods to connect the MIDI data to your process.

   MidiBuffer::Iterator it(midiMessages);
    MidiMessage msg(0x80,0,0,0);
    int pos;
    int32_t note;

    // start with the MIDI processing
    while(it.getNextEvent(msg , pos))
    {
        if(msg.isNoteOn())
        {
            note = msg.getNoteNumber();
            noteOn(note);
        }
        if(msg.isNoteOff())
        {
            note = msg.getNoteNumber();
            noteOff(note);
        }
        if(msg.isPitchWheel())
        {
            bendPitch(msg.getPitchWheelValue());
        }
    }

Simple code to save your current settings to a session (pulled from my plugin ++pitchsift)

void PitchsiftAudioProcessor::getStateInformation (MemoryBlock& destData)
{
    // You should use this method to store your parameters in the memory block.
    // You could do that either as raw data, or use the XML or ValueTree classes
    // as intermediaries to make it easy to save and load complex data.
    
    ScopedPointer xml (parameters.state.createXml());
    copyXmlToBinary (*xml, destData);
}

void PitchsiftAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    // You should use this method to restore your parameters from this memory block,
    // whose contents will have been created by the getStateInformation() call.
    
    // This getXmlFromBinary() helper function retrieves our XML from the binary blob..
    ScopedPointer xmlState (getXmlFromBinary (data, sizeInBytes));
}

mus177/206 – introduction to JUCE

This week we will be getting started in using JUCE to create audio plugins. I will show you both the audio processing and graphic layers, and how they connect. To start, you need to download the JUCE SDK from www.juce.com. Join with either the Education or Personal license.

Along with these lessons, you should look at the following youtube videos from The Audio Programmer (Joshua Hodge) – https://youtu.be/7n16Yw51xkI. These cover most relevant aspects of JUCE in detail. Much better than the tutorials on the JUCE website.

Here is the class example from week 7. It was pointed out to me that JUCE has deprecated the existing createAndAddParameter method (with all the arguments). The valid way to use this method is to create a RangedParameter and hand it to createAndAddParameter. Here is gain from the class example: I create an AudioParameterFloat which gets added to the value tree (std::make_unique is a safer substitute for new).

 parameters.createAndAddParameter(std::make_unique<AudioParameterFloat>("gain", // parameter ID
 "gain", // parameter name
 NormalisableRange<float> (-60.0f, 12.0f), // range
 0.0f, // default value
 "dB"));

 

mus177/206 – filter code

Here is a collection of filter external source code for Pure Data.

  • 1 pole lowpass
  • 4 pole lowpass with feedback
  • 1st order allpass
  • 2nd order allpass with bandwidth
  • state variable filter

Lowpass, highpass and phasors can be created by modifying the 1st order allpass. Bandpass and bandstop filters can be created by modifying the 2nd order allpass.

Download this zip archive for the source code: fulters.zip

mus177/206 – third assignment

  1. make 2 audio externals, one which generates sound, one which processes sound.
  2. both externals should take a float message or an extra signal inlet to allow timbre modulation
  3. make a pd patch which uses both externals and demonstrates controls
  4. make sure input signals and messages do not go beyond allowed limits
  5. comment your code and pd patch
  6. turn in code and pd patch week 6 tuesday

mus177/206 – 3 oscillator examples

a basic triangle oscillator o1

a flexible (and probably too complicated) overtone oscillator otosc

a bell like oscillator, with non integer harmonics, and LCM phasor o2

—-

a couple extra methods to create sine waves:

// a resonant sin wave - based on the state variable filter
ResonantFilterSine(float *frequency, float *output, long samplesPerBlock)
{
long sample;
float freqAngle;
for(sample = 0; sample<samplesPerBlock; sample++)
{
    freqAngle = twoPi * *(frequency + sample)/sampleRate
    *(output+sample) = sinZ = sinZ + freqAngle * cosZ;
    cosZ = cosZ - freqAngle * sinZ;
}
}

// method six - complex multiply sin
SinComplex(float freq, float *freqBlock, float *output, long samplesPerBlock)
{
	long sample;
	float freqAngle, angleReal, angleImag;
	
	freqAngle = twoPi * freq;
	angleReal = cos(freqAngle);
	angleImag = sin(freqAngle);
	for(sample = 0; sample<samplesPerBlock; sample++) 	
        { 		
            if(freqBlock) 		
            { 			
                freqAngle = twoPi * *(freqBlock + sample);
                angleReal = cos(freqAngle);
                angleImag = sin(freqAngle);
            } 
            *(output+sample) = angleReal * sinZ - angleImag * cosZ;
            cosZ = angleReal * cosZ + angleImag * sinZ;
            sinZ = *(output+sample);
            if(sinZ > 1.0f) sinZ = 1.0f; 
        }
}

MUS 177/206 – two simple audio externals: softclip~ and rms~

source for softclip~. this shows how to pass the object structure “x” into the audio perform function.

#include "m_pd.h"
#include 
#include 
#ifdef NT
#pragma warning( disable : 4244 )
#pragma warning( disable : 4305 )
#endif

/* ------------------------ softclip~ ----------------------------- */

/* tilde object to do a simple soft clipping algorithm. */

static t_class *softclip_class;

typedef struct _softclip
{
    t_object x_obj; 	/* obligatory header */
    t_float gain;    	/* floats on the first inlet set the gain */
	t_float sample_rate;
    t_float freq;
} t_softclip;

    /* this is the actual performance routine which acts on the samples.
    It's called with a single pointer "w" which is our location in the
    DSP call list.  We return a new "w" which will point to the next item
    after us.  Meanwhile, w[0] is just a pointer to dsp-perform itself
    (no use to us), w[1] and w[2] are the input and output vector locations,
    and w[3] is the number of points to calculate. */
static t_int *softclip_perform(t_int *w)
{
	t_softclip *x = (t_softclip *)(w[1]);
    t_float *in = (t_float *)(w[2]);
    t_float *out = (t_float *)(w[3]);
    int n = (int)(w[4]);

	// i like counting from zero, so i use samplenumber to count the offset from
	// the start of the in and out blocks
	int samplenumber = 0;
	float a = -0.5f;
	float b = 0.0;
	float c = 1.5f;
	float d = 0.0;
	float ingain;

    while (n--)
    {
		ingain = x->gain * *(in+samplenumber);
		if(ingain > 1)
			*(out+samplenumber) = 1.0f;
		else if(ingain < -1) 			*(out+samplenumber) = -1.0f; 		else 			*(out+samplenumber) = a * ingain * ingain * ingain 							+ b * ingain * ingain 							+ c * ingain 							+ d; 		samplenumber++;     }     return (w+5); }     /* called to start DSP.  Here we call Pd back to add our perform     routine to a linear callback list which Pd in turn calls to grind     out the samples. */ static void softclip_dsp(t_softclip *x, t_signal **sp) { 	x->sample_rate = sp[0]->s_sr;
    dsp_add(softclip_perform, 4, x, sp[0]->s_vec, sp[1]->s_vec, sp[0]->s_n);
}

static void *softclip_new(void)
{
    t_softclip *x = (t_softclip *)pd_new(softclip_class);
    x->freq = 432.0f;
   outlet_new(&x->x_obj, gensym("signal"));
    return (x);
}

    /* this routine, which must have exactly this name (with the "~" replaced
    by "_tilde) is called when the code is first loaded, and tells Pd how
    to build the "class". */
void softclip_tilde_setup(void)
{
    softclip_class = class_new(gensym("softclip~"), (t_newmethod)softclip_new, 0,
    	sizeof(t_softclip), 0, A_DEFFLOAT, 0);
	
	/* this is magic to declare that the leftmost, "main" inlet
	    takes signals; other signal inlets are done differently... */
	/* also installs gain as the leftmost inlet float */
    CLASS_MAINSIGNALIN(softclip_class, t_softclip, gain);
	
	/* here we tell Pd about the "dsp" method, which is called back
	when DSP is turned on. */
    class_addmethod(softclip_class, (t_method)softclip_dsp, gensym("dsp"), (t_atomtype)0);
}

source for rms~. this shows how to allocate and deallocate an array

#include "m_pd.h"
#include 
#include 
#ifdef NT
#pragma warning( disable : 4244 )
#pragma warning( disable : 4305 )
#endif

/* ------------------------ rms~ ----------------------------- */

/* tilde object to take the absolute value of a signal at audio rate */
/* an argument for window size may be entered upon instantiation */

static t_class *rms_class;

typedef struct _rms
{
    t_object x_obj;			// obligatory variable
    t_float x_f;			// stores message recieved on audio inlet
	t_int window;			// window size set by object argument
	t_float sum;			// running sum of squared values
	t_float *squaretab;		// table to hold squared values
	t_int tabread1;			// pointer offset for newest table value
	t_int tabread2;			// pointer offset for oldest table value
} t_rms;

static t_int *rms_perform(t_int *w)
{
	t_rms *x = (t_rms *)(w[1]);
    t_float *in = (t_float *)(w[2]);
    t_float *out = (t_float *)(w[3]);
    int n = (int)(w[4]);

	// number of samples passed
	int blocksize = n;
	// sample from 0 up
	int sample = 0;
	// mean of squared value
	float mean = 0;
	// used to multiply instead of divide
	float oneOverWindow = 1.0f/x->window;

    while (n--)
    {
		// store newest squared value in the table
    	*(x->squaretab+x->tabread1) = *(in+sample) * *(in+sample);
		// add this value to the running sum
		x->sum += *(x->squaretab+x->tabread1);
		// subtract the oldest value in the table
		x->sum -= *(x->squaretab+x->tabread2);

		// compute the current mean value
    	mean = x->sum * oneOverWindow;

		// take the square root and send the value to outlet
        
    	*(out+sample) = sqrt(mean);
		// increment to next sample
		sample++;
		// increment to next table position
        x->tabread1++;
        x->tabread2++;
		// reset to beginning of table
		if(x->tabread1 >= x->window)
			x->tabread1 = 0;
		// increment to next table position
 		// reset to beginning of table
		if(x->tabread2 >= x->window)
			x->tabread2 = 0;
    }
    return (w+5);
}

static void rms_dsp(t_rms *x, t_signal **sp)
{
    dsp_add(rms_perform, 4, x, sp[0]->s_vec, sp[1]->s_vec, sp[0]->s_n);
}

// setting window size
void rms_windowset(t_rms *x, t_floatarg f)
{
	// using a good value
	if( f >= 256 && f <= 4096 )
		x->window = f;
	// blocking bad values
	else if( f < 256 )
	{
		x->window = 256;
		post("rms~: window size must be set to a value between 256 and 4096");
	}
	else if( f > 4096 )
	{
		x->window = 4096;
		post("rms~: window size must be set to a value between 256 and 4096");
	}
}

static void *rms_new(t_floatarg f)
{
	int i;
	
    t_rms *x = (t_rms *)pd_new(rms_class);
    outlet_new(&x->x_obj, gensym("signal"));
	x->squaretab = 0;
    x->x_f = 0;
	// initialize the sum
	x->sum = 0;
	// changing windowsize on creation
	rms_windowset(x, f);
	// intializing table for largest window size
    x->squaretab = (t_float *)malloc(4097 * sizeof(t_float));
	// initialzing the table to 0
	for( i = 0; i < x->window; i++)
		*(x->squaretab+i) = 0;
	// initializing newest sample
	x->tabread1 = 0;
	// initializing oldest sample
	x->tabread2 = 1;

    return (x);
}

// function to clear the allocated square table
static void rms_free(t_rms *x)
{
    free(x->squaretab);
	if(x->squaretab != 0)
	{
		free(x->squaretab);
		x->squaretab = 0;
	}
}

void rms_tilde_setup(void)
{
    rms_class = class_new(gensym("rms~"), (t_newmethod)rms_new, (t_method)rms_free,
    	sizeof(t_rms), 0, A_DEFFLOAT, 0);
    CLASS_MAINSIGNALIN(rms_class, t_rms, x_f);
    class_addmethod(rms_class, (t_method)rms_dsp, gensym("dsp"), (t_atomtype)0);
}