spek

Acoustic spectrum analyser
git clone http://git.hanabi.in/repos/spek.git
Log | Files | Refs | README

commit 0e03fc977b211e038ecd028fee6a76d961cb767e
parent 718daecc98154df076e89f348a56ef35749dccd4
Author: Alexander Kojevnikov <alexander@kojevnikov.com>
Date:   Wed, 10 Apr 2013 10:40:12 -0700

C++ify FFT functions

Diffstat:
Msrc/spek-fft.cc | 66+++++++++++++++++++++++++++++++++---------------------------------
Msrc/spek-fft.h | 54++++++++++++++++++++++++++++++++++++------------------
Msrc/spek-pipeline.cc | 36+++++++++++++++++-------------------
Msrc/spek-pipeline.h | 9+++++++--
Msrc/spek-spectrogram.cc | 8+++++---
Msrc/spek-spectrogram.h | 2++
6 files changed, 100 insertions(+), 75 deletions(-)

diff --git a/src/spek-fft.cc b/src/spek-fft.cc @@ -1,6 +1,6 @@ /* spek-fft.cc * - * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * Copyright (C) 2010-2013 Alexander Kojevnikov <alexander@kojevnikov.com> * * Spek is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,53 +16,53 @@ * along with Spek. If not, see <http://www.gnu.org/licenses/>. */ -#include <math.h> +#include <cmath> #define __STDC_CONSTANT_MACROS extern "C" { #include <libavcodec/avfft.h> -#include <libavutil/mem.h> } #include "spek-fft.h" -struct spek_fft_plan * spek_fft_plan_new(int n) +class FFTPlanImpl : public FFTPlan { - struct spek_fft_plan *p = (spek_fft_plan*)malloc(sizeof(struct spek_fft_plan)); - p->input = (float*)av_mallocz(sizeof(float) * n); - p->output = (float*)av_mallocz(sizeof(float) * (n / 2 + 1)); - int bits = 0; - while (n) { - n >>= 1; - ++bits; - } - p->n = 1 << --bits; - p->cx = av_rdft_init(bits, DFT_R2C); - return p; +public: + FFTPlanImpl(int nbits); + ~FFTPlanImpl() override; + + void execute() override; + +private: + struct RDFTContext *cx; +}; + +std::unique_ptr<FFTPlan> FFT::create(int nbits) +{ + return std::unique_ptr<FFTPlan>(new FFTPlanImpl(nbits)); +} + +FFTPlanImpl::FFTPlanImpl(int nbits) : FFTPlan(nbits), cx(av_rdft_init(nbits, DFT_R2C)) +{ +} + +FFTPlanImpl::~FFTPlanImpl() +{ + av_rdft_end(this->cx); } -void spek_fft_execute(struct spek_fft_plan *p) +void FFTPlanImpl::execute() { - av_rdft_calc(p->cx, p->input); + av_rdft_calc(this->cx, this->get_input()); // Calculate magnitudes. - int n = p->n; + int n = this->get_input_size(); float n2 = n * n; - p->output[0] = 10.0f * log10f(p->input[0] * p->input[0] / n2); - p->output[n / 2] = 10.0f * log10f(p->input[1] * p->input[1] / n2); + this->set_output(0, 10.0f * log10f(this->get_input(0) * this->get_input(0) / n2)); + this->set_output(n / 2, 10.0f * log10f(this->get_input(1) * this->get_input(1) / n2)); for (int i = 1; i < n / 2; i++) { - float val = - p->input[i * 2] * p->input[i * 2] + - p->input[i * 2 + 1] * p->input[i * 2 + 1]; - val /= n2; - p->output[i] = 10.0f * log10f(val); + float re = this->get_input(i * 2); + float im = this->get_input(i * 2 + 1); + this->set_output(i, 10.0f * log10f((re * re + im * im) / n2)); } } - -void spek_fft_delete(struct spek_fft_plan *p) -{ - av_rdft_end(p->cx); - av_free(p->input); - av_free(p->output); - free(p); -} diff --git a/src/spek-fft.h b/src/spek-fft.h @@ -1,6 +1,6 @@ /* spek-fft.h * - * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * Copyright (C) 2010-2013 Alexander Kojevnikov <alexander@kojevnikov.com> * * Spek is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,26 +19,44 @@ #ifndef SPEK_FFT_H_ #define SPEK_FFT_H_ -struct RDFTContext; +#include <memory> +#include <vector> -struct spek_fft_plan -{ - // Internal data. - struct RDFTContext *cx; - int n; +class FFTPlan; - // Exposed properties. - float *input; - float *output; +class FFT +{ +public: + FFT() {} + std::unique_ptr<FFTPlan> create(int nbits); }; -// Allocate buffers and create a new FFT plan. -struct spek_fft_plan * spek_fft_plan_new(int n); - -// Execute the FFT on plan->input. -void spek_fft_execute(struct spek_fft_plan *p); - -// Destroy the plan and de-allocate buffers. -void spek_fft_delete(struct spek_fft_plan *p); +class FFTPlan +{ +public: + FFTPlan(int nbits) : + nbits(nbits), input_size(1 << nbits), output_size((1 << (nbits - 1)) + 1), + input(input_size), output(output_size) {} + virtual ~FFTPlan() {} + + int get_input_size() const { return this->input_size; } + int get_output_size() const { return this->output_size; } + float get_input(int i) const { return this->input[i]; } + void set_input(int i, float v) { this->input[i] = v; } + float get_output(int i) const { return this->output[i]; } + void set_output(int i, float v) { this->output[i] = v; } + + virtual void execute() = 0; + +protected: + float *get_input() { return this->input.data(); } + +private: + int nbits; + int input_size; + int output_size; + std::vector<float> input; + std::vector<float> output; +}; #endif diff --git a/src/spek-pipeline.cc b/src/spek-pipeline.cc @@ -49,12 +49,11 @@ enum struct spek_pipeline { std::unique_ptr<AudioFile> file; - int bands; + std::unique_ptr<FFTPlan> fft; int samples; spek_pipeline_cb cb; void *cb_data; - struct spek_fft_plan *fft; float *coss; // Pre-computed cos table. int nfft; // Size of the FFT transform. int input_size; @@ -84,17 +83,21 @@ static void * worker_func(void *); static void reader_sync(struct spek_pipeline *p, int pos); struct spek_pipeline * spek_pipeline_open( - std::unique_ptr<AudioFile> file, int bands, int samples, spek_pipeline_cb cb, void *cb_data) + std::unique_ptr<AudioFile> file, + std::unique_ptr<FFTPlan> fft, + int samples, + spek_pipeline_cb cb, + void *cb_data +) { spek_pipeline *p = new spek_pipeline(); p->file = std::move(file); - p->bands = bands; + p->fft = std::move(fft); p->samples = samples; p->cb = cb; p->cb_data = cb_data; p->coss = NULL; - p->fft = NULL; p->input = NULL; p->output = NULL; p->has_reader_thread = false; @@ -105,16 +108,15 @@ struct spek_pipeline * spek_pipeline_open( p->has_worker_cond = false; if (!p->file->get_error()) { - p->nfft = 2 * bands - 2; + p->nfft = p->fft->get_input_size(); p->coss = (float*)malloc(p->nfft * sizeof(float)); float cf = 2.0f * (float)M_PI / p->nfft; for (int i = 0; i < p->nfft; ++i) { p->coss[i] = cosf(cf * i); } - p->fft = spek_fft_plan_new(p->nfft); p->input_size = p->nfft * (NFFT * 2 + 1); p->input = (float*)malloc(p->input_size * sizeof(float)); - p->output = (float*)malloc(bands * sizeof(float)); + p->output = (float*)malloc(p->fft->get_output_size() * sizeof(float)); p->file->start(samples); } @@ -173,10 +175,6 @@ void spek_pipeline_close(struct spek_pipeline *p) free(p->input); p->input = NULL; } - if (p->fft) { - spek_fft_delete(p->fft); - p->fft = NULL; - } if (p->coss) { free(p->coss); p->coss = NULL; @@ -357,7 +355,7 @@ static void * worker_func(void *pp) int head = 0, tail = 0; int prev_head = 0; - memset(p->output, 0, sizeof(float) * p->bands); + memset(p->output, 0, sizeof(float) * p->fft->get_output_size()); while (true) { pthread_mutex_lock(&p->reader_mutex); @@ -402,12 +400,12 @@ static void * worker_func(void *pp) // val *= 0.53836f - 0.46164f * coss[i]; // Hann window. val *= 0.5f * (1.0f - p->coss[i]); - p->fft->input[i] = val; + p->fft->set_input(i, val); } - spek_fft_execute(p->fft); + p->fft->execute(); num_fft++; - for (int i = 0; i < p->bands; i++) { - p->output[i] += p->fft->output[i]; + for (int i = 0; i < p->fft->get_output_size(); i++) { + p->output[i] += p->fft->get_output(i); } } @@ -419,14 +417,14 @@ static void * worker_func(void *pp) acc_error += p->file->get_error_per_interval(); } - for (int i = 0; i < p->bands; i++) { + for (int i = 0; i < p->fft->get_output_size(); i++) { p->output[i] /= num_fft; } if (sample == p->samples) break; p->cb(sample++, p->output, p->cb_data); - memset(p->output, 0, sizeof(float) * p->bands); + memset(p->output, 0, sizeof(float) * p->fft->get_output_size()); frames = 0; num_fft = 0; } diff --git a/src/spek-pipeline.h b/src/spek-pipeline.h @@ -22,13 +22,18 @@ #include <memory> #include <string> -class Audio; +class AudioFile; +class FFTPlan; struct spek_pipeline; typedef void (*spek_pipeline_cb)(int sample, float *values, void *cb_data); struct spek_pipeline * spek_pipeline_open( - std::unique_ptr<AudioFile> file, int bands, int samples, spek_pipeline_cb cb, void *cb_data + std::unique_ptr<AudioFile> file, + std::unique_ptr<FFTPlan> fft, + int samples, + spek_pipeline_cb cb, + void *cb_data ); void spek_pipeline_start(struct spek_pipeline *pipeline); diff --git a/src/spek-spectrogram.cc b/src/spek-spectrogram.cc @@ -22,6 +22,7 @@ #include "spek-audio.h" #include "spek-events.h" +#include "spek-fft.h" #include "spek-palette.h" #include "spek-pipeline.h" #include "spek-platform.h" @@ -43,8 +44,8 @@ enum MIN_RANGE = -140, URANGE = -20, LRANGE = -120, - NFFT = 2048, - BANDS = NFFT / 2 + 1, + FFT_BITS = 11, + BANDS = (1 << (FFT_BITS - 1)) + 1, LPAD = 60, TPAD = 60, RPAD = 90, @@ -62,6 +63,7 @@ SpekSpectrogram::SpekSpectrogram(wxFrame *parent) : wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS ), audio(new Audio()), // TODO: refactor + fft(new FFT()), pipeline(NULL), duration(0.0), sample_rate(0), @@ -365,7 +367,7 @@ void SpekSpectrogram::start() this->image.Create(samples, BANDS); this->pipeline = spek_pipeline_open( this->audio->open(std::string(this->path.utf8_str())), - BANDS, + this->fft->create(FFT_BITS), samples, pipeline_cb, this diff --git a/src/spek-spectrogram.h b/src/spek-spectrogram.h @@ -24,6 +24,7 @@ #include <wx/wx.h> class Audio; +class FFT; class SpekHaveSampleEvent; struct spek_pipeline; @@ -46,6 +47,7 @@ private: void stop(); std::unique_ptr<Audio> audio; + std::unique_ptr<FFT> fft; spek_pipeline *pipeline; wxString path; wxString desc;