robert@464: /*
robert@464:  ____  _____ _        _    
robert@464: | __ )| ____| |      / \   
robert@464: |  _ \|  _| | |     / _ \  
robert@464: | |_) | |___| |___ / ___ \ 
robert@464: |____/|_____|_____/_/   \_\
robert@464: 
robert@464: The platform for ultra-low latency audio and sensor processing
robert@464: 
robert@464: http://bela.io
robert@464: 
robert@464: A project of the Augmented Instruments Laboratory within the
robert@464: Centre for Digital Music at Queen Mary University of London.
robert@464: http://www.eecs.qmul.ac.uk/~andrewm
robert@464: 
robert@464: (c) 2016 Augmented Instruments Laboratory: Andrew McPherson,
robert@464:   Astrid Bin, Liam Donovan, Christian Heinrichs, Robert Jack,
robert@464:   Giulio Moro, Laurel Pardue, Victor Zappi. All rights reserved.
robert@464: 
robert@464: The Bela software is distributed under the GNU Lesser General Public License
robert@464: (LGPL 3.0), available here: https://www.gnu.org/licenses/lgpl-3.0.txt
robert@464: */
robert@464: 
robert@464: 
robert@464: #include <Bela.h>
robert@464: #include <cmath>
robert@464: 
robert@464: #define NUMBER_OF_SEGMENTS	10
robert@464: 
robert@464: // Two levels of audio: one follows current value, the other holds
robert@464: // peaks for longer
robert@464: float gAudioLocalLevel = 0, gAudioPeakLevel = 0;
robert@464: 
robert@464: // Decay rates for detecting levels
robert@464: float gLocalDecayRate = 0.99, gPeakDecayRate = 0.999;
robert@464: 
robert@464: // Thresholds for LEDs: set in setup()
robert@464: float gThresholds[NUMBER_OF_SEGMENTS + 1];
robert@464: int gSamplesToLight[NUMBER_OF_SEGMENTS];
robert@464: 
robert@464: // High-pass filter on the input
robert@464: float gLastX[2] = {0};
robert@464: float gLastY[2] = {0};
robert@464: 
robert@464: // These coefficients make a high-pass filter at 5Hz for 44.1kHz sample rate
robert@464: double gB0 = 0.99949640;
robert@464: double gB1 = -1.99899280;
robert@464: double gB2 = gB0;
robert@464: double gA1 = -1.99899254;
robert@464: double gA2 = 0.99899305;
robert@464: 
robert@464: bool setup(BelaContext *context, void *userData)
robert@464: {	
robert@464: 	// This project makes the assumption that the audio and digital
robert@464: 	// sample rates are the same. But check it to be sure!
robert@464: 	if(context->audioFrames != context->digitalFrames) {
robert@464: 		rt_printf("Error: this project needs the audio and digital sample rates to be the same.\n");
robert@464: 		return false;
robert@464: 	}
robert@464: 	
robert@464: 	// Initialise threshold levels in -3dB steps. One extra for efficiency in render()
robert@464: 	// Level = 10^(dB/20)
robert@464: 	for(int i = 0; i < NUMBER_OF_SEGMENTS + 1; i++) {
robert@464: 		gThresholds[i] = powf(10.0f, (-1.0 * (NUMBER_OF_SEGMENTS - i)) * .05);
robert@464: 	}
robert@464: 	
robert@464: 	for(int i = 0; i < NUMBER_OF_SEGMENTS; i++) {
robert@464: 		gSamplesToLight[i] = 0;
robert@464: 		pinMode(context, 0, i, OUTPUT);
robert@464: 	}
robert@464: 
robert@464: 	return true;
robert@464: }
robert@464: 
robert@464: void render(BelaContext *context, void *userData)
robert@464: {
robert@464: 	for(unsigned int n = 0; n < context->audioFrames; n++) {
robert@464: 		// Get average of audio input channels
robert@464: 		float sample = 0;
robert@464: 		for(unsigned int ch = 0; ch < context->audioChannels; ch++) {
robert@464: 			context->audioOut[n * context->audioChannels + ch] = 
robert@464: 				context->audioIn[n * context->audioChannels + ch];
robert@464: 			sample += context->audioIn[n * context->audioChannels + ch];
robert@464: 		}
robert@464: 		
robert@464: 		// Do DC-blocking on the sum
robert@464: 		float out = gB0 * sample + gB1 * gLastX[0] + gB2 * gLastX[1]
robert@464: 						- gA1 * gLastY[0] - gA2 * gLastY[1];
robert@464: 
robert@464: 		gLastX[1] = gLastX[0];
robert@464: 		gLastX[0] = sample;
robert@464: 		gLastY[1] = gLastY[0];
robert@464: 		gLastY[0] = out;
robert@464: 		
robert@464: 		out = fabsf(out / (float)context->audioChannels);
robert@464: 		
robert@464: 		// Do peak detection: fast-responding local level
robert@464: 		if(out > gAudioLocalLevel)
robert@464: 			gAudioLocalLevel = out;
robert@464: 		else
robert@464: 			gAudioLocalLevel *= gLocalDecayRate;
robert@464: 		
robert@464: 		// Do peak detection: slow-responding peak level
robert@464: 		if(out > gAudioPeakLevel)
robert@464: 			gAudioPeakLevel = out;
robert@464: 		else {
robert@464: 			// Make peak decay slowly by only multiplying
robert@464: 			// every few samples
robert@464: 			if(((context->audioFramesElapsed + n) & 31) == 0)
robert@464: 				gAudioPeakLevel *= gPeakDecayRate;
robert@464: 		}	
robert@464: 		// LED bargraph on digital outputs 0-9
robert@464: 		for(int led = 0; led < NUMBER_OF_SEGMENTS; led++) {
robert@464: 			// All LEDs up to the local level light up. The LED
robert@464: 			// for the peak level also remains lit.
robert@464: 			int state = LOW;
robert@464: 				
robert@464: 			if(gAudioLocalLevel > gThresholds[led]) {
robert@464: 				state = HIGH;
robert@464: 				gSamplesToLight[led] = 1000;
robert@464: 			}
robert@464: 			/*else if(gAudioPeakLevel > gThresholds[led] && gAudioPeakLevel <= gThresholds[led + 1]) {
robert@464: 				state = HIGH;
robert@464: 				gSamplesToLight[led] = 1000;
robert@464: 			}*/
robert@464: 			else if(--gSamplesToLight[led] > 0)
robert@464: 				state = HIGH;
robert@464: 			
robert@464: 			// Write LED
robert@464: 			digitalWriteOnce(context, n, led, state);
robert@464: 		}
robert@464: 	}
robert@464: }
robert@464: 
robert@464: void cleanup(BelaContext *context, void *userData)
robert@464: {
robert@464: 
robert@464: }