From 6c8a062be9a0e34ee0f60ad16499827b5fdaa627 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Wed, 10 Feb 2016 15:10:40 +0100 Subject: move export-analysis implementation into cc-file. lib/libfftw3f.a(apiplan.o):apiplan.c:(.text+0x430): multiple definition of `fftwf_destroy_plan' This is because static symbols in a .dll have no fixed address and are mapped when loading the dll. Static functions in .exe do have a fixed address. With a header-only implementation the functions are provided libardour.dll and ardour.exe --- libs/audiographer/audiographer/general/analyser.h | 158 +------------------ libs/audiographer/src/general/analyser.cc | 183 ++++++++++++++++++++++ libs/audiographer/wscript | 3 +- 3 files changed, 193 insertions(+), 151 deletions(-) create mode 100644 libs/audiographer/src/general/analyser.cc (limited to 'libs') diff --git a/libs/audiographer/audiographer/general/analyser.h b/libs/audiographer/audiographer/general/analyser.h index a9ce538fba..fb4c6aa3f9 100644 --- a/libs/audiographer/audiographer/general/analyser.h +++ b/libs/audiographer/audiographer/general/analyser.h @@ -20,15 +20,14 @@ #define AUDIOGRAPHER_ANALYSER_H #include + #include #include #include "audiographer/visibility.h" #include "audiographer/sink.h" -#include "audiographer/routines.h" #include "audiographer/utils/listed_source.h" -#include "pbd/fastlog.h" #include "ardour/export_analysis.h" namespace AudioGrapher @@ -37,152 +36,16 @@ namespace AudioGrapher class /*LIBAUDIOGRAPHER_API*/ Analyser : public ListedSource, public Sink { public: - Analyser (float sample_rate, unsigned int channels, framecnt_t bufsize, framecnt_t n_samples) - : _ebur128_plugin (0) - , _sample_rate (sample_rate) - , _channels (channels) - , _bufsize (bufsize / channels) - , _n_samples (n_samples) - , _pos (0) - { - assert (bufsize % channels == 0); - //printf("NEW ANALYSER %p r:%.1f c:%d f:%ld l%ld\n", this, sample_rate, channels, bufsize, n_samples); - if (channels > 0 && channels <= 2) { - using namespace Vamp::HostExt; - PluginLoader* loader (PluginLoader::getInstance()); - _ebur128_plugin = loader->loadPlugin ("libardourvampplugins:ebur128", sample_rate, PluginLoader::ADAPT_ALL_SAFE); - assert (_ebur128_plugin); - _ebur128_plugin->reset (); - _ebur128_plugin->initialise (channels, _bufsize, _bufsize); - } - _bufs[0] = (float*) malloc (sizeof(float) * _bufsize); - _bufs[1] = (float*) malloc (sizeof(float) * _bufsize); - const size_t peaks = sizeof(_result._peaks) / sizeof (ARDOUR::PeakData::PeakDatum) / 2; - _spp = ceil ((_n_samples + 1.f) / (float) peaks); - - _fft_data_size = _bufsize / 2; - _fft_freq_per_bin = sample_rate / _fft_data_size / 2.f; - - _fft_data_in = (float *) fftwf_malloc (sizeof(float) * _bufsize); - _fft_data_out = (float *) fftwf_malloc (sizeof(float) * _bufsize); - _fft_power = (float *) malloc (sizeof(float) * _fft_data_size); - - for (uint32_t i = 0; i < _fft_data_size; ++i) { - _fft_power[i] = 0; - } - for (uint32_t i = 0; i < _bufsize; ++i) { - _fft_data_out[i] = 0; - } - - _fft_plan = fftwf_plan_r2r_1d (_bufsize, _fft_data_in, _fft_data_out, FFTW_R2HC, FFTW_MEASURE); - - _hann_window = (float *) malloc(sizeof(float) * _bufsize); - double sum = 0.0; - - for (uint32_t i = 0; i < _bufsize; ++i) { - _hann_window[i] = 0.5f - (0.5f * (float) cos (2.0f * M_PI * (float)i / (float)(_bufsize))); - sum += _hann_window[i]; - } - const double isum = 2.0 / sum; - for (uint32_t i = 0; i < _bufsize; ++i) { - _hann_window[i] *= isum; - } - } - - ~Analyser () - { - delete _ebur128_plugin; - free (_bufs[0]); - free (_bufs[1]); - fftwf_destroy_plan (_fft_plan); - fftwf_free (_fft_data_in); - fftwf_free (_fft_data_out); - free (_fft_power); - free (_hann_window); - } - - void process (ProcessContext const & c) - { - framecnt_t n_samples = c.frames() / c.channels(); - assert (c.frames() % c.channels() == 0); - assert (n_samples <= _bufsize); - //printf("PROC %p @%ld F: %ld, S: %ld C:%d\n", this, _pos, c.frames(), n_samples, c.channels()); - float const * d = c.data (); - framecnt_t s; - for (s = 0; s < n_samples; ++s) { - _fft_data_in[s] = 0; - const framecnt_t pk = (_pos + s) / _spp; - for (unsigned int c = 0; c < _channels; ++c) { - const float v = *d; - _bufs[c][s] = v; - if (_result._peaks[pk].min > v) { _result._peaks[pk].min = *d; } - if (_result._peaks[pk].max < v) { _result._peaks[pk].max = *d; } - _fft_data_in[s] += v * _hann_window[s] / (float) _channels; - ++d; - } - } - for (; s < _bufsize; ++s) { - for (unsigned int c = 0; c < _channels; ++c) { - _bufs[c][s] = 0.f; - _fft_data_in[s] = 0; - } - } - if (_ebur128_plugin) { - _ebur128_plugin->process (_bufs, Vamp::RealTime::fromSeconds ((double) _pos / _sample_rate)); - } - fftwf_execute (_fft_plan); - - _fft_power[0] = _fft_data_out[0] * _fft_data_out[0]; - -#define FRe (_fft_data_out[i]) -#define FIm (_fft_data_out[_bufsize - i]) - for (uint32_t i = 1; i < _fft_data_size - 1; ++i) { - _fft_power[i] = (FRe * FRe) + (FIm * FIm); - } -#undef FRe -#undef FIm - - // TODO handle case where _pos / _spp != (_pos + _bufsize) / _spp - // TODO: get geometry from ExportAnalysis - const framecnt_t x = _pos / _spp; - const float range = 80; // dB - const double ypb = 256.0 / _fft_data_size; - - for (uint32_t i = 1; i < _fft_data_size - 1; ++i) { - const float level = fft_power_at_bin (i, i); - if (level < -range) continue; - const float pk = level > 0.0 ? 1.0 : (range + level) / range; - const uint32_t y = 256 - ceil (i * ypb); // log-y? - assert (x >= 0 && x < 800); - assert (y < 256); - if (_result._spectrum[x][y] < pk) { _result._spectrum[x][y] = pk; } - } - - _pos += n_samples; - - /* pass audio audio through */ - ListedSource::output(c); - } - - ARDOUR::ExportAnalysisPtr result () { - //printf("PROCESSED %ld / %ld samples\n", _pos, _n_samples); - if (_pos == 0) { - return ARDOUR::ExportAnalysisPtr (); - } - if (_ebur128_plugin) { - Vamp::Plugin::FeatureSet features = _ebur128_plugin->getRemainingFeatures (); - if (!features.empty() && features.size() == 2) { - _result.loudness = features[0][0].values[0]; - _result.loudness_range = features[1][0].values[0]; - _result.have_loudness = true; - } - } - return ARDOUR::ExportAnalysisPtr (new ARDOUR::ExportAnalysis (_result)); - } + Analyser (float sample_rate, unsigned int channels, framecnt_t bufsize, framecnt_t n_samples); + ~Analyser (); + void process (ProcessContext const & c); + ARDOUR::ExportAnalysisPtr result (); using Sink::process; - private: + private: + float fft_power_at_bin (const uint32_t b, const float norm) const; + ARDOUR::ExportAnalysis _result; Vamp::Plugin* _ebur128_plugin; @@ -203,11 +66,6 @@ class /*LIBAUDIOGRAPHER_API*/ Analyser : public ListedSource, public Sink float* _fft_power; fftwf_plan _fft_plan; - inline float fft_power_at_bin (const uint32_t b, const float norm) const { - const float a = _fft_power[b] * norm; - return a > 1e-12 ? 10.0 * fast_log10(a) : -INFINITY; - } - }; } // namespace diff --git a/libs/audiographer/src/general/analyser.cc b/libs/audiographer/src/general/analyser.cc new file mode 100644 index 0000000000..9f1ed8a97a --- /dev/null +++ b/libs/audiographer/src/general/analyser.cc @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2016 Robin Gareus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "audiographer/general/analyser.h" +#include "pbd/fastlog.h" + +using namespace AudioGrapher; + +Analyser::Analyser (float sample_rate, unsigned int channels, framecnt_t bufsize, framecnt_t n_samples) + : _ebur128_plugin (0) + , _sample_rate (sample_rate) + , _channels (channels) + , _bufsize (bufsize / channels) + , _n_samples (n_samples) + , _pos (0) +{ + assert (bufsize % channels == 0); + //printf("NEW ANALYSER %p r:%.1f c:%d f:%ld l%ld\n", this, sample_rate, channels, bufsize, n_samples); + if (channels > 0 && channels <= 2) { + using namespace Vamp::HostExt; + PluginLoader* loader (PluginLoader::getInstance()); + _ebur128_plugin = loader->loadPlugin ("libardourvampplugins:ebur128", sample_rate, PluginLoader::ADAPT_ALL_SAFE); + assert (_ebur128_plugin); + _ebur128_plugin->reset (); + _ebur128_plugin->initialise (channels, _bufsize, _bufsize); + } + _bufs[0] = (float*) malloc (sizeof(float) * _bufsize); + _bufs[1] = (float*) malloc (sizeof(float) * _bufsize); + const size_t peaks = sizeof(_result.peaks) / sizeof (ARDOUR::PeakData::PeakDatum) / 2; + _spp = ceil ((_n_samples + 1.f) / (float) peaks); + + _fft_data_size = _bufsize / 2; + _fft_freq_per_bin = sample_rate / _fft_data_size / 2.f; + + _fft_data_in = (float *) fftwf_malloc (sizeof(float) * _bufsize); + _fft_data_out = (float *) fftwf_malloc (sizeof(float) * _bufsize); + _fft_power = (float *) malloc (sizeof(float) * _fft_data_size); + + for (uint32_t i = 0; i < _fft_data_size; ++i) { + _fft_power[i] = 0; + } + for (uint32_t i = 0; i < _bufsize; ++i) { + _fft_data_out[i] = 0; + } + + _fft_plan = fftwf_plan_r2r_1d (_bufsize, _fft_data_in, _fft_data_out, FFTW_R2HC, FFTW_MEASURE); + + _hann_window = (float *) malloc(sizeof(float) * _bufsize); + double sum = 0.0; + + for (uint32_t i = 0; i < _bufsize; ++i) { + _hann_window[i] = 0.5f - (0.5f * (float) cos (2.0f * M_PI * (float)i / (float)(_bufsize))); + sum += _hann_window[i]; + } + const double isum = 2.0 / sum; + for (uint32_t i = 0; i < _bufsize; ++i) { + _hann_window[i] *= isum; + } +} + +Analyser::~Analyser () +{ + delete _ebur128_plugin; + free (_bufs[0]); + free (_bufs[1]); + fftwf_destroy_plan (_fft_plan); + fftwf_free (_fft_data_in); + fftwf_free (_fft_data_out); + free (_fft_power); + free (_hann_window); +} + +void +Analyser::process (ProcessContext const & c) +{ + framecnt_t n_samples = c.frames() / c.channels(); + assert (c.frames() % c.channels() == 0); + assert (n_samples <= _bufsize); + //printf("PROC %p @%ld F: %ld, S: %ld C:%d\n", this, _pos, c.frames(), n_samples, c.channels()); + float const * d = c.data (); + framecnt_t s; + for (s = 0; s < n_samples; ++s) { + _fft_data_in[s] = 0; + const framecnt_t pk = (_pos + s) / _spp; + for (unsigned int c = 0; c < _channels; ++c) { + const float v = *d; + _bufs[c][s] = v; + if (_result.peaks[pk].min > v) { _result.peaks[pk].min = *d; } + if (_result.peaks[pk].max < v) { _result.peaks[pk].max = *d; } + _fft_data_in[s] += v * _hann_window[s] / (float) _channels; + ++d; + } + } + for (; s < _bufsize; ++s) { + for (unsigned int c = 0; c < _channels; ++c) { + _bufs[c][s] = 0.f; + _fft_data_in[s] = 0; + } + } + if (_ebur128_plugin) { + _ebur128_plugin->process (_bufs, Vamp::RealTime::fromSeconds ((double) _pos / _sample_rate)); + } + fftwf_execute (_fft_plan); + + _fft_power[0] = _fft_data_out[0] * _fft_data_out[0]; + +#define FRe (_fft_data_out[i]) +#define FIm (_fft_data_out[_bufsize - i]) + for (uint32_t i = 1; i < _fft_data_size - 1; ++i) { + _fft_power[i] = (FRe * FRe) + (FIm * FIm); + } +#undef FRe +#undef FIm + + // TODO: get geometry from ExportAnalysis + const framecnt_t x0 = _pos / _spp; + const framecnt_t x1 = (_pos + n_samples) / _spp; + const float range = 80; // dB + const double ypb = 200.0 / _fft_data_size; + + for (uint32_t i = 1; i < _fft_data_size - 1; ++i) { + const float level = fft_power_at_bin (i, i); + if (level < -range) continue; + const float pk = level > 0.0 ? 1.0 : (range + level) / range; + const uint32_t y = 200 - ceil (i * ypb); // log-y? + assert (y < 200); + for (int x = x0; x < x1; ++x) { + assert (x >= 0 && x < 800); + if (_result.spectrum[x][y] < pk) { _result.spectrum[x][y] = pk; } + } + } + + _pos += n_samples; + + /* pass audio audio through */ + ListedSource::output(c); +} + +ARDOUR::ExportAnalysisPtr +Analyser::result () +{ + //printf("PROCESSED %ld / %ld samples\n", _pos, _n_samples); + if (_pos == 0) { + return ARDOUR::ExportAnalysisPtr (); + } + if (_ebur128_plugin) { + Vamp::Plugin::FeatureSet features = _ebur128_plugin->getRemainingFeatures (); + if (!features.empty() && features.size() == 3) { + _result.loudness = features[0][0].values[0]; + _result.loudness_range = features[1][0].values[0]; + assert (features[2][0].values.size() == 540); + for (int i = 0; i < 540; ++i) { + _result.loudness_hist[i] = features[2][0].values[i]; + if (_result.loudness_hist[i] > _result.loudness_hist_max) { + _result.loudness_hist_max = _result.loudness_hist[i]; } + } + _result.have_loudness = true; + } + } + return ARDOUR::ExportAnalysisPtr (new ARDOUR::ExportAnalysis (_result)); +} + +float +Analyser::fft_power_at_bin (const uint32_t b, const float norm) const +{ + const float a = _fft_power[b] * norm; + return a > 1e-12 ? 10.0 * fast_log10(a) : -INFINITY; +} diff --git a/libs/audiographer/wscript b/libs/audiographer/wscript index aef85f985c..e70f1234a9 100644 --- a/libs/audiographer/wscript +++ b/libs/audiographer/wscript @@ -64,6 +64,7 @@ def build(bld): 'src/general/sample_format_converter.cc', 'src/routines.cc', 'src/debug_utils.cc', + 'src/general/analyser.cc', 'src/general/broadcast_info.cc', 'src/general/normalizer.cc' ] @@ -83,7 +84,7 @@ def build(bld): audiographer.name = 'libaudiographer' audiographer.target = 'audiographer' audiographer.export_includes = ['.', './src'] - audiographer.includes = ['.', './src'] + audiographer.includes = ['.', './src','../ardour','../timecode','../evoral'] audiographer.uselib = 'GLIB GLIBMM GTHREAD SAMPLERATE SNDFILE FFTW3F' audiographer.use = 'libpbd' audiographer.vnum = AUDIOGRAPHER_LIB_VERSION -- cgit v1.2.3