diff --git a/presets/tests/100-square.milk b/presets/tests/100-square.milk new file mode 100755 index 000000000..ba697319e --- /dev/null +++ b/presets/tests/100-square.milk @@ -0,0 +1,18 @@ +[preset00] +per_frame_1000=// just a double box +per_frame_1001=// + +fDecay=1 +zoom=0 +rot=0.0 +warp=1 +ob_size=0.2 +ob_r=0.0 +ob_g=0.0 +ob_b=0.0 +ob_a=0.0 +ib_size=0.1 +ib_r=1.0 +ib_g=0.0 +ib_b=0.0 +ib_a=1.0 diff --git a/presets/tests/101-per_frame.milk b/presets/tests/101-per_frame.milk new file mode 100755 index 000000000..33cb3a2ac --- /dev/null +++ b/presets/tests/101-per_frame.milk @@ -0,0 +1,20 @@ +[preset00] +per_frame_1000=// one per frame equation +per_frame_1001=// varies outer frame color - red only + +fDecay=0.98 +zoom=0 +rot=0.0 +warp=0.0 +ob_size=0.2 +ob_r=0.0 +ob_g=0.0 +ob_b=0.0 +ob_a=0.0 +ib_size=0.1 +ib_r=1.0 +ib_g=0.0 +ib_b=0.0 +ib_a=1.0 + +per_frame_1=ib_r=0.7+0.4*sin(3*time); diff --git a/presets/tests/102-per_frame3.milk b/presets/tests/102-per_frame3.milk new file mode 100755 index 000000000..271d0034d --- /dev/null +++ b/presets/tests/102-per_frame3.milk @@ -0,0 +1,22 @@ +[preset00] +per_frame_1000=// one per frame equation +per_frame_1001=// varies outer frame color - multi-color + +fDecay=0.98 +zoom=0 +rot=0.0 +warp=0.0 +ob_size=0.2 +ob_r=0.0 +ob_g=0.0 +ob_b=0.0 +ob_a=0.0 +ib_size=0.1 +ib_r=0.0 +ib_g=0.0 +ib_b=0.0 +ib_a=1.0 + +per_frame_1=ib_r=0.7+0.4*sin(3*time); +per_frame_2=ib_g=0.7+0.4*sin(4*time); +per_frame_3=ib_b=0.7+0.4*sin(5*time); \ No newline at end of file diff --git a/presets/tests/103-multiple-eqn.milk b/presets/tests/103-multiple-eqn.milk new file mode 100755 index 000000000..65353e791 --- /dev/null +++ b/presets/tests/103-multiple-eqn.milk @@ -0,0 +1,20 @@ +[preset00] +per_frame_1000=// one per frame equation +per_frame_1001=// test multiple equations one line + +fDecay=0.98 +zoom=0 +rot=0.0 +warp=0.0 +ob_size=0.2 +ob_r=0.0 +ob_g=0.0 +ob_b=0.0 +ob_a=0.0 +ib_size=0.1 +ib_r=0.0 +ib_g=0.0 +ib_b=0.0 +ib_a=1.0 + +per_frame_1=ib_r=0.7+0.4*sin(3*time); ib_g=0.7+0.4*sin(4*time); ib_b=0.7+0.4*sin(5*time); \ No newline at end of file diff --git a/presets/tests/104-continued-eqn.milk b/presets/tests/104-continued-eqn.milk new file mode 100755 index 000000000..e16612f0c --- /dev/null +++ b/presets/tests/104-continued-eqn.milk @@ -0,0 +1,21 @@ +[preset00] +per_frame_1000=// one per frame equation +per_frame_1001=// test equation spanning two lines + +fDecay=0.98 +zoom=0 +rot=0.0 +warp=0.0 +ob_size=0.2 +ob_r=0.0 +ob_g=0.0 +ob_b=0.0 +ob_a=0.0 +ib_size=0.1 +ib_r=0.0 +ib_g=0.0 +ib_b=0.0 +ib_a=1.0 + +per_frame_1=ib_r=0.7+0.4* +per_frame_2= sin(3*time); \ No newline at end of file diff --git a/presets/tests/105-per_frame_init.milk b/presets/tests/105-per_frame_init.milk new file mode 100755 index 000000000..16d61414b --- /dev/null +++ b/presets/tests/105-per_frame_init.milk @@ -0,0 +1,20 @@ +[preset00] +per_frame_1000=// one per frame equation +per_frame_1001=// initialize user variable + +fDecay=0.98 +zoom=0 +rot=0.0 +warp=0.0 +ob_size=0.2 +ob_r=0.0 +ob_b=0.0 +ob_a=0.0 +ib_size=0.1 +ib_r=1.0 +ib_g=0.0 +ib_a=1.0 + +per_frame_init_1=SPEED=10; + +per_frame_1=ib_r=0.7+0.4*sin(time*SPEED); diff --git a/presets/tests/110-per_pixel.milk b/presets/tests/110-per_pixel.milk new file mode 100755 index 000000000..f6e0c59ac --- /dev/null +++ b/presets/tests/110-per_pixel.milk @@ -0,0 +1,23 @@ +[preset00] +fDecay=0.980000 +fWarpScale=2.853000 +fZoomExponent=1.000000 +warp=0.000000 +sx=1.000000 +sy=1.000000 +ob_size=0.2 +ob_r=0.0 +ob_g=0.0 +ob_b=0.0 +ob_a=0.0 +ib_size=0.01 +ib_r=1.0 +ib_g=0.0 +ib_b=0.0 +ib_a=1.0 + +per_frame_1=ib_r=0.7+0.4*sin(3*time); +per_frame_2=ib_g=0.7+0.4*sin(4*time); +per_frame_3=ib_b=0.7+0.4*sin(5*time); + +per_pixel_1=zoom=0.9615-rad*0.1; diff --git a/presets/tests/200-wave.milk b/presets/tests/200-wave.milk new file mode 100755 index 000000000..866500924 --- /dev/null +++ b/presets/tests/200-wave.milk @@ -0,0 +1,20 @@ +[preset00] +per_frame_1000=// simple wave to build +per_frame_1001=// + +fDecay=0.980000 +nWaveMode=0 +bMaximizeWaveColor=1 +fWaveAlpha=4.400000 +fWaveScale=1.605447 +fZoomExponent=1.000000 +zoom=1.000000 +rot=0.006000 +warp=0.000000 +sx=1.000000 +sy=1.000000 +wave_r=0.900000 +wave_g=0.90000 +wave_b=0.900000 +wave_x=0.500000 +wave_y=0.500000 diff --git a/presets/tests/201-wavecode.milk b/presets/tests/201-wavecode.milk new file mode 100755 index 000000000..b3e7bf33d --- /dev/null +++ b/presets/tests/201-wavecode.milk @@ -0,0 +1,36 @@ +[preset00] +per_frame_1000=// use wavecode +per_frame_1001=// red circle with blue line + +fDecay=0.980000 +nWaveMode=0 +bMaximizeWaveColor=1 +fWaveAlpha=4.400000 +fWaveScale=1.5 +fZoomExponent=1.000000 +zoom=1.000000 +rot=0.006000 +warp=0.000000 +sx=1.000000 +sy=1.000000 +wave_r=0.00000 +wave_g=0.0000 +wave_b=0.00000 +wave_a=0.00000 +wave_x=0.500000 +wave_y=0.5000000 + +wavecode_0_enabled=1 +wavecode_0_mode=0 +wavecode_0_bDrawThick=0 +wavecode_0_bAdditive=1 +wavecode_0_scaling=1.000000 +wavecode_0_smoothing=0.500000 +wavecode_0_r=0.000000 +wavecode_0_g=1.000000 +wavecode_0_b=1.000000 +wavecode_0_a=1.000000 +wave_0_per_point41=x=x+value1; +wave_0_per_point42=y=y+value2; + +per_frame_1=zoom=1 \ No newline at end of file diff --git a/presets/tests/README.md b/presets/tests/README.md new file mode 100644 index 000000000..dd1c36aa7 --- /dev/null +++ b/presets/tests/README.md @@ -0,0 +1 @@ +These presets are designed to test very specific functionality to help with regression testing when code changes are made. diff --git a/src/libprojectM/Makefile.am b/src/libprojectM/Makefile.am index 43bb4235a..88843cd47 100644 --- a/src/libprojectM/Makefile.am +++ b/src/libprojectM/Makefile.am @@ -24,6 +24,7 @@ libprojectM_la_LIBADD = \ libprojectM_la_SOURCES = ConfigFile.cpp Preset.cpp PresetLoader.cpp timer.cpp \ KeyHandler.cpp PresetChooser.cpp TimeKeeper.cpp PCM.cpp PresetFactory.cpp \ fftsg.cpp wipemalloc.cpp PipelineMerger.cpp PresetFactoryManager.cpp projectM.cpp \ + TestRunner.cpp TestRunner.hpp \ Common.hpp PipelineMerger.hpp PresetLoader.hpp\ HungarianMethod.hpp Preset.hpp RandomNumberGenerators.hpp\ IdleTextures.hpp PresetChooser.hpp TimeKeeper.hpp\ diff --git a/src/libprojectM/MilkdropPresetFactory/BuiltinParams.cpp b/src/libprojectM/MilkdropPresetFactory/BuiltinParams.cpp index 91a92720a..8d3573f92 100644 --- a/src/libprojectM/MilkdropPresetFactory/BuiltinParams.cpp +++ b/src/libprojectM/MilkdropPresetFactory/BuiltinParams.cpp @@ -52,7 +52,7 @@ int BuiltinParams::load_builtin_param_float(const std::string & name, void * eng std::string lowerName(name); std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), tolower); - if ((param = new Param(lowerName, P_TYPE_DOUBLE, flags, engine_val, matrix, iv, ub, lb)) == NULL) + if ((param = Param::create(lowerName, P_TYPE_DOUBLE, flags, engine_val, matrix, iv, ub, lb)) == NULL) { return PROJECTM_OUTOFMEM_ERROR; } @@ -171,7 +171,7 @@ int BuiltinParams::load_builtin_param_int(const std::string & name, void * engin std::string lowerName(name); std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), tolower); - param = new Param(lowerName, P_TYPE_INT, flags, engine_val, NULL, iv, ub, lb); + param = Param::create(lowerName, P_TYPE_INT, flags, engine_val, NULL, iv, ub, lb); if (param == NULL) { @@ -224,7 +224,7 @@ int BuiltinParams::load_builtin_param_bool(const std:: string & name, void * eng std::string lowerName(name); std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), tolower); - param = new Param(lowerName, P_TYPE_BOOL, flags, engine_val, NULL, iv, ub, lb); + param = Param::create(lowerName, P_TYPE_BOOL, flags, engine_val, NULL, iv, ub, lb); if (param == NULL) { @@ -397,7 +397,7 @@ int BuiltinParams::load_all_builtin_param(const PresetInputs & presetInputs, Pre for (unsigned int i = 0; i < NUM_Q_VARIABLES;i++) { std::ostringstream os; os << "q" << i; - load_builtin_param_float(os.str().c_str(), (void*)&presetOutputs.q[i], NULL, P_FLAG_PER_PIXEL |P_FLAG_QVAR, 0, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, ""); + load_builtin_param_float(os.str().c_str(), (void*)&presetOutputs.q[i], NULL, P_FLAG_QVAR, 0, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, ""); } diff --git a/src/libprojectM/MilkdropPresetFactory/CustomWave.cpp b/src/libprojectM/MilkdropPresetFactory/CustomWave.cpp index a08dab696..28b02eab1 100755 --- a/src/libprojectM/MilkdropPresetFactory/CustomWave.cpp +++ b/src/libprojectM/MilkdropPresetFactory/CustomWave.cpp @@ -49,7 +49,8 @@ CustomWave::CustomWave(int _id) : Waveform(512), r(0), g(0), b(0), - a(0) + a(0), + per_point_program(nullptr) { Param * param; @@ -308,7 +309,7 @@ CustomWave::CustomWave(int _id) : Waveform(512), abort(); } - if ((param = Param::new_param_float("t1", P_FLAG_PER_POINT | P_FLAG_TVAR, &this->t1, NULL, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, 0.0)) == NULL) + if ((param = Param::new_param_float("t1", P_FLAG_TVAR, &this->t1, NULL, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, 0.0)) == NULL) { ; abort(); @@ -320,7 +321,7 @@ CustomWave::CustomWave(int _id) : Waveform(512), abort(); } - if ((param = Param::new_param_float("t2", P_FLAG_PER_POINT |P_FLAG_TVAR, &this->t2, NULL, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, 0.0)) == NULL) + if ((param = Param::new_param_float("t2", P_FLAG_TVAR, &this->t2, NULL, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, 0.0)) == NULL) { ; abort(); @@ -331,7 +332,7 @@ CustomWave::CustomWave(int _id) : Waveform(512), abort(); } - if ((param = Param::new_param_float("t3", P_FLAG_PER_POINT |P_FLAG_TVAR, &this->t3, NULL, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, 0.0)) == NULL) + if ((param = Param::new_param_float("t3", P_FLAG_TVAR, &this->t3, NULL, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, 0.0)) == NULL) { abort(); } @@ -341,7 +342,7 @@ CustomWave::CustomWave(int _id) : Waveform(512), abort(); } - if ((param = Param::new_param_float("t4", P_FLAG_PER_POINT |P_FLAG_TVAR, &this->t4, NULL, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, 0.0)) == NULL) + if ((param = Param::new_param_float("t4", P_FLAG_TVAR, &this->t4, NULL, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, 0.0)) == NULL) { abort(); @@ -361,7 +362,7 @@ CustomWave::CustomWave(int _id) : Waveform(512), abort(); } - if ((param = Param::new_param_float("t6", P_FLAG_TVAR | P_FLAG_PER_POINT, &this->t6, NULL, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, 0.0)) == NULL) + if ((param = Param::new_param_float("t6", P_FLAG_TVAR, &this->t6, NULL, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, 0.0)) == NULL) { abort(); } @@ -371,7 +372,7 @@ CustomWave::CustomWave(int _id) : Waveform(512), abort(); } - if ((param = Param::new_param_float("t7", P_FLAG_TVAR | P_FLAG_PER_POINT, &this->t7, NULL, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, 0.0)) == NULL) + if ((param = Param::new_param_float("t7", P_FLAG_TVAR, &this->t7, NULL, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, 0.0)) == NULL) { abort(); @@ -383,7 +384,7 @@ CustomWave::CustomWave(int _id) : Waveform(512), abort(); } - if ((param = Param::new_param_float("t8", P_FLAG_TVAR | P_FLAG_PER_POINT, &this->t8, NULL, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, 0.0)) == NULL) + if ((param = Param::new_param_float("t8", P_FLAG_TVAR, &this->t8, NULL, MAX_DOUBLE_SIZE, -MAX_DOUBLE_SIZE, 0.0)) == NULL) { ; abort(); @@ -471,10 +472,10 @@ int CustomWave::add_per_point_eqn(char * name, Expr * gen_expr) index = per_point_eqn_tree.size(); /* Create the per point equation given the index, parameter, and general expression */ - if ((per_point_eqn = new PerPointEqn(index, param, gen_expr, samples)) == NULL) + if ((per_point_eqn = new PerPointEqn(index, param, gen_expr)) == NULL) return PROJECTM_FAILURE; if (CUSTOM_WAVE_DEBUG) - printf("add_per_point_eqn: created new equation (index = %d) (name = \"%s\")\n", per_point_eqn->index, per_point_eqn->param->name.c_str()); + printf("add_per_point_eqn: created new equation (index = %d) (name = \"%s\")\n", per_point_eqn->index, param->name.c_str()); /* Insert the per pixel equation into the preset per pixel database */ @@ -498,18 +499,28 @@ void CustomWave::evalInitConds() ColoredPoint CustomWave::PerPoint(ColoredPoint p, const WaveformContext context) { - r_mesh[context.sample_int] = r; - g_mesh[context.sample_int] = g; - b_mesh[context.sample_int] = b; - a_mesh[context.sample_int] = a; - x_mesh[context.sample_int] = x; - y_mesh[context.sample_int] = y; - sample = context.sample; - v1 = context.left; - v2 = context.right; + if (nullptr == per_point_program) + { + // see comment in MilkdropPreset, collect a list of assignments into one ProgramExpr + // which (theoretically) could be compiled together. + std::vector steps; + for (auto pos = per_point_eqn_tree.begin(); pos != per_point_eqn_tree.end();++pos) + steps.push_back((*pos)->assign_expr); + per_point_program = new ProgramExpr(steps, false); + } - for (std::vector::iterator pos = per_point_eqn_tree.begin(); pos != per_point_eqn_tree.end();++pos) - (*pos)->evaluate(context.sample_int); + + r_mesh[context.sample_int] = r; + g_mesh[context.sample_int] = g; + b_mesh[context.sample_int] = b; + a_mesh[context.sample_int] = a; + x_mesh[context.sample_int] = x; + y_mesh[context.sample_int] = y; + sample = context.sample; + v1 = context.left; + v2 = context.right; + + per_point_program->eval(context.sample_int, -1); p.a = a_mesh[context.sample_int]; p.r = r_mesh[context.sample_int]; diff --git a/src/libprojectM/MilkdropPresetFactory/CustomWave.hpp b/src/libprojectM/MilkdropPresetFactory/CustomWave.hpp index 3584bcdd6..f7f30e59a 100755 --- a/src/libprojectM/MilkdropPresetFactory/CustomWave.hpp +++ b/src/libprojectM/MilkdropPresetFactory/CustomWave.hpp @@ -105,6 +105,7 @@ public: std::map init_cond_tree; std::vector per_frame_eqn_tree; std::vector per_point_eqn_tree; + Expr *per_point_program; std::map per_frame_init_eqn_tree; /* Denotes the index of the last character for each string buffer */ diff --git a/src/libprojectM/MilkdropPresetFactory/Eval.cpp b/src/libprojectM/MilkdropPresetFactory/Eval.cpp index 1810f7441..00b3d65a0 100755 --- a/src/libprojectM/MilkdropPresetFactory/Eval.cpp +++ b/src/libprojectM/MilkdropPresetFactory/Eval.cpp @@ -47,18 +47,20 @@ InfixOp *Eval::infix_positive = NULL; /* Initializes all infix operators */ int Eval::init_infix_ops() { - Eval::infix_add = new InfixOp(INFIX_ADD, 4); - Eval::infix_minus = new InfixOp(INFIX_MINUS, 3); - Eval::infix_div = new InfixOp(INFIX_DIV, 2); - Eval::infix_or = new InfixOp(INFIX_OR, 5); - Eval::infix_and = new InfixOp(INFIX_AND,4); - Eval::infix_mod = new InfixOp(INFIX_MOD, 1); - Eval::infix_mult = new InfixOp(INFIX_MULT, 2); - - /* Prefix operators */ - Eval::infix_positive = new InfixOp(INFIX_ADD, 0); - Eval::infix_negative = new InfixOp(INFIX_MINUS, 0); + if (nullptr == Eval::infix_add) + { + Eval::infix_add = new InfixOp(INFIX_ADD, 4); + Eval::infix_minus = new InfixOp(INFIX_MINUS, 3); + Eval::infix_div = new InfixOp(INFIX_DIV, 2); + Eval::infix_or = new InfixOp(INFIX_OR, 5); + Eval::infix_and = new InfixOp(INFIX_AND, 4); + Eval::infix_mod = new InfixOp(INFIX_MOD, 1); + Eval::infix_mult = new InfixOp(INFIX_MULT, 2); + /* Prefix operators */ + Eval::infix_positive = new InfixOp(INFIX_ADD, 0); + Eval::infix_negative = new InfixOp(INFIX_MINUS, 0); + } return PROJECTM_SUCCESS; } diff --git a/src/libprojectM/MilkdropPresetFactory/Expr.cpp b/src/libprojectM/MilkdropPresetFactory/Expr.cpp index 79bf4231b..b10d750f7 100755 --- a/src/libprojectM/MilkdropPresetFactory/Expr.cpp +++ b/src/libprojectM/MilkdropPresetFactory/Expr.cpp @@ -26,6 +26,7 @@ #include #include "Eval.hpp" +#include "BuiltinFuncs.hpp" /* Evaluates functions in prefix form */ @@ -77,6 +78,42 @@ class PrefunExprOne : public PrefunExpr } }; +class SinExpr : public PrefunExpr +{ + float eval ( int mesh_i, int mesh_j ) override + { + float val = expr_list[0]->eval ( mesh_i, mesh_j ); + return sinf(val); + } +}; + +class CosExpr : public PrefunExpr +{ + float eval ( int mesh_i, int mesh_j ) override + { + float val = expr_list[0]->eval ( mesh_i, mesh_j ); + return cosf(val); + } +}; + +class LogExpr : public PrefunExpr +{ + float eval ( int mesh_i, int mesh_j ) override + { + float val = expr_list[0]->eval ( mesh_i, mesh_j ); + return logf(val); + } +}; + +class PowExpr : public PrefunExpr +{ + float eval ( int mesh_i, int mesh_j ) override + { + float x = expr_list[0]->eval ( mesh_i, mesh_j ); + float y = expr_list[1]->eval ( mesh_i, mesh_j ); + return powf(x, y); + } +}; class ConstantExpr : public Expr { @@ -98,79 +135,6 @@ public: } }; -class ParameterExpr : public Expr -{ -protected: - Term term; -public: - ParameterExpr( int _type, Term *_term ) : Expr(PARAMETER), term(*_term) {} - float eval(int mesh_i, int mesh_j ) = 0; - std::ostream& to_string(std::ostream& out) - { - out << term.param->name; - return out; - } -}; - -class BoolParameterExpr : public ParameterExpr -{ -public: - BoolParameterExpr( int _type, Term *_term ) : ParameterExpr(_type,_term) {} - float eval ( int mesh_i, int mesh_j ) - { - assert( term.param->type == P_TYPE_BOOL ); - return ( float ) ( * ( ( bool* ) ( term.param->engine_val ) ) ); - } -}; -class IntParameterExpr : public ParameterExpr -{ -public: - IntParameterExpr( int _type, Term *_term ) : ParameterExpr(_type,_term) {} - float eval ( int mesh_i, int mesh_j ) - { - assert( term.param->type == P_TYPE_INT ); - return ( float ) ( * ( ( int* ) ( term.param->engine_val ) ) ); - } -}; -class FloatParameterExpr : public ParameterExpr -{ -public: - FloatParameterExpr( int _type, Term *_term ) : ParameterExpr(_type,_term) {} - float eval ( int mesh_i, int mesh_j ); -}; -/* TODO optimize FloatParameterExpr::eval() further. -// - flag "never matrix" parameters -// - always pass in 2d matrix. instead of using (i, -1) for 1d matrix, we could just use (0, i) and avoid check -// - instead of using matrix_flag to give "copy on write" behavior, maybe pre-copy from engine_val to matrix[] -*/ -float FloatParameterExpr::eval ( int mesh_i, int mesh_j ) -{ - assert( term.param->type == P_TYPE_DOUBLE ); - if ( term.param->matrix_flag | ( term.param->flags & P_FLAG_ALWAYS_MATRIX ) ) - { - /* Sanity check the matrix is there... */ - assert ( term.param->matrix != NULL ); - - /// @slow boolean check could be expensive in this critical (and common) step of evaluation - if ( mesh_i >= 0 ) - { - if ( mesh_j >= 0 ) - { - return ( ( ( float** ) term.param->matrix ) [mesh_i][mesh_j] ); - } - else - { - // issue 64, make sure we're not reading a P_FLAG_PER_PIXEL matrix variable - if (!(term.param->flags & P_FLAG_PER_PIXEL)) - return ( ( ( float* ) term.param->matrix ) [mesh_i] ); - } - } - //assert(mesh_i >=0); - } - //std::cout << term.param->name << ": " << (*((float*)term.param->engine_val)) << std::endl; - return * ( ( float* ) ( term.param->engine_val ) ); -} - class MultAndAddExpr : public Expr { @@ -180,25 +144,50 @@ public: a(_a), b(_b), c(_c) { } - ~MultAndAddExpr() { - delete a; - delete b; - delete c; + ~MultAndAddExpr() override { + Expr::delete_expr(a); + Expr::delete_expr(b); + Expr::delete_expr(c); } - float eval(int mesh_i, int mesh_j) + float eval(int mesh_i, int mesh_j) override { float a_value = a->eval(mesh_i,mesh_j); float b_value = b->eval(mesh_i,mesh_j); float c_value = c->eval(mesh_i,mesh_j); return a_value * b_value + c_value; } - std::ostream &to_string(std::ostream &out) + std::ostream &to_string(std::ostream &out) override { out << "(" << a << " * " << b << ") + " << c; return out; } }; +class MultConstExpr : public Expr +{ + Expr *expr; + float c; +public: + MultConstExpr(Expr *_expr, float _c) : Expr(OTHER), + expr(_expr), c(_c) + { + } + ~MultConstExpr() override + { + Expr::delete_expr(expr); + } + float eval(int mesh_i, int mesh_j) override + { + float value = expr->eval(mesh_i,mesh_j); + return value * c; + } + std::ostream &to_string(std::ostream &out) override + { + out << "(" << expr << " * " << c << ") + " << c; + return out; + } +}; + std::ostream &TreeExpr::to_string(std::ostream &out) { if (NULL == infix_op) @@ -232,31 +221,31 @@ std::ostream &TreeExpr::to_string(std::ostream &out) return out; } -/* NOTE: Parser.cpp directly manipulates TreeExpr, so it is easier to optimizer AFTER parsing +/* NOTE: Parser.cpp directly manipulates TreeExpr, so it is easier to _optimizer AFTER parsing * than while building up the tree initially */ -Expr *TreeExpr::optimize() +Expr *TreeExpr::_optimize() { if (infix_op == NULL) { - Expr *opt = gen_expr->optimize(); + Expr *opt = gen_expr->_optimize(); if (opt != gen_expr) - delete gen_expr; + Expr::delete_expr(gen_expr); gen_expr = NULL; return opt; } if (left != NULL) { - Expr *l = left->optimize(); + Expr *l = left->_optimize(); if (l != left) - delete left; + Expr::delete_expr(left); left = l; } if (right != NULL) { - Expr *r = right->optimize(); + Expr *r = right->_optimize(); if (r != right) - delete right; + Expr::delete_expr(right); right = r; } if (left == NULL) @@ -281,25 +270,26 @@ Expr *TreeExpr::optimize() { Expr *a, *b, *c; if (left->clazz == TREE && ((TreeExpr *)left)->infix_op->type == INFIX_MULT) - { - a = ((TreeExpr *)left)->left; - b = ((TreeExpr *)left)->right; - c = right; - ((TreeExpr *)left)->left = NULL; - ((TreeExpr *)left)->right = NULL; - right = NULL; - } - else - { - a = ((TreeExpr *)right)->left; - b = ((TreeExpr *)right)->right; - c = left; - ((TreeExpr *)right)->left = NULL; - ((TreeExpr *)right)->right = NULL; - left = NULL; - } + std::swap(left,right); + a = ((TreeExpr *)right)->left; + b = ((TreeExpr *)right)->right; + c = left; + ((TreeExpr *)right)->left = NULL; + ((TreeExpr *)right)->right = NULL; + left = NULL; return new MultAndAddExpr(a,b,c); } + + if (infix_op->type == INFIX_MULT && (left->isConstant() || right->isConstant())) + { + if (right->isConstant()) + std::swap(left, right); + float c = left->eval(-1,-1); + Expr *expr = right; + right = left = nullptr; + return new MultConstExpr(expr, c); + } + return this; } @@ -308,7 +298,7 @@ float TreeExpr::eval ( int mesh_i, int mesh_j ) { float left_arg, right_arg; - /* shouldn't be null if we've called optimize() */ + /* shouldn't be null if we've called _optimize() */ assert(NULL != infix_op); left_arg = left->eval ( mesh_i, mesh_j ); @@ -356,46 +346,52 @@ Expr * Expr::const_to_expr ( float val ) /* Converts a regular parameter to an expression */ Expr * Expr::param_to_expr ( Param * param ) { - Term term; - if ( param == NULL ) return NULL; - /* This code is still a work in progress. We need - to figure out if the initial condition is used for - each per frame equation or not. I am guessing that - it isn't, and it is thusly implemented this way */ - - /* Current guess of true behavior (08/01/03) note from carm - First try to use the per_pixel_expr (with cloning) - If it is null however, use the engine variable instead. */ - - /* 08/20/03 : Presets are now objects, as well as per pixel equations. This ends up - making the parser handle the case where parameters are essentially per pixel equation - substitutions */ - - term.param = param; - switch ( param->type ) { case P_TYPE_BOOL: - return new BoolParameterExpr( PARAM_TERM_T, &term ); + return param->getExpr(); + //return new BoolParameterExpr( PARAM_TERM_T, &term ); case P_TYPE_INT: - return new IntParameterExpr( PARAM_TERM_T, &term ); + return param->getExpr(); + //return new IntParameterExpr( PARAM_TERM_T, &term ); case P_TYPE_DOUBLE: - return new FloatParameterExpr( PARAM_TERM_T, &term ); + return param->getExpr(); + //return new FloatParameterExpr( PARAM_TERM_T, &term ); + default: + return NULL; } - return NULL; } /* Converts a prefix function to an expression */ Expr * Expr::prefun_to_expr ( float ( *func_ptr ) ( void * ), Expr ** expr_list, int num_args ) { - PrefunExpr * prefun_expr; - if (num_args == 1) - prefun_expr = new PrefunExprOne(); - else - prefun_expr = new PrefunExpr(); + PrefunExpr *prefun_expr; + if (num_args == 1) + { + if (func_ptr == (float (*)(void *)) FuncWrappers::sin_wrapper) + prefun_expr = new SinExpr(); + else if (func_ptr == (float (*)(void *)) FuncWrappers::cos_wrapper) + prefun_expr = new CosExpr(); + else if (func_ptr == (float (*)(void *)) FuncWrappers::log_wrapper) + prefun_expr = new LogExpr(); + else + prefun_expr = new PrefunExprOne(); + } + else if (num_args == 2) + { + if (func_ptr == (float (*)(void *)) FuncWrappers::pow_wrapper) + prefun_expr = new PowExpr(); + else + prefun_expr = new PrefunExpr(); + } + else + { + prefun_expr = new PrefunExpr(); + } + prefun_expr->num_args = num_args; prefun_expr->func_ptr = ( float ( * ) ( void* ) ) func_ptr; prefun_expr->expr_list = expr_list; @@ -463,7 +459,7 @@ PrefunExpr::~PrefunExpr() /* Free every element in expression list */ for ( i = 0 ; i < num_args; i++ ) { - delete expr_list[i]; + Expr::delete_expr(expr_list[i]); } free ( expr_list ); } @@ -475,13 +471,13 @@ TreeExpr::~TreeExpr() /* free left tree */ if ( left != NULL ) { - delete left; + Expr::delete_expr(left); } /* free general expression object */ if ( gen_expr != NULL ) { - delete gen_expr; + Expr::delete_expr(gen_expr); } /* Note that infix operators are always @@ -491,7 +487,7 @@ TreeExpr::~TreeExpr() /* free right tree */ if ( right != NULL ) { - delete right; + Expr::delete_expr(right); } } @@ -506,18 +502,27 @@ PrefunExpr::PrefunExpr() : Expr(FUNCTION) { } -Expr *PrefunExpr::optimize() +bool isConstantFn(float (* fn)(void*)) +{ + return (float (*)(float *))fn != FuncWrappers::print_wrapper && + (float (*)(float *))fn != FuncWrappers::rand_wrapper; +} + +Expr *PrefunExpr::_optimize() { bool constant_args = true; for (int i=0 ; i < num_args ; i++) { Expr *orig = expr_list[i]; - expr_list[i] = orig->optimize(); + expr_list[i] = orig->_optimize(); if (orig != expr_list[i]) - delete orig; - constant_args &= expr_list[i]->isConstant(); + Expr::delete_expr(orig); + constant_args = constant_args && expr_list[i]->isConstant(); } - // TODO most functions can be pre-evaluated if inputs are constant, but not all + if (constant_args && isConstantFn(func_ptr)) + { + return Expr::const_to_expr(eval(-1, -1)); + } return this; } @@ -534,3 +539,127 @@ std::ostream& PrefunExpr::to_string(std::ostream& out) out << ")"; return out; } + + +AssignExpr::AssignExpr(LValue *lhs_, Expr *rhs_) : Expr(ASSIGN), lhs(lhs_), rhs(rhs_) {} + +AssignExpr::~AssignExpr() +{ + Expr::delete_expr(lhs); + Expr::delete_expr(rhs); +} + +Expr * AssignExpr::_optimize() +{ + Expr *t = rhs->_optimize(); + if (t != rhs) + Expr::delete_expr(rhs); + rhs = t; + return this; +} + +float AssignExpr::eval(int mesh_i, int mesh_j) +{ + float v = rhs->eval( mesh_i, mesh_j ); + lhs->set( v ); + return v; +} + +std::ostream& AssignExpr::to_string(std::ostream &out) +{ + out << lhs << " = " << rhs; + return out; +} + +AssignMatrixExpr::AssignMatrixExpr(LValue *lhs_, Expr *rhs_) : AssignExpr(lhs_, rhs_) {} + +float AssignMatrixExpr::eval(int mesh_i, int mesh_j) +{ + float v = rhs->eval( mesh_i, mesh_j ); + lhs->set_matrix( mesh_i, mesh_j, v ); + return v; +} + +std::ostream& AssignMatrixExpr::to_string(std::ostream &out) +{ + out << lhs << "[i,j] = " << rhs; + return out; +} + + + + + +// TESTS + + +#include + +#ifndef NDEBUG + +#define TEST(cond) if (!verify(#cond,cond)) return false + + +struct ExprTest : public Test +{ + ExprTest() : Test("ExprTest") + {} + +public: + bool optimize_constant_expr() + { + TreeExpr *a = TreeExpr::create(nullptr, Expr::const_to_expr( 1.0 ), nullptr, nullptr); + TreeExpr *b = TreeExpr::create(nullptr, Expr::const_to_expr( 2.0 ), nullptr, nullptr); + Expr *c = TreeExpr::create(Eval::infix_add, nullptr, a, b); + //TEST(3.0f == c->eval(-1,-1)); + Expr *x = Expr::optimize(c); + TEST(x != c); + Expr::delete_expr(c); + TEST(x->clazz == CONSTANT); + TEST(3.0f == x->eval(-1,-1)); + Expr::delete_expr(x); + + Expr **expr_array = (Expr **)malloc(sizeof(Expr *)); + expr_array[0] = TreeExpr::create(nullptr, Expr::const_to_expr( (float)M_PI ), nullptr, nullptr); + Expr *sin = Expr::prefun_to_expr((float (*)(void *))FuncWrappers::sin_wrapper, expr_array, 1); + x = Expr::optimize(sin); + TEST(x != sin); + Expr::delete_expr( sin ); + TEST(x->clazz == CONSTANT); + TEST(sinf( (float)M_PI ) == x->eval(-1,-10)); + Expr::delete_expr(x); + + // make sure rand() is not optimized away + expr_array = (Expr **)malloc(sizeof(Expr *)); + expr_array[0] = TreeExpr::create(nullptr, Expr::const_to_expr( (float)M_PI ), nullptr, nullptr); + Expr *rand = Expr::prefun_to_expr((float (*)(void *))FuncWrappers::rand_wrapper, expr_array, 1); + x = Expr::optimize(rand); + TEST(x == rand); + TEST(x->clazz != CONSTANT); + Expr::delete_expr(x); + + return true; + } + + bool test() override + { + Eval::init_infix_ops(); + bool result = true; + result &= optimize_constant_expr(); + return result; + } +}; + +Test* Expr::test() +{ + return new ExprTest(); +} + +#else + +Test* Expr::test() +{ + return null; +} + +#endif \ No newline at end of file diff --git a/src/libprojectM/MilkdropPresetFactory/Expr.hpp b/src/libprojectM/MilkdropPresetFactory/Expr.hpp index 28abb062f..becc366ba 100755 --- a/src/libprojectM/MilkdropPresetFactory/Expr.hpp +++ b/src/libprojectM/MilkdropPresetFactory/Expr.hpp @@ -31,8 +31,10 @@ #include "dlldefs.h" #include "CValue.hpp" -#include +#include +#include +class Test; class Param; #define CONST_STACK_ELEMENT 0 @@ -63,7 +65,7 @@ public: enum ExprClass { - TREE, CONSTANT, PARAMETER, FUNCTION, OTHER + TREE, CONSTANT, PARAMETER, FUNCTION, ASSIGN, PROGRAM, OTHER }; class Expr @@ -72,7 +74,7 @@ public: ExprClass clazz; Expr(ExprClass c) : clazz(c) {}; virtual ~Expr() {}; - virtual Expr *optimize() { return this; }; + virtual bool isConstant() { return false; }; virtual float eval(int mesh_i, int mesh_j) = 0; virtual std::ostream& to_string(std::ostream &out) @@ -80,9 +82,23 @@ public: std::cout << "nyi"; return out; } + static Test *test(); static Expr *const_to_expr( float val ); static Expr *param_to_expr( Param *param ); static Expr *prefun_to_expr( float (*func_ptr)(void *), Expr **expr_list, int num_args ); + + static void delete_expr(Expr *expr) { if (nullptr != expr) expr->_delete_from_tree(); } + static Expr *optimize(Expr *root) { return root->_optimize(); }; + +public: // but don't call these from outside Expr.cpp + + virtual Expr *_optimize() { return this; }; + + // override if this expr is not 'owned' by the containg expression tree + virtual void _delete_from_tree() + { + delete this; + } }; inline std::ostream& operator<<(std::ostream& out, Expr *expr) @@ -111,11 +127,11 @@ public: TreeExpr *leftTree() { return dynamic_cast(left); } TreeExpr *rightTree() { return dynamic_cast(right); } - ~TreeExpr(); + ~TreeExpr() override; - Expr *optimize(); - float eval(int mesh_i, int mesh_j); - std::ostream& to_string(std::ostream &out); + Expr *_optimize() override; + float eval(int mesh_i, int mesh_j) override; + std::ostream& to_string(std::ostream &out) override; }; /* A function expression in prefix form */ @@ -126,12 +142,69 @@ public: int num_args; Expr **expr_list; PrefunExpr(); - ~PrefunExpr(); + ~PrefunExpr() override; /* Evaluates functions in prefix form */ - Expr *optimize(); - float eval(int mesh_i, int mesh_j); - std::ostream& to_string(std::ostream &out); + Expr *_optimize() override; + float eval(int mesh_i, int mesh_j) override; + std::ostream& to_string(std::ostream &out) override; +}; + +class LValue : public Expr +{ +public: + explicit LValue(ExprClass c) : Expr(c) {}; + virtual void set(float value) = 0; + virtual void set_matrix(int mesh_i, int mesh_j, float value) = 0; +}; + + +class AssignExpr : public Expr +{ +protected: + LValue *lhs; + Expr *rhs; +public: + AssignExpr(LValue *lhs, Expr *rhs); + ~AssignExpr() override; + Expr *_optimize() override; + float eval(int mesh_i, int mesh_j) override; + std::ostream& to_string(std::ostream &out) override; +}; + + +class AssignMatrixExpr : public AssignExpr +{ +public: + AssignMatrixExpr(LValue *lhs, Expr *rhs); + float eval(int mesh_i, int mesh_j) override; + std::ostream& to_string(std::ostream &out) override; +}; + + +class ProgramExpr : public Expr +{ +protected: + std::vector steps; + bool own; +public: + ProgramExpr(std::vector &steps_, bool ownSteps) : Expr(PROGRAM), steps(steps_), own(ownSteps) + { + } + ~ProgramExpr() + { + if (!own) + return; + for (auto it=steps.begin() ; iteval(mesh_i,mesh_j); + return f; + } }; #endif /** _EXPR_H */ diff --git a/src/libprojectM/MilkdropPresetFactory/InitCond.cpp b/src/libprojectM/MilkdropPresetFactory/InitCond.cpp index c43b91f35..051433ffe 100755 --- a/src/libprojectM/MilkdropPresetFactory/InitCond.cpp +++ b/src/libprojectM/MilkdropPresetFactory/InitCond.cpp @@ -40,13 +40,9 @@ char InitCond::init_cond_string_buffer[STRING_BUFFER_SIZE]; int InitCond::init_cond_string_buffer_index = 0; /* Creates a new initial condition */ -InitCond::InitCond( Param * _param, CValue _init_val ):param(_param), init_val(_init_val) { - - - // std::cerr << "InitCond::InitCond: " << this->param->name << std::endl; - +InitCond::InitCond( Param * _param, CValue _init_val ):param(_param), init_val(_init_val) +{ assert(param); - assert(param->engine_val); } /* Frees initial condition structure */ @@ -58,59 +54,23 @@ void InitCond::evaluate() } /* Evaluate an initial conditon */ -void InitCond::evaluate(bool evalUser) { +void InitCond::evaluate(bool evalUser) +{ + assert(this); + assert(param); + if (param->flags & P_FLAG_USERDEF && !evalUser) + return; - - assert(this); - assert(param); - -if (param->flags & P_FLAG_USERDEF && !evalUser) - return; - - /* Set matrix flag to zero. This ensures - its constant value will be used rather than a matrix value - */ -param->matrix_flag = false; - - /* Parameter is of boolean type, either true/false */ - - if (param->type == P_TYPE_BOOL) { - - // printf( "init_cond: %s = %d (TYPE BOOL)\n", param->name.c_str(), init_val.bool_val); - //std::cerr << "[InitCond] param is a boolean of with name " - // << param->name << std::endl; - - assert(param->engine_val); - - *((bool*)param->engine_val) = init_val.bool_val; - - return; - } - - /* Parameter is an integer type, just like C */ - - if ( param->type == P_TYPE_INT) { - assert(param->engine_val); - *((int*)param->engine_val) = init_val.int_val; - return; - } - - /* Parameter is of a float type, just like C */ - - if (param->type == P_TYPE_DOUBLE) { - assert(param->engine_val); - *((float*)param->engine_val) = init_val.float_val; - return; - } - - /* Unknown type of parameter */ - return; + /* Has side-effect of also setting matrix flag to zero. This ensures + its constant value will be used rather than a matrix value + */ + param->set_param(init_val); } /* WIP */ -void InitCond::init_cond_to_string() { - +void InitCond::init_cond_to_string() +{ int string_length; char string[MAX_TOKEN_SIZE]; diff --git a/src/libprojectM/MilkdropPresetFactory/InitCondUtils.hpp b/src/libprojectM/MilkdropPresetFactory/InitCondUtils.hpp index 218619946..e63cda7a0 100644 --- a/src/libprojectM/MilkdropPresetFactory/InitCondUtils.hpp +++ b/src/libprojectM/MilkdropPresetFactory/InitCondUtils.hpp @@ -25,10 +25,6 @@ inline void LoadUnspecInitCond::operator() (Param * param) { InitCond * init_cond = 0; CValue init_val; - assert(param); - assert(param->engine_val); - - /* Don't count these parameters as initial conditions */ if (param->flags & P_FLAG_READONLY) return; @@ -48,15 +44,7 @@ inline void LoadUnspecInitCond::operator() (Param * param) { if (m_perFrameInitEqnTree.find(param->name) != m_perFrameInitEqnTree.end()) return; - // Set an initial vialue via correct union member - if (param->type == P_TYPE_BOOL) - init_val.bool_val = param->default_init_val.bool_val; - else if (param->type == P_TYPE_INT) - init_val.int_val = param->default_init_val.int_val; - - else if (param->type == P_TYPE_DOUBLE) { - init_val.float_val = param->default_init_val.float_val; - } + init_val = param->default_init_val; //printf("%s\n", param->name); /* Create new initial condition */ diff --git a/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.cpp b/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.cpp index a84d9d865..b6ed65a9c 100755 --- a/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.cpp +++ b/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.cpp @@ -44,10 +44,15 @@ #include "PresetFactoryManager.hpp" #include "MilkdropPresetFactory.hpp" +#ifdef __SSE2__ +#include +#endif + MilkdropPreset::MilkdropPreset(MilkdropPresetFactory *factory, std::istream & in, const std::string & presetName, PresetOutputs & presetOutputs): Preset(presetName), builtinParams(_presetInputs, presetOutputs), + per_pixel_program(nullptr), _factory(factory), _presetOutputs(presetOutputs) { @@ -58,6 +63,7 @@ MilkdropPreset::MilkdropPreset(MilkdropPresetFactory *factory, std::istream & in MilkdropPreset::MilkdropPreset(MilkdropPresetFactory *factory, const std::string & absoluteFilePath, const std::string & presetName, PresetOutputs & presetOutputs): Preset(presetName), builtinParams(_presetInputs, presetOutputs), + per_pixel_program(nullptr), _filename(parseFilename(absoluteFilePath)), _absoluteFilePath(absoluteFilePath), _factory(factory), @@ -76,6 +82,7 @@ MilkdropPreset::~MilkdropPreset() traverse >(per_frame_init_eqn_tree); traverse >(per_pixel_eqn_tree); + Expr::delete_expr(per_pixel_program); traverseVector >(per_frame_eqn_tree); @@ -387,94 +394,61 @@ void MilkdropPreset::evaluateFrame() } + +#ifdef __SSE2__ +inline void init_mesh(float **mesh, const float value, const int gx, const int gy) +{ + __m128 mvalue = _mm_set_ps1(value); + for (int x = 0; x < gx; x++) + for (int y = 0; y < gy; y += 4) + _mm_store_ps(&mesh[x][y], mvalue); +} +#else +inline void init_mesh(float **mesh, const float value, const int gx, const int gy) +{ + for (x=0;x steps; + for (std::map::iterator pos = per_pixel_eqn_tree.begin(); pos != per_pixel_eqn_tree.end(); ++pos) + steps.push_back(pos->second->assign_expr); + per_pixel_program = new ProgramExpr(steps,false); + } - /* Evaluate all per pixel equations in the tree datastructure */ - for (int mesh_x = 0; mesh_x < presetInputs().gx; mesh_x++) - for (int mesh_y = 0; mesh_y < presetInputs().gy; mesh_y++) - for (std::map::iterator pos = per_pixel_eqn_tree.begin(); - pos != per_pixel_eqn_tree.end(); ++pos) - pos->second->evaluate(mesh_x, mesh_y); - + for (int mesh_x = 0; mesh_x < presetInputs().gx; mesh_x++) + for (int mesh_y = 0; mesh_y < presetInputs().gy; mesh_y++) + per_pixel_program->eval( mesh_x, mesh_y ); } int MilkdropPreset::readIn(std::istream & fs) { diff --git a/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.hpp b/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.hpp index a4c9183fd..c10fa43c3 100644 --- a/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.hpp +++ b/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.hpp @@ -130,6 +130,7 @@ public: /* Data structures that contain equation and initial condition information */ std::vector per_frame_eqn_tree; /* per frame equations */ std::map per_pixel_eqn_tree; /* per pixel equation tree */ + Expr *per_pixel_program; std::map per_frame_init_eqn_tree; /* per frame initial equations */ std::map init_cond_tree; /* initial conditions */ std::map user_param_tree; /* user parameter splay tree */ diff --git a/src/libprojectM/MilkdropPresetFactory/Param.cpp b/src/libprojectM/MilkdropPresetFactory/Param.cpp index 9d7cec7cf..cdf52a0c0 100755 --- a/src/libprojectM/MilkdropPresetFactory/Param.cpp +++ b/src/libprojectM/MilkdropPresetFactory/Param.cpp @@ -40,9 +40,10 @@ #include /** Constructor */ -Param::Param( std::string _name, short int _type, short int _flags, void * _engine_val, void * _matrix, +Param::Param( const std::string &_name, short int _type, short int _flags, void * _engine_val, void * _matrix, CValue _default_init_val, CValue _upper_bound, CValue _lower_bound): - name(_name), + LValue(PARAMETER), + name(_name), type(_type), flags (_flags), matrix_flag (0), @@ -52,24 +53,22 @@ Param::Param( std::string _name, short int _type, short int _flags, void * _engi upper_bound (_upper_bound), lower_bound (_lower_bound), local_value(0.0) - { - +{ } /* Creates a user defined parameter */ -Param::Param(std::string _name) : - name(_name), +Param::Param(const std::string &_name) : LValue(PARAMETER), + name(_name), type(P_TYPE_DOUBLE), - flags(P_FLAG_USERDEF), + flags(P_FLAG_USERDEF), matrix_flag(0), matrix(0), - local_value(0.0) - { + local_value(0.0) +{ + engine_val = (float *)&local_value; - engine_val = (float *)&local_value; - - default_init_val.float_val = DEFAULT_DOUBLE_IV; + default_init_val.float_val = DEFAULT_DOUBLE_IV; upper_bound.float_val = DEFAULT_DOUBLE_UB; lower_bound.float_val = DEFAULT_DOUBLE_LB; @@ -123,7 +122,7 @@ Param * Param::new_param_float(const char * name, short int flags, void * engine ub.float_val = upper_bound; lb.float_val = lower_bound; - if ((param = new Param(name, P_TYPE_DOUBLE, flags, engine_val, matrix,iv, ub, lb)) == NULL) + if ((param = Param::create(name, P_TYPE_DOUBLE, flags, engine_val, matrix,iv, ub, lb)) == NULL) return NULL; @@ -132,7 +131,7 @@ Param * Param::new_param_float(const char * name, short int flags, void * engine } /* Creates a new parameter of type int */ -Param * Param::new_param_int(const char * name, short int flags, void * engine_val, +Param * Param::new_param_int(const char * name, short int flags, void * engine_val, int upper_bound, int lower_bound, int init_val) { Param * param; @@ -143,7 +142,7 @@ Param * Param::new_param_int(const char * name, short int flags, void * engine_v ub.int_val = upper_bound; lb.int_val = lower_bound; - if ((param = new Param(name, P_TYPE_INT, flags, engine_val, NULL, iv, ub, lb)) == NULL) + if ((param = Param::create(name, P_TYPE_INT, flags, engine_val, NULL, iv, ub, lb)) == NULL) return NULL; @@ -163,7 +162,7 @@ Param * Param::new_param_bool(const char * name, short int flags, void * engine_ ub.bool_val = upper_bound; lb.bool_val = lower_bound; - if ((param = new Param(name, P_TYPE_BOOL, flags, engine_val, NULL, iv, ub, lb)) == NULL) + if ((param = Param::create(name, P_TYPE_BOOL, flags, engine_val, NULL, iv, ub, lb)) == NULL) return NULL; @@ -182,7 +181,7 @@ Param * Param::new_param_string(const char * name, short int flags, void * engin ub.bool_val = 0; lb.bool_val = 0; - if ((param = new Param(name, P_TYPE_STRING, flags, engine_val, NULL, iv, ub, lb)) == NULL) + if ((param = Param::create(name, P_TYPE_STRING, flags, engine_val, NULL, iv, ub, lb)) == NULL) return NULL; @@ -191,3 +190,267 @@ Param * Param::new_param_string(const char * name, short int flags, void * engin } +/* + * I made Param extend Expr just to avoid inheritence of multiple virtual classes, + * however, this is really the subclass that knows how to interact with Expr + */ + +struct _Param : public Param +{ + _Param( const std::string &name_, short int type_, short int flags_, + void * eqn_val_, void *matrix_, + CValue default_init_val_, CValue upper_bound_, + CValue lower_bound_) : + Param(name_, type_, flags_, eqn_val_, matrix_, default_init_val_, upper_bound_, lower_bound_) + { + if (flags & P_FLAG_ALWAYS_MATRIX) + matrix_flag = true; + } + explicit _Param( const std::string &name_) : Param(name_) {} + + LValue *getExpr() override + { + return (LValue *)this; + } + + void _delete_from_tree() override + { + /* do nothing, as the param isn't owned by the expresion tree */ + } + + void set(float value) override + { + set_param(value); + } + void set_matrix(int mesh_i, int mesh_j, float value) override + { + set_param(value); + } +}; + +class _BoolParam : public _Param +{ +public: + _BoolParam( const std::string &name_, short int type_, short int flags_, + void * eqn_val_, void *matrix_, + CValue default_init_val_, CValue upper_bound_, + CValue lower_bound_) : + _Param(name_, type_, flags_, eqn_val_, matrix_, default_init_val_, upper_bound_, lower_bound_) {} + + float eval(int mesh_i, int mesh_j) override + { + return *(bool *)engine_val ? 1 : 0; + } +}; + +class _IntParam : public _Param +{ +public: + _IntParam( const std::string &name_, short int type_, short int flags_, + void * eqn_val_, void *matrix_, + CValue default_init_val_, CValue upper_bound_, + CValue lower_bound_) : + _Param(name_, type_, flags_, eqn_val_, matrix_, default_init_val_, upper_bound_, lower_bound_) {} + float eval(int mesh_i, int mesh_j) override + { + return *(int *)engine_val; + } +}; + +class _StringParam : public _Param +{ +public: + _StringParam( const std::string &name_, short int type_, short int flags_, + void * eqn_val_, void *matrix_, + CValue default_init_val_, CValue upper_bound_, + CValue lower_bound_) : + _Param(name_, type_, flags_, eqn_val_, matrix_, default_init_val_, upper_bound_, lower_bound_) {} + float eval(int mesh_i, int mesh_j) override + { + return 0; + } +}; + +class _FloatParam : public _Param +{ +public: + _FloatParam( const std::string &name_, short int type_, short int flags_, + void * eqn_val_, void *matrix_, + CValue default_init_val_, CValue upper_bound_, + CValue lower_bound_) : + _Param(name_, type_, flags_, eqn_val_, matrix_, default_init_val_, upper_bound_, lower_bound_) {} + explicit _FloatParam( const std::string &name_) : _Param(name_) {} + float eval(int mesh_i, int mesh_j) override + { + return *(float *)engine_val; + } +}; + +/*This isn't that useful yet. Maybe later +class _AlwaysMatrixParam : public _FloatParam +{ +public: + _AlwaysMatrixParam( const std::string &name_, short int type_, short int flags_, + void * eqn_val_, void *matrix_, + CValue default_init_val_, CValue upper_bound_, + CValue lower_bound_) : + _FloatParam(name_, type_, flags_, eqn_val_, matrix_, default_init_val_, upper_bound_, lower_bound_) + { + matrix_flag = true; + } + float eval(int mesh_i, int mesh_j) override + { + assert( mesh_i >=0 && mesh_j >= 0 ); + return ((float **)matrix)[mesh_i][mesh_j]; + } + void set_matrix(int mesh_i, int mesh_j, float value) override + { + assert( mesh_i >=0 && mesh_j >= 0); + // Yup, presets write to read-only ALWAYS_MATRIX parameters + // assert(!(flags & P_FLAG_READONLY)); + ((float **)matrix)[mesh_i][mesh_j] = value; + } +};*/ + +class _MeshParam : public _FloatParam +{ +public: + _MeshParam( const std::string &name_, short int type_, short int flags_, + void * eqn_val_, void *matrix_, + CValue default_init_val_, CValue upper_bound_, + CValue lower_bound_) : + _FloatParam(name_, type_, flags_, eqn_val_, matrix_, default_init_val_, upper_bound_, lower_bound_) {} + float eval(int mesh_i, int mesh_j) override + { + assert( type == P_TYPE_DOUBLE ); + // see issue 64: There are presets with per_point equations that read from pre_frame/per_pixel parameters, + // e.g. per_point1=dx=dx*1.01 + // any this means that we get called with (i>=0,j==-1) + if ( matrix_flag && mesh_i >= 0 && mesh_j >= 0) + return ( ( ( float** ) matrix ) [mesh_i][mesh_j] ); + return * ( ( float* ) ( engine_val ) ); + } + void set_matrix(int mesh_i, int mesh_j, float value) override + { + if (nullptr == matrix) + { + *(float *)engine_val = value; + } + else + { + ((float **) matrix)[mesh_i][mesh_j] = value; + matrix_flag = true; + } + } +}; + + +// TODO merge PointsParam/MeshParam +class _PointsParam : public _FloatParam +{ +public: + _PointsParam( const std::string &name_, short int type_, short int flags_, + void * eqn_val_, void *matrix_, + CValue default_init_val_, CValue upper_bound_, + CValue lower_bound_) : + _FloatParam(name_, type_, flags_, eqn_val_, matrix_, default_init_val_, upper_bound_, lower_bound_) {} + float eval(int mesh_i, int mesh_j) override + { + assert( mesh_j == -1 ); + assert( type == P_TYPE_DOUBLE ); + if ( matrix_flag && mesh_i >= 0 ) + return ( ( ( float* ) matrix ) [mesh_i] ); + return * ( ( float* ) ( engine_val ) ); + } + void set_matrix(int mesh_i, int mesh_j, float value) override + { + if (nullptr == matrix) + { + *(float *)engine_val = value; + } + else + { + ((float *) matrix)[mesh_i] = value; + matrix_flag = true; + } + } +}; + + +Param * Param::create( const std::string &name, short int type, short int flags, + void * eqn_val, void *matrix, + CValue default_init_val, CValue upper_bound, + CValue lower_bound) +{ + if (type == P_TYPE_BOOL) + { + assert(nullptr == matrix); + assert(0 == (flags & (P_FLAG_PER_PIXEL|P_FLAG_PER_POINT))); + return new _BoolParam( name, type, flags, eqn_val, matrix, default_init_val, upper_bound, lower_bound ); + } + if (type == P_TYPE_INT) + { + assert(nullptr == matrix); + assert(0 ==(flags & (P_FLAG_PER_PIXEL|P_FLAG_PER_POINT))); + return new _IntParam( name, type, flags, eqn_val, matrix, default_init_val, upper_bound, lower_bound ); + } + if (type == P_TYPE_STRING) + { + assert(0 == (flags & (P_FLAG_PER_PIXEL|P_FLAG_PER_POINT))); + return new _StringParam( name, type, flags, eqn_val, matrix, default_init_val, upper_bound, lower_bound ); + } + assert(type == P_TYPE_DOUBLE); + if (matrix == nullptr) + { + assert(0 == (flags & (P_FLAG_PER_PIXEL|P_FLAG_PER_POINT))); + return new _FloatParam( name, type, flags, eqn_val, matrix, default_init_val, upper_bound, lower_bound ); + } + assert( flags & (P_FLAG_PER_PIXEL|P_FLAG_PER_POINT) ); + if (flags & P_FLAG_PER_PIXEL) + { + return new _MeshParam( name, type, flags, eqn_val, matrix, default_init_val, upper_bound, lower_bound ); + } + else + { + return new _PointsParam( name, type, flags, eqn_val, matrix, default_init_val, upper_bound, lower_bound ); + } +} + +Param * Param::createUser( const std::string &name ) +{ + return new _FloatParam( name ); +} + + +// TESTS + + +#include + +#ifndef NDEBUG + +struct ParamTest : public Test +{ + ParamTest() : Test("ParamTest") + {} + +public: + bool test() override + { + return true; + } +}; + +Test* Param::test() +{ + return new ParamTest(); +} + +#else + +Test* Param::test() +{ + return null; +} + +#endif diff --git a/src/libprojectM/MilkdropPresetFactory/Param.hpp b/src/libprojectM/MilkdropPresetFactory/Param.hpp index 2169caa5b..f780ba2d6 100755 --- a/src/libprojectM/MilkdropPresetFactory/Param.hpp +++ b/src/libprojectM/MilkdropPresetFactory/Param.hpp @@ -57,64 +57,86 @@ class InitCond; class Param; class Preset; +class Test; + #ifdef __SSE2__ #include #endif /* Parameter Type */ -class Param { +class Param : public LValue +{ +protected: + Param(const std::string &name, short int type, short int flags, + void * eqn_val, void *matrix, + CValue default_init_val, CValue upper_bound, + CValue lower_bound); + + /// Create a user defined floating point parameter + explicit Param( const std::string &name ); + public: std::string name; /* name of the parameter, not necessary but useful neverthless */ short int type; /* parameter number type (int, bool, or float) */ short int flags; /* read, write, user defined, etc */ +protected: short int matrix_flag; /* for optimization purposes */ void * engine_val; /* pointer to the engine variable */ void * matrix; /* per pixel / per point matrix for this variable */ +public: CValue default_init_val; /* a default initial condition value */ +protected: CValue upper_bound; /* this parameter's upper bound */ CValue lower_bound; /* this parameter's lower bound */ // for a local variable, engine_val can point here float local_value; +public: /// Create a new parameter - Param(std::string name, short int type, short int flags, + static Param * create(const std::string &name, short int type, short int flags, void * eqn_val, void *matrix, CValue default_init_val, CValue upper_bound, CValue lower_bound); - ~Param(); + static Param * createUser(const std::string &name); - /// Create a user defined floating point parameter - Param( std::string name ); + static Test *test(); + + virtual ~Param(); static bool is_valid_param_string( const char *string ); void set_param( float val ); + void set_param( CValue val ); + void set_param( std::string &text) { *((std::string*)engine_val) = text; } static Param *new_param_float( const char *name, short int flags, void *engine_val, void *matrix, float upper_bound, float lower_bound, float init_val ); - static Param *new_param_double(const char *name, short int flags, void *engine_val, - void *matrix, double upper_bound, - double lower_bound, - double init_val ); + static Param * new_param_int(const char * name, short int flags, void * engine_val, int upper_bound, int lower_bound, int init_val ); static Param * new_param_bool(const char * name, short int flags, void * engine_val, bool upper_bound, bool lower_bound, bool init_val ); static Param * new_param_string(const char * name, short int flags, void * engine_val); + // return an Expr to inject directly into an Eqn + // this allows the parameter to stay encapsulated, but not add extra levels of virtual functions + // into the evaluation process + + virtual LValue *getExpr() = 0; }; /* Sets the parameter engine value to value val. clipping occurs if necessary */ -inline void Param::set_param( float val) { - - switch (type) { - +inline void Param::set_param( float val) +{ + matrix_flag = false; + switch (type) + { case P_TYPE_BOOL: if (val < 0) *((bool*)engine_val) = false; @@ -135,8 +157,6 @@ inline void Param::set_param( float val) { break; case P_TYPE_DOUBLE: /* Make sure value is an integer */ - - if (val < lower_bound.float_val) *((float*)engine_val) = lower_bound.float_val; else if (val > upper_bound.float_val) @@ -147,10 +167,27 @@ inline void Param::set_param( float val) { default: //abort(); break; - } +} - return; +inline void Param::set_param( CValue val) +{ + matrix_flag = false; + switch (type) + { + case P_TYPE_BOOL: + set_param( val.bool_val ); + break; + case P_TYPE_INT: + set_param( val.int_val ); + break; + case P_TYPE_DOUBLE: + set_param( val.float_val ); + break; + default: + //abort(); + break; + } } #endif /** !_PARAM_TYPES_H */ diff --git a/src/libprojectM/MilkdropPresetFactory/ParamUtils.hpp b/src/libprojectM/MilkdropPresetFactory/ParamUtils.hpp index dd94ff73a..39f8678d4 100644 --- a/src/libprojectM/MilkdropPresetFactory/ParamUtils.hpp +++ b/src/libprojectM/MilkdropPresetFactory/ParamUtils.hpp @@ -46,7 +46,7 @@ public: return NULL; /* Now, create the user defined parameter given the passed name */ - if ((param = new Param(name)) == NULL) + if ((param = Param::createUser(name)) == NULL) return NULL; /* Finally, insert the new parameter into this preset's parameter tree */ diff --git a/src/libprojectM/MilkdropPresetFactory/Parser.cpp b/src/libprojectM/MilkdropPresetFactory/Parser.cpp index 90c63e4cb..aadd1771f 100755 --- a/src/libprojectM/MilkdropPresetFactory/Parser.cpp +++ b/src/libprojectM/MilkdropPresetFactory/Parser.cpp @@ -46,6 +46,7 @@ #include #include #include "BuiltinFuncs.hpp" +#include "MilkdropPresetFactory.hpp" /* Grabs the next token from the file. The second argument points to the raw string */ @@ -276,9 +277,8 @@ Expr **Parser::parse_prefix_args(std::istream & fs, int num_args, MilkdropPrese { //if (PARSE_DEBUG) printf("parse_prefix_args: failed to get parameter # %d for function (LINE %d)\n", i+1, line_count); for (j = 0; j < i; j++) - delete expr_list[j]; + Expr::delete_expr(expr_list[j]); free(expr_list); - expr_list = NULL; return NULL; } /* Assign entry in expression list */ @@ -360,7 +360,7 @@ int Parser::parse_per_pixel_eqn(std::istream & fs, MilkdropPreset * preset, cha { } - delete gen_expr; + Expr::delete_expr(gen_expr); return PROJECTM_PARSE_ERROR; } @@ -751,7 +751,7 @@ Expr * Parser::_parse_gen_expr ( std::istream & fs, TreeExpr * tree_expr, Milkd } if ( tree_expr != NULL ) { - delete tree_expr; + Expr::delete_expr(tree_expr); } return NULL; } @@ -762,11 +762,10 @@ Expr * Parser::_parse_gen_expr ( std::istream & fs, TreeExpr * tree_expr, Milkd if (PARSE_DEBUG) printf("parse_prefix_args: failed to convert prefix function to general expression (LINE %d) \n", line_count); if (tree_expr) - delete tree_expr; + Expr::delete_expr(tree_expr); for (i = 0; i < func->getNumArgs();i++) - delete expr_list[i]; + Expr::delete_expr(expr_list[i]); free(expr_list); - expr_list = NULL; return NULL; } @@ -789,7 +788,7 @@ Expr * Parser::_parse_gen_expr ( std::istream & fs, TreeExpr * tree_expr, Milkd std::cerr << "token prefix is " << *string << std::endl; if (PARSE_DEBUG) printf("parse_gen_expr: implicit multiplication case unimplemented!\n"); if (tree_expr) - delete tree_expr; + Expr::delete_expr(tree_expr); return NULL; } @@ -800,7 +799,7 @@ Expr * Parser::_parse_gen_expr ( std::istream & fs, TreeExpr * tree_expr, Milkd { if (PARSE_DEBUG) printf("parse_gen_expr: found left parentice, but failed to create new expression tree \n"); if (tree_expr) - delete tree_expr; + Expr::delete_expr(tree_expr); return NULL; } @@ -854,7 +853,7 @@ Expr * Parser::_parse_gen_expr ( std::istream & fs, TreeExpr * tree_expr, Milkd if (*string == 0) { if (tree_expr) - delete tree_expr; + Expr::delete_expr(tree_expr); return NULL; } @@ -864,7 +863,7 @@ Expr * Parser::_parse_gen_expr ( std::istream & fs, TreeExpr * tree_expr, Milkd if ((gen_expr = Expr::const_to_expr(val)) == NULL) { if (tree_expr) - delete tree_expr; + Expr::delete_expr(tree_expr); return NULL; } @@ -883,7 +882,7 @@ Expr * Parser::_parse_gen_expr ( std::istream & fs, TreeExpr * tree_expr, Milkd if ((param = ParamUtils::find(std::string(string), ¤t_shape->param_tree)) == NULL) { if (tree_expr) - delete tree_expr; + Expr::delete_expr(tree_expr); return NULL; } } @@ -898,7 +897,7 @@ Expr * Parser::_parse_gen_expr ( std::istream & fs, TreeExpr * tree_expr, Milkd /* Convert parameter to an expression */ if ((gen_expr = Expr::param_to_expr(param)) == NULL) { - delete tree_expr; + Expr::delete_expr(tree_expr); return NULL; } @@ -917,7 +916,7 @@ Expr * Parser::_parse_gen_expr ( std::istream & fs, TreeExpr * tree_expr, Milkd if ((param = ParamUtils::find(std::string(string), ¤t_wave->param_tree)) == NULL) { if (tree_expr) - delete tree_expr; + Expr::delete_expr(tree_expr); return NULL; } } @@ -932,7 +931,7 @@ Expr * Parser::_parse_gen_expr ( std::istream & fs, TreeExpr * tree_expr, Milkd /* Convert parameter to an expression */ if ((gen_expr = Expr::param_to_expr(param)) == NULL) { - delete tree_expr; + Expr::delete_expr(tree_expr); return NULL; } @@ -955,7 +954,7 @@ Expr * Parser::_parse_gen_expr ( std::istream & fs, TreeExpr * tree_expr, Milkd /* Convert parameter to an expression */ if ((gen_expr = Expr::param_to_expr(param)) == NULL) { - delete tree_expr; + Expr::delete_expr(tree_expr); return NULL; } @@ -973,7 +972,7 @@ Expr * Parser::_parse_gen_expr ( std::istream & fs, TreeExpr * tree_expr, Milkd } if (tree_expr) - delete tree_expr; + Expr::delete_expr(tree_expr); return NULL; } } @@ -985,10 +984,10 @@ Expr * Parser::parse_gen_expr ( std::istream & fs, TreeExpr * tree_expr, Milkdr if (NULL == gen_expr) return NULL; //std::cout << gen_expr << std::endl; - Expr *opt = gen_expr->optimize(); + Expr *opt = Expr::optimize(gen_expr); if (opt != gen_expr) { - delete gen_expr; + Expr::delete_expr(gen_expr); } //std::cout << opt << std::endl << std::endl; return opt; @@ -1193,31 +1192,31 @@ Expr * Parser::parse_infix_op(std::istream & fs, token_t token, TreeExpr * tree if (PARSE_DEBUG) printf("parse_infix_op: found addition operator (LINE %d)\n", line_count); if (PARSE_DEBUG) std::cerr << "WRAP AROUND IS " << tokenWrapAroundEnabled << std::endl; - return parse_gen_expr(fs, insert_infix_op(Eval::infix_add, &tree_expr), preset); + return _parse_gen_expr(fs, insert_infix_op(Eval::infix_add, &tree_expr), preset); case tMinus: if (PARSE_DEBUG) printf("parse_infix_op: found subtraction operator (LINE %d)\n", line_count); - return parse_gen_expr(fs, insert_infix_op(Eval::infix_minus, &tree_expr), preset); + return _parse_gen_expr(fs, insert_infix_op(Eval::infix_minus, &tree_expr), preset); case tMult: if (PARSE_DEBUG) printf("parse_infix_op: found multiplication operator (LINE %d)\n", line_count); - return parse_gen_expr(fs, insert_infix_op(Eval::infix_mult, &tree_expr), preset); + return _parse_gen_expr(fs, insert_infix_op(Eval::infix_mult, &tree_expr), preset); case tDiv: if (PARSE_DEBUG) printf("parse_infix_op: found division operator (LINE %d)\n", line_count); - return parse_gen_expr(fs, insert_infix_op(Eval::infix_div, &tree_expr), preset); + return _parse_gen_expr(fs, insert_infix_op(Eval::infix_div, &tree_expr), preset); case tMod: if (PARSE_DEBUG) printf("parse_infix_op: found modulo operator (LINE %d)\n", line_count); - return parse_gen_expr(fs, insert_infix_op(Eval::infix_mod, &tree_expr), preset); + return _parse_gen_expr(fs, insert_infix_op(Eval::infix_mod, &tree_expr), preset); case tOr: if (PARSE_DEBUG) printf("parse_infix_op: found bitwise or operator (LINE %d)\n", line_count); - return parse_gen_expr(fs, insert_infix_op(Eval::infix_or, &tree_expr), preset); + return _parse_gen_expr(fs, insert_infix_op(Eval::infix_or, &tree_expr), preset); case tAnd: if (PARSE_DEBUG) printf("parse_infix_op: found bitwise and operator (LINE %d)\n", line_count); - return parse_gen_expr(fs, insert_infix_op(Eval::infix_and, &tree_expr), preset); + return _parse_gen_expr(fs, insert_infix_op(Eval::infix_and, &tree_expr), preset); case tPositive: if (PARSE_DEBUG) printf("parse_infix_op: found positive operator (LINE %d)\n", line_count); - return parse_gen_expr(fs, insert_infix_op(Eval::infix_positive, &tree_expr), preset); + return _parse_gen_expr(fs, insert_infix_op(Eval::infix_positive, &tree_expr), preset); case tNegative: if (PARSE_DEBUG) printf("parse_infix_op: found negative operator (LINE %d)\n", line_count); - return parse_gen_expr(fs, insert_infix_op(Eval::infix_negative, &tree_expr), preset); + return _parse_gen_expr(fs, insert_infix_op(Eval::infix_negative, &tree_expr), preset); case tEOL: case tEOF: @@ -1230,7 +1229,7 @@ Expr * Parser::parse_infix_op(std::istream & fs, token_t token, TreeExpr * tree return gen_expr; default: if (PARSE_DEBUG) printf("parse_infix_op: operator or terminal expected, but not found (LINE %d)\n", line_count); - delete tree_expr; + Expr::delete_expr(tree_expr); return NULL; } @@ -1406,7 +1405,7 @@ PerFrameEqn * Parser::parse_per_frame_eqn(std::istream & fs, int index, Milkdro if ((per_frame_eqn = new PerFrameEqn(index, param, gen_expr)) == NULL) { if (PARSE_DEBUG) printf("parse_per_frame_eqn: failed to create a new per frame eqn, out of memory?\n"); - delete gen_expr; + Expr::delete_expr(gen_expr); return NULL; } @@ -1459,7 +1458,7 @@ PerFrameEqn * Parser::parse_implicit_per_frame_eqn(std::istream & fs, char * pa if ((per_frame_eqn = new PerFrameEqn(index, param, gen_expr)) == NULL) { if (PARSE_DEBUG) printf("parse_implicit_per_frame_eqn: failed to create a new per frame eqn, out of memory?\n"); - delete gen_expr; + Expr::delete_expr(gen_expr); return NULL; } @@ -1616,7 +1615,7 @@ InitCond * Parser::parse_per_frame_init_eqn(std::istream & fs, MilkdropPreset * val = gen_expr->eval(-1,-1); /* Free the general expression now that we are done with it */ - delete gen_expr; + Expr::delete_expr(gen_expr); /* integer value (boolean is an integer in C) */ if (param->type == P_TYPE_BOOL) @@ -1918,7 +1917,7 @@ int Parser::parse_shapecode(char * token, std::istream & fs, MilkdropPreset * p fs >> text; - *((std::string*)param->engine_val) = text; + param->set_param(text); if (PARSE_DEBUG) std::cerr << "parse_shapecode: found image url, text is \"" << text << "\"" << std::endl; @@ -2278,7 +2277,7 @@ int Parser::parse_wave_helper(std::istream & fs, MilkdropPreset * preset, int if ((per_frame_eqn = new PerFrameEqn(custom_wave->per_frame_count++, param, gen_expr)) == NULL) { if (PARSE_DEBUG) printf("parse_wave (per_frame): failed to create a new per frame eqn, out of memory?\n"); - delete gen_expr; + Expr::delete_expr(gen_expr); return PROJECTM_FAILURE; } @@ -2328,7 +2327,7 @@ int Parser::parse_wave_helper(std::istream & fs, MilkdropPreset * preset, int /* Add the per point equation */ if (custom_wave->add_per_point_eqn(string, gen_expr) < 0) { - delete gen_expr; + Expr::delete_expr(gen_expr); return PROJECTM_PARSE_ERROR; } @@ -2511,7 +2510,7 @@ int Parser::parse_shape_per_frame_eqn(std::istream & fs, CustomShape * custom_sh if ((per_frame_eqn = new PerFrameEqn(custom_shape->per_frame_count++, param, gen_expr)) == NULL) { if (PARSE_DEBUG) printf("parse_shape (per_frame): failed to create a new per frame eqn, out of memory?\n"); - delete gen_expr; + Expr::delete_expr(gen_expr); return PROJECTM_FAILURE; } @@ -2573,7 +2572,7 @@ int Parser::parse_wave_per_frame_eqn(std::istream & fs, CustomWave * custom_wav if ((per_frame_eqn = new PerFrameEqn(custom_wave->per_frame_count++, param, gen_expr)) == NULL) { if (PARSE_DEBUG) printf("parse_wave (per_frame): failed to create a new per frame eqn, out of memory?\n"); - delete gen_expr; + Expr::delete_expr(gen_expr); return PROJECTM_FAILURE; } @@ -2605,3 +2604,144 @@ else return false; } + + + + + + + +// TESTS + + +#include + +#ifndef NDEBUG + +#include + +#define TEST(cond) if (!verify(#cond,cond)) return false +#define TEST2(str,cond) if (!verify(str,cond)) return false + +struct ParserTest : public Test +{ + ParserTest() : Test("ParserTest") + {} + + MilkdropPreset *preset; + std::istringstream is; + std::istringstream &ss(const char *s) { return is = std::istringstream(s); } + + bool eq(float a, float b) + { + return abs(a-b) < (abs(a)+abs(b))/1000.0f; + } + +public: + + bool test_float() + { + float f=-1.0f; + TEST(PROJECTM_SUCCESS == Parser::parse_float(ss("1.1"),&f)); + TEST(1.1f == f); + TEST(PROJECTM_SUCCESS == Parser::parse_float(ss("+1.2"),&f)); + TEST(PROJECTM_SUCCESS == Parser::parse_float(ss("-1.3"),&f)); + TEST(PROJECTM_PARSE_ERROR == Parser::parse_float(ss(""),&f)); + TEST(PROJECTM_PARSE_ERROR == Parser::parse_float(ss("\n"),&f)); + TEST(PROJECTM_PARSE_ERROR == Parser::parse_float(ss("+"),&f)); + return true; + } + + bool test_int() + { + int i=-1; + TEST(PROJECTM_SUCCESS == Parser::parse_int(ss("1"),&i)); + TEST(1 == i); + TEST(PROJECTM_SUCCESS == Parser::parse_int(ss("+2"),&i)); + TEST(PROJECTM_SUCCESS == Parser::parse_int(ss("-3"),&i)); + TEST(PROJECTM_PARSE_ERROR == Parser::parse_int(ss(""),&i)); + TEST(PROJECTM_PARSE_ERROR == Parser::parse_int(ss("\n"),&i)); + TEST(PROJECTM_PARSE_ERROR == Parser::parse_int(ss("+"),&i)); + return true; + } + + bool eval_expr(float expected, const char *s) + { + float f1, f2; + Expr *expr = Parser::parse_gen_expr(ss(s),nullptr,preset); + TEST(expr != nullptr); + TEST(ParserTest::eq(expected, f1=expr->eval(-1,-1))); + // this is really a test for Expr.cpp, but since we're here + Expr *opt = Expr::optimize(expr); + if (opt != expr) + { + TEST(ParserTest::eq(expected, f2 = opt->eval(-1, -1))); + Expr::delete_expr(opt); + } + Expr::delete_expr(expr); + return true; + } + + bool test_eqn() + { + TEST(eval_expr(1.0f, "1.0")); + TEST(eval_expr(-1.0f, "-(1.0)")); // unary` + TEST(eval_expr(0.5f, "5/10.000")); // binary + TEST(eval_expr(1.0f, "sin(3.14159/2)")); + preset->presetOutputs().rot = 0.99f; + TEST(eval_expr(0.99f, "rot")); + return true; + } + + // test multi-line expression, and multi-expression line + bool test_lines() + { + // TODO + return true; + } + + bool test_params() + { + // TODO + return true; + } + + + bool _test() + { + bool success = true; + success &= test_float(); + success &= test_int(); + success &= test_eqn(); + success &= test_lines(); + success &= test_params(); + return success; + } + + bool test() override + { + // load IdlePreset + PresetLoader *presetLoader = new PresetLoader ( 400, 400, "" ); + std::unique_ptr preset_ptr = presetLoader->loadPreset("idle://Geiss & Sperl - Feedback (projectM idle HDR mix).milk"); + preset = (MilkdropPreset *)preset_ptr.get(); + + bool success = _test(); + + delete presetLoader; + return success; + } +}; + +Test* Parser::test() +{ + return new ParserTest(); +} + +#else + +Test* Param::test() +{ + return null; +} + +#endif diff --git a/src/libprojectM/MilkdropPresetFactory/Parser.hpp b/src/libprojectM/MilkdropPresetFactory/Parser.hpp index 1ce5d48fc..edcf5c357 100755 --- a/src/libprojectM/MilkdropPresetFactory/Parser.hpp +++ b/src/libprojectM/MilkdropPresetFactory/Parser.hpp @@ -120,6 +120,7 @@ typedef enum { tStringBufferFilled /* the string buffer for this line is maxed out */ } token_t; +class Test; class CustomShape; class CustomWave; class Expr; @@ -145,6 +146,7 @@ public: static int last_token_size; static bool tokenWrapAroundEnabled; + static Test *test(); static PerFrameEqn *parse_per_frame_eqn( std::istream & fs, int index, MilkdropPreset * preset); static int parse_per_pixel_eqn( std::istream & fs, MilkdropPreset * preset, diff --git a/src/libprojectM/MilkdropPresetFactory/PerFrameEqn.cpp b/src/libprojectM/MilkdropPresetFactory/PerFrameEqn.cpp index 4d4efb856..9a49b1e63 100755 --- a/src/libprojectM/MilkdropPresetFactory/PerFrameEqn.cpp +++ b/src/libprojectM/MilkdropPresetFactory/PerFrameEqn.cpp @@ -36,8 +36,8 @@ #include /* Evaluate an equation */ -void PerFrameEqn::evaluate() { - +void PerFrameEqn::evaluate() +{ if (PER_FRAME_EQN_DEBUG) { printf("per_frame_%d=%s= ", index, param->name.c_str()); fflush(stdout); @@ -46,20 +46,19 @@ void PerFrameEqn::evaluate() { //*((float*)per_frame_eqn->param->engine_val) = eval(per_frame_eqn->gen_expr); assert(gen_expr); assert(param); - param->set_param(gen_expr->eval(-1,-1)); + float v = gen_expr->eval(-1,-1); + param->set_param(v); - if (PER_FRAME_EQN_DEBUG) printf(" = %.4f\n", *((float*)param->engine_val)); - + if (PER_FRAME_EQN_DEBUG) printf(" = %.4f\n", v); } /* Frees perframe equation structure. Warning: assumes gen_expr pointer is not freed by anyone else! */ -PerFrameEqn::~PerFrameEqn() { - - delete gen_expr; - - // param is freed in param_tree container of some other class +PerFrameEqn::~PerFrameEqn() +{ + Expr::delete_expr(gen_expr); + // param is freed in param_tree container of some other class } /* Create a new per frame equation */ diff --git a/src/libprojectM/MilkdropPresetFactory/PerPixelEqn.cpp b/src/libprojectM/MilkdropPresetFactory/PerPixelEqn.cpp index b32324596..4bb0e851a 100755 --- a/src/libprojectM/MilkdropPresetFactory/PerPixelEqn.cpp +++ b/src/libprojectM/MilkdropPresetFactory/PerPixelEqn.cpp @@ -35,47 +35,23 @@ #include "wipemalloc.h" #include + /* Evaluates a per pixel equation */ -void PerPixelEqn::evaluate(int mesh_i, int mesh_j) { - - Expr * eqn_ptr = 0; - - - eqn_ptr = this->gen_expr; - - float ** param_matrix = (float**)this->param->matrix; - - if (param_matrix == 0) { - assert(param->engine_val); - (*(float*)param->engine_val) = eqn_ptr->eval(mesh_i, mesh_j); - - } else { - - assert(!(eqn_ptr == NULL || param_matrix == NULL)); - - param_matrix[mesh_i][mesh_j] = eqn_ptr->eval(mesh_i, mesh_j); - - /* Now that this parameter has been referenced with a per - pixel equation, we let the evaluator know by setting - this flag */ - /// @bug review and verify this behavior - param->matrix_flag = true; - param->flags |= P_FLAG_PER_PIXEL; - } +void PerPixelEqn::evaluate(int mesh_i, int mesh_j) +{ + assign_expr->eval( mesh_i, mesh_j ); } -PerPixelEqn::PerPixelEqn(int _index, Param * _param, Expr * _gen_expr):index(_index), param(_param), gen_expr(_gen_expr) { - +PerPixelEqn::PerPixelEqn(int _index, Param * param, Expr * gen_expr):index(_index) +{ assert(index >= 0); assert(param != 0); assert(gen_expr != 0); - + assign_expr = new AssignMatrixExpr(param, gen_expr); } PerPixelEqn::~PerPixelEqn() { - if (gen_expr) - delete (gen_expr); - + Expr::delete_expr(assign_expr); } diff --git a/src/libprojectM/MilkdropPresetFactory/PerPixelEqn.hpp b/src/libprojectM/MilkdropPresetFactory/PerPixelEqn.hpp index e991c7c1c..06e72dadc 100755 --- a/src/libprojectM/MilkdropPresetFactory/PerPixelEqn.hpp +++ b/src/libprojectM/MilkdropPresetFactory/PerPixelEqn.hpp @@ -51,9 +51,6 @@ class Preset; class PerPixelEqn { public: int index; /* used for splay tree ordering. */ - int flags; /* primarily to specify if this variable is user-defined */ - Param *param; - Expr *gen_expr; void evalPerPixelEqns( Preset *preset ); void evaluate(int mesh_i, int mesh_j); @@ -61,6 +58,7 @@ public: PerPixelEqn(int index, Param * param, Expr * gen_expr); + Expr *assign_expr; }; diff --git a/src/libprojectM/MilkdropPresetFactory/PerPointEqn.cpp b/src/libprojectM/MilkdropPresetFactory/PerPointEqn.cpp index 60bfb0ed5..3f046fe0f 100755 --- a/src/libprojectM/MilkdropPresetFactory/PerPointEqn.cpp +++ b/src/libprojectM/MilkdropPresetFactory/PerPointEqn.cpp @@ -40,51 +40,17 @@ /* Evaluates a per point equation for the current custom wave given by interface_wave ptr */ void PerPointEqn::evaluate(int i) { - - float * param_matrix; - Expr * eqn_ptr; - - // samples = CustomWave::interface_wave->samples; - - eqn_ptr = gen_expr; - - if (param->matrix == NULL) - { - assert(param->matrix_flag == false); - (*(float*)param->engine_val) = eqn_ptr->eval(i,-1); - - return; - } - - else - { - param_matrix = (float*)param->matrix; - - // -1 is because per points only use one dimension - param_matrix[i] = eqn_ptr->eval(i, -1); - - - /* Now that this parameter has been referenced with a per - point equation, we let the evaluator know by setting - this flag */ - - if (!param->matrix_flag) - param->matrix_flag = true; - - } - + assign_expr->eval( i, -1 ); } -PerPointEqn::PerPointEqn(int _index, Param * _param, Expr * _gen_expr, int _samples): - index(_index), - samples(_samples), - param(_param), - gen_expr(_gen_expr) - -{} +PerPointEqn::PerPointEqn(int _index, Param * param, Expr * gen_expr): + index(_index) +{ + assign_expr = new AssignExpr(param, gen_expr); +} PerPointEqn::~PerPointEqn() { - delete gen_expr; + Expr::delete_expr(assign_expr); } diff --git a/src/libprojectM/MilkdropPresetFactory/PerPointEqn.hpp b/src/libprojectM/MilkdropPresetFactory/PerPointEqn.hpp index 17838a492..6d78e89e1 100755 --- a/src/libprojectM/MilkdropPresetFactory/PerPointEqn.hpp +++ b/src/libprojectM/MilkdropPresetFactory/PerPointEqn.hpp @@ -37,12 +37,10 @@ class PerPointEqn; class PerPointEqn { public: int index; - int samples; // the number of samples to iterate over - Param *param; - Expr * gen_expr; + Expr * assign_expr; ~PerPointEqn(); void evaluate(int i); - PerPointEqn( int index, Param *param, Expr *gen_expr, int samples); + PerPointEqn( int index, Param *param, Expr *gen_expr ); }; diff --git a/src/libprojectM/TestRunner.cpp b/src/libprojectM/TestRunner.cpp new file mode 100644 index 000000000..91c5821af --- /dev/null +++ b/src/libprojectM/TestRunner.cpp @@ -0,0 +1,43 @@ +// +// Created by matthew on 1/7/19. +// + +#include +#include +#include +#include + +std::vector TestRunner::tests; + + +bool TestRunner::run() +{ + if (tests.empty()) + { + // We still call register/run tests in NDEBUG (useful for performance testing) + // but tests may choose to comment out body to save space + tests.push_back(Param::test()); + tests.push_back(Parser::test()); + tests.push_back(Expr::test()); + } + + int count = 0; + bool successful = true; + for (auto it=tests.begin() ; it < tests.end() ; it++ ) + { + if (nullptr == (*it)) + continue; + count++; + std::cout << "TestRunner: " << (*it)->getName() << " started" << std::endl; + std::cout.flush(); + bool result = (*it)->test(); + successful &= result; + if (result) + std::cout << "TestRunner: " << (*it)->getName() << " passed" << std::endl; + else + std::cout << "TestRunner: " << (*it)->getName() << " FAILED" << std::endl; + } + if (0 == count) + std::cout << "TestRunner: no tests found to run" << std::endl; + return successful; +} \ No newline at end of file diff --git a/src/libprojectM/TestRunner.hpp b/src/libprojectM/TestRunner.hpp new file mode 100644 index 000000000..aaf1ebe8f --- /dev/null +++ b/src/libprojectM/TestRunner.hpp @@ -0,0 +1,48 @@ +// +// Created by matthew on 1/7/19. +// +// This is a place to collect/run non-graphical unit tests +// CONSIDER using some framework like googletest etc, but for now didn't want new dependencies +// + +#ifndef PROJECTM_LUA_TESTRUNNER_H +#define PROJECTM_LUA_TESTRUNNER_H + + +#include +#include + + +class Test +{ +public: + const std::string getName() { return name; }; +#ifdef NDEBUG + virtual bool test() {return true;} +#else + virtual bool test() = 0; +#endif + virtual ~Test() = default; +protected: + explicit Test(std::string name_) : name(name_) {}; + const std::string name; + + bool verify(const char *test, bool success) + { + if (!success) + std::cout << "failed " << test << std::endl; + return success; + } +}; + + +class TestRunner +{ +public: + static bool run(); +private: + static std::vector tests; +}; + + +#endif //PROJECTM_LUA_TESTRUNNER_H diff --git a/src/projectM-pulseaudio/qprojectM-pulseaudio.cpp b/src/projectM-pulseaudio/qprojectM-pulseaudio.cpp index 2b5f2eacd..0824b2889 100644 --- a/src/projectM-pulseaudio/qprojectM-pulseaudio.cpp +++ b/src/projectM-pulseaudio/qprojectM-pulseaudio.cpp @@ -74,6 +74,7 @@ std::string read_config(); #include #include #include +#include //#include @@ -108,6 +109,8 @@ class ProjectMApplication : public QApplication { int main ( int argc, char*argv[] ) { +// if (!TestRunner::run()) exit(1); + int i; char projectM_data[1024];