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);
}

MUS177/206 – sunangle.c

sunangle.c – this external uses the date and time from the seconds external to show the height and azimuth position of the sun. also shows how to handle an input list (same as A_GIMME)


 

/* sunangle.c - 

find the angle and azimuth of the sun at any latitude and longitude

*/
#include "m_pd.h"
#include 
#include 

#define lotsOplaces 100 // generic, big-enough string length
#define PI 3.141592653589793

typedef struct _sunangle	// defines our object's internal variables for each instance in a patch
{
	t_object x_ob;
	t_outlet *p_outletH;
	t_outlet *p_outletA;
	float year, month, date, hour, min, sec;
	float latitude;		// first input
	float longitude;	// second input
} t_sunangle;

t_class *sunangle_class;		// global pointer to the object class - so max can reference the object 

// these are prototypes for the methods that are defined below
void sunangle_bang(t_sunangle *x);
void sunangle_seconds(t_sunangle *x, t_symbol *selector, int argcount, t_atom *argvec);
void sunangle_latitude(t_sunangle *x, float f);
void sunangle_longitude(t_sunangle *x, float f);
void *sunangle_new(float lati, float longi);

// prototype functions
double calcJD(float, float, double);
double calcTimeJulianCent(double);
double calcEquationOfTime(double);
double radToDeg(double);
double degToRad(double);
double calcGeomMeanLongSun(double);
double calcGeomMeanAnomalySun(double);
double calcSunDeclination(double);
double calcObliquityCorrection(double);
double calcMeanObliquityOfEcliptic(double);
double calcSunApparentLong(double);
double calcSunTrueLong(double);
double calcGeomMeanLongSun(double);
double calcSunEqOfCenter(double);
double calcEccentricityEarthOrbit(double);
void calcHA(float,float,float, float, float, float, float, 
	    float, float *, float *);



//--------------------------------------------------------------------------

void sunangle_bang(t_sunangle *x)		// x = reference to this instance of the object 
{	
    float H, A;
	
	calcHA(x->latitude, x->longitude, x->year, x->month, x->date, x->hour, x->min, x->sec, &H, &A);
    outlet_float(x->p_outletH, H);
    outlet_float(x->p_outletA, A);
}

void sunangle_seconds(t_sunangle *x, t_symbol *selector, int argcount, t_atom *argvec)
{
	if(argcount != 6)
		post("input to sunangle should be \"year month day hour minute second\"");
	else
	{
		if(argvec[0].a_type == A_FLOAT)
			x->year = argvec[0].a_w.w_float;
		else
		{
			post("the first item in list should be a float");
			return;
		}
		if(argvec[1].a_type == A_FLOAT)
			x->month = argvec[1].a_w.w_float;
		else
		{
			post("the second item in list should be a float");
			return;
		}
		if(argvec[2].a_type == A_FLOAT)
			x->date = argvec[2].a_w.w_float;
		else
		{
			post("the third item in list should be a float");
			return;
		}
		if(argvec[3].a_type == A_FLOAT)
			x->hour = argvec[3].a_w.w_float;
		else
		{
			post("the fourth item in list should be a float");
			return;
		}
		if(argvec[4].a_type == A_FLOAT)
			x->min = argvec[4].a_w.w_float;
		else
		{
			post("the fifth item in list should be a float");
			return;
		}
		if(argvec[5].a_type == A_FLOAT)
			x->sec = argvec[5].a_w.w_float;
		else
		{
			post("the sixth item in list should be a float");
			return;
		}
	}
	sunangle_bang(x);
}

void sunangle_latitude(t_sunangle *x, float f)
{
	x->latitude = f;
}

// this gets called when something goes into inlet 2
void sunangle_longitude(t_sunangle *x, float f)
{
    x->longitude = f;
}

