From 2b1099a653db3bf8a20864c29a46f49876e1adc5 Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Sun, 6 Nov 2022 18:47:59 +0100 Subject: [PATCH] Implement external preset switching control and add stream loading support. Lots of interconnected changes in this commit: - Removed unnecessary name/author/filename members all over the place. - Started using exceptions to deliver preset loading and rendering errors to the topmost ProjectM class. - Added stream loading methods to factories and the base Preset class. - Added new events for requesting preset switch and telling the user about loading errors. - Consolidated preset switching logic in ProjectM class a bit. --- src/api/include/libprojectM/projectM.h | 47 ++- src/libprojectM/CMakeLists.txt | 1 - src/libprojectM/Common.hpp | 40 --- .../MilkdropPresetFactory/IdlePreset.cpp | 334 +++++++++--------- .../MilkdropPresetFactory/IdlePreset.hpp | 3 +- .../MilkdropPresetFactory/MilkdropPreset.cpp | 25 +- .../MilkdropPresetFactory/MilkdropPreset.hpp | 15 +- .../MilkdropPresetFactory.cpp | 128 ++++--- .../MilkdropPresetFactory.hpp | 21 +- .../MilkdropPresetFactory/PresetFrameIO.hpp | 4 +- src/libprojectM/Preset.cpp | 23 -- src/libprojectM/Preset.hpp | 47 +-- src/libprojectM/PresetFactory.cpp | 13 +- src/libprojectM/PresetFactory.hpp | 67 ++-- src/libprojectM/PresetFactoryManager.cpp | 178 ++++++---- src/libprojectM/PresetFactoryManager.hpp | 122 ++++--- src/libprojectM/ProjectM.cpp | 179 +++++----- src/libprojectM/ProjectM.hpp | 64 +++- src/libprojectM/ProjectMCWrapper.cpp | 10 +- src/libprojectM/ProjectMCWrapper.hpp | 2 +- src/libprojectM/Renderer/Renderer.cpp | 25 +- src/libprojectM/Renderer/Renderer.hpp | 33 +- src/libprojectM/Renderer/ShaderEngine.cpp | 105 +++--- src/libprojectM/Renderer/ShaderEngine.hpp | 23 +- 24 files changed, 790 insertions(+), 719 deletions(-) delete mode 100755 src/libprojectM/Preset.cpp diff --git a/src/api/include/libprojectM/projectM.h b/src/api/include/libprojectM/projectM.h index d964ed1cd..724e9252b 100644 --- a/src/api/include/libprojectM/projectM.h +++ b/src/api/include/libprojectM/projectM.h @@ -187,13 +187,12 @@ typedef void (*projectm_shuffle_enable_changed_event)(bool shuffle_enabled, void * The message and filename pointers are only valid inside the callback. Make a copy if these values * need to be retained for later use. * - * @param is_hard_cut True if the preset was switched using a hard cut via beat detection. * @param preset_filename The filename of the failed preset. * @param message The error message. * @param user_data A user-defined data pointer that was provided when registering the callback, * e.g. context information. */ -typedef void (*projectm_preset_switch_failed_event)(bool is_hard_cut, const char* preset_filename, +typedef void (*projectm_preset_switch_failed_event)(const char* preset_filename, const char* message, void* user_data); /** @@ -238,6 +237,42 @@ PROJECTM_EXPORT projectm_handle projectm_create_settings(const projectm_settings */ PROJECTM_EXPORT void projectm_destroy(projectm_handle instance); +/** + * @brief Loads a preset from the given filename/URL. + * + * Ideally, the filename should be given as a standard local path. projectM also supports loading + * "file://" URLs. Additionally, the special filename "idle://" can be used to load the default + * idle preset, displaying the "M" logo. + * + * Other URL schemas aren't supported and will cause a loading error. + * + * If the preset can't be loaded, no switch takes place and the current preset will continue to + * be displayed. Note that if there's a transition in progress when calling this function, the + * transition will be finished immediately, even if the new preset can't be loaded. + * + * @param instance The projectM instance handle. + * @param filename The preset filename or URL to load. + * @param smooth_transition If true, the new preset is smoothly blended over. + */ +PROJECTM_EXPORT void projectm_load_preset_file(projectm_handle instance, const char* filename, + bool smooth_transition); + +/** + * @brief Loads a preset from the data pointer. + * + * Currently, the preset data is assumed to be in Milkdrop format. + * + * If the preset can't be loaded, no switch takes place and the current preset will continue to + * be displayed. Note that if there's a transition in progress when calling this function, the + * transition will be finished immediately, even if the new preset can't be loaded. + * + * @param instance The projectM instance handle. + * @param data The preset contents to load. + * @param smooth_transition If true, the new preset is smoothly blended over. + */ +PROJECTM_EXPORT void projectm_load_preset_data(projectm_handle instance, const char* data, + bool smooth_transition); + /** * @brief Sets a callback function that will be called when a preset changes. * @@ -669,14 +704,6 @@ PROJECTM_EXPORT void projectm_get_window_size(projectm_handle instance, size_t* */ PROJECTM_EXPORT void projectm_set_window_size(projectm_handle instance, size_t width, size_t height); -/** - * @brief Returns whether the current preset was loaded successfully or not. - * @param instance The projectM instance handle. - * @return True if the preset was not loaded successfully, false if it is displayed correctly. - */ -PROJECTM_EXPORT bool projectm_get_error_loading_current_preset(projectm_handle instance); - - /** * @brief Returns the maximum number of audio samples that can be stored. * diff --git a/src/libprojectM/CMakeLists.txt b/src/libprojectM/CMakeLists.txt index c01915966..294400c93 100644 --- a/src/libprojectM/CMakeLists.txt +++ b/src/libprojectM/CMakeLists.txt @@ -52,7 +52,6 @@ add_library(projectM_main OBJECT PCM.hpp PipelineMerger.cpp PipelineMerger.hpp - Preset.cpp Preset.hpp PresetFactory.cpp PresetFactory.hpp diff --git a/src/libprojectM/Common.hpp b/src/libprojectM/Common.hpp index 03b890f1b..dfe8ae786 100755 --- a/src/libprojectM/Common.hpp +++ b/src/libprojectM/Common.hpp @@ -66,10 +66,6 @@ double constexpr defaultDoubleLb{minDoubleSize}; double constexpr defaultDoubleUb{maxDoubleSize}; unsigned int const numQVariables(32); -std::string const projectmFileExtension("prjm"); -std::string const milkdropFileExtension("milk"); -std::string const projectmModuleExtension("so"); - //CPP17: std::filesystem::path::extension inline auto ParseExtension(const std::string& filename) -> std::string @@ -98,40 +94,4 @@ inline auto ParseFilename(const std::string& filename) -> std::string return filename.substr(start + 1, filename.length()); } -inline auto MeanSquaredError(const double& x, const double& y) -> double -{ - return (x - y) * (x - y); -} - -inline auto CaseInsensitiveSubstringFind(std::string const& haystack, std::string const& needle) -> size_t -{ - auto const it = std::search( - haystack.cbegin(), - haystack.cend(), - needle.cbegin(), - needle.cend(), - [](char ch1, char ch2) { - return std::toupper(ch1) == std::toupper(ch2); - }); - - if (it != haystack.end()) - { - return std::distance(haystack.begin(), it); - } - - return std::string::npos; -} - -enum PresetRatingType -{ - FIRST_RATING_TYPE = 0, - HARD_CUT_RATING_TYPE = FIRST_RATING_TYPE, - SOFT_CUT_RATING_TYPE, - LAST_RATING_TYPE = SOFT_CUT_RATING_TYPE, - TOTAL_RATING_TYPES = SOFT_CUT_RATING_TYPE + 1 -}; - - -using RatingList = std::vector; - #endif diff --git a/src/libprojectM/MilkdropPresetFactory/IdlePreset.cpp b/src/libprojectM/MilkdropPresetFactory/IdlePreset.cpp index 785f29feb..66f993dd7 100644 --- a/src/libprojectM/MilkdropPresetFactory/IdlePreset.cpp +++ b/src/libprojectM/MilkdropPresetFactory/IdlePreset.cpp @@ -1,140 +1,140 @@ #include "IdlePreset.hpp" +#include "MilkdropPreset.hpp" #include #include -#include "MilkdropPreset.hpp" -const std::string IdlePresets::IDLE_PRESET_NAME - ("Geiss & Sperl - Feedback (projectM idle HDR mix).milk"); +// Original preset name: "Geiss & Sperl - Feedback (projectM idle HDR mix).milk" std::string IdlePresets::presetText() { std::ostringstream out; - out << "[preset00]\n" << - "fRating=2.000000\n" << - "fGammaAdj=1.700000\n" << - "fDecay=0.940000\n" << - "fVideoEchoZoom=1.000000\n" << - "fVideoEchoAlpha=0.000000\n" << - "nVideoEchoOrientation=0\n" << - "nWaveMode=0\n" << - "bAdditiveWaves=1\n" << - "bWaveDots=0\n" << - "bWaveThick=0\n" << - "bModWaveAlphaByVolume=0\n" << - "bMaximizeWaveColor=0\n" << - "bTexWrap=1\n" << - "bDarkenCenter=0\n" << - "bRedBlueStereo=0\n" << - "bBrighten=0\n" << - "bDarken=0\n" << - "bSolarize=0\n" << - "bInvert=0\n" << - "fWaveAlpha=0.001000\n" << - "fWaveScale=0.010000\n" << - "fWaveSmoothing=0.630000\n" << - "fWaveParam=-1.000000\n" << - "fModWaveAlphaStart=0.710000\n" << - "fModWaveAlphaEnd=1.300000\n" << - "fWarpAnimSpeed=1.000000\n" << - "fWarpScale=1.331000\n" << - "fZoomExponent=1.000000\n" << - "fShader=0.000000\n" << - "zoom=13.290894\n" << - "rot=-0.020000\n" << - "cx=0.500000\n" << - "cy=0.500000\n" << - "dx=-0.280000\n" << - "dy=-0.320000\n" << - "warp=0.010000\n" << - "sx=1.000000\n" << - "sy=1.000000\n" << - "wave_r=0.650000\n" << - "wave_g=0.650000\n" << - "wave_b=0.650000\n" << - "wave_x=0.500000\n" << - "wave_y=0.500000\n" << - "ob_size=0.000000\n" << - "ob_r=0.010000\n" << - "ob_g=0.000000\n" << - "ob_b=0.000000\n" << - "ob_a=1.000000\n" << - "ib_size=0.000000\n" << - "ib_r=0.950000\n" << - "ib_g=0.850000\n" << - "ib_b=0.650000\n" << - "ib_a=1.000000\n" << - "nMotionVectorsX=64.000000\n" << - "nMotionVectorsY=0.000000\n" << - "mv_dx=0.000000\n" << - "mv_dy=0.000000\n" << - "mv_l=0.900000\n" << - "mv_r=1.000000\n" << - "mv_g=1.000000\n" << - "mv_b=1.000000\n" << - "mv_a=0.000000\n" << - "shapecode_3_enabled=1\n" << - "shapecode_3_sides=4\n" << - "shapecode_3_additive=0\n" << - "shapecode_3_thickOutline=0\n" << - "shapecode_3_textured=1\n" << - "shapecode_3_image=m\n" << - "shapecode_3_x=0.68\n" << - "shapecode_3_y=0.5\n" << - "shapecode_3_rad=0.41222\n" << - "shapecode_3_ang=0\n" << - "shapecode_3_tex_ang=0\n" << - "shapecode_3_tex_zoom=0.5\n" << - "shapecode_3_r=1\n" << - "shapecode_3_g=1\n" << - "shapecode_3_b=1\n" << - "shapecode_3_a=1\n" << - "shapecode_3_r2=1\n" << - "shapecode_3_g2=1\n" << - "shapecode_3_b2=1\n" << - "shapecode_3_a2=1\n" << - "shapecode_3_border_r=0\n" << - "shapecode_3_border_g=0\n" << - "shapecode_3_border_b=0\n" << - "shapecode_3_border_a=0\n" << - "shape_3_per_frame1=x = x + q1;\n" << - "shape_3_per_frame2=y = y + q2;\n" << - "shape_3_per_frame3=r =0.5 + 0.5*sin(q8*0.613 + 1);\n" << - "shape_3_per_frame4=g = 0.5 + 0.5*sin(q8*0.763 + 2);\n" << - "shape_3_per_frame5=b = 0.5 + 0.5*sin(q8*0.771 + 5);\n" << - "shape_3_per_frame6=r2 = 0.5 + 0.5*sin(q8*0.635 + 4);\n" << - "shape_3_per_frame7=g2 = 0.5 + 0.5*sin(q8*0.616+ 1);\n" << - "shape_3_per_frame8=b2 = 0.5 + 0.5*sin(q8*0.538 + 3);\n" << - "shapecode_4_enabled=1\n" << - "shapecode_4_sides=4\n" << - "shapecode_4_additive=0\n" << - "shapecode_4_thickOutline=0\n" << - "shapecode_4_textured=1\n" << - "shapecode_4_image=headphones\n" << - "shapecode_4_x=0.68\n" << - "shapecode_4_y=0.58\n" << - "shapecode_4_rad=0.6\n" << - "shapecode_4_ang=0\n" << - "shapecode_4_tex_ang=0\n" << - "shapecode_4_tex_zoom=0.5\n" << - "shapecode_4_r=1\n" << - "shapecode_4_g=1\n" << - "shapecode_4_b=1\n" << - "shapecode_4_a=1\n" << - "shapecode_4_r2=1\n" << - "shapecode_4_g2=1\n" << - "shapecode_4_b2=1\n" << - "shapecode_4_a2=1\n" << - "shapecode_4_border_r=0\n" << - "shapecode_4_border_g=0\n" << - "shapecode_4_border_b=0\n" << - "shapecode_4_border_a=0\n" << - "shape_4_per_frame1=x = x + q1;\n" << - "shape_4_per_frame2=y = y + q2;\n" << - "shape_4_per_frame3=rad = rad + bass * 0.1;\n" << - "shape_4_per_frame4=a = q3;\n" << - "shape_4_per_frame5=a2 = q3;\n" << + out << "[preset00]\n" + << "fRating=2.000000\n" + << "fGammaAdj=1.700000\n" + << "fDecay=0.940000\n" + << "fVideoEchoZoom=1.000000\n" + << "fVideoEchoAlpha=0.000000\n" + << "nVideoEchoOrientation=0\n" + << "nWaveMode=0\n" + << "bAdditiveWaves=1\n" + << "bWaveDots=0\n" + << "bWaveThick=0\n" + << "bModWaveAlphaByVolume=0\n" + << "bMaximizeWaveColor=0\n" + << "bTexWrap=1\n" + << "bDarkenCenter=0\n" + << "bRedBlueStereo=0\n" + << "bBrighten=0\n" + << "bDarken=0\n" + << "bSolarize=0\n" + << "bInvert=0\n" + << "fWaveAlpha=0.001000\n" + << "fWaveScale=0.010000\n" + << "fWaveSmoothing=0.630000\n" + << "fWaveParam=-1.000000\n" + << "fModWaveAlphaStart=0.710000\n" + << "fModWaveAlphaEnd=1.300000\n" + << "fWarpAnimSpeed=1.000000\n" + << "fWarpScale=1.331000\n" + << "fZoomExponent=1.000000\n" + << "fShader=0.000000\n" + << "zoom=13.290894\n" + << "rot=-0.020000\n" + << "cx=0.500000\n" + << "cy=0.500000\n" + << "dx=-0.280000\n" + << "dy=-0.320000\n" + << "warp=0.010000\n" + << "sx=1.000000\n" + << "sy=1.000000\n" + << "wave_r=0.650000\n" + << "wave_g=0.650000\n" + << "wave_b=0.650000\n" + << "wave_x=0.500000\n" + << "wave_y=0.500000\n" + << "ob_size=0.000000\n" + << "ob_r=0.010000\n" + << "ob_g=0.000000\n" + << "ob_b=0.000000\n" + << "ob_a=1.000000\n" + << "ib_size=0.000000\n" + << "ib_r=0.950000\n" + << "ib_g=0.850000\n" + << "ib_b=0.650000\n" + << "ib_a=1.000000\n" + << "nMotionVectorsX=64.000000\n" + << "nMotionVectorsY=0.000000\n" + << "mv_dx=0.000000\n" + << "mv_dy=0.000000\n" + << "mv_l=0.900000\n" + << "mv_r=1.000000\n" + << "mv_g=1.000000\n" + << "mv_b=1.000000\n" + << "mv_a=0.000000\n" + << "shapecode_3_enabled=1\n" + << "shapecode_3_sides=4\n" + << "shapecode_3_additive=0\n" + << "shapecode_3_thickOutline=0\n" + << "shapecode_3_textured=1\n" + << "shapecode_3_image=m\n" + << "shapecode_3_x=0.68\n" + << "shapecode_3_y=0.5\n" + << "shapecode_3_rad=0.41222\n" + << "shapecode_3_ang=0\n" + << "shapecode_3_tex_ang=0\n" + << "shapecode_3_tex_zoom=0.5\n" + << "shapecode_3_r=1\n" + << "shapecode_3_g=1\n" + << "shapecode_3_b=1\n" + << "shapecode_3_a=1\n" + << "shapecode_3_r2=1\n" + << "shapecode_3_g2=1\n" + << "shapecode_3_b2=1\n" + << "shapecode_3_a2=1\n" + << "shapecode_3_border_r=0\n" + << "shapecode_3_border_g=0\n" + << "shapecode_3_border_b=0\n" + << "shapecode_3_border_a=0\n" + << "shape_3_per_frame1=x = x + q1;\n" + << "shape_3_per_frame2=y = y + q2;\n" + << "shape_3_per_frame3=r =0.5 + 0.5*sin(q8*0.613 + 1);\n" + << "shape_3_per_frame4=g = 0.5 + 0.5*sin(q8*0.763 + 2);\n" + << "shape_3_per_frame5=b = 0.5 + 0.5*sin(q8*0.771 + 5);\n" + << "shape_3_per_frame6=r2 = 0.5 + 0.5*sin(q8*0.635 + 4);\n" + << "shape_3_per_frame7=g2 = 0.5 + 0.5*sin(q8*0.616+ 1);\n" + << "shape_3_per_frame8=b2 = 0.5 + 0.5*sin(q8*0.538 + 3);\n" + << "shapecode_4_enabled=1\n" + << "shapecode_4_sides=4\n" + << "shapecode_4_additive=0\n" + << "shapecode_4_thickOutline=0\n" + << "shapecode_4_textured=1\n" + << "shapecode_4_image=headphones\n" + << "shapecode_4_x=0.68\n" + << "shapecode_4_y=0.58\n" + << "shapecode_4_rad=0.6\n" + << "shapecode_4_ang=0\n" + << "shapecode_4_tex_ang=0\n" + << "shapecode_4_tex_zoom=0.5\n" + << "shapecode_4_r=1\n" + << "shapecode_4_g=1\n" + << "shapecode_4_b=1\n" + << "shapecode_4_a=1\n" + << "shapecode_4_r2=1\n" + << "shapecode_4_g2=1\n" + << "shapecode_4_b2=1\n" + << "shapecode_4_a2=1\n" + << "shapecode_4_border_r=0\n" + << "shapecode_4_border_g=0\n" + << "shapecode_4_border_b=0\n" + << "shapecode_4_border_a=0\n" + << "shape_4_per_frame1=x = x + q1;\n" + << "shape_4_per_frame2=y = y + q2;\n" + << "shape_4_per_frame3=rad = rad + bass * 0.1;\n" + << "shape_4_per_frame4=a = q3;\n" + << "shape_4_per_frame5=a2 = q3;\n" + << // disabling projectM logo thingey // "shapecode_6_enabled=1\n" << // "shapecode_6_sides=4\n" << @@ -164,56 +164,44 @@ std::string IdlePresets::presetText() // "shape_6_per_frame2=y = y + q2;\n" << // "shape_6_per_frame3=a = q3;\n" << // "shape_6_per_frame4=a2 = q3;\n" << - "per_frame_1=ob_r = 0.5 + 0.4*sin(time*1.324);\n" << - "per_frame_2=ob_g = 0.5 + 0.4*cos(time*1.371);\n" << - "per_frame_3=ob_b = 0.5+0.4*sin(2.332*time);\n" << - "per_frame_4=ib_r = 0.5 + 0.25*sin(time*1.424);\n" << - "per_frame_5=ib_g = 0.25 + 0.25*cos(time*1.871);\n" << - "per_frame_6=ib_b = 1-ob_b;\n" << - "per_frame_7=volume = 0.15*(bass+bass_att+treb+treb_att+mid+mid_att);\n" << - "per_frame_8=xamptarg = if(equal(frame%15,0),min(0.5*volume*bass_att,0.5),xamptarg);\n" << - "per_frame_9=xamp = xamp + 0.5*(xamptarg-xamp);\n" << - "per_frame_10=xdir = if(above(abs(xpos),xamp),-sign(xpos),if(below(abs(xspeed),0.1),2*above(xpos,0)-1,xdir));\n" - << - "per_frame_11=xaccel = xdir*xamp - xpos - xspeed*0.055*below(abs(xpos),xamp);\n" << - "per_frame_12=xspeed = xspeed + xdir*xamp - xpos - xspeed*0.055*below(abs(xpos),xamp);\n" << - "per_frame_13=xpos = xpos + 0.001*xspeed;\n" << - "per_frame_14=dx = xpos*0.05;\n" << - "per_frame_15=yamptarg = if(equal(frame%15,0),min(0.3*volume*treb_att,0.5),yamptarg);\n" << - "per_frame_16=yamp = yamp + 0.5*(yamptarg-yamp);\n" << - "per_frame_17=ydir = if(above(abs(ypos),yamp),-sign(ypos),if(below(abs(yspeed),0.1),2*above(ypos,0)-1,ydir));\n" - << - "per_frame_18=yaccel = ydir*yamp - ypos - yspeed*0.055*below(abs(ypos),yamp);\n" << - "per_frame_19=yspeed = yspeed + ydir*yamp - ypos - yspeed*0.055*below(abs(ypos),yamp);\n" << - "per_frame_20=ypos = ypos + 0.001*yspeed;\n" << - "per_frame_21=dy = ypos*0.05;\n" << - "per_frame_22=wave_a = 0;\n" << - "per_frame_23=q8 = oldq8 + 0.0003*(pow(1+1.2*bass+0.4*bass_att+0.1*treb+0.1*treb_att+0.1*mid+0.1*mid_att,6)/fps);\n" - << - "per_frame_24=oldq8 = q8;\n" << - "per_frame_25=q7 = 0.003*(pow(1+1.2*bass+0.4*bass_att+0.1*treb+0.1*treb_att+0.1*mid+0.1*mid_att,6)/fps);\n" << - "per_frame_26=rot = 0.4 + 1.5*sin(time*0.273) + 0.4*sin(time*0.379+3);\n" << - "per_frame_27=q1 = 0.05*sin(time*1.14);\n" << - "per_frame_28=q2 = 0.03*sin(time*0.93+2);\n" << - "per_frame_29=q3 = if(above(frame,60),1, frame/60.0);\n" << - "per_frame_30=oldq8 = if(above(oldq8,1000),0,oldq8);\n" << - "per_pixel_1=zoom =( log(sqrt(2)-rad) -0.24)*1;\n"; + "per_frame_1=ob_r = 0.5 + 0.4*sin(time*1.324);\n" + << "per_frame_2=ob_g = 0.5 + 0.4*cos(time*1.371);\n" + << "per_frame_3=ob_b = 0.5+0.4*sin(2.332*time);\n" + << "per_frame_4=ib_r = 0.5 + 0.25*sin(time*1.424);\n" + << "per_frame_5=ib_g = 0.25 + 0.25*cos(time*1.871);\n" + << "per_frame_6=ib_b = 1-ob_b;\n" + << "per_frame_7=volume = 0.15*(bass+bass_att+treb+treb_att+mid+mid_att);\n" + << "per_frame_8=xamptarg = if(equal(frame%15,0),min(0.5*volume*bass_att,0.5),xamptarg);\n" + << "per_frame_9=xamp = xamp + 0.5*(xamptarg-xamp);\n" + << "per_frame_10=xdir = if(above(abs(xpos),xamp),-sign(xpos),if(below(abs(xspeed),0.1),2*above(xpos,0)-1,xdir));\n" + << "per_frame_11=xaccel = xdir*xamp - xpos - xspeed*0.055*below(abs(xpos),xamp);\n" + << "per_frame_12=xspeed = xspeed + xdir*xamp - xpos - xspeed*0.055*below(abs(xpos),xamp);\n" + << "per_frame_13=xpos = xpos + 0.001*xspeed;\n" + << "per_frame_14=dx = xpos*0.05;\n" + << "per_frame_15=yamptarg = if(equal(frame%15,0),min(0.3*volume*treb_att,0.5),yamptarg);\n" + << "per_frame_16=yamp = yamp + 0.5*(yamptarg-yamp);\n" + << "per_frame_17=ydir = if(above(abs(ypos),yamp),-sign(ypos),if(below(abs(yspeed),0.1),2*above(ypos,0)-1,ydir));\n" + << "per_frame_18=yaccel = ydir*yamp - ypos - yspeed*0.055*below(abs(ypos),yamp);\n" + << "per_frame_19=yspeed = yspeed + ydir*yamp - ypos - yspeed*0.055*below(abs(ypos),yamp);\n" + << "per_frame_20=ypos = ypos + 0.001*yspeed;\n" + << "per_frame_21=dy = ypos*0.05;\n" + << "per_frame_22=wave_a = 0;\n" + << "per_frame_23=q8 = oldq8 + 0.0003*(pow(1+1.2*bass+0.4*bass_att+0.1*treb+0.1*treb_att+0.1*mid+0.1*mid_att,6)/fps);\n" + << "per_frame_24=oldq8 = q8;\n" + << "per_frame_25=q7 = 0.003*(pow(1+1.2*bass+0.4*bass_att+0.1*treb+0.1*treb_att+0.1*mid+0.1*mid_att,6)/fps);\n" + << "per_frame_26=rot = 0.4 + 1.5*sin(time*0.273) + 0.4*sin(time*0.379+3);\n" + << "per_frame_27=q1 = 0.05*sin(time*1.14);\n" + << "per_frame_28=q2 = 0.03*sin(time*0.93+2);\n" + << "per_frame_29=q3 = if(above(frame,60),1, frame/60.0);\n" + << "per_frame_30=oldq8 = if(above(oldq8,1000),0,oldq8);\n" + << "per_pixel_1=zoom =( log(sqrt(2)-rad) -0.24)*1;\n"; return out.str(); - } std::unique_ptr -IdlePresets::allocate(MilkdropPresetFactory* factory, const std::string& name, PresetOutputs* presetOutputs) +IdlePresets::allocate(MilkdropPresetFactory* factory, PresetOutputs* presetOutputs) { - - if (name == IDLE_PRESET_NAME) - { - std::istringstream in(presetText()); - return std::unique_ptr(new MilkdropPreset(factory, in, IDLE_PRESET_NAME, presetOutputs)); - } - else - { - return std::unique_ptr(); - } + std::istringstream in(presetText()); + return std::unique_ptr(new MilkdropPreset(factory, in, presetOutputs)); } diff --git a/src/libprojectM/MilkdropPresetFactory/IdlePreset.hpp b/src/libprojectM/MilkdropPresetFactory/IdlePreset.hpp index fd53f3462..8e3980f30 100644 --- a/src/libprojectM/MilkdropPresetFactory/IdlePreset.hpp +++ b/src/libprojectM/MilkdropPresetFactory/IdlePreset.hpp @@ -19,12 +19,11 @@ public: /// Allocate a new idle preset instance /// \returns a newly allocated auto pointer of an idle preset instance static std::unique_ptr - allocate(MilkdropPresetFactory* factory, const std::string& path, PresetOutputs* outputs); + allocate(MilkdropPresetFactory* factory, PresetOutputs* outputs); private: static std::string presetText(); - static const std::string IDLE_PRESET_NAME; }; #endif diff --git a/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.cpp b/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.cpp index 72536b51b..04b714785 100755 --- a/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.cpp +++ b/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.cpp @@ -52,10 +52,8 @@ #endif -MilkdropPreset::MilkdropPreset(MilkdropPresetFactory* factory, std::istream& in, const std::string& presetName, - PresetOutputs* presetOutputs) - : Preset(presetName) - , builtinParams(_presetInputs, presetOutputs) +MilkdropPreset::MilkdropPreset(MilkdropPresetFactory* factory, std::istream& in, PresetOutputs* presetOutputs) + : builtinParams(_presetInputs, presetOutputs) , per_pixel_program(nullptr) , _factory(factory) , _presetOutputs(presetOutputs) @@ -65,11 +63,9 @@ 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) + PresetOutputs* presetOutputs) + : builtinParams(_presetInputs, presetOutputs) , per_pixel_program(nullptr) - , _filename(ParseFilename(absoluteFilePath)) , _absoluteFilePath(absoluteFilePath) , _factory(factory) , _presetOutputs(presetOutputs) @@ -151,7 +147,7 @@ int MilkdropPreset::add_per_pixel_eqn(char* name, Expr* gen_expr) { if (PER_PIXEL_EQN_DEBUG) { - printf("add_per_pixel_eqn: failed to allocate a new parameter!\n"); + printf("add_per_pixel_eqn: failed to CreatePresetFromFile a new parameter!\n"); } return PROJECTM_FAILURE; } @@ -339,6 +335,8 @@ void MilkdropPreset::initialize(const std::string& pathname) std::cerr << "[Preset] loading file \"" << pathname << "\"..." << std::endl; } + SetFilename(ParseFilename(pathname)); + loadPresetFile(pathname); postloadInitialize(); @@ -590,7 +588,7 @@ int MilkdropPreset::loadPresetFile(const std::string& pathname) /* Open the file corresponding to pathname */ std::ifstream fs(pathname.c_str()); - if (!fs || fs.eof()) + if (!fs.good() || fs.eof()) { std::ostringstream oss; @@ -603,10 +601,3 @@ int MilkdropPreset::loadPresetFile(const std::string& pathname) return readIn(fs); } - -const std::string& MilkdropPreset::name() const -{ - - return filename(); -} - diff --git a/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.hpp b/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.hpp index 92df2f1d2..a846d541b 100644 --- a/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.hpp +++ b/src/libprojectM/MilkdropPresetFactory/MilkdropPreset.hpp @@ -68,17 +68,14 @@ public: /// Load a MilkdropPreset by filename with input and output buffers specified. /// \param absoluteFilePath the absolute file path of a MilkdropPreset to load from the file system - /// \param milkdropPresetName a descriptive name for the MilkdropPreset. Usually just the file name /// \param presetOutputs initialized and filled with data parsed from a MilkdropPreset MilkdropPreset(MilkdropPresetFactory* factory, const std::string& absoluteFilePath, - const std::string& milkdropPresetName, PresetOutputs* presetOutputs); + PresetOutputs* presetOutputs); /// Load a MilkdropPreset from an input stream with input and output buffers specified. /// \param in an already initialized input stream to read the MilkdropPreset file from - /// \param milkdropPresetName a descriptive name for the MilkdropPreset. Usually just the file name /// \param presetOutputs initialized and filled with data parsed from a MilkdropPreset - MilkdropPreset(MilkdropPresetFactory* factory, std::istream& in, const std::string& milkdropPresetName, - PresetOutputs* presetOutputs); + MilkdropPreset(MilkdropPresetFactory* factory, std::istream& in, PresetOutputs* presetOutputs); ~MilkdropPreset(); @@ -148,15 +145,7 @@ public: void Render(const BeatDetect& music, const PipelineContext& context); - const std::string& name() const; - - const std::string& filename() const - { - return _filename; - } - private: - std::string _filename; PresetInputs _presetInputs; /// Evaluates the MilkdropPreset for a frame given the current values of MilkdropPreset inputs / outputs diff --git a/src/libprojectM/MilkdropPresetFactory/MilkdropPresetFactory.cpp b/src/libprojectM/MilkdropPresetFactory/MilkdropPresetFactory.cpp index c892c668d..aff752a44 100644 --- a/src/libprojectM/MilkdropPresetFactory/MilkdropPresetFactory.cpp +++ b/src/libprojectM/MilkdropPresetFactory/MilkdropPresetFactory.cpp @@ -17,10 +17,9 @@ #include "IdlePreset.hpp" #include "PresetFrameIO.hpp" -MilkdropPresetFactory::MilkdropPresetFactory(int gx_, int gy_) - : gx(gx_) - , gy(gy_) - , _presetOutputsCache(nullptr) +MilkdropPresetFactory::MilkdropPresetFactory(int meshX, int meshY) + : m_meshX(meshX) + , m_meshY(meshY) { /* Initializes the builtin function database */ BuiltinFuncs::init_builtin_func_db(); @@ -33,18 +32,12 @@ MilkdropPresetFactory::~MilkdropPresetFactory() { Eval::destroy_infix_ops(); BuiltinFuncs::destroy_builtin_func_db(); - - if (_presetOutputsCache) - { - delete (_presetOutputsCache); - _presetOutputsCache = nullptr; - } } /* Reinitializes the engine variables to a default (conservative and sane) value */ -void resetPresetOutputs(PresetOutputs* presetOutputs) +void MilkdropPresetFactory::ResetPresetOutputs(PresetOutputs* presetOutputs) { - if (!presetOutputs) + if (presetOutputs == nullptr) { return; } @@ -151,18 +144,17 @@ void resetPresetOutputs(PresetOutputs* presetOutputs) /* Reinitializes the engine variables to a default (conservative and sane) value */ void MilkdropPresetFactory::reset() { - if (_presetOutputsCache) + if (m_presetOutputsCache) { - resetPresetOutputs(_presetOutputsCache); + ResetPresetOutputs(m_presetOutputsCache); } } -PresetOutputs* MilkdropPresetFactory::createPresetOutputs(int gx, int gy) +PresetOutputs* MilkdropPresetFactory::CreatePresetOutputs(int meshX, int meshY) { + auto* presetOutputs = new PresetOutputs(); - PresetOutputs* presetOutputs = new PresetOutputs(); - - presetOutputs->Initialize(gx, gy); + presetOutputs->Initialize(meshX, meshY); /* PER FRAME CONSTANTS BEGIN */ presetOutputs->zoom = 1.0; @@ -177,31 +169,26 @@ PresetOutputs* MilkdropPresetFactory::createPresetOutputs(int gx, int gy) presetOutputs->cx = 0.5; presetOutputs->cy = 0.5; - presetOutputs->screenDecay = .98; - - -//_presetInputs.meshx = 0; -//_presetInputs.meshy = 0; - + presetOutputs->screenDecay = 0.98f; /* PER_FRAME CONSTANTS END */ - presetOutputs->fRating = 0; - presetOutputs->fGammaAdj = 1.0; - presetOutputs->videoEcho.zoom = 1.0; - presetOutputs->videoEcho.a = 0; - presetOutputs->videoEcho.orientation = Normal; + presetOutputs->fRating = 0.0f; + presetOutputs->fGammaAdj = 1.0f; + presetOutputs->videoEcho.zoom = 1.0f; + presetOutputs->videoEcho.a = 0.0f; + presetOutputs->videoEcho.orientation = Orientation::Normal; - presetOutputs->textureWrap = 0; - presetOutputs->bDarkenCenter = 0; - presetOutputs->bRedBlueStereo = 0; - presetOutputs->bBrighten = 0; - presetOutputs->bDarken = 0; - presetOutputs->bSolarize = 0; - presetOutputs->bInvert = 0; - presetOutputs->bMotionVectorsOn = 1; - presetOutputs->fWarpAnimSpeed = 0; - presetOutputs->fWarpScale = 0; - presetOutputs->fShader = 0; + presetOutputs->textureWrap = false; + presetOutputs->bDarkenCenter = false; + presetOutputs->bRedBlueStereo = false; + presetOutputs->bBrighten = false; + presetOutputs->bDarken = false; + presetOutputs->bSolarize = false; + presetOutputs->bInvert = false; + presetOutputs->bMotionVectorsOn = true; + presetOutputs->fWarpAnimSpeed = 0.0f; + presetOutputs->fWarpScale = 0.0f; + presetOutputs->fShader = 0.0f; /* PER_PIXEL CONSTANTS BEGIN */ @@ -220,32 +207,57 @@ PresetOutputs* MilkdropPresetFactory::createPresetOutputs(int gx, int gy) std::unique_ptr -MilkdropPresetFactory::allocate(const std::string& url, const std::string& name, const std::string& author) +MilkdropPresetFactory::LoadPresetFromFile(const std::string& filename) +{ + PresetOutputs* presetOutputs; + // use cached PresetOutputs if there is one, otherwise allocate + if (m_presetOutputsCache) + { + presetOutputs = m_presetOutputsCache; + m_presetOutputsCache = nullptr; + } + else + { + presetOutputs = CreatePresetOutputs(m_meshX, m_meshY); + } + + ResetPresetOutputs(presetOutputs); + + std::string path; + auto protocol = PresetFactory::Protocol(filename, path); + if (protocol == PresetFactory::IDLE_PRESET_PROTOCOL) + { + return IdlePresets::allocate(this, presetOutputs); + } + else if (protocol == "" or protocol == "file") + { + return std::make_unique(this, path, presetOutputs); + } + else + { + // ToDO: Throw unsupported protocol exception instead to provide more information. + return nullptr; + } +} + +std::unique_ptr MilkdropPresetFactory::LoadPresetFromStream(std::istream& data) { PresetOutputs* presetOutputs; // use cached PresetOutputs if there is one, otherwise allocate - if (_presetOutputsCache) + if (m_presetOutputsCache) { - presetOutputs = _presetOutputsCache; - _presetOutputsCache = nullptr; + presetOutputs = m_presetOutputsCache; + m_presetOutputsCache = nullptr; } else { - presetOutputs = createPresetOutputs(gx, gy); + presetOutputs = CreatePresetOutputs(m_meshX, m_meshY); } - resetPresetOutputs(presetOutputs); + ResetPresetOutputs(presetOutputs); - std::string path; - if (PresetFactory::protocol(url, path) == PresetFactory::IDLE_PRESET_PROTOCOL) - { - return IdlePresets::allocate(this, path, presetOutputs); - } - else - { - return std::unique_ptr(new MilkdropPreset(this, url, name, presetOutputs)); - } + return std::make_unique(this, data, presetOutputs); } // this gives the preset a way to return the PresetOutput w/o dependency on class projectM behavior @@ -260,12 +272,12 @@ void MilkdropPresetFactory::releasePreset(Preset* preset) } // return PresetOutputs to the cache - if (!_presetOutputsCache) + if (!m_presetOutputsCache) { - _presetOutputsCache = milkdropPreset->_presetOutputs; + m_presetOutputsCache = milkdropPreset->_presetOutputs; } else { delete milkdropPreset->_presetOutputs; } -} +} \ No newline at end of file diff --git a/src/libprojectM/MilkdropPresetFactory/MilkdropPresetFactory.hpp b/src/libprojectM/MilkdropPresetFactory/MilkdropPresetFactory.hpp index c00dba8cd..ea4979419 100644 --- a/src/libprojectM/MilkdropPresetFactory/MilkdropPresetFactory.hpp +++ b/src/libprojectM/MilkdropPresetFactory/MilkdropPresetFactory.hpp @@ -12,8 +12,9 @@ #pragma once +#include + #include -#include "../PresetFactory.hpp" class PresetOutputs; @@ -23,15 +24,16 @@ class MilkdropPresetFactory : public PresetFactory { public: - MilkdropPresetFactory(int gx, int gy); + MilkdropPresetFactory(int meshX, int meshY); ~MilkdropPresetFactory() override; // called by ~MilkdropPreset void releasePreset(Preset* preset); - std::unique_ptr allocate(const std::string& url, const std::string& name, - const std::string& author) override; + std::unique_ptr LoadPresetFromFile(const std::string& filename) override; + + std::unique_ptr LoadPresetFromStream(std::istream& data) override; std::string supportedExtensions() const override { @@ -39,11 +41,14 @@ public: } private: - static PresetOutputs* createPresetOutputs(int gx, int gy); + void ResetPresetOutputs(PresetOutputs* presetOutputs); + + static PresetOutputs* CreatePresetOutputs(int meshX, int meshY); void reset(); - int gx{ 0 }; - int gy{ 0 }; - PresetOutputs* _presetOutputsCache{ nullptr }; + int m_meshX{0}; + int m_meshY{0}; + + PresetOutputs* m_presetOutputsCache{nullptr}; }; diff --git a/src/libprojectM/MilkdropPresetFactory/PresetFrameIO.hpp b/src/libprojectM/MilkdropPresetFactory/PresetFrameIO.hpp index b528f3e43..06188a192 100644 --- a/src/libprojectM/MilkdropPresetFactory/PresetFrameIO.hpp +++ b/src/libprojectM/MilkdropPresetFactory/PresetFrameIO.hpp @@ -116,8 +116,10 @@ public: Invert invert; Solarize solarize; + // ToDo: Check if redeclaration here is wanted, as the Pipeline base class also defines these. + int gy; + int gx; - int gy,gx; /* PER_FRAME VARIABLES END */ float fRating; diff --git a/src/libprojectM/Preset.cpp b/src/libprojectM/Preset.cpp deleted file mode 100755 index 84e823af6..000000000 --- a/src/libprojectM/Preset.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Preset.cpp - * - * Created on: Aug 5, 2008 - * Author: struktured - */ - -#include "Preset.hpp" - -Preset::~Preset() {} - -Preset::Preset(const std::string & presetName, const std::string & presetAuthor): - _name(presetName), _author(presetAuthor) {} - -void Preset::setName(const std::string & value) { _name = value; } - -const std::string & Preset::name() const { return _name; } - -void Preset::setAuthor(const std::string & value) { _author = value; } - -const std::string & Preset::author() const { return _author; } - - diff --git a/src/libprojectM/Preset.hpp b/src/libprojectM/Preset.hpp index 4171e9757..9d68cd0eb 100644 --- a/src/libprojectM/Preset.hpp +++ b/src/libprojectM/Preset.hpp @@ -1,38 +1,27 @@ -/* - * Preset.hpp - * - * Created on: Aug 5, 2008 - * Author: carm - */ +#pragma once -#ifndef PRESET_HPP_ -#define PRESET_HPP_ +#include +#include +#include -#include - -#include "Renderer/BeatDetect.hpp" -#include "Renderer/Pipeline.hpp" -#include "Renderer/PipelineContext.hpp" - -class Preset { +class Preset +{ public: + virtual ~Preset() = default; - - Preset(const std::string & name=std::string(), const std::string & author = std::string()); - virtual ~Preset(); + virtual Pipeline& pipeline() = 0; + virtual void Render(const BeatDetect& music, const PipelineContext& context) = 0; - void setName(const std::string & value); - const std::string & name() const; + inline void SetFilename(const std::string& filename) + { + m_filename = filename; + } - void setAuthor(const std::string & value); - const std::string & author() const; - - virtual Pipeline & pipeline() = 0; - virtual void Render(const BeatDetect &music, const PipelineContext &context) = 0; + inline const std::string& Filename() const + { + return m_filename; + } private: - std::string _name; - std::string _author; + std::string m_filename; }; - -#endif /* PRESET_HPP_ */ diff --git a/src/libprojectM/PresetFactory.cpp b/src/libprojectM/PresetFactory.cpp index c7b2178d8..99cee6c02 100644 --- a/src/libprojectM/PresetFactory.cpp +++ b/src/libprojectM/PresetFactory.cpp @@ -2,7 +2,7 @@ const std::string PresetFactory::IDLE_PRESET_PROTOCOL("idle"); -std::string PresetFactory::protocol(const std::string & url, std::string & path) { +std::string PresetFactory::Protocol(const std::string& url, std::string& path) { #ifdef __APPLE__ // NOTE: Brian changed this from url.find_first_of to url.find, since presumably we want to find the first occurence of @@ -12,12 +12,15 @@ std::string PresetFactory::protocol(const std::string & url, std::string & path) std::size_t pos = url.find_first_of("://"); #endif if (pos == std::string::npos) - return std::string(); - else { + { + path = url; + return std::string(); + } + else + { path = url.substr(pos + 3, url.length()); -// std::cout << "[PresetFactory] path is " << path << std::endl; #ifdef DEBUG - std::cout << "[PresetFactory] url is " << url << std::endl; + std::cout << "[PresetFactory] Filename is URL: " << url << std::endl; #endif return url.substr(0, pos); } diff --git a/src/libprojectM/PresetFactory.hpp b/src/libprojectM/PresetFactory.hpp index 07bb30b52..370b1f540 100644 --- a/src/libprojectM/PresetFactory.hpp +++ b/src/libprojectM/PresetFactory.hpp @@ -1,42 +1,49 @@ -// -// C++ Interface: PresetFactory -// -// Description: -// -// -// Author: Carmelo Piccione , (C) 2008 -// -// Copyright: See COPYING file that comes with this distribution -// -// +#pragma once #include "Preset.hpp" + #include -#ifndef __PRESET_FACTORY_HPP -#define __PRESET_FACTORY_HPP - -class PresetFactory { +class PresetFactory +{ public: - static const std::string IDLE_PRESET_PROTOCOL; - static std::string protocol(const std::string & url, std::string & path); + static const std::string IDLE_PRESET_PROTOCOL; //!< The string "idle" - inline PresetFactory() {} + PresetFactory() = default; - inline virtual ~PresetFactory() {} + virtual ~PresetFactory() = default; - /// Constructs a new preset given an url and optional meta data - /// \param url a locational identifier referencing the preset - /// \param name the preset name - /// \param author the preset author - /// \returns a valid preset object - virtual std::unique_ptr allocate(const std::string & url, const std::string & name=std::string(), - const std::string & author=std::string()) = 0; + /** + * @brief Returns the protocol portion for the given URL + * + * Some implementation may hand over "file:///path/to/preset" as the preset filename. This method + * takes care of cutting of the URL portion and return the path. Internally, it can be used to load + * the default idle preset via the "idle://" URL. + * + * @param url The URL to parse + * @param path [out] The path portion of the URL, basically everything after the "://" delimiter. + * @return The protocol, e.g. "file" + */ + static std::string Protocol(const std::string& url, std::string& path); - /// Returns a space separated list of supported extensions - virtual std::string supportedExtensions() const = 0; + /** + * @brief Constructs a new preset from a local file + * @param filename The preset filename + * @returns A valid preset object + */ + virtual std::unique_ptr LoadPresetFromFile(const std::string& filename) = 0; + /** + * @brief Constructs a new preset from a stream + * @param data The preset data stream + * @returns A valid preset object + */ + virtual std::unique_ptr LoadPresetFromStream(std::istream& data) = 0; + + /** + * Returns a space separated list of supported extensions + * @return A space separated list of supported extensions + */ + virtual std::string supportedExtensions() const = 0; }; - -#endif diff --git a/src/libprojectM/PresetFactoryManager.cpp b/src/libprojectM/PresetFactoryManager.cpp index 1ddafd725..1d9bf3929 100644 --- a/src/libprojectM/PresetFactoryManager.cpp +++ b/src/libprojectM/PresetFactoryManager.cpp @@ -1,7 +1,7 @@ // // C++ Implementation: PresetFactoryManager // -// Description: +// Description: // // // Author: Carmelo Piccione , (C) 2008 @@ -10,95 +10,133 @@ // // #include "PresetFactoryManager.hpp" -#include "MilkdropPresetFactory/MilkdropPresetFactory.hpp" -#include "config.h" +#include + #include -PresetFactoryManager::PresetFactoryManager() : _gx(0), _gy(0), initialized(false) {} +PresetFactoryManager::~PresetFactoryManager() +{ + for (std::vector::iterator pos = m_factoryList.begin(); + pos != m_factoryList.end(); ++pos) + { + assert(*pos); + delete (*pos); + } -PresetFactoryManager::~PresetFactoryManager() { - for (std::vector::iterator pos = _factoryList.begin(); - pos != _factoryList.end(); ++pos) { - assert(*pos); - delete(*pos); - } - - initialized = false; + m_initialized = false; } -void PresetFactoryManager::initialize(int gx, int gy) { - _gx = gx; - _gy = gy; - - if (!initialized) { - initialized = true; - } else { - std::cout << "already initialized " << std::endl; - return; - } - - PresetFactory * factory; +void PresetFactoryManager::initialize(int meshX, int meshY) +{ + m_meshX = meshX; + m_meshY = meshY; - factory = new MilkdropPresetFactory(_gx, _gy); - registerFactory(factory->supportedExtensions(), factory); + if (!m_initialized) + { + m_initialized = true; + } + else + { + std::cout << "already initialized " << std::endl; + return; + } + + PresetFactory* factory; + + factory = new MilkdropPresetFactory(m_meshX, m_meshY); + registerFactory(factory->supportedExtensions(), factory); } // Current behavior if a conflict is occurs is to override the previous request -void PresetFactoryManager::registerFactory(const std::string & extensions, PresetFactory * factory) { - - std::stringstream ss(extensions); - std::string extension; - - _factoryList.push_back(factory); - - while (ss >> extension) { - if (_factoryMap.count(extension)) { - std::cerr << "[PresetFactoryManager] Warning: extension \"" << extension << - "\" already has a factory. New factory handler ignored." << std::endl; - } else { - _factoryMap.insert(std::make_pair(extension, factory)); - } - } -} - - - -std::unique_ptr PresetFactoryManager::allocate(const std::string & url, const std::string & name) +void PresetFactoryManager::registerFactory(const std::string& extensions, PresetFactory* factory) { - try { - const std::string extension = "." + ParseExtension(url); - return factory(extension).allocate(url, name); - } catch (const PresetFactoryException & e) { - throw e; - } catch (const std::exception & e) { - throw PresetFactoryException(e.what()); - } catch (...) { - throw PresetFactoryException("uncaught preset factory exception"); - } - return std::unique_ptr(); + std::stringstream ss(extensions); + std::string extension; + + m_factoryList.push_back(factory); + + while (ss >> extension) + { + if (m_factoryMap.count(extension)) + { + std::cerr << "[PresetFactoryManager] Warning: extension \"" << extension << "\" already has a factory. New factory handler ignored." << std::endl; + } + else + { + m_factoryMap.insert(std::make_pair(extension, factory)); + } + } } -PresetFactory & PresetFactoryManager::factory(const std::string & extension) { - if (!extensionHandled(extension)) { - std::ostringstream os; - os << "No preset factory associated with \"" << extension << "\"." << std::endl; - throw PresetFactoryException(os.str()); - } - return *_factoryMap[extension]; +std::unique_ptr PresetFactoryManager::CreatePresetFromFile(const std::string& filename) +{ + try + { + const std::string extension = "." + ParseExtension(filename); + + return factory(extension).LoadPresetFromFile(filename); + } + catch (const PresetFactoryException& e) + { + throw; + } + catch (const std::exception& e) + { + throw PresetFactoryException(e.what()); + } + catch (...) + { + throw PresetFactoryException("Uncaught preset factory exception"); + } } -bool PresetFactoryManager::extensionHandled(const std::string & extension) const { - return _factoryMap.count(extension); +std::unique_ptr PresetFactoryManager::CreatePresetFromStream(const std::string& extension, std::istream& data) +{ + try + { + return factory(extension).LoadPresetFromStream(data); + } + catch (const PresetFactoryException& e) + { + throw; + } + catch (const std::exception& e) + { + throw PresetFactoryException(e.what()); + } + catch (...) + { + throw PresetFactoryException("Uncaught preset factory exception"); + } } -std::vector PresetFactoryManager::extensionsHandled() const { +PresetFactory& PresetFactoryManager::factory(const std::string& extension) +{ + + if (!extensionHandled(extension)) + { + std::ostringstream os; + os << "No preset factory associated with \"" << extension << "\"." << std::endl; + throw PresetFactoryException(os.str()); + } + return *m_factoryMap[extension]; +} + +bool PresetFactoryManager::extensionHandled(const std::string& extension) const +{ + return m_factoryMap.count(extension); +} + +std::vector PresetFactoryManager::extensionsHandled() const +{ std::vector retval; - for (auto const& element : _factoryMap) { - retval.push_back(element.first); + for (auto const& element : m_factoryMap) + { + retval.push_back(element.first); } return retval; -} +} \ No newline at end of file diff --git a/src/libprojectM/PresetFactoryManager.hpp b/src/libprojectM/PresetFactoryManager.hpp index 05f9e716f..dcea6357f 100644 --- a/src/libprojectM/PresetFactoryManager.hpp +++ b/src/libprojectM/PresetFactoryManager.hpp @@ -1,62 +1,90 @@ -// -// C++ Implementation: PresetFactoryManager -// -// Description: -// -// -// Author: Carmelo Piccione , (C) 2008 -// -// Copyright: See COPYING file that comes with this distribution -// -// -#ifndef __PRESET_FACTORY_MANAGER_HPP -#define __PRESET_FACTORY_MANAGER_HPP +#pragma once + +#include + #include "PresetFactory.hpp" /// A simple exception class to strongly type all preset factory related issues class PresetFactoryException : public std::exception { - public: - inline PresetFactoryException(const std::string & message) : _message(message) {} - virtual ~PresetFactoryException() throw() {} - const std::string & message() const { return _message; } +public: + inline PresetFactoryException(std::string message) + : m_message(std::move(message)) + { + } - private: - std::string _message; + virtual ~PresetFactoryException() = default; + + const std::string& message() const + { + return m_message; + } + +private: + std::string m_message; }; /// A manager of preset factories -class PresetFactoryManager { +class PresetFactoryManager +{ - public: - PresetFactoryManager(); - ~PresetFactoryManager(); +public: + PresetFactoryManager() = default; - /// Initializes the manager with mesh sizes specified - /// \param gx the width of the mesh - /// \param gy the height of the mesh - /// \note This must be called once before any other methods - void initialize(int gx, int gy); - - /// Requests a factory given a preset extension type - /// \param extension a string denoting the preset suffix type - /// \throws PresetFactoryException if the extension is unhandled - /// \returns a valid preset factory associated with the extension - PresetFactory & factory(const std::string & extension); + ~PresetFactoryManager(); - /// Tests if an extension has been registered with a factory - /// \param extension the file name extension to verify - /// \returns true if a factory exists, false otherwise - bool extensionHandled(const std::string & extension) const; - std::unique_ptr allocate(const std::string & url, const std::string & name); - std::vector extensionsHandled() const; + /** + * @brief Initializes the manager with mesh sizes specified. + * @note This must be called once before any other methods and whenever the mesh size changes. + * @param meshX The width of the mesh + * @param meshY The height of the mesh + */ + void initialize(int meshX, int meshY); + + /// Requests a factory given a preset extension type + /// \param extension a string denoting the preset suffix type + /// \throws PresetFactoryException if the extension is unhandled + /// \returns a valid preset factory associated with the extension + PresetFactory& factory(const std::string& extension); + + /// Tests if an extension has been registered with a factory + /// \param extension the file name extension to verify + /// \returns true if a factory exists, false otherwise + bool extensionHandled(const std::string& extension) const; + + /** + * @brief Loads a preset by a given filename or URL. + * + * Supported URLs are "idle://" (loads the idle preset) and "file://". Other URL schemes will + * throw an exception. + * + * @param filename The filename/URL to load. + * @throws PresetFactoryException If any error occurs during preset loading. Exception message + * contains additional details. + * @return A valid pointer to the loaded preset. + */ + std::unique_ptr CreatePresetFromFile(const std::string& filename); + + /** + * @brief Loads a preset from a stream. + * @param extension The "original" extension. Used to determine preset data format. + * @param data A stream with preset data to load. + * @throws PresetFactoryException If any error occurs during preset loading. Exception message + * contains additional details. + * @return A valid pointer to the loaded preset. + */ + std::unique_ptr CreatePresetFromStream(const std::string& extension, std::istream& data); + + std::vector extensionsHandled() const; - private: - int _gx, _gy; - mutable std::map _factoryMap; - mutable std::vector _factoryList; - void registerFactory(const std::string & extension, PresetFactory * factory); - volatile bool initialized; +private: + void registerFactory(const std::string& extension, PresetFactory* factory); + + int m_meshX{0}; //!< The width of the mesh + int m_meshY{0}; //!< The height of the mesh + bool m_initialized{false}; //!< True if the factory maps are already initialized. + + mutable std::map m_factoryMap; + mutable std::vector m_factoryList; }; -#endif diff --git a/src/libprojectM/ProjectM.cpp b/src/libprojectM/ProjectM.cpp index 9f63d113f..6d489eea9 100644 --- a/src/libprojectM/ProjectM.cpp +++ b/src/libprojectM/ProjectM.cpp @@ -26,34 +26,12 @@ #include "PCM.hpp" //Sound data handler (buffering, FFT, etc.) #include "PipelineMerger.hpp" #include "Preset.hpp" +#include "PresetFactoryManager.hpp" #include "Renderer.hpp" #include "TimeKeeper.hpp" #include -namespace { -constexpr int kMaxSwitchRetries = 10; -} - -ProjectM::~ProjectM() -{ -#if USE_THREADS - m_workerSync.FinishUp(); - m_workerThread.join(); -#endif -} - -auto ProjectM::InitRenderToTexture() -> unsigned -{ - return m_renderer->initRenderToTexture(); -} - -void ProjectM::ResetTextures() -{ - m_renderer->ResetTextures(); -} - - ProjectM::ProjectM(const std::string& configurationFilename) { ReadConfig(configurationFilename); @@ -68,6 +46,68 @@ ProjectM::ProjectM(const class Settings& settings) ResetOpenGL(m_settings.windowWidth, m_settings.windowHeight); } +ProjectM::~ProjectM() +{ +#if USE_THREADS + m_workerSync.FinishUp(); + m_workerThread.join(); +#endif +} + +void ProjectM::PresetSwitchRequestedEvent(bool isHardCut) const +{ +} + +void ProjectM::PresetSwitchFailedEvent(const std::string&, const std::string&) const +{ +} + +void ProjectM::LoadPresetFile(const std::string& presetFilename, bool smoothTransition) +{ + // If already in a transition, force immediate completion. + if (m_transitioningPreset != nullptr) + { + m_activePreset = std::move(m_transitioningPreset); + } + + try + { + StartPresetTransition(m_presetFactoryManager.CreatePresetFromFile(presetFilename), !smoothTransition); + } + catch (const PresetFactoryException& ex) + { + PresetSwitchFailedEvent(presetFilename, ex.message()); + } +} + +void ProjectM::LoadPresetData(std::istream& presetData, bool smoothTransition) +{ + // If already in a transition, force immediate completion. + if (m_transitioningPreset != nullptr) + { + m_activePreset = std::move(m_transitioningPreset); + } + + try + { + StartPresetTransition(m_presetFactoryManager.CreatePresetFromStream("milk", presetData), !smoothTransition); + } + catch (const PresetFactoryException& ex) + { + PresetSwitchFailedEvent("", ex.message()); + } +} + +auto ProjectM::InitRenderToTexture() -> unsigned +{ + return m_renderer->initRenderToTexture(); +} + +void ProjectM::ResetTextures() +{ + m_renderer->ResetTextures(); +} + auto ProjectM::WriteConfig(const std::string& configurationFilename, const class Settings& settings) -> bool { @@ -188,7 +228,7 @@ void ProjectM::EvaluateSecondPreset() PipelineContext2().frame = m_timeKeeper->PresetFrameB(); PipelineContext2().progress = m_timeKeeper->PresetProgressB(); - m_activePreset2->Render(*m_beatDetect, PipelineContext2()); + m_transitioningPreset->Render(*m_beatDetect, PipelineContext2()); } void ProjectM::RenderFrame() @@ -220,8 +260,8 @@ auto ProjectM::RenderFrameOnlyPass1(Pipeline* pipeline) -> Pipeline* m_beatDetect->CalculateBeatStatistics(); - //if the preset isn't locked and there are more presets - if (!m_renderer->noSwitch) + // Check if the preset isn't locked, and we've not already notified the user + if (!m_presetChangeNotified) { //if preset is done and we're not already switching if (m_timeKeeper->PresetProgressA() >= 1.0 && !m_timeKeeper->IsSmoothing()) @@ -234,10 +274,11 @@ auto ProjectM::RenderFrameOnlyPass1(Pipeline* pipeline) -> Pipeline* { // Call preset change callback } + m_presetChangeNotified = true; } - if (m_timeKeeper->IsSmoothing() && m_timeKeeper->SmoothRatio() <= 1.0 && m_activePreset2 != nullptr) + if (m_timeKeeper->IsSmoothing() && m_timeKeeper->SmoothRatio() <= 1.0 && m_transitioningPreset != nullptr) { #if USE_THREADS m_workerSync.WakeUpBackgroundTask(); @@ -256,7 +297,7 @@ auto ProjectM::RenderFrameOnlyPass1(Pipeline* pipeline) -> Pipeline* PipelineMerger::mergePipelines( m_activePreset->pipeline(), - m_activePreset2->pipeline(), + m_transitioningPreset->pipeline(), *pipeline, m_timeKeeper->SmoothRatio()); @@ -267,7 +308,7 @@ auto ProjectM::RenderFrameOnlyPass1(Pipeline* pipeline) -> Pipeline* if (m_timeKeeper->IsSmoothing() && m_timeKeeper->SmoothRatio() > 1.0) { - m_activePreset = std::move(m_activePreset2); + m_activePreset = std::move(m_transitioningPreset); m_timeKeeper->EndSmoothing(); } @@ -290,7 +331,6 @@ void ProjectM::RenderFrameOnlyPass2(Pipeline* pipeline, m_renderer->RenderFrameOnlyPass2(*pipeline, PipelineContext(), offsetX, offsetY, 0); } - void ProjectM::RenderFrameEndOnSeparatePasses(Pipeline* pipeline) { if (pipeline != nullptr) @@ -320,6 +360,8 @@ void ProjectM::Reset() { this->m_count = 0; + m_presetFactoryManager.initialize(m_settings.meshX, m_settings.meshY); + ResetEngine(); } @@ -388,7 +430,14 @@ void ProjectM::ResetOpenGL(size_t width, size_t height) /** Stash the new dimensions */ m_settings.windowWidth = width; m_settings.windowHeight = height; - m_renderer->reset(width, height); + try + { + m_renderer->reset(width, height); + } + catch (const RenderException& ex) + { + // ToDo: Add generic error callback + } } auto ProjectM::InitializePresetTools() -> void @@ -396,38 +445,40 @@ auto ProjectM::InitializePresetTools() -> void /* Set the seed to the current time in seconds */ srand(time(nullptr)); - m_renderer->setPresetName("Geiss & Sperl - Feedback (projectM idle HDR mix)"); m_renderer->SetPipeline(m_activePreset->pipeline()); ResetEngine(); } -bool ProjectM::StartPresetTransition(bool hardCut) +void ProjectM::StartPresetTransition(std::unique_ptr&& preset, bool hardCut) { - std::unique_ptr new_preset = SwitchToCurrentPreset(); - if (new_preset == nullptr) + if (preset == nullptr) { - PresetSwitchFailedEvent(hardCut, "", "No preset available to switch to"); - m_errorLoadingCurrentPreset = true; + return; + } - return false; + try { + m_renderer->SetPipeline(preset->pipeline()); + } + catch(const RenderException& ex) + { + PresetSwitchFailedEvent(preset->Filename(), ex.message()); + return; } if (hardCut) { - m_activePreset = std::move(new_preset); + m_activePreset = std::move(preset); m_timeKeeper->StartPreset(); } else { - m_activePreset2 = std::move(new_preset); + m_transitioningPreset = std::move(preset); m_timeKeeper->StartPreset(); m_timeKeeper->StartSmoothing(); } - m_errorLoadingCurrentPreset = false; - - return true; + m_presetChangeNotified = m_presetLocked; } auto ProjectM::WindowWidth() -> int @@ -440,55 +491,19 @@ auto ProjectM::WindowHeight() -> int return m_settings.windowHeight; } -auto ProjectM::ErrorLoadingCurrentPreset() const -> bool -{ - return m_errorLoadingCurrentPreset; -} - -/** - * Switches the pipeline and renderer to the current preset. - * @return the resulting Preset object, or nullptr on failure. - */ -auto ProjectM::SwitchToCurrentPreset() -> std::unique_ptr -{ - std::unique_ptr new_preset; -#if USE_THREADS - std::lock_guard guard(m_presetSwitchMutex); -#endif - - // ToDo: Load preset by filename here - - if (new_preset == nullptr) - { - return nullptr; - } - - // Set preset name here - event is not done because at the moment this function - // is oblivious to smooth/hard switches - m_renderer->setPresetName(new_preset->name()); - std::string result = m_renderer->SetPipeline(new_preset->pipeline()); - if (!result.empty()) - { - std::cerr << "problem setting pipeline: " << result << std::endl; - } - - return new_preset; -} - void ProjectM::SetPresetLocked(bool locked) { // ToDo: Add a preset switch timer separate from the display timer and reset to 0 when // disabling the preset switch lock. - m_renderer->noSwitch = locked; + m_presetLocked = locked; + m_presetChangeNotified = locked; } auto ProjectM::PresetLocked() const -> bool { - return m_renderer->noSwitch; + return m_presetLocked; } -void ProjectM::PresetSwitchFailedEvent(bool, const std::string&, const std::string&) const {} - void ProjectM::SetTextureSize(size_t size) { m_settings.textureSize = size; diff --git a/src/libprojectM/ProjectM.hpp b/src/libprojectM/ProjectM.hpp index 809cf1e72..9420d97ff 100644 --- a/src/libprojectM/ProjectM.hpp +++ b/src/libprojectM/ProjectM.hpp @@ -22,8 +22,8 @@ #include "Common.hpp" #include "PCM.hpp" +#include "PresetFactoryManager.hpp" #include "fatal.h" -#include "Renderer/PipelineContext.hpp" #include "libprojectM/event.h" @@ -44,6 +44,9 @@ #if USE_THREADS #include "BackgroundWorker.h" + +#include + #include #include @@ -93,6 +96,42 @@ public: virtual ~ProjectM(); + /** + * @brief Callback for notifying the integrating app that projectM wants to switch to a new preset. + * + * It is safe to call LoadPreset() from inside the callback. The app can decide when to actually + * call the function or even ignore the request completely. + * + * @param isHardCut True if the switch event was caused by a hard cut, false if it is a soft cut. + */ + virtual void PresetSwitchRequestedEvent(bool isHardCut) const; + + /** + * @brief Callback for notifying the integrating app that the requested preset file couldn't be loaded. + * @param presetFilename The filename of the preset that failed to load. Empty if loaded from a stream. + * @param message The error message with the failure reason. + */ + virtual void PresetSwitchFailedEvent(const std::string& presetFilename, const std::string& message) const; + + /** + * @brief Loads the given preset file and performs a smooth or immediate transition. + * @param presetFilename The preset filename to load. + * @param smoothTransition If set to true, old and new presets will be blended over smoothly. + * If set to false, the new preset will be rendered immediately. + */ + void LoadPresetFile(const std::string& presetFilename, bool smoothTransition); + + /** + * @brief Loads the given preset data and performs a smooth or immediate transition. + * + * This function assumes the data to be in Milkdrop format. + * + * @param presetData The preset data stream to load from. + * @param smoothTransition If set to true, old and new presets will be blended over smoothly. + * If set to false, the new preset will be rendered immediately. + */ + void LoadPresetData(std::istream& presetData, bool smoothTransition); + void ResetOpenGL(size_t width, size_t height); void ResetTextures(); @@ -181,16 +220,12 @@ public: /// Returns true if the active preset is locked auto PresetLocked() const -> bool; - virtual void PresetSwitchFailedEvent(bool hardCut, const std::string& presetFilename, const std::string& message) const; - auto Pcm() -> class Pcm&; auto WindowWidth() -> int; auto WindowHeight() -> int; - auto ErrorLoadingCurrentPreset() const -> bool; - void DefaultKeyHandler(projectMEvent event, projectMKeycode keycode); /** @@ -231,9 +266,7 @@ private: /// Initializes preset loading / management libraries auto InitializePresetTools() -> void; - auto SwitchToCurrentPreset() -> std::unique_ptr; - - auto StartPresetTransition(bool hardCut) -> bool; + void StartPresetTransition(std::unique_ptr&& preset, bool hardCut); void RecreateRenderer(); @@ -252,16 +285,19 @@ private: /** Timing information */ int m_count{0}; //!< Rendered frame count since start - bool m_errorLoadingCurrentPreset{false}; //!< Error flag for preset loading errors. + bool m_presetLocked{false}; //!< If true, the preset change event will not be sent. + bool m_presetChangeNotified{false}; //!< Stores whether the user has been notified that projectM wants to switch the preset. + + PresetFactoryManager m_presetFactoryManager; //!< Provides access to all available preset factories. class PipelineContext m_pipelineContext; //!< Pipeline context for the first/current preset. class PipelineContext m_pipelineContext2; //!< Pipeline context for the next/transitioning preset. - std::unique_ptr m_renderer; //!< The Preset renderer. - std::unique_ptr m_beatDetect; //!< The beat detection class. - std::unique_ptr m_activePreset; //!< Currently loaded preset. - std::unique_ptr m_activePreset2; //!< Destination preset when smooth preset switching. - std::unique_ptr m_timeKeeper; //!< Keeps the different timers used to render and switch presets. + std::unique_ptr m_renderer; //!< The Preset renderer. + std::unique_ptr m_beatDetect; //!< The beat detection class. + std::unique_ptr m_activePreset; //!< Currently loaded preset. + std::unique_ptr m_transitioningPreset; //!< Destination preset when smooth preset switching. + std::unique_ptr m_timeKeeper; //!< Keeps the different timers used to render and switch presets. #if USE_THREADS mutable std::recursive_mutex m_presetSwitchMutex; //!< Mutex for locking preset switching while rendering and vice versa. diff --git a/src/libprojectM/ProjectMCWrapper.cpp b/src/libprojectM/ProjectMCWrapper.cpp index 6e295a3f1..6921a580e 100644 --- a/src/libprojectM/ProjectMCWrapper.cpp +++ b/src/libprojectM/ProjectMCWrapper.cpp @@ -15,12 +15,12 @@ projectMWrapper::projectMWrapper(class ProjectM::Settings settings) { } -void projectMWrapper::PresetSwitchFailedEvent(bool isHardCut, const std::string& presetFilename, +void projectMWrapper::PresetSwitchFailedEvent(const std::string& presetFilename, const std::string& failureMessage) const { if (_presetSwitchFailedEventCallback) { - _presetSwitchFailedEventCallback(isHardCut, presetFilename.c_str(), + _presetSwitchFailedEventCallback(presetFilename.c_str(), failureMessage.c_str(), _presetSwitchFailedEventUserData); } } @@ -417,12 +417,6 @@ void projectm_set_window_size(projectm_handle instance, size_t width, size_t hei projectMInstance->ResetOpenGL(width, height); } -bool projectm_get_error_loading_current_preset(projectm_handle instance) -{ - auto projectMInstance = handle_to_instance(instance); - return projectMInstance->ErrorLoadingCurrentPreset(); -} - unsigned int projectm_pcm_get_max_samples() { return Pcm::maxSamples; diff --git a/src/libprojectM/ProjectMCWrapper.hpp b/src/libprojectM/ProjectMCWrapper.hpp index e301e0075..a6da7c41d 100644 --- a/src/libprojectM/ProjectMCWrapper.hpp +++ b/src/libprojectM/ProjectMCWrapper.hpp @@ -32,7 +32,7 @@ public: projectMWrapper(class Settings settings); - void PresetSwitchFailedEvent(bool isHardCut, const std::string& presetFilename, + void PresetSwitchFailedEvent(const std::string& presetFilename, const std::string& failureMessage) const override; projectm_preset_switch_failed_event _presetSwitchFailedEventCallback{ nullptr }; diff --git a/src/libprojectM/Renderer/Renderer.cpp b/src/libprojectM/Renderer/Renderer.cpp index f57a0b422..d9e94aa7e 100644 --- a/src/libprojectM/Renderer/Renderer.cpp +++ b/src/libprojectM/Renderer/Renderer.cpp @@ -24,7 +24,6 @@ Renderer::Renderer(int width, int height, int gx, int gy, BeatDetect* beatDetect, std::vector& textureSearchPaths) : m_perPixelMesh(gx, gy) , m_beatDetect(beatDetect) - , m_presetName("None") , m_viewportWidth(width) , m_viewportHeight(height) , m_textureSearchPaths(textureSearchPaths) @@ -137,16 +136,19 @@ Renderer::Renderer(int width, int height, int gx, int gy, glBindBuffer(GL_ARRAY_BUFFER, 0); } -std::string Renderer::SetPipeline(Pipeline& pipeline) +void Renderer::SetPipeline(Pipeline& pipeline) { m_currentPipeline = &pipeline; m_shaderEngine.reset(); - if (!m_shaderEngine.loadPresetShaders(pipeline, m_presetName)) - { - return "Shader compilation error"; - } - return std::string(); + try { + m_shaderEngine.loadPresetShaders(pipeline); + + } + catch(const ShaderException& ex) + { + throw RenderException("Shader compilation error: " + ex.message()); + } } void Renderer::ResetTextures() @@ -418,7 +420,14 @@ void Renderer::reset(int w, int h) m_shaderEngine.setParams(m_textureSizeX, m_textureSizeY, m_fAspectX, m_fAspectY, m_beatDetect, m_textureManager.get()); m_shaderEngine.reset(); - m_shaderEngine.loadPresetShaders(*m_currentPipeline, m_presetName); + try + { + m_shaderEngine.loadPresetShaders(*m_currentPipeline); + } + catch(const ShaderException& ex) + { + throw RenderException("Shader compilation error: " + ex.message()); + } glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); diff --git a/src/libprojectM/Renderer/Renderer.hpp b/src/libprojectM/Renderer/Renderer.hpp index 5e7641f2d..3a668a907 100644 --- a/src/libprojectM/Renderer/Renderer.hpp +++ b/src/libprojectM/Renderer/Renderer.hpp @@ -39,6 +39,25 @@ class BeatDetect; class TextureManager; class TimeKeeper; +class RenderException : public std::exception +{ +public: + inline RenderException(std::string message) + : m_message(std::move(message)) + { + } + + virtual ~RenderException() = default; + + const std::string& message() const + { + return m_message; + } + +private: + std::string m_message; +}; + class Renderer { @@ -56,17 +75,7 @@ public: bool timeCheck(const milliseconds currentTime, const milliseconds lastTime, const double difference); - std::string SetPipeline(Pipeline &pipeline); - - void setPresetName(const std::string& theValue) - { - m_presetName = theValue; - } - - std::string presetName() const - { - return m_presetName; - } + void SetPipeline(Pipeline &pipeline); void setFPS(const int &theValue) { m_fps = std::to_string(theValue); @@ -93,7 +102,6 @@ public: bool correction{ true }; - bool noSwitch{ false }; bool writeNextFrameToFile{ false }; milliseconds lastTimeFPS{ nowMilliseconds() }; @@ -129,7 +137,6 @@ private: RenderContext m_renderContext; ShaderEngine m_shaderEngine; - std::string m_presetName; std::string m_fps; float* m_perPointMeshBuffer{nullptr}; diff --git a/src/libprojectM/Renderer/ShaderEngine.cpp b/src/libprojectM/Renderer/ShaderEngine.cpp index efa248b01..522ccc658 100644 --- a/src/libprojectM/Renderer/ShaderEngine.cpp +++ b/src/libprojectM/Renderer/ShaderEngine.cpp @@ -130,18 +130,11 @@ GLuint ShaderEngine::compilePresetShader(const PresentShaderType shaderType, Sha std::string program = pmShader.programSource; if (program.length() <= 0) - return GL_FALSE; - - // replace "}" with return statement (this can probably be optimized for the GLSL conversion...) - size_t found = program.rfind('}'); - if (found != std::string::npos) { - //std::cout << "last '}' found at: " << int(found) << std::endl; - program.replace(int(found), 1, "_return_value = float4(ret.xyz, 1.0);\n" - "}\n"); + throw ShaderException("Preset shader is declared, but empty."); } - else - return GL_FALSE; + + size_t found; // replace shader_body with entry point function found = program.find("shader_body"); @@ -157,7 +150,22 @@ GLuint ShaderEngine::compilePresetShader(const PresentShaderType shaderType, Sha } } else - return GL_FALSE; + { + throw ShaderException("Preset shader is missing \"shader_body\" entry point."); + } + + // replace "}" with return statement (this can probably be optimized for the GLSL conversion...) + found = program.rfind('}'); + if (found != std::string::npos) + { + //std::cout << "last '}' found at: " << int(found) << std::endl; + program.replace(int(found), 1, "_return_value = float4(ret.xyz, 1.0);\n" + "}\n"); + } + else + { + throw ShaderException("Preset shader has no closing brace."); + } // replace "{" with some variable declarations found = program.find('{',found); @@ -170,7 +178,9 @@ GLuint ShaderEngine::compilePresetShader(const PresentShaderType shaderType, Sha program.replace(int(found), 1, progMain); } else - return GL_FALSE; + { + throw ShaderException("Preset shader has no opening braces."); + } // Find matching closing brace and cut off excess text after shader's main function int bracesOpen = 1; @@ -344,16 +354,12 @@ GLuint ShaderEngine::compilePresetShader(const PresentShaderType shaderType, Sha // preprocess define macros std::string sourcePreprocessed; if (!parser.ApplyPreprocessor(shaderFilename.c_str(), fullSource.c_str(), fullSource.size(), sourcePreprocessed)) { - std::cerr << "Failed to preprocess HLSL(step1) " << shaderTypeString << " shader" << std::endl; - -#if !DUMP_SHADERS_ON_ERROR - std::cerr << "Source: " << std::endl << fullSource << std::endl; -#else +#if DUMP_SHADERS_ON_ERROR std::ofstream out("/tmp/shader_" + shaderTypeString + "_step1.txt"); out << fullSource; out.close(); #endif - return GL_FALSE; + throw ShaderException("Failed to preprocess HLSL(step1) " + shaderTypeString + " shader.\nSource:\n" + fullSource); } // Remove previous shader declarations @@ -396,64 +402,50 @@ GLuint ShaderEngine::compilePresetShader(const PresentShaderType shaderType, Sha // parse if( !parser.Parse(shaderFilename.c_str(), sourcePreprocessed.c_str(), sourcePreprocessed.size()) ) { - std::cerr << "Failed to parse HLSL(step2) " << shaderTypeString << " shader" << std::endl; - -#if !DUMP_SHADERS_ON_ERROR - std::cerr << "Source: " << std::endl << sourcePreprocessed << std::endl; -#else +#if DUMP_SHADERS_ON_ERROR std::ofstream out2("/tmp/shader_" + shaderTypeString + "_step2.txt"); out2 << sourcePreprocessed; out2.close(); #endif - return GL_FALSE; + throw ShaderException("Failed to parse HLSL(step2) " + shaderTypeString + " shader.\nSource:\n" + sourcePreprocessed); } // generate GLSL if (!generator.Generate(&tree, M4::GLSLGenerator::Target_FragmentShader, StaticGlShaders::Get()->GetGlslGeneratorVersion(), "PS")) { - std::cerr << "Failed to transpile HLSL(step3) " << shaderTypeString << " shader to GLSL" << std::endl; -#if !DUMP_SHADERS_ON_ERROR - std::cerr << "Source: " << std::endl << sourcePreprocessed << std::endl; -#else +#if DUMP_SHADERS_ON_ERROR std::ofstream out2("/tmp/shader_" + shaderTypeString + "_step2.txt"); out2 << sourcePreprocessed; out2.close(); #endif - return GL_FALSE; + throw ShaderException("Failed to transpile HLSL(step3) " + shaderTypeString + " shader to GLSL.\nSource:\n" + sourcePreprocessed); } // now we have GLSL source for the preset shader program (hopefully it's // valid!) copmile the preset shader fragment shader with the standard // vertex shader and cross our fingers - GLuint ret = 0; + GLuint compiledProgramId = 0; if (shaderType == PresentWarpShader) { - ret = CompileShaderProgram( + compiledProgramId = CompileShaderProgram( StaticGlShaders::Get()->GetPresetWarpVertexShader(), generator.GetResult(), shaderTypeString); } else { - ret = CompileShaderProgram( + compiledProgramId = CompileShaderProgram( StaticGlShaders::Get()->GetPresetCompVertexShader(), generator.GetResult(), shaderTypeString); } - if (ret != GL_FALSE) { -#ifdef DEBUG - std::cerr << "Successful compilation of " << shaderTypeString << std::endl; -#endif - } else { - std::cerr << "Compilation error (step3) of " << shaderTypeString << std::endl; - -#if !DUMP_SHADERS_ON_ERROR - std::cerr << "Source:" << std::endl << generator.GetResult() << std::endl; -#else + if (compiledProgramId == GL_FALSE) { +#if DUMP_SHADERS_ON_ERROR std::ofstream out3("/tmp/shader_" + shaderTypeString + "_step3.txt"); out3 << generator.GetResult(); out3.close(); #endif + throw ShaderException("Compilation error (step3) of " + shaderTypeString + " shader.\nSource:\n" + generator.GetResult()); } - return ret; + return compiledProgramId; } @@ -808,40 +800,27 @@ bool ShaderEngine::linkProgram(GLuint programID) { } -bool ShaderEngine::loadPresetShaders(Pipeline &pipeline, const std::string & presetName) { - - bool ok = true; - +void ShaderEngine::loadPresetShaders(Pipeline &pipeline) +{ // blur programs blur1_enabled = false; blur2_enabled = false; blur3_enabled = false; - m_presetName = presetName; + programID_presetWarp = GL_FALSE; + programID_presetComp = GL_FALSE; // compile and link warp and composite shaders from pipeline if (!pipeline.warpShader.programSource.empty()) { programID_presetWarp = loadPresetShader(PresentWarpShader, pipeline.warpShader, pipeline.warpShaderFilename); - if (programID_presetWarp != GL_FALSE) { - uniform_vertex_transf_warp_shader = glGetUniformLocation(programID_presetWarp, "vertex_transformation"); - presetWarpShaderLoaded = true; - } else { - ok = false; - } + uniform_vertex_transf_warp_shader = glGetUniformLocation(programID_presetWarp, "vertex_transformation"); + presetWarpShaderLoaded = true; } if (!pipeline.compositeShader.programSource.empty()) { programID_presetComp = loadPresetShader(PresentCompositeShader, pipeline.compositeShader, pipeline.compositeShaderFilename); - if (programID_presetComp != GL_FALSE) { - presetCompShaderLoaded = true; - } else { - ok = false; - } + presetCompShaderLoaded = true; } - -// std::cout << "Preset composite shader active: " << presetCompShaderLoaded << ", preset warp shader active: " << presetWarpShaderLoaded << std::endl; - - return ok; } GLuint ShaderEngine::loadPresetShader(const ShaderEngine::PresentShaderType shaderType, Shader &presetShader, std::string &shaderFilename) { diff --git a/src/libprojectM/Renderer/ShaderEngine.hpp b/src/libprojectM/Renderer/ShaderEngine.hpp index 1bc5fd63c..850375e03 100644 --- a/src/libprojectM/Renderer/ShaderEngine.hpp +++ b/src/libprojectM/Renderer/ShaderEngine.hpp @@ -23,6 +23,25 @@ class ShaderEngine; #include "Shader.hpp" #include +class ShaderException : public std::exception +{ +public: + inline ShaderException(std::string message) + : m_message(std::move(message)) + { + } + + virtual ~ShaderException() = default; + + const std::string& message() const + { + return m_message; + } + +private: + std::string m_message; +}; + class ShaderEngine { @@ -37,7 +56,7 @@ public: ShaderEngine(); virtual ~ShaderEngine(); - bool loadPresetShaders(Pipeline &pipeline, const std::string &presetName); + void loadPresetShaders(Pipeline &pipeline); bool enableWarpShader(Shader &shader, const Pipeline &pipeline, const PipelineContext &pipelineContext, const glm::mat4 & mat_ortho); bool enableCompositeShader(Shader &shader, const Pipeline &pipeline, const PipelineContext &pipelineContext); void RenderBlurTextures(const Pipeline &pipeline, const PipelineContext &pipelineContext); @@ -116,8 +135,6 @@ private: GLuint programID_presetComp, programID_presetWarp; bool presetCompShaderLoaded, presetWarpShaderLoaded; - - std::string m_presetName; }; #endif /* SHADERENGINE_HPP_ */