andrewm@88: /*
andrewm@88:  * render.cpp
andrewm@88:  *
andrewm@88:  *  Created on: Oct 24, 2014
andrewm@88:  *      Author: parallels
andrewm@88:  */
andrewm@88: 
andrewm@88: 
giuliomoro@301: #include <Bela.h>
andrewm@88: #include <cmath>
andrewm@88: 
andrewm@88: #define NUMBER_OF_SEGMENTS	10
andrewm@88: 
andrewm@88: // Two levels of audio: one follows current value, the other holds
andrewm@88: // peaks for longer
andrewm@88: float gAudioLocalLevel = 0, gAudioPeakLevel = 0;
andrewm@88: 
andrewm@88: // Decay rates for detecting levels
andrewm@88: float gLocalDecayRate = 0.99, gPeakDecayRate = 0.999;
andrewm@88: 
andrewm@88: // Thresholds for LEDs: set in setup()
andrewm@88: float gThresholds[NUMBER_OF_SEGMENTS + 1];
andrewm@268: int gSamplesToLight[NUMBER_OF_SEGMENTS];
andrewm@88: 
andrewm@88: // High-pass filter on the input
andrewm@88: float gLastX[2] = {0};
andrewm@88: float gLastY[2] = {0};
andrewm@88: 
andrewm@88: // These coefficients make a high-pass filter at 5Hz for 44.1kHz sample rate
andrewm@88: double gB0 = 0.99949640;
andrewm@88: double gB1 = -1.99899280;
andrewm@88: double gB2 = gB0;
andrewm@88: double gA1 = -1.99899254;
andrewm@88: double gA2 = 0.99899305;
andrewm@88: 
andrewm@88: // setup() is called once before the audio rendering starts.
andrewm@88: // Use it to perform any initialisation and allocation which is dependent
andrewm@88: // on the period size or sample rate.
andrewm@88: //
andrewm@88: // userData holds an opaque pointer to a data structure that was passed
andrewm@88: // in from the call to initAudio().
andrewm@88: //
andrewm@88: // Return true on success; returning false halts the program.
andrewm@88: 
giuliomoro@301: bool setup(BelaContext *context, void *userData)
andrewm@88: {	
andrewm@88: 	// This project makes the assumption that the audio and digital
andrewm@88: 	// sample rates are the same. But check it to be sure!
andrewm@88: 	if(context->audioFrames != context->digitalFrames) {
andrewm@88: 		rt_printf("Error: this project needs the audio and digital sample rates to be the same.\n");
andrewm@88: 		return false;
andrewm@88: 	}
andrewm@88: 	
andrewm@88: 	// Initialise threshold levels in -3dB steps. One extra for efficiency in render()
andrewm@88: 	// Level = 10^(dB/20)
andrewm@88: 	for(int i = 0; i < NUMBER_OF_SEGMENTS + 1; i++) {
andrewm@88: 		gThresholds[i] = powf(10.0f, (-1.0 * (NUMBER_OF_SEGMENTS - i)) * .05);
andrewm@88: 	}
andrewm@88: 	
andrewm@268: 	for(int i = 0; i < NUMBER_OF_SEGMENTS; i++) {
andrewm@268: 		gSamplesToLight[i] = 0;
andrewm@310: 		pinMode(context, 0, i, OUTPUT);
andrewm@268: 	}
andrewm@88: 
andrewm@88: 	return true;
andrewm@88: }
andrewm@88: 
andrewm@88: // render() is called regularly at the highest priority by the audio engine.
andrewm@88: // Input and output are given from the audio hardware and the other
andrewm@88: // ADCs and DACs (if available). If only audio is available, numMatrixFrames
andrewm@88: // will be 0.
andrewm@88: 
giuliomoro@301: void render(BelaContext *context, void *userData)
andrewm@88: {
andrewm@88: 	for(unsigned int n = 0; n < context->audioFrames; n++) {
andrewm@88: 		// Get average of audio input channels
andrewm@88: 		float sample = 0;
andrewm@88: 		for(unsigned int ch = 0; ch < context->audioChannels; ch++) {
andrewm@88: 			context->audioOut[n * context->audioChannels + ch] = 
andrewm@88: 				context->audioIn[n * context->audioChannels + ch];
andrewm@88: 			sample += context->audioIn[n * context->audioChannels + ch];
andrewm@88: 		}
andrewm@88: 		
andrewm@88: 		// Do DC-blocking on the sum
andrewm@88: 		float out = gB0 * sample + gB1 * gLastX[0] + gB2 * gLastX[1]
andrewm@88: 						- gA1 * gLastY[0] - gA2 * gLastY[1];
andrewm@88: 
andrewm@88: 		gLastX[1] = gLastX[0];
andrewm@88: 		gLastX[0] = sample;
andrewm@88: 		gLastY[1] = gLastY[0];
andrewm@88: 		gLastY[0] = out;
andrewm@88: 		
andrewm@88: 		out = fabsf(out / (float)context->audioChannels);
andrewm@88: 		
andrewm@88: 		// Do peak detection: fast-responding local level
andrewm@88: 		if(out > gAudioLocalLevel)
andrewm@88: 			gAudioLocalLevel = out;
andrewm@88: 		else
andrewm@88: 			gAudioLocalLevel *= gLocalDecayRate;
andrewm@88: 		
andrewm@88: 		// Do peak detection: slow-responding peak level
andrewm@88: 		if(out > gAudioPeakLevel)
andrewm@88: 			gAudioPeakLevel = out;
andrewm@88: 		else {
andrewm@88: 			// Make peak decay slowly by only multiplying
andrewm@88: 			// every few samples
andrewm@311: 			if(((context->audioFramesElapsed + n) & 31) == 0)
andrewm@88: 				gAudioPeakLevel *= gPeakDecayRate;
andrewm@88: 		}	
andrewm@88: 		// LED bargraph on digital outputs 0-9
andrewm@88: 		for(int led = 0; led < NUMBER_OF_SEGMENTS; led++) {
andrewm@88: 			// All LEDs up to the local level light up. The LED
andrewm@88: 			// for the peak level also remains lit.
andrewm@88: 			int state = LOW;
andrewm@88: 				
andrewm@268: 			if(gAudioLocalLevel > gThresholds[led]) {
andrewm@88: 				state = HIGH;
andrewm@268: 				gSamplesToLight[led] = 1000;
andrewm@268: 			}
andrewm@268: 			/*else if(gAudioPeakLevel > gThresholds[led] && gAudioPeakLevel <= gThresholds[led + 1]) {
andrewm@268: 				state = HIGH;
andrewm@268: 				gSamplesToLight[led] = 1000;
andrewm@268: 			}*/
andrewm@268: 			else if(--gSamplesToLight[led] > 0)
andrewm@88: 				state = HIGH;
andrewm@88: 			
andrewm@88: 			// Write LED
andrewm@308: 			digitalWriteOnce(context, n, led, state);
andrewm@88: 		}
andrewm@88: 	}
andrewm@88: }
andrewm@88: 
andrewm@88: // cleanup() is called once at the end, after the audio has stopped.
andrewm@88: // Release any resources that were allocated in setup().
andrewm@88: 
giuliomoro@301: void cleanup(BelaContext *context, void *userData)
andrewm@88: {
andrewm@88: 
andrewm@88: }