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: }