//--------------------------------------------------------------------------
void *sunangle_new(float lati, float longi)		// n = int argument typed into object box (A_DEFLONG) -- defaults to 0 if no args are typed
{
	t_sunangle *x;				// local variable (pointer to a t_sunangle data structure)

	x = (t_sunangle *)pd_new(sunangle_class); // create a new instance of this object
	
	// add inputs and outputs 
	x->p_outletH = outlet_new(&x->x_ob, gensym("float"));
	x->p_outletA = outlet_new(&x->x_ob, gensym("float"));
	
	x->latitude	= lati;			// set initial (default) left operand value in the instance's data structure
	x->longitude = longi;			// set initial (default) right operand value (n = variable passed to sunangle_new)
		
	return(x);					// return a reference to the object instance 
}

//--------------------------------------------------------------------------
void sunangle_setup(void)
{
    sunangle_class = class_new(gensym("sunangle"), (t_newmethod)sunangle_new,
    	0, sizeof(t_sunangle), 0, A_DEFFLOAT, A_DEFFLOAT, 0);
	// class_new() loads our external into pd's memory so it can be used in a patch
	// sunangle_new = object creation method defined above
	
	class_addbang(sunangle_class, (t_method)sunangle_bang); 
	class_addlist(sunangle_class, (t_method)sunangle_seconds); 
    class_addmethod(sunangle_class, (t_method)sunangle_latitude, gensym("latitude"), A_FLOAT, 0);
    class_addmethod(sunangle_class, (t_method)sunangle_longitude, gensym("longitude"), A_FLOAT, 0);
}

// below this line is the actual sun position code
//--------------------------------------------------------------------------
void calcHA(float lat, float lon, float year, float month, 
	    float date, float hour, float min, float sec, 
	    float *H, float *A){// sec is type double for calculations

  // declare calculated things
  double day, JD, T, minTime, time_offset, tst, sha, theta, cosPhi, 
    exoatmElevation, refractionCorrection, te, azimuth;
  double zenith; 
  double azDenom;
  int TZ = 0, DS = 0;
  
  // calculate astronomical times
  day = date+(hour+(min+sec/60.)/60.)/24.;
  JD = calcJD(year,month,day);
  T = calcTimeJulianCent(JD);
  minTime = calcEquationOfTime(T);
  time_offset = minTime-4*(-lon)+60*(TZ-DS);
  tst = hour*60+min+sec/60+time_offset;
  sha = tst/4-180;
  if (sha < -180)
    sha += 360.0;
  theta = calcSunDeclination(T);
  cosPhi = sin(degToRad(lat))*sin(degToRad(theta))+cos(degToRad(lat))*
    cos(degToRad(theta))*cos(degToRad(sha));
  // exoatmospheric elevation angle
  exoatmElevation = 90 - radToDeg(acos(cosPhi));

  // rudimentary refraction correction
  if (exoatmElevation > 85.0) {
    refractionCorrection = 0.0;
  } else {
    te = tan(degToRad(exoatmElevation));
    if (exoatmElevation > 5.0) {
      refractionCorrection = 58.1 / te - 0.07 
	/ (te*te*te) +
	0.000086 / (te*te*te*te*te);
    } else if (exoatmElevation > -0.575) {
      refractionCorrection = 1735.0 + exoatmElevation *
	(-518.2 + exoatmElevation * (103.4 + 
	exoatmElevation * (-12.79 + exoatmElevation * 0.711) ) );
    } else {
      refractionCorrection = -20.774 / te;
    }
    refractionCorrection = refractionCorrection / 3600.0;
  }
  
  // refraction-corrected elevation angle
  *H = exoatmElevation + refractionCorrection;

  // work on azimuthal concerns
  if (cosPhi > 1.0) 
    {
      cosPhi = 1.0;
    } else if (cosPhi < -1.0) 
      { 
	cosPhi = -1.0; 
      }
  zenith = radToDeg(acos(cosPhi));
  azDenom = cos(degToRad(lat))*sin(degToRad(zenith));
  if (fabs(azDenom) > 0.001) {
    double azRad = (( sin(degToRad(lat)) * 
		      cos(degToRad(zenith)) ) - 
		    sin(degToRad(theta))) / azDenom;
    if (fabs(azRad) > 1.0) {
      if (azRad < 0) {
	azRad = -1.0;
      } else {
	azRad = 1.0;
      }
    }
    azimuth = 180.0 - radToDeg(acos(azRad));
    if (sha > 0.0) {
      azimuth = -azimuth;
    }
  } else {
    if (lat > 0.0) {
      azimuth = 180.0;
    } else { 
      azimuth = 0.0;
    }
  }
  if (azimuth < 0.0) {
    azimuth += 360.0;
  }
  *A = azimuth;
}

