//**************************************************************************************************
/////////////////////////////// SPECTRA EXTERNAL CONTROLLER /////////////////////////////////
/***************************************************************************************************
Created by: David Deininger
Created: December 7, 2015
Last Modified: January 16, 2016
Revision 5
Thanks to: Arduino! For an awesome electornics learning platform
http://www.arduino.cc/
*/
// Dependent Libraries and files
#include <avr/pgmspace.h>
#include <ADC.h>
#include <Audio.h>
// Timing Constants and variables
const int standard_heart_beat = 10; //the interval time of timed operations in milliseconds
int heart_beat_start = 0;
// AMDF Variables
ADC *adc = new ADC(); // adc object
const int min_amdf = 30;
const int max_amdf = 260;
const int amdf_window_size = 100;
const int max_power_value = 3000;
int amdf[max_amdf];
int dc_sum = 0;
int dc_offset_array[8]= {0,0,0,0,0,0,0,0};
int dc_offset_array_pointer = 0;
int dc_offset = 0;
int power_calc = 0;
// HPS_AMDF Variables
int power_input_limit = 400;
boolean hps_amdf_release = true;
int hps_amdf[max_amdf];
int input_power = 0;
int input_power_array[4] = {0,0,0,0};
int input_power_array_pointer = 0;
int peak_average = 0;
int peak_current = 0;
int peak_index[8]= {0,0,0,0,0,0,0,0};
int peak_index_pointer = 0;
int pointer = min_amdf;
boolean test_sample_release = true;
int window_sample[max_amdf + amdf_window_size];
// Audio Library Setup
// These must always be after the ADC object
// AudioInputAnalog audio_passthrough(A3);
// AudioOutputAnalog dac1;
// AudioConnection patchCord1(audio_passthrough, dac1);
//**************************************************************************************************
///////////////////////////////////////////// SETUP //////////////////////////////////////////////
//**************************************************************************************************
void setup(){
//Setup the ADC
pinMode(A2, INPUT);
adc->setReference(ADC_REF_3V3, ADC_1);
adc->setAveraging(4, ADC_1); // set number of averages
adc->setResolution(16, ADC_1); // set bits of resolution
adc->setConversionSpeed(ADC_MED_SPEED, ADC_1); // change the conversion speed
adc->setSamplingSpeed(ADC_MED_SPEED, ADC_1); // change the sampling speed
adc->enableInterrupts(ADC_1);
adc->startContinuous(A2, ADC_1);
//just because
delay(500);
// Audio connections require memory to work. For more
// detailed information, see the MemoryAndCpuUsage example
AudioMemory(20);
Serial.begin(115200);
}//end setup()
//**************************************************************************************************
////////////////////////////////////////// Main Program //////////////////////////////////////////
//**************************************************************************************************
void loop(){
//Hearbeat Counter
boolean heart_beat = false;
int heart_beat_end = millis();
if(heart_beat_end - heart_beat_start >= standard_heart_beat){
heart_beat_start = heart_beat_end;
heart_beat = true;
}
/*Calculate the HPS result in the following Manner (in order):
- Takes the Current AMDF calculation from ADC0_ISR
- Invert the spectrum
- Performs harmonic product spectrum on result
- Performs level clipping
- Performs convolution on the result
- Detects the max peak
- Averages the result over 4 previous results */
hps_amdf_result();
/* All reduced time operations */
if(heart_beat == true){
//Serial.println(input_power); // print out the power (approximately 3000 - 0)
Serial.println(window_sample[0]); // print out a sample
// if(input_power > power_input_limit)
// Serial.println(48250/peak_average); // print out the estimated frequency in hz
// else
// Serial.println(0);
}//end print outputs
}//end loop()
//**************************************************************************************************
////////////////////////////////////////// ADC INTERRUPT //////////////////////////////////////////
//**************************************************************************************************
void adc1_isr(void) {
int read_input = (uint16_t)adc->analogReadContinuous(ADC_1); //Grab the data
window_sample[pointer] = read_input - dc_offset; //dc offset the sample
dc_sum += read_input; //Add up input samples to calculate dc offset
power_calc += abs(window_sample[pointer]); //Add up input samples to calculate power
//Process the amdf when enough results are in
int temp_result = 0;
int temp_shift = pointer - amdf_window_size;
if(pointer >= min_amdf + amdf_window_size){
for(int i = 0; i < amdf_window_size; i++){
temp_result += abs(window_sample[i] - window_sample[ i + temp_shift ]);
}
amdf[temp_shift] = temp_result;
}
//Advance pointer for next data input
pointer++;
//Once the AMDF is complete, perform wrap up routines to send it to HPS calculation
if(pointer >= max_amdf + amdf_window_size){
// Copy amdf into HPS working array, update results availability
// Block any subsequent update until the old data is processed
if(hps_amdf_release == true){
for(int a = min_amdf; a < max_amdf; a++){
hps_amdf[a] = amdf[a];
}
hps_amdf_release = false;
input_power_array[input_power_array_pointer] = sqrt(power_calc);
}//end if
//reset power var and collection point
power_calc = 0;
pointer = 0;
//adjust dc offset calculation with averaging
dc_offset_array[dc_offset_array_pointer] = dc_sum / (max_amdf + amdf_window_size);
dc_sum = 0;
dc_offset = (dc_offset_array[0] + dc_offset_array[1] +
dc_offset_array[2] + dc_offset_array[3] +
dc_offset_array[4] + dc_offset_array[5] +
dc_offset_array[6] + dc_offset_array[7] )/8;
dc_offset_array_pointer++;
if(dc_offset_array_pointer > 7){ dc_offset_array_pointer = 0; }
}// end if
}// end adc_isr
//**************************************************************************************************
/////////////////////////////////// HARMONIC PRODUCT SPECTRUM ////////////////////////////////////
//**************************************************************************************************
void hps_amdf_result(){
//Allow HPS to work on AMDF if new data is available
if(hps_amdf_release == false){
//calculate the new power
input_power = ( input_power_array[0] + input_power_array[1] + input_power_array[2] + input_power_array[3] ) / 4;
input_power_array_pointer++;
if(input_power_array_pointer > 3) input_power_array_pointer = 0;
//if input power is above limit, process the array
if(input_power >= power_input_limit){
//find the maximum value of the amdf result
int temp_max = 0;
int temp_min = 123456;
for(int i = min_amdf; i < max_amdf; i++){
if(hps_amdf[i] > temp_max){ temp_max = hps_amdf[i]; }
if(hps_amdf[i] < temp_min){ temp_min = hps_amdf[i]; }
}
//ensure values below min_amdf are neglected
for(int i = 0; i < min_amdf + 3; i++){ hps_amdf[i] = temp_max; }
//normalize and invert the result array, allow high correlation to pass
int strong_correlation = (int)((float)(temp_max - temp_min) * 0.75);
for(int i = 0 ; i < max_amdf; i++){
hps_amdf[i] = temp_max - hps_amdf[i];
if(hps_amdf[i] < strong_correlation){ hps_amdf[i] = 0; }
}
//perform partial harmonic spectrum (HPS) analysis on amdf result
float norm_fact = 1.00;
float first_harm_fact = 0.10;
float second_harm_fact = 0.10;
float third_harm_fact = 0.10;
for(int i = min_amdf; i < max_amdf-1; i++){
if(i*2 < max_amdf){
hps_amdf[i] = hps_amdf[i] * norm_fact;
hps_amdf[i] += (hps_amdf[i*2] * first_harm_fact +
hps_amdf[i*2-1] * first_harm_fact +
hps_amdf[i*2+1] * first_harm_fact); }
if(i*3 < max_amdf){
hps_amdf[i] = hps_amdf[i] * norm_fact;
hps_amdf[i] += (hps_amdf[i*3] * second_harm_fact +
hps_amdf[i*3-1] * second_harm_fact +
hps_amdf[i*3+1] * second_harm_fact);}
if(i*4 < max_amdf){
hps_amdf[i] = hps_amdf[i] * norm_fact;
hps_amdf[i] += (hps_amdf[i*4] * third_harm_fact +
hps_amdf[i*4-1] * third_harm_fact +
hps_amdf[i*4+1] * third_harm_fact); }
}//end if
//perform 11 digit wide square convolution on the HPS spectrum if less than half
if(peak_average >= max_amdf/2){
int temp_hps[max_amdf];
temp_max = 0;
for(int i = 0; i<max_amdf; i++){ temp_hps[i] = 0; }
for(int i = 5; i < max_amdf-5; i++){
temp_hps[i] = 0;
for(int j = -5; j<6 ; j++){ temp_hps[i] += hps_amdf[i-j]; }
if(temp_hps[i] > temp_max){ temp_max = temp_hps[i]; }
}
for(int i = 0; i<max_amdf; i++){ hps_amdf[i] = temp_hps[i]; }
}
//Find the max of the resulting HPS spectrum
temp_max = 0;
int temp_peak_index = 0;
for(int i = 0; i < max_amdf; i++){
if(hps_amdf[i] > temp_max){
temp_max = hps_amdf[i];
temp_peak_index = i; }
}
//Pass peak value to the averaging value output
peak_index[peak_index_pointer] = temp_peak_index;
peak_current = temp_peak_index;
peak_average = 0;
for(int i = 0; i < 4; i++){
peak_average += peak_index[i];
}
peak_average = peak_average / 4;
}else{
//Update for too low of an input to prevent spurious results
peak_current = 0;
}//end power limitation if statement
//Update the average pointer and average value
peak_index_pointer++;
if(peak_index_pointer > 3){ peak_index_pointer = 0; }
//Release the HPS to gather another AMDF result
hps_amdf_release = true;
}//end hps amdf release if statement
}//Harmonic product spectrum