extend lua API:
authorRobin Gareus <robin@gareus.org>
Sat, 2 Jul 2016 21:35:00 +0000 (23:35 +0200)
committerRobin Gareus <robin@gareus.org>
Sat, 2 Jul 2016 21:36:34 +0000 (23:36 +0200)
* add a basic FFT spectrum analyzer
* prepare Cairo::ImageSurface
* HSL colorspace conversion

libs/ardour/ardour/dsp_filter.h
libs/ardour/ardour/lua_api.h
libs/ardour/dsp_filter.cc
libs/ardour/lua_api.cc
libs/ardour/luabindings.cc

index 994d8725ea418576c6c3edacfacd779f49f8dcb1..6220dddf5a2f9d5af95b03f903a0699c5cca16f9 100644 (file)
@@ -23,6 +23,8 @@
 #include <string.h>
 #include <assert.h>
 #include <glib.h>
+#include <glibmm.h>
+#include <fftw3.h>
 #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
index e3cf7bb30f545ed6d1d92863f8dc293dad7110e5..9ae6bf2ff6b4cbcb35b13b4c22f9a1a39ac1e144 100644 (file)
@@ -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 {
index 9d6e2cf7109233e309ecbd846d26cf69bd0b388f..3b93c9c6f3e71cba0fd227d20b8f06f8e519e90b 100644 (file)
@@ -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;
+}
index 911280305af89b0fc379bcef5c3cb1840f285a57..94b3f816c3cf72c503de6235837fd05ea1b0f8f2 100644 (file)
@@ -304,3 +304,44 @@ ARDOUR::LuaOSC::Address::send (lua_State *L)
        luabridge::Stack<bool>::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<double>::get (L, 1);
+       double s = luabridge::Stack<double>::get (L, 2);
+       double l = luabridge::Stack<double>::get (L, 3);
+       double a = 1.0;
+       if (top > 3) {
+               a = luabridge::Stack<double>::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<double>::push (L, r);
+       luabridge::Stack<double>::push (L, g);
+       luabridge::Stack<double>::push (L, b);
+       luabridge::Stack<double>::push (L, a);
+       return 4;
+}
index f9a9b7bc11cb9d4033d06dbdd492b279808575b3..aebe60c9ceb90f7867bfd33faeb7bd81c3a915a8 100644 (file)
@@ -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 <void (*) (double)> ()
                .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 <DSP::FFTSpectrum> ("FFTSpectrum")
+               .addConstructor <void (*) (uint32_t, double)> ()
+               .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")