Add the faust helpers for cmake

This commit is contained in:
Jean Pierre Cimalando 2021-02-24 23:54:32 +01:00
parent 4a3c920f40
commit 9bb633e50d
4 changed files with 484 additions and 0 deletions

View file

@ -41,6 +41,7 @@ option_ex (SFIZZ_RELEASE_ASSERTS "Forced assertions in release builds" OFF)
include (SfizzConfig)
include (SfizzDeps)
include (SfizzFaust)
# Don't use IPO in non Release builds
include (CheckIPO)

72
cmake/SfizzFaust.cmake Normal file
View file

@ -0,0 +1,72 @@
include(CMakeParseArguments)
option(SFIZZ_RECOMPILE_FAUST "Recompile faust sources" OFF)
if(SFIZZ_RECOMPILE_FAUST)
find_program(RDMD "rdmd")
if(NOT RDMD)
message(FATAL_ERROR "rdmd is missing, it is required for regenerating faust sources.")
endif()
endif()
function(add_faust_command INPUT OUTPUT)
set(_options ONE_SAMPLE DOUBLE IN_PLACE VECTORIZE MATH_APPROXIMATION)
set(_one_args PROCESS_NAME CLASS_NAME SUPERCLASS_NAME)
set(_multi_args IMPORT_DIRS)
cmake_parse_arguments(_FAUST "${_options}" "${_one_args}" "${_multi_args}" ${ARGN})
if(NOT SFIZZ_RECOMPILE_FAUST)
return()
endif()
if(NOT RDMD)
return()
endif()
if(NOT INPUT)
message(FATAL_ERROR "No input file given.")
endif()
if(NOT OUTPUT)
message(FATAL_ERROR "No output file given.")
endif()
set(_cmd "${RDMD}" "${PROJECT_SOURCE_DIR}/scripts/faustwrap.d")
if(NOT IS_ABSOLUTE "${INPUT}")
set(INPUT "${CMAKE_CURRENT_SOURCE_DIR}/${INPUT}")
endif()
if(NOT IS_ABSOLUTE "${OUTPUT}")
set(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/${OUTPUT}")
endif()
get_filename_component(_output_dir "${OUTPUT}" DIRECTORY)
file(MAKE_DIRECTORY "${_output_dir}")
list(APPEND _cmd "-o" "${OUTPUT}" "${INPUT}")
if(_FAUST_ONE_SAMPLE)
list(APPEND _cmd "--os")
endif()
if(_FAUST_DOUBLE)
list(APPEND _cmd "--double")
endif()
if(_FAUST_IN_PLACE)
list(APPEND _cmd "--inpl")
endif()
if(_FAUST_VECTORIZE)
list(APPEND _cmd "--vec")
endif()
if(_FAUST_MATH_APPROXIMATION)
list(APPEND _cmd "--mapp")
endif()
if(_FAUST_PROCESS_NAME)
list(APPEND _cmd "--pn" "${_FAUST_PROCESS_NAME}")
endif()
if(_FAUST_CLASS_NAME)
list(APPEND _cmd "--cn" "${_FAUST_CLASS_NAME}")
endif()
if(_FAUST_SUPERCLASS_NAME)
list(APPEND _cmd "--scn" "${_FAUST_SUPERCLASS_NAME}")
endif()
if (_FAUST_IMPORT_DIRS)
foreach(_dir IN LISTS _FAUST_IMPORT_DIRS)
if(NOT IS_ABSOLUTE "${_dir}")
set(_dir "${CMAKE_CURRENT_SOURCE_DIR}/${_dir}")
endif()
list(APPEND _cmd "--import-dir" "${_dir}")
endforeach()
endif()
add_custom_command(OUTPUT "${OUTPUT}" COMMAND ${_cmd} DEPENDS "${INPUT}")
endfunction()

393
scripts/faustwrap.d Executable file
View file

@ -0,0 +1,393 @@
#!/usr/bin/env rdmd
import std.conv;
import std.algorithm;
import std.getopt;
import std.process;
import std.regex;
import std.uni;
import std.ascii;
import std.string;
import std.array;
import std.stdio;
import core.stdc.stdlib;
enum string prologue = `#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
`;
enum string epilogue = `
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
#undef FAUSTFLOAT
#undef FAUSTCLASS
`;
void main(string[] args)
{
struct Opts {
string outputPath;
string className = "mydsp";
string superclassName = null;
bool oneSample = false;
bool doublePrecision = false;
bool inPlace = false;
bool vectorize = false;
bool mathApproximation = false;
string processName = null;
string[] importDirs;
}
Opts opts;
auto optInfo = getopt(args,
"out|o", "Output path", &opts.outputPath,
"cn", "Class name", &opts.className,
"scn", "Superclass name", &opts.superclassName,
"os", "One sample", &opts.oneSample,
"double", "Double precision", &opts.doublePrecision,
"inpl", "In-place", &opts.inPlace,
"vec", "Vectorization", &opts.vectorize,
"mapp", "Math approximation", &opts.mathApproximation,
"pn", "Process name", &opts.processName,
"import-dir|I", "Import directory", &opts.importDirs);
if (optInfo.helpWanted)
{
defaultGetoptPrinter("MyFaust", optInfo.options);
return;
}
if (args.length != 2)
{
writeln("You must indicate exactly 1 input file.");
exit(1);
}
string[] cmd = [getFaust()];
cmd ~= "-cn";
cmd ~= opts.className;
if (opts.superclassName)
{
cmd ~= "-scn";
cmd ~= opts.superclassName;
}
if (opts.oneSample)
cmd ~= "-os";
if (opts.doublePrecision)
cmd ~= "-double";
if (opts.inPlace)
cmd ~= "-inpl";
if (opts.vectorize)
cmd ~= "-vec";
if (opts.mathApproximation)
cmd ~= "-mapp";
if (opts.processName)
{
cmd ~= "-pn";
cmd ~= opts.processName;
}
foreach (string dir; opts.importDirs)
{
cmd ~= "-I";
cmd ~= dir;
}
cmd ~= args[1];
auto result = execute(cmd, null, Config.stderrPassThrough|Config.suppressConsole);
if (result.status != 0)
exit(1);
string code = result.output;
Parameter[] params = findParameters(code);
code = removeVirtualKeyword(code);
foreach (string method; ["metadata", "getInputRate", "getOutputRate", "clone", "buildUserInterface"])
code = removeMethod(code, method);
foreach (string method; ["getNumInputs", "getNumOutputs"])
code = makeMethodStaticConstexpr(code, method);
if (!opts.superclassName)
code = removeSuperclass(code);
code = makePointerArgsConst(code, opts.oneSample);
code = addParameters(code, params);
foreach (string method; ["compute", "classInit", "instanceConstants", "instanceResetUserInterface", "instanceClear", "init", "instanceInit"])
code = addMethodStartEnd(code, method);
code = addClassStartEnd(code);
File outFile = stdout;
if (opts.outputPath)
outFile = File(opts.outputPath, "w");
outFile.writef("%s%s%s", prologue, code, epilogue);
outFile.flush();
}
final class Parameter
{
string name;
string var;
bool readonly;
};
Parameter[] findParameters(string code)
{
Parameter[] params;
auto expr = regex(`->add(Button|CheckButton|VerticalSlider|HorizontalSlider|NumEntry|HorizontalBargraph|VerticalBargraph)\("([^"]*)", &([a-zA-Z0-9_]+)`);
foreach (string line; code.lineSplitter)
{
auto match = line.matchFirst(expr);
if (match)
{
Parameter param = new Parameter;
param.name = match[2];
param.var = match[3];
param.readonly = ["HorizontalBargraph", "VerticalBargraph"].canFind(match[1]);
params ~= param;
}
}
return params;
}
string removeVirtualKeyword(string code)
{
auto expr = regex(`\bvirtual\s*\b`);
return code.replaceAll(expr, "");
}
string removeMethod(string code, string method)
{
string[] newLines;
newLines.reserve(1024);
auto expr = regex(`^(\s*).*\b` ~ method.escaper.to!string ~ `\s*\(.*\{\s*$`);
bool inMethod = false;
string eom = null;
foreach (string line; code.lineSplitter)
{
if (inMethod)
{
if (line.stripRight == eom)
inMethod = false;
}
else
{
auto match = line.matchFirst(expr);
if (match)
{
inMethod = true;
eom = match[1] ~ '}';
}
else
newLines ~= line;
}
}
return newLines.join('\n');
}
string makeMethodStaticConstexpr(string code, string method)
{
string[] newLines;
newLines.reserve(1024);
auto expr = regex(`^(\s*)(.*\b` ~ method.escaper.to!string ~ `\s*\(.*\{\s*)$`);
foreach (string line; code.lineSplitter)
{
auto match = line.matchFirst(expr);
if (match)
newLines ~= match[1] ~ "static constexpr " ~ match[2];
else
newLines ~= line;
}
return newLines.join('\n');
}
string removeSuperclass(string code)
{
auto expr = regex(`\s*:\s*public\s+dsp\s*`);
return code.replaceFirst(expr, " ");
}
string makePointerArgsConst(string code, bool oneSample)
{
if (!oneSample)
{
auto expr = regex(`\bvoid\s+compute\s*\(\s*([^,)]+)\s*,\s*([^,)]+)\s*,\s*([^,)]+)\s*\)`);
auto match = code.matchFirst(expr);
string arg1 = match[1];
string arg2 = match[2];
string arg3 = match[3];
auto exprArg = regex(`^FAUSTFLOAT\s*\*\s*\*`);
arg2 = arg2.replaceFirst(exprArg, "FAUSTFLOAT const* const*");
arg3 = arg3.replaceFirst(exprArg, "FAUSTFLOAT* const*");
ulong start = match[0].ptr - code.ptr;
ulong end = start + match[0].length;
code = format("%svoid compute(%s, %s, %s)%s", code[0..start], arg1, arg2, arg3, code[end..$]);
//
auto exprStmt = regex(`FAUSTFLOAT\s*\*\s*(input\d+)`);
code = code.replaceAll(exprStmt, "FAUSTFLOAT const* $1");
}
else
{
auto expr = regex(`\bvoid\s+compute\s*\(\s*([^,)]+)\s*,\s*([^,)]+)\s*,\s*([^,)]+)\s*,\s*([^,)]+)\s*\)`);
auto match = code.matchFirst(expr);
string arg1 = match[1];
string arg2 = match[2];
string arg3 = match[3];
string arg4 = match[4];
auto exprArg = regex(`^(FAUSTFLOAT|int)\s*\*`);
arg1 = arg1.replaceFirst(exprArg, "$1 const*");
arg3 = arg3.replaceFirst(exprArg, "$1 const*");
arg4 = arg4.replaceFirst(exprArg, "$1 const*");
ulong start = match[0].ptr - code.ptr;
ulong end = start + match[0].length;
code = format("%svoid compute(%s, %s, %s, %s)%s", code[0..start], arg1, arg2, arg3, arg4, code[end..$]);
}
return code;
}
string addParameters(string code, Parameter[] params)
{
string[] addedLines;
foreach (Parameter param; params)
{
string camelName = param.name.camelify;
addedLines ~= "";
addedLines ~= format(` FAUSTFLOAT get%s() const { return %s; }`, camelName, param.var);
if (!param.readonly)
addedLines ~= format(` void set%s(FAUSTFLOAT value) { %s = value; }`, camelName, param.var);
}
return addToClass(code, addedLines.join('\n'));
}
string addMethodStartEnd(string code, string method)
{
string[] newLines;
newLines.reserve(1024);
auto expr = regex(`^(\s*).*\b` ~ method.escaper.to!string ~ `\s*\(.*\{\s*$`);
bool inMethod = false;
string eom = null;
foreach (string line; code.lineSplitter)
{
if (inMethod)
{
if (line.stripRight == eom)
{
newLines ~= "\t\t//[End:" ~ method ~ "]";
inMethod = false;
}
newLines ~= line;
}
else
{
newLines ~= line;
auto match = line.matchFirst(expr);
if (match)
{
inMethod = true;
eom = match[1] ~ '}';
newLines ~= "\t\t//[Begin:" ~ method ~ "]";
}
}
}
return newLines.join('\n');
}
string addClassStartEnd(string code)
{
{
auto expr = regex(`^class\b.*$`, "m");
auto match = code.matchFirst(expr);
ulong start = match[0].ptr - code.ptr;
ulong end = start + match[0].length;
code = code[0..start] ~ "\n//[Before:class]\n" ~ match[0] ~ "\n\t//[Begin:class]\n" ~ code[end..$];
}
{
auto expr = regex(`^\};`, "m");
auto match = code.matchLast(expr);
ulong start = match[0].ptr - code.ptr;
ulong end = start + match[0].length;
code = code[0..start] ~ "\n\t//[End:class]\n" ~ match[0] ~ "\n//[After:class]\n" ~ code[end..$];
}
return code;
}
string addToClass(string code, string addend)
{
if (addend.empty)
return code;
auto expr = regex(`^\};`, "m");
auto match = code.matchLast(expr);
ulong index = match[0].ptr - code.ptr;
return code[0..index] ~ addend ~ '\n' ~ code[index..$];
}
string camelify(string name)
{
dchar[] result;
result.reserve(name.length);
bool isIdentifierChar(dchar ch)
{
return std.ascii.isAlphaNum(ch) || ch == '_';
}
dchar[] temp = name.to!(dchar[]);
foreach (ref dchar uniChar; temp)
{
uniChar = [uniChar].normalize!NFD[0];
if (!isIdentifierChar(uniChar))
uniChar = ' ';
}
foreach (dchar[] part; temp.split(' '))
{
if (!part.empty)
part[0] = std.uni.toUpper(part[0]);
result ~= part;
}
return result.to!string;
}
Captures!S matchLast(S, R)(S input, R expr)
{
Captures!S last;
foreach (Captures!S current; input.matchAll(expr))
last = current;
return last;
}
string getFaust()
{
char *env = getenv("FAUST");
return env ? env.to!string : "faust";
}

View file

@ -0,0 +1,18 @@
//-----------------------------------------------------------------------------
// A version of platform.lib which does not limit the sample rate.
// This allows use of high oversampling factors.
//-----------------------------------------------------------------------------
declare name "sfizz Generic Platform Library";
declare license "BSD-2-Clause";
//---------------------------------`(pl.)SR`-----------------------------------
// Current sampling rate (between 1Hz and 192000Hz). Constant during
// program execution.
//-----------------------------------------------------------------------------
SR = fconstant(int fSamplingFreq, <math.h>);
//---------------------------------`(pl.)tablesize`----------------------------
// Oscillator table size
//-----------------------------------------------------------------------------
tablesize = 1 << 16;