diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index f03f459d6df..a818726b98f 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -392,6 +392,7 @@ target_link_libraries(${PROJECT_NAME} surge::sqlite surge-common-binary tuning-library + pffft PRIVATE surge-lua-src surge-platform diff --git a/src/common/LuaSupport.cpp b/src/common/LuaSupport.cpp index 3d5ad39f5d3..234ab579adc 100644 --- a/src/common/LuaSupport.cpp +++ b/src/common/LuaSupport.cpp @@ -146,7 +146,7 @@ static int lua_sandboxPrint(lua_State *L) for (int i = 1; i <= n; i++) { if (!lua_isstring(L, i)) - return luaL_error(L, "Error: print() only accepts strings or numbers!"); + return luaL_error(L, "print() error: only accepts strings or numbers!"); const char *s = lua_tostring(L, i); // get the string fputs(s, stdout); // print the string } @@ -155,6 +155,126 @@ static int lua_sandboxPrint(lua_State *L) return 0; } +// Wrapper function for PFFFT submodule +// From pffft.h: +// Transforms are not scaled: PFFFT_BACKWARD(PFFFT_FORWARD(x)) = N*x. +// Typically you will want to scale the backward transform by 1/N. +static int pffft_Transform(lua_State *L, pffft_direction_t direction, pffft_transform_t transform, + bool unordered, const char *functionName) +{ +#if HAS_LUA + // Check for an input table + if (lua_istable(L, 1) != 1) + return luaL_error(L, "%s error: table expected", functionName); + + int tableLength = lua_objlen(L, 1); + + // Check if we're doing a complex transformation, if so, the FFT size is determined by only one + // set of numbers but we still need the full array length to iterate and allocate buffers + int transformLength = tableLength; + if (transform == PFFFT_COMPLEX) + transformLength /= 2; + + // Enforce size constraints: minimum 32 FFT size, maximum 524,288 table size + if (transformLength < 32 || tableLength > (1 << 19)) + return luaL_error( + L, "%s error: table must contain between 32 and 524,288 elements, received %d elements", + functionName, transformLength); + + // PFFFT supports sizes in the form N=(2^a)*(3^b)*(5^c) where a >= 5, b >=0, c >= 0 but a + // size like 180 seems to result in undefined behaviour so restricting N to a power of two is + // the safe option + if ((tableLength & (tableLength - 1)) != 0) + return luaL_error(L, + "%s error: input table length must be a power of 2, received %d elements", + functionName, transformLength); + + // Create PFFFT setup + PFFFT_Setup *setup = pffft_new_setup(transformLength, transform); + if (!setup) + return luaL_error(L, "%s error: failed to create setup for %d elements", functionName, + transformLength); + + // Allocate memory for aligned buffers for SIMD + // TODO: Consider whether the temporary buffers should be managed as static objects or + // alternatively, a state table with an initialization step, followed by calls to a + // type-specific API + float *input = (float *)pffft_aligned_malloc(tableLength * sizeof(float)); + float *output = (float *)pffft_aligned_malloc(tableLength * sizeof(float)); + float *work = (float *)pffft_aligned_malloc(tableLength * sizeof(float)); + + // Populate the input buffer from the Lua table + for (int i = 0; i < tableLength; i++) + { + lua_rawgeti(L, 1, i + 1); + input[i] = luaL_checknumber(L, -1); + lua_pop(L, 1); + } + + // Perform the ordered or unordered transform + if (unordered) + pffft_transform_ordered(setup, input, output, work, direction); + else + pffft_transform(setup, input, output, work, direction); + + // Push the output back to Lua as a table + lua_newtable(L); + for (int i = 0; i < tableLength; i++) + { + lua_pushnumber(L, output[i]); + lua_rawseti(L, -2, i + 1); + } + + // Clean up + pffft_aligned_free(input); + pffft_aligned_free(output); + pffft_aligned_free(work); + pffft_destroy_setup(setup); +#endif + return 1; +} + +// Different modes for pffft_Transform +static int pffft_ForwardReal(lua_State *L) +{ + return pffft_Transform(L, PFFFT_FORWARD, PFFFT_REAL, false, "fft_real()"); +} + +static int pffft_ForwardComplex(lua_State *L) +{ + return pffft_Transform(L, PFFFT_FORWARD, PFFFT_COMPLEX, false, "fft_complex()"); +} + +static int pffft_BackwardReal(lua_State *L) +{ + return pffft_Transform(L, PFFFT_BACKWARD, PFFFT_REAL, false, "ifft_real()"); +} + +static int pffft_BackwardComplex(lua_State *L) +{ + return pffft_Transform(L, PFFFT_BACKWARD, PFFFT_COMPLEX, false, "ifft_complex()"); +} + +static int pffft_ForwardRealUnordered(lua_State *L) +{ + return pffft_Transform(L, PFFFT_FORWARD, PFFFT_REAL, true, "fft_real_unordered()"); +} + +static int pffft_ForwardComplexUnordered(lua_State *L) +{ + return pffft_Transform(L, PFFFT_FORWARD, PFFFT_COMPLEX, true, "fft_complex_unordered()"); +} + +static int pffft_BackwardRealUnordered(lua_State *L) +{ + return pffft_Transform(L, PFFFT_BACKWARD, PFFFT_REAL, true, "ifft_real_unordered()"); +} + +static int pffft_BackwardComplexUnordered(lua_State *L) +{ + return pffft_Transform(L, PFFFT_BACKWARD, PFFFT_COMPLEX, true, "ifft_complex_unordered()"); +} + bool Surge::LuaSupport::setSurgeFunctionEnvironment(lua_State *L) { #if HAS_LUA @@ -174,6 +294,24 @@ bool Surge::LuaSupport::setSurgeFunctionEnvironment(lua_State *L) lua_pushcfunction(L, lua_sandboxPrint); lua_setfield(L, eidx, "print"); + // FFT functions + lua_pushcfunction(L, pffft_ForwardReal); + lua_setfield(L, eidx, "fft_real"); + lua_pushcfunction(L, pffft_ForwardComplex); + lua_setfield(L, eidx, "fft_complex"); + lua_pushcfunction(L, pffft_BackwardReal); + lua_setfield(L, eidx, "ifft_real"); + lua_pushcfunction(L, pffft_BackwardComplex); + lua_setfield(L, eidx, "ifft_complex"); + lua_pushcfunction(L, pffft_ForwardRealUnordered); + lua_setfield(L, eidx, "fft_real_unordered"); + lua_pushcfunction(L, pffft_ForwardComplexUnordered); + lua_setfield(L, eidx, "fft_complex_unordered"); + lua_pushcfunction(L, pffft_BackwardRealUnordered); + lua_setfield(L, eidx, "ifft_real_unordered"); + lua_pushcfunction(L, pffft_BackwardComplexUnordered); + lua_setfield(L, eidx, "ifft_complex_unordered"); + // add global tables lua_getglobal(L, surgeTableName); lua_setfield(L, eidx, surgeTableName); diff --git a/src/common/LuaSupport.h b/src/common/LuaSupport.h index 84c7a614590..d97e204dbe5 100644 --- a/src/common/LuaSupport.h +++ b/src/common/LuaSupport.h @@ -41,6 +41,8 @@ extern "C" #include "luajit.h" #include "lj_arch.h" + +#include "pffft.h" } #else typedef int lua_State;