double calcJD(float year, float month, double day){

  double JD, A, B;  

  if(month<=2) {
    year = year-1;
    month = month+12;
  }
  A = floor((double)(year)/100.0);  // note cast of year
  B = 2 - A + floor(A/4);
  JD = floor(365.25*(year + 4716.0)) + floor(30.6001*(month+1)) 
    + day + B - 1524.5;
  
  return JD;
}

double calcTimeJulianCent(double jd){
  double T;
  T = (jd - 2451545.0)/36525.0;
  return T;
}

double calcEquationOfTime(double t){

  double minTime, epsilon, l0, e, m, y, sin2l0, sinm, cos2l0, sin4l0;
  double sin2m, Etime;

  epsilon = calcObliquityCorrection(t);
  l0 = calcGeomMeanLongSun(t);
  e = calcEccentricityEarthOrbit(t);
  m = calcGeomMeanAnomalySun(t);
  y = tan(degToRad(epsilon)/2.0);
  y = y*y;
  sin2l0 = sin(2.0 * degToRad(l0));
  sinm   = sin(degToRad(m));
  cos2l0 = cos(2.0 * degToRad(l0));
  sin4l0 = sin(4.0 * degToRad(l0));
 sin2m  = sin(2.0 * degToRad(m));
 Etime = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0
   - 0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m;
 minTime = radToDeg(Etime)*4.0;
 return minTime;
}

double radToDeg(double angleRad){
  return (180.0 * angleRad / PI);
}
double degToRad(double angleDeg){
  return (PI * angleDeg / 180.0);
}

double calcGeomMeanLongSun(double t){
  double L0 = 280.46646 + t * (36000.76983 + 0.0003032 * t);
  while(L0 > 360.0)
    {
      L0 -= 360.0;
    }
  while(L0 < 0.0)
    {
      L0 += 360.0;
    }
  return L0;              // in degrees
}

double calcEccentricityEarthOrbit(double t){
  double e = 0.016708634 - t * (0.000042037 + 0.0000001267 * t);
  return e;               // unitless
}

double calcGeomMeanAnomalySun(double t){
  double M = 357.52911 + t * (35999.05029 - 0.0001537 * t);
  return M;               // in degrees
}
double calcObliquityCorrection(double t){
  double e0 = calcMeanObliquityOfEcliptic(t);
  double omega = 125.04 - 1934.136 * t;
  double e = e0 + 0.00256 * cos(degToRad(omega));
  return e;               // in degrees
}
double calcSunDeclination(double t){
  double e = calcObliquityCorrection(t);
  double lambda = calcSunApparentLong(t);
  double sint = sin(degToRad(e)) * sin(degToRad(lambda));
  double theta = radToDeg(asin(sint));
  return theta;           // in degrees
}

double calcMeanObliquityOfEcliptic(double t){
  double seconds = 21.448 - t*(46.8150 + t*(0.00059 - t*(0.001813)));
  double e0 = 23.0 + (26.0 + (seconds/60.0))/60.0;
  return e0;              // in degrees
}
double calcSunApparentLong(double t){
  double o = calcSunTrueLong(t);
  double omega = 125.04 - 1934.136 * t;
  double lambda = o - 0.00569 - 0.00478 * sin(degToRad(omega));
  return lambda;          // in degrees
}
double calcSunTrueLong(double t){
  double l0 = calcGeomMeanLongSun(t);
  double c = calcSunEqOfCenter(t);
  double O = l0 + c;
  return O;               // in degrees
}

