From f169ff3db3943b9992042e71048cade2ca1fe39d Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Sat, 2 Jul 2016 23:35:00 +0200 Subject: [PATCH] extend lua API: * add a basic FFT spectrum analyzer * prepare Cairo::ImageSurface * HSL colorspace conversion --- libs/ardour/ardour/dsp_filter.h | 51 ++++++++++++++++ libs/ardour/ardour/lua_api.h | 12 ++++ libs/ardour/dsp_filter.cc | 105 ++++++++++++++++++++++++++++++++ libs/ardour/lua_api.cc | 41 +++++++++++++ libs/ardour/luabindings.cc | 10 +++ 5 files changed, 219 insertions(+) diff --git a/libs/ardour/ardour/dsp_filter.h b/libs/ardour/ardour/dsp_filter.h index 994d8725ea..6220dddf5a 100644 --- a/libs/ardour/ardour/dsp_filter.h +++ b/libs/ardour/ardour/dsp_filter.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include "ardour/libardour_visibility.h" namespace ARDOUR { namespace DSP { @@ -229,6 +231,9 @@ namespace ARDOUR { namespace DSP { */ void compute (Type t, double freq, double Q, double gain); + /** setup filter, set coefficients directly */ + void configure (double a1, double a2, double b0, double b1, double b2); + /** filter transfer function (filter response for spectrum visualization) * @param freq frequency * @return gain at given frequency in dB (clamped to -120..+120) @@ -244,5 +249,51 @@ namespace ARDOUR { namespace DSP { double _b0, _b1, _b2; }; + class LIBARDOUR_API FFTSpectrum { + public: + FFTSpectrum (uint32_t window_size, double rate); + ~FFTSpectrum (); + + /** set data to be analyzed and pre-process with hanning window + * n_samples + offset must not be larger than the configured window_size + * + * @param data raw audio data + * @param n_samples number of samples to write to analysis buffer + * @param offset destination offset + */ + void set_data_hann (float const * const data, const uint32_t n_samples, const uint32_t offset = 0); + + /** process current data in buffer */ + void execute (); + + /** query + * @param bin the frequency bin 0 .. window_size / 2 + * @param norm gain factor (set equal to @bin for 1/f normalization) + * @return signal power at given bin (in dBFS) + */ + float power_at_bin (const uint32_t bin, const float norm = 1.f) const; + + float freq_at_bin (const uint32_t bin) const { + return bin * _fft_freq_per_bin; + } + + private: + static Glib::Threads::Mutex fft_planner_lock; + float* hann_window; + + void init (uint32_t window_size, double rate); + void reset (); + + uint32_t _fft_window_size; + uint32_t _fft_data_size; + double _fft_freq_per_bin; + + float* _fft_data_in; + float* _fft_data_out; + float* _fft_power; + + fftwf_plan _fftplan; + }; + } } /* namespace */ #endif diff --git a/libs/ardour/ardour/lua_api.h b/libs/ardour/ardour/lua_api.h index e3cf7bb30f..9ae6bf2ff6 100644 --- a/libs/ardour/ardour/lua_api.h +++ b/libs/ardour/ardour/lua_api.h @@ -118,6 +118,18 @@ namespace ARDOUR { namespace LuaAPI { * @returns 3 parameters: AutomationList, ControlList, ParamaterDescriptor */ int plugin_automation (lua_State *lua); + + /** + * A convenience function for colorspace HSL to RGB conversion. + * All ranges are 0..1 + * + * Example: + * @code + * local r, g, b, a = ARDOUR.LuaAPI.hsla_to_rgba (hue, saturation, luminosity, alpha) + * @endcode + * @returns 4 parameters: red, green, blue, alpha (in range 0..1) + */ + int hsla_to_rgba (lua_State *lua); } } /* namespace */ namespace ARDOUR { namespace LuaOSC { diff --git a/libs/ardour/dsp_filter.cc b/libs/ardour/dsp_filter.cc index 9d6e2cf710..3b93c9c6f3 100644 --- a/libs/ardour/dsp_filter.cc +++ b/libs/ardour/dsp_filter.cc @@ -154,6 +154,16 @@ Biquad::run (float *data, const uint32_t n_samples) if (!isfinite_local (_z2)) { _z2 = 0; } } +void +Biquad::configure (double a1, double a2, double b0, double b1, double b2) +{ + _a1 = a1; + _a2 = a2; + _b0 = b0; + _b1 = b1; + _b2 = b2; +} + void Biquad::compute (Type type, double freq, double Q, double gain) { @@ -289,3 +299,98 @@ Biquad::dB_at_freq (float freq) const if (!isfinite_local (rv)) { rv = 0; } return std::min (120.f, std::max(-120.f, rv)); } + + +Glib::Threads::Mutex FFTSpectrum::fft_planner_lock; + +FFTSpectrum::FFTSpectrum (uint32_t window_size, double rate) + : hann_window (0) +{ + init (window_size, rate); +} + +FFTSpectrum::~FFTSpectrum () +{ + { + Glib::Threads::Mutex::Lock lk (fft_planner_lock); + fftwf_destroy_plan (_fftplan); + } + fftwf_free (_fft_data_in); + fftwf_free (_fft_data_out); + free (_fft_power); + free (hann_window); +} + +void +FFTSpectrum::init (uint32_t window_size, double rate) +{ + Glib::Threads::Mutex::Lock lk (fft_planner_lock); + + _fft_window_size = window_size; + _fft_data_size = window_size / 2; + _fft_freq_per_bin = rate / _fft_data_size / 2.f; + + _fft_data_in = (float *) fftwf_malloc (sizeof(float) * _fft_window_size); + _fft_data_out = (float *) fftwf_malloc (sizeof(float) * _fft_window_size); + _fft_power = (float *) malloc (sizeof(float) * _fft_data_size); + + reset (); + + _fftplan = fftwf_plan_r2r_1d (_fft_window_size, _fft_data_in, _fft_data_out, FFTW_R2HC, FFTW_MEASURE); + + hann_window = (float *) malloc(sizeof(float) * window_size); + double sum = 0.0; + + for (uint32_t i = 0; i < window_size; ++i) { + hann_window[i] = 0.5f - (0.5f * (float) cos (2.0f * M_PI * (float)i / (float)(window_size))); + sum += hann_window[i]; + } + const double isum = 2.0 / sum; + for (uint32_t i = 0; i < window_size; ++i) { + hann_window[i] *= isum; + } +} + +void +FFTSpectrum::reset () +{ + for (uint32_t i = 0; i < _fft_data_size; ++i) { + _fft_power[i] = 0; + } + for (uint32_t i = 0; i < _fft_window_size; ++i) { + _fft_data_out[i] = 0; + } +} + +void +FFTSpectrum::set_data_hann (float const * const data, uint32_t n_samples, uint32_t offset) +{ + assert(n_samples + offset <= _fft_window_size); + for (uint32_t i = 0; i < n_samples; ++i) { + _fft_data_in[i + offset] = data[i] * hann_window[i + offset]; + } +} + +void +FFTSpectrum::execute () +{ + fftwf_execute (_fftplan); + + _fft_power[0] = _fft_data_out[0] * _fft_data_out[0]; + +#define FRe (_fft_data_out[i]) +#define FIm (_fft_data_out[_fft_window_size - i]) + for (uint32_t i = 1; i < _fft_data_size - 1; ++i) { + _fft_power[i] = (FRe * FRe) + (FIm * FIm); + //_fft_phase[i] = atan2f (FIm, FRe); + } +#undef FRe +#undef FIm +} + +float +FFTSpectrum::power_at_bin (const uint32_t b, const float norm) const { + assert (b >= 0 && b < _fft_data_size); + const float a = _fft_power[b] * norm; + return a > 1e-12 ? 10.0 * fast_log10 (a) : -INFINITY; +} diff --git a/libs/ardour/lua_api.cc b/libs/ardour/lua_api.cc index 911280305a..94b3f816c3 100644 --- a/libs/ardour/lua_api.cc +++ b/libs/ardour/lua_api.cc @@ -304,3 +304,44 @@ ARDOUR::LuaOSC::Address::send (lua_State *L) luabridge::Stack::push (L, (rv == 0)); return 1; } + +static double hue2rgb (const double p, const double q, double t) { + if (t < 0.0) t += 1.0; + if (t > 1.0) t -= 1.0; + if (t < 1.0 / 6.0) return p + (q - p) * 6.0 * t; + if (t < 1.0 / 2.0) return q; + if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6.0; + return p; +} + +int +ARDOUR::LuaAPI::hsla_to_rgba (lua_State *L) +{ + int top = lua_gettop(L); + if (top < 3) { + return luaL_argerror (L, 1, "invalid number of arguments, :hsla_to_rgba (h, s, l [,a])"); + } + double h = luabridge::Stack::get (L, 1); + double s = luabridge::Stack::get (L, 2); + double l = luabridge::Stack::get (L, 3); + double a = 1.0; + if (top > 3) { + a = luabridge::Stack::get (L, 4); + } + + // we can't use ArdourCanvas::hsva_to_color here + // besides we want HSL not HSV and without intermediate + // color_to_rgba (rgba_to_color ()) + double r,g,b; + const double cq = l < 0.5 ? l * (1 + s) : l + s - l * s; + const double cp = 2.f * l - cq; + r = hue2rgb (cp, cq, h + 1.0 / 3.0); + g = hue2rgb (cp, cq, h); + b = hue2rgb (cp, cq, h - 1.0 / 3.0); + + luabridge::Stack::push (L, r); + luabridge::Stack::push (L, g); + luabridge::Stack::push (L, b); + luabridge::Stack::push (L, a); + return 4; +} diff --git a/libs/ardour/luabindings.cc b/libs/ardour/luabindings.cc index f9a9b7bc11..aebe60c9ce 100644 --- a/libs/ardour/luabindings.cc +++ b/libs/ardour/luabindings.cc @@ -140,6 +140,7 @@ CLASSINFO(RegionSelection); CLASSINFO(PublicEditor); CLASSINFO(Selection); CLASSINFO(ArdourMarker); +CLASSINFO(LuaCairoImageSurface); namespace Cairo { class Context; @@ -1204,6 +1205,7 @@ LuaBindings::common (lua_State* L) .addFunction ("set_processor_param", ARDOUR::LuaAPI::set_processor_param) .addFunction ("set_plugin_insert_param", ARDOUR::LuaAPI::set_plugin_insert_param) .addCFunction ("plugin_automation", ARDOUR::LuaAPI::plugin_automation) + .addCFunction ("hsla_to_rgba", ARDOUR::LuaAPI::hsla_to_rgba) .addFunction ("usleep", Glib::usleep) .endNamespace () // end LuaAPI .endNamespace ();// end ARDOUR @@ -1300,9 +1302,17 @@ LuaBindings::dsp (lua_State* L) .addConstructor () .addFunction ("run", &DSP::Biquad::run) .addFunction ("compute", &DSP::Biquad::compute) + .addFunction ("configure", &DSP::Biquad::configure) .addFunction ("reset", &DSP::Biquad::reset) .addFunction ("dB_at_freq", &DSP::Biquad::dB_at_freq) .endClass () + .beginClass ("FFTSpectrum") + .addConstructor () + .addFunction ("set_data_hann", &DSP::FFTSpectrum::set_data_hann) + .addFunction ("execute", &DSP::FFTSpectrum::execute) + .addFunction ("power_at_bin", &DSP::FFTSpectrum::power_at_bin) + .addFunction ("freq_at_bin", &DSP::FFTSpectrum::freq_at_bin) + .endClass () /* DSP enums */ .beginNamespace ("BiquadType") -- 2.30.2