chris@164: /*
chris@164:  * AIR-HARP
chris@164:  * Physically modelled strings using waveguide junctions and mass-spring-dampers
chris@164:  *
chris@164:  * render.cpp
chris@164:  *
chris@164:  * Christian Heinrichs 04/2015
chris@164:  *
chris@164:  */
chris@164: 
chris@164: 
chris@164: #include "MassSpringDamper.h"
chris@164: #include "String.h"
chris@164: #include "Plectrum.h"
chris@164: 
giuliomoro@301: #include <Bela.h>
chris@164: #include <cmath>
chris@164: #include <stdio.h>
chris@164: #include <cstdlib>
chris@164: #include <rtdk.h>
chris@164: 
chris@164: #define ACCEL_BUF_SIZE 8
chris@164: #define NUMBER_OF_STRINGS 9
chris@164: 
chris@164: // PENTATONIC SCALE
chris@164: float gMidinotes[NUMBER_OF_STRINGS] = {40,45,50,55,57,60,62,64,67};
chris@164: 
chris@164: float gInverseSampleRate;
chris@164: 
chris@164: float out_gain = 5.0;
chris@164: 
chris@164: int accelPin_x = 0;
chris@164: int accelPin_y = 1;
chris@164: int accelPin_z = 2;
chris@164: 
chris@164: MassSpringDamper msd = MassSpringDamper(1,0.1,10);// (10,0.001,10);
chris@164: String strings[NUMBER_OF_STRINGS];
chris@164: Plectrum plectrums[NUMBER_OF_STRINGS];
chris@164: 
chris@164: float gPlectrumDisplacement = 0;
chris@164: 
chris@164: float gAccel_x[ACCEL_BUF_SIZE] = {0};
chris@164: int gAccelReadPtr = 0;
chris@164: 
chris@164: // DC BLOCK BUTTERWORTH
chris@164: 
chris@164: // Coefficients for 100hz cut-off
chris@164: float a0_l = 0.9899759179893742;
chris@164: float a1_l = -1.9799518359787485;
chris@164: float a2_l = 0.9899759179893742;
chris@164: float a3_l = -1.979851353142371;
chris@164: float a4_l = 0.9800523188151258;
chris@164: 
chris@164: float a0_r = a0_l;
chris@164: float a1_r = a1_l;
chris@164: float a2_r = a2_l;
chris@164: float a3_r = a3_l;
chris@164: float a4_r = a4_l;
chris@164: 
chris@164: float x1_l = 0;
chris@164: float x2_l = 0;
chris@164: float y1_l = 0;
chris@164: float y2_l = 0;
chris@164: 
chris@164: float x1_r = 0;
chris@164: float x2_r = 0;
chris@164: float y1_r = 0;
chris@164: float y2_r = 0;
chris@164: 
chris@164: 
giuliomoro@301: bool setup(BelaContext *context, void *userData)
chris@164: {
chris@164: 
chris@164: 	gInverseSampleRate = 1.0 / context->audioSampleRate;
chris@164: 
chris@164: 	// initialise strings & plectrums
chris@164: 	for(int i=0;i<NUMBER_OF_STRINGS;i++)	{
chris@164: 
chris@164: 		plectrums[i] = Plectrum();
chris@164: 		plectrums[i].setup(250,0.25,0.05);
chris@164: 
chris@164: 		strings[i] = String();
chris@164: 		strings[i].setMidinote(gMidinotes[i]);
chris@164: 
chris@164: 		float spacing = 2.0 / (NUMBER_OF_STRINGS+1);
chris@164: 
chris@164: 		strings[i].setGlobalPosition( -1 + spacing*(i+1) );
chris@164: 
chris@164: 		rt_printf("STRING %d // midinote: %f position: %f\n",i,gMidinotes[i],( -1 + spacing*(i+1) ));
chris@164: 
chris@164: 	}
chris@164: 
chris@164: 	return true;
chris@164: }
chris@164: 
giuliomoro@301: void render(BelaContext *context, void *userData)
chris@164: {
chris@164: 
chris@164: 	float lastAccel = 0;
chris@164: 
chris@164: 	for(int n = 0; n < context->audioFrames; n++) {
chris@164: 
chris@164: 		/*
chris@164: 		 *
chris@164: 		 * ACCELEROMETER DATA
chris@164: 		 *
chris@164: 		 */
chris@164: 
chris@164: 		// Read accelerometer data from analog input
chris@164: 		float accel_x = 0;
chris@164: 		if(n%2)	{
chris@164: 			accel_x = (float)context->analogIn[(n/2)*8+accelPin_x] * 2 - 1;	// 15800 - 28300 - 41500
chris@164: 			lastAccel = accel_x;
chris@164: 		} else {
chris@164: 			// grab previous value if !n%2
chris@164: 			accel_x = lastAccel;
chris@164: 		}
chris@164: 
chris@164: 		// Dead-zone avoids noise when box is lying horizontally on a surface
chris@164: 
chris@164: 		float accelDeadZone = 0.1;
chris@164: 
chris@164: 		if(accel_x <= accelDeadZone && accel_x >= -accelDeadZone)
chris@164: 			accel_x = 0;
chris@164: 
chris@164: 		// Perform smoothing (moving average) on acceleration value
chris@164: 		if(++gAccelReadPtr >= ACCEL_BUF_SIZE)
chris@164: 			gAccelReadPtr = 0;
chris@164: 		gAccel_x[gAccelReadPtr] = accel_x;
chris@164: 		float gravity = 0;
chris@164: 		for(int i=0;i<ACCEL_BUF_SIZE;i++)	{
chris@164: 			gravity = gAccel_x[(gAccelReadPtr-i+ACCEL_BUF_SIZE)%ACCEL_BUF_SIZE];
chris@164: 		}
chris@164: 		gravity /= ACCEL_BUF_SIZE;
chris@164: 
chris@164: 		/*
chris@164: 		 *
chris@164: 		 * PHYSICS SIMULATION
chris@164: 		 *
chris@164: 		 */
chris@164: 
chris@164: 		// The horizontal force (which can be gravity if box is tipped on its side)
chris@164: 		// is used as the input to a Mass-Spring-Damper model
chris@164: 		// Plectrum displacement (i.e. when interacting with string) is included
chris@164: 		float massPosition = (float)msd.update(gravity - gPlectrumDisplacement);
chris@164: 
chris@164: 		float out_l = 0;
chris@164: 		float out_r = 0;
chris@164: 		// Use this parameter to quickly adjust output gain
chris@164: 		float gain = 0.0015;	// 0.0015 is a good value or 12 strings
chris@164: 		gPlectrumDisplacement = 0;
chris@164: 
chris@164: 		for(int s=0;s<NUMBER_OF_STRINGS;s++)	{
chris@164: 
chris@164: 			float stringPosition = strings[s].getGlobalPosition();
chris@164: 
chris@164: 			float plectrumForce = plectrums[s].update(massPosition, stringPosition);
chris@164: 			gPlectrumDisplacement += strings[s].getPlectrumDisplacement();
chris@164: 
chris@164: 			// calculate panning based on string position (-1->left / 1->right)
chris@164: 			float panRight = map(stringPosition,1,-1,0.1,1);
chris@164: 			float panLeft = map(stringPosition,-1,1,0.1,1);
chris@164: 			panRight *= panRight;
chris@164: 			panLeft *= panLeft;
chris@164: 
chris@164: 			float out = strings[s].update(plectrumForce)*gain;
chris@164: 
chris@164: 			out_l += out*panLeft;
chris@164: 			out_r += out*panRight;
chris@164: 
chris@164: 		}
chris@164: 
chris@164: 		// APPLY DC-BLOCK FILTER TO OUTPUTS
chris@164: 
chris@164: 		// LEFT CHANNEL
chris@164: 		float temp_in = out_l;
chris@164: 		/* compute result */
chris@164:     	out_l = a0_l * out_l + a1_l * x1_l + a2_l * x2_l - a3_l * y1_l - a4_l * y2_l;
chris@164:     	/* shift x1 to x2, sample to x1 */
chris@164:     	x2_l = x1_l;
chris@164:     	x1_l = temp_in;
chris@164:     	/* shift y1 to y2, result to y1 */
chris@164:     	y2_l = y1_l;
chris@164:    	 	y1_l = out_l;
chris@164: 
chris@164:    	 	// RIGHT CHANNEL
chris@164: 		temp_in = out_r;
chris@164: 		/* compute result */
chris@164:     	out_r = a0_r * out_r + a1_r * x1_r + a2_r * x2_r - a3_r * y1_r - a4_r * y2_r;
chris@164:     	/* shift x1 to x2, sample to x1 */
chris@164:     	x2_r = x1_r;
chris@164:     	x1_r = temp_in;
chris@164:     	/* shift y1 to y2, result to y1 */
chris@164:     	y2_r = y1_r;
chris@164:    	 	y1_r = out_r;
chris@164: 
chris@164: 		context->audioOut[n * context->audioChannels + 1] = out_l * out_gain;
chris@164: 		context->audioOut[n * context->audioChannels + 0] = out_r * out_gain;
chris@164: 
chris@164: 	}
chris@164: 
chris@164: }
chris@164: 
chris@164: 
chris@164: // cleanup_render() is called once at the end, after the audio has stopped.
chris@164: // Release any resources that were allocated in initialise_render().
chris@164: 
giuliomoro@301: void cleanup(BelaContext *context, void *userData)
chris@164: {
chris@164: 
chris@164: }