double calcSunEqOfCenter(double t){
  double m = calcGeomMeanAnomalySun(t);
  double mrad = degToRad(m);
  double sinm = sin(mrad);
  double sin2m = sin(mrad+mrad);
  double sin3m = sin(mrad+mrad+mrad);
  double C = sinm * (1.914602 - t * (0.004817 + 0.000014 * t)) + sin2m * 
    (0.019993 - 0.000101 * t) + sin3m * 0.000289;
  return C;               // in degrees
}

MUS177/206 – seconds.c

Here is seconds.c, which uses <time.h> functions to send the day and time out as a list (out of a single list outlet).


 

/* seconds.c - 
 this object has 1 inlets and 6 outlets
 it responds the 'bang' message in the left inlet
 it responds to the 'assistance' message sent by Max when the mouse is positioned over an inlet or outlet
 (including an assistance method is optional, but strongly sugggested)
*/
#include <time.h>
#include "m_pd.h"

typedef struct _seconds // defines our object's internal variables for each instance in a patch
{
    t_object x_ob;
    t_outlet *x_outlist;
    t_atom date[6];
} t_seconds;

t_class *seconds_class; // global pointer to the object class - so max can reference the object 
// these are prototypes for the methods that are defined below
void seconds_bang(t_seconds *x);
void *seconds_new(void);
void seconds_setup(void);

//--------------------------------------------------------------------------
void seconds_setup(void)
{
    seconds_class = class_new(gensym("seconds"), (t_newmethod)seconds_new, 0, sizeof(t_seconds), 0, 0);
    class_addbang(seconds_class, seconds_bang);// the method it uses when it gets a bang in the left inlet 
}

//--------------------------------------------------------------------------
void seconds_bang(t_seconds *x) // x = reference to this instance of the object 
{
    time_t rawTime;
        float year, month, date, hour, min, sec;
        struct tm *now;

    time(&rawTime);
    now = gmtime(&rawTime);
    year = (now->tm_year + 1900);
    month = (now->tm_mon + 1);
    date = (now->tm_mday);
    hour = now->tm_hour;
    min = now->tm_min;
    sec = now->tm_sec;
    SETFLOAT(x->date+0, year);
    SETFLOAT(x->date+1, month);
    SETFLOAT(x->date+2, date);
    SETFLOAT(x->date+3, hour);
    SETFLOAT(x->date+4, min);
    SETFLOAT(x->date+5, sec);
    outlet_list(x->x_outlist, gensym("list"), 6, (x->date));
}

//--------------------------------------------------------------------------
void *seconds_new(void) // n = int argument typed into object box (A_DEFLONG) -- defaults to 0 if no args are typed
{
    t_seconds *x;// local variable (pointer to a t_seconds data structure        x = (t_seconds *)pd_new(seconds_class); // create a new instance of this object
       x->x_outlist = outlet_new(&x->x_ob, gensym("list"));
      return(x); // return a reference to the object instance 
}

MUS177/206 – window makefile changes for Pd externals

The makefile to compile the example externals from Pd 0.49 needs to be edited in order to work with Visual Studio Community 2017.

Do all of your editing and compilation on a copy of Pd in your \Users folder

The VC definition needs to be changed (as usual) to point to the 64-bit tools in VSC. Your setting will likely be different than mine.

VC="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.15.26726"

