13
0

extend lua API:

* add a basic FFT spectrum analyzer
* prepare Cairo::ImageSurface
* HSL colorspace conversion
This commit is contained in:
Robin Gareus 2016-07-02 23:35:00 +02:00
parent 8b142a2fd6
commit f169ff3db3
5 changed files with 219 additions and 0 deletions

View 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

View 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 {

View 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;
}

View 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;
}

View 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")