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

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 
}