blob: a7b1e5628ba508c7bd7dbedc7021ad97427954e2 [file] [log] [blame]
/*
* Code for frequency analysis
*
* Copyright (C) 2015 Nest Labs. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <math.h>
#include "fftw3.h"
#include "wav_header.h"
#define SAMPLE_BYTES 2
#define MAX_NUM_CHANNELS 2
#define RECTANGULAR 1
#define HAMMING 2
#define BLACKMAN_NUTTALL 3
#define HANNING 4
#define BLACKMAN_HARRIS 5
#define WINDOW_TYPE HANNING
#define SQUARE(a) ( (a) * (a) )
void apply_window(int16_t *input, double *windowed, int32_t num_samples);
#if WINDOW_TYPE == RECTANGULAR
void apply_window(int16_t *input, double *windowed, int32_t num_samples)
{
int32_t sample_index;
for(sample_index = 0; sample_index < num_samples; sample_index++)
windowed[sample_index] = (double)input[sample_index]/32767.0;
}
#elif WINDOW_TYPE == HAMMING
void apply_window(int16_t *input, double *windowed, int32_t num_samples)
{
int32_t sample_index;
for(sample_index = 0; sample_index < num_samples; sample_index++) {
windowed[sample_index] = 0.54 - 0.46 * cos(2 * M_PI * sample_index/(num_samples -1));
windowed[sample_index] *= (double)input[sample_index]/32767.0;
}
}
#elif WINDOW_TYPE == BLACKMAN_NUTTALL
void apply_window(int16_t *input, double *windowed, int32_t num_samples)
{
int32_t sample_index;
for(sample_index = 0; sample_index < num_samples; sample_index++) {
windowed[sample_index] = 0.3635819;
windowed[sample_index] -= 0.4891775 * cos(2 * M_PI * sample_index/(num_samples -1));
windowed[sample_index] += 0.1365995 * cos(4 * M_PI * sample_index/(num_samples -1));
windowed[sample_index] -= 0.0106411 * cos(6 * M_PI * sample_index/(num_samples -1));
windowed[sample_index] *= (double)input[sample_index]/32767.0;
}
}
#elif WINDOW_TYPE == HANNING
void apply_window(int16_t *input, double *windowed, int32_t num_samples)
{
int32_t sample_index;
for(sample_index = 0; sample_index < num_samples; sample_index++) {
windowed[sample_index] = 0.5 - 0.5 * cos(2 * M_PI * sample_index/(num_samples -1));
windowed[sample_index] *= (double)input[sample_index]/32767.0;
}
}
#elif WINDOW_TYPE == BLACKMAN_HARRIS
void apply_window(int16_t *input, double *windowed, int32_t num_samples)
{
int32_t sample_index;
for(sample_index = 0; sample_index < num_samples; sample_index++) {
windowed[sample_index] = 0.35875;
windowed[sample_index] -= 0.48829 * cos(2 * M_PI * sample_index/(num_samples -1));
windowed[sample_index] += 0.14128 * cos(4 * M_PI * sample_index/(num_samples -1));
windowed[sample_index] -= 0.01168 * cos(6 * M_PI * sample_index/(num_samples -1));
windowed[sample_index] *= (double)input[sample_index]/32767.0;
}
}
#endif
typedef struct {
double *data_r;
double *mag;
fftw_complex *data_c;
} scratch_t;
typedef struct {
double max_freq;
double max;
} output_t;
void execute_fft(int16_t * input_buffer,
int32_t nfft,
int32_t offset,
int32_t num_input_samples,
int32_t sample_rate,
scratch_t *scratch,
output_t *fft_output)
{
fftw_plan p;
int32_t max_freq_index, sample_index, block_index;
double *data_r = scratch->data_r;
double *mag = scratch->mag;
double max = -32768.0; /* Initialize to a very low dB */
fftw_complex *data_c = scratch->data_c;
/* This array will contain the average over multiple blocks*/
for(sample_index = 0; sample_index < nfft/2+1; sample_index++) {
mag[sample_index] = 0;
}
/* Setup the plan to compute FFT */
p = fftw_plan_dft_r2c_1d(nfft, data_r, data_c, FFTW_ESTIMATE);
/* Execute FFT */
for(block_index = 0; block_index * offset + nfft < num_input_samples; block_index++) {
/* Windowing */
apply_window(input_buffer + block_index * offset, data_r, nfft);
/* FFT */
fftw_execute(p);
/* Accumulate for averaging. nfft is the normalization factor to
* account for the difference in forward and reverse DFT
*/
for(sample_index = 0; sample_index < nfft/2+1; sample_index++) {
mag[sample_index] += SQUARE(data_c[sample_index][0]/nfft) + SQUARE(data_c[sample_index][1]/nfft);
}
}
/* Find the tone with the highest frequency, ignore the DC bin */
for(sample_index = 1; sample_index < nfft/2+1; sample_index++) {
mag[sample_index] = 10 * log10(mag[sample_index]/block_index);
if (mag[sample_index] > max) {
max = mag[sample_index];
max_freq_index = sample_index;
}
}
fft_output->max = max;
fft_output->max_freq = (float)max_freq_index/nfft * sample_rate;
printf("Number of blocks for FFT %d\n", block_index);
#if 0
/* Print the final output */
for(sample_index = 0; sample_index < nfft/2+1; sample_index++) {
mag[sample_index] = 10 * log10(mag[sample_index]/block_index);
printf("%d: %lf(dB)\n", sample_index, mag[sample_index]);
}
#endif
fftw_destroy_plan(p);
}
int check_boundary(output_t *fft_output, double input_freq, double freq_dev,
double input_vol, double vol_dev)
{
double lower_limit, upper_limit;
/* check if freq is within deviation */
lower_limit = input_freq * (1 - freq_dev/100);
upper_limit = input_freq * (1 + freq_dev/100);
if ( (fft_output->max_freq < lower_limit) || (fft_output->max_freq > upper_limit) )
return -1;
/* Check if volume is within deviation */
lower_limit = input_vol * (1 - vol_dev/100);
lower_limit = 20 * log10(lower_limit);
upper_limit = input_vol * (1 + vol_dev/100);
upper_limit = 20 * log10(upper_limit);
if ( (fft_output->max < lower_limit) || (fft_output->max > upper_limit) )
return -1;
return 0;
}
int main(int argc, char **argv)
{
FILE *fin = NULL;
int16_t *input_buffer_i = NULL; /* contains interleaved data */
int16_t *input_buffer_n = NULL; /* contains one channel of non-interleaved data */
wave_header_t hdr;
int ret = 0;
int32_t nfft, offset, channel_index, num_input_samples, sample_index;
scratch_t scratch = {NULL, NULL, NULL};
output_t fft_output;
double input_freq[MAX_NUM_CHANNELS], freq_dev[MAX_NUM_CHANNELS];
double input_vol[MAX_NUM_CHANNELS], vol_dev[MAX_NUM_CHANNELS];
/* Verify all required arguments are present */
if (argc < 8) {
printf("Usage: freq_analyzer <num FFT points> <overlap percent> <input file> <input freq> <%% freq deviation> <volume (dB)> <\%% volume deviation>\n");
printf(" channel 2: <input freq> <%% freq deviation> <volume (dB)> <\%% volume deviation>\n");
printf(" ........... \n");
ret = -1;
goto _done;
}
/* Number of FFT points */
nfft = atoi(argv[1]);
/* overlap */
offset = ( (100 - atoi(argv[2])) * nfft) / 100;
/* Read the samples from the input file into an array */
fin = fopen(argv[3], "rb");
if (!fin) {
printf("Unable to open input file %s\n", argv[3]);
ret = -1;
goto _done;
}
/* input frequency */
input_freq[0] = atof(argv[4]);
/* Deviation in input frequency */
freq_dev[0] = atof(argv[5]);
/* Input volume */
input_vol[0] = pow(10,atof(argv[6])/20);
/* Deviation from input volume */
vol_dev[0] = atof(argv[7]);
/* Read the header & input samples */
fread(&hdr, sizeof(wave_header_t), 1, fin);
if (hdr.num_channels > MAX_NUM_CHANNELS) {
printf("Only upto stereo supported\n");
ret = -1;
goto _done;
}
if ((hdr.num_channels == 2) && (argc < 12) ) {
printf("Not enough arguments\n");
ret = -1;
goto _done;
}
if (hdr.num_channels == 2) {
/* input frequency */
input_freq[1] = atof(argv[8]);
/* Deviation in input frequency */
freq_dev[1] = atof(argv[9]);
/* Input volume */
input_vol[1] = pow(10,atof(argv[10])/20);
/* Deviation from input volume */
vol_dev[1] = atof(argv[11]);
}
num_input_samples = hdr.subchunk2_size/(hdr.num_channels * SAMPLE_BYTES);
input_buffer_i = malloc(hdr.subchunk2_size);
if (NULL == input_buffer_i) {
printf("Unable to allocate space for input buffer\n");
ret = -1;
goto _done;
}
fread(input_buffer_i, 1, hdr.subchunk2_size, fin);
/* Make necessary memory allocations */
scratch.data_r = fftw_alloc_real(nfft);
if (NULL == scratch.data_r) {
printf("Unable to allocate space for input buffer\n");
ret = -1;
goto _done;
}
scratch.data_c = fftw_alloc_complex(nfft/2 + 1);
if (NULL == scratch.data_c) {
printf("Unable to allocate space for output buffer\n");
ret = -1;
goto _done;
}
scratch.mag = malloc(sizeof(double) * (nfft/2 + 1));
if (NULL == scratch.mag) {
printf("Unable to allocate space for output averaging buffer\n");
ret = -1;
goto _done;
}
/* FFT */
if (1 == hdr.num_channels) {
printf("Channel 0: ");
/* Mono */
execute_fft(input_buffer_i, nfft, offset, num_input_samples, hdr.sample_rate, &scratch, &fft_output);
/* Print the output data */
printf("%f (hz): %lf(dB)\n", fft_output.max_freq, fft_output.max);
if (check_boundary(&fft_output, input_freq[0], freq_dev[0], input_vol[0], vol_dev[0]) == 0)
printf("PASS\n");
else
printf("FAIL\n");
}
else {
/* Processing for more than 1 channel */
input_buffer_n = malloc(num_input_samples * SAMPLE_BYTES);
if (NULL == input_buffer_n) {
printf("Unable to allocate space for deinterleaving buffer\n");
ret = -1;
goto _done;
}
for(channel_index = 0; channel_index < hdr.num_channels; channel_index++) {
/* Deinterleave and fft for one channel at a time */
for(sample_index = 0; sample_index < num_input_samples; sample_index++)
input_buffer_n[sample_index] = input_buffer_i[hdr.num_channels * sample_index + channel_index];
/* FFT */
printf("Channel %d: ", channel_index);
execute_fft(input_buffer_n, nfft, offset, num_input_samples, hdr.sample_rate, &scratch, &fft_output);
/* Print the output data */
printf("%f (hz): %lf(dB)\n", fft_output.max_freq, fft_output.max);
if (check_boundary(&fft_output, input_freq[channel_index], freq_dev[channel_index], input_vol[channel_index], vol_dev[channel_index]) == 0)
printf("PASS\n");
else
printf("FAIL\n");
}
}
_done:
if (fin) fclose(fin);
if (input_buffer_i) free(input_buffer_i);
if (input_buffer_n) free(input_buffer_n);
if (scratch.data_r) fftw_free(scratch.data_r);
if (scratch.data_c) fftw_free(scratch.data_c);
if (scratch.mag) free(scratch.mag);
return ret;
}