The PDNTCFLAGS need to be changed to eliminate /WX (treat all warnings as errors, and add a definition for PD_LONGINTTYPE to give PD 64-bit pointers. Without this, the audio example (dspobj~) will crash.

PDNTCFLAGS = /W3 /DNT /DPD /DPD_LONGINTTYPE="long long" /nologo

Now, make sure PDNTLDIR points to the 64 bit library folder by appending “\x64″ to the path

PDNTLDIR = $(VC)\lib\x64

Finally, remove references to old names.lib and kernel32.lib and change libc.lib to libcmt.lib

PDNTLIB = $(PDNTLDIR)\libcmt.lib \
..\..\bin\pd.lib

music 177/206 syllabus – fall 2018

music 177/206 – custom programming for music – fall 2018

instructor – tom erbe – tre@music.ucsd.edu

•••••focus

this year we will be learning to program a pd external and a JUCE compatible plugin. to do this we will cover 4 topics.

  1. c/c++ programming
  2. the pd SDK
  3. the JUCE SDK
  4. computer music algorithms

••••schedule

  1. basics (basics and compilers – setting up a dev environment)
  2. pd 1 – making externals and c rehash
  3. pd 2 – adding dsp (first assignment)
  4. dsp code 1 – amplifiers delay and oscillators
  5. dsp code 2 – filters and ffts (second assignment)
  6. JUCE 1 – first examples
  7. JUCE 2 – more examples and (final project assignment)
  8. JUCE 3 (integrating dsp)
  9. guis
  10. other topics

••••texts – software

these books are very helpful – especially if you feel the need to catch up.

  • C Primer Plus by Stephen Prata
  • Computer Music by Charles Dodge
  • DAFX by Udo Zolzer
  • JUCE – download the SDK from http://www.juce.com
  • pd – download the latest pd from http://crca.ucsd.edu/~msp

you will also need a decent compiler. this is xcode for macintosh or visual studio 2018  for windows.

 

••••• outline – likely topics

  1. overview of PD – externals – streaming/control/objects/messages in typical audio software
  2. look over simple PD examples – see what we understand
  3. c++ – structure of an object
  4. c++ – memory allocation – structures, arrays and pointers
  5. signal processing basics – simple amplitude modulation
  6. signal processing – lfo – random generators
  7. signal processing – distortion
  8. signal processing – compression expansion gating
  9. signal processing – simple filtering
  10. signal processing – oscillators
  11. the JUCE object – process
  12. the JUCE object – setparameter, getparameter
  13. the JUCE object – GUI code
  14. using visual c++
  15. using the debugger
  16. using xcode

•••••my office hours

1-3 tuesday and thursday

pm or email me for other hours

tre@music.ucsd.edu

tre@soundhack.com

•••••class requirements

3 projects of increasing complexity…. one is a final project. 206 students will be required to use their final project in a piece or for research.

****class notes & assignments

week 1 – windows makefile changes for Pd externals

week 1 – assignment 1

week 2 – seconds.c external, showing how to output a list

week 2 – sunangle.c external, calculating the position of the sun

week 2 – assignment 2

week 2 – audio external examples: softclip~ and rms~

MUS177/267 full duplex audio 1

Gettinng full duplex audio to work on the discovery boards is tricky, as they were not designed to do this. We will dig deep into the hardware configuration to find some work-arounds.

STM32F4Discovery

This board has no high fidelity line audio in, so cannot be used for high fidelity full duplex audio. However, it does have many 12 bit ADC inputs and 2 12 bit DAC outputs.

To get an ADC and DAC to work together, they have to be synchronized. In my example code we will use a timer set at 48000 Hz to clock both. We also need a timer which can be used for both the ADC and DAC. If you look at the ADC HAL header file, you will see the following:

/** @defgroup ADC_External_trigger_Source_Regular ADC External Trigger Source Regular  
* @{
*/
/* Note: Parameter ADC_SOFTWARE_START is a software parameter used for        */
/*       compatibility with other STM32 devices.                              */
#define ADC_EXTERNALTRIGCONV_T1_CC1    ((uint32_t)0x00000000U)
#define ADC_EXTERNALTRIGCONV_T1_CC2    ((uint32_t)ADC_CR2_EXTSEL_0)
#define ADC_EXTERNALTRIGCONV_T1_CC3    ((uint32_t)ADC_CR2_EXTSEL_1)
#define ADC_EXTERNALTRIGCONV_T2_CC2    ((uint32_t)(ADC_CR2_EXTSEL_1 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T2_CC3    ((uint32_t)ADC_CR2_EXTSEL_2)
#define ADC_EXTERNALTRIGCONV_T2_CC4    ((uint32_t)(ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T2_TRGO   ((uint32_t)(ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_1))
#define ADC_EXTERNALTRIGCONV_T3_CC1    ((uint32_t)(ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_1 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T3_TRGO   ((uint32_t)ADC_CR2_EXTSEL_3)
#define ADC_EXTERNALTRIGCONV_T4_CC4    ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T5_CC1    ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_1))
#define ADC_EXTERNALTRIGCONV_T5_CC2    ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_1 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T5_CC3    ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_2))
#define ADC_EXTERNALTRIGCONV_T8_CC1    ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T8_TRGO   ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_1))

And in the DAC HAL header we see:

/** @defgroup DAC_trigger_selection DAC Trigger Selection
  * @{
  */
#define DAC_TRIGGER_NONE                   ((uint32_t)0x00000000U) /*!< Conversion is automatic once the DAC1_DHRxxxx register has been loaded, and not by external trigger */
#define DAC_TRIGGER_T2_TRGO                ((uint32_t)(DAC_CR_TSEL1_2 | DAC_CR_TEN1)) /*!< TIM2 TRGO selected as external conversion trigger for DAC channel */
#define DAC_TRIGGER_T4_TRGO                ((uint32_t)(DAC_CR_TSEL1_2 | DAC_CR_TSEL1_0 | DAC_CR_TEN1)) /*!< TIM4 TRGO selected as external conversion trigger for DAC channel */
#define DAC_TRIGGER_T5_TRGO                ((uint32_t)(DAC_CR_TSEL1_1 | DAC_CR_TSEL1_0 | DAC_CR_TEN1)) /*!< TIM5 TRGO selected as external conversion trigger for DAC channel */
#define DAC_TRIGGER_T6_TRGO                ((uint32_t)DAC_CR_TEN1) /*!< TIM6 TRGO selected as external conversion trigger for DAC channel */
#define DAC_TRIGGER_T7_TRGO                ((uint32_t)(DAC_CR_TSEL1_1 | DAC_CR_TEN1)) /*!< TIM7 TRGO selected as external conversion trigger for DAC channel */
#define DAC_TRIGGER_T8_TRGO                ((uint32_t)(DAC_CR_TSEL1_0 | DAC_CR_TEN1)) /*!< TIM8 TRGO selected as external conversion trigger for DAC channel */

Timer 2 and 8 trigger output are in common for both ADC and DAC. In my example I will use timer 8. Look at functions timer8_init(), adc1_init() and dac1_init() for details.

Finally, notice that in the audio callback I am copying and scaling the input first, then processing audio (an echo in this case), and finally copying and scaling the output. Also, both input and output can be processed in the DAC callback. No need to do it in the ADC callback as they are synchronized by the same clock.

void HAL_DAC_ConvHalfCpltCallbackCh1(DAC_HandleTypeDef* DacHandle)
{
  int i,j;
  for(i = 0, j = 0; i < 16; i++, j+=2)
  {
    inBuffer[i] = inBuffer[i+1] = (float)(adcBuffer[i] * 0.00048828125f) - 1.0f;
  }
  audioBlock(inBuffer, outBuffer, 16);
  for(i = 0, j = 0; i < 16; i++, j+=2)
  {
    dacBuffer[i] = (int16_t)((outBuffer[j] + outBuffer[j+1] + 2.0f) * 1023.0f);
  }
}

Here is the example code:

f4-adcdac-passthru

STM32F746Discovery

The BSP code supplied with the board is inadequate as it doesn’t allow the line in to be used. I have found a developer who modified the code to do full duplex input and output, but I have yet to try it myself. I will do so by next Thursday. The code is here:

https://community.st.com/thread/19377