jsfx: cleaned up implementationtags/v2.5.0
| @@ -65,11 +65,12 @@ jobs: | |||
| sudo dpkg --add-architecture i386 | |||
| sudo apt-get update -qq | |||
| sudo apt-get install -yq libasound2-dev libfluidsynth-dev libgl1-mesa-dev liblo-dev libmagic-dev libpulse-dev libqt4-dev libsndfile1-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev pkg-config pyqt5-dev-tools qtbase5-dev | |||
| sudo apt-get install -yq g++-multilib libfreetype6:i386 libx11-6:i386 libxext6:i386 | |||
| sudo apt-get install -yq g++-multilib libfreetype6:i386 libfontconfig1:i386 libx11-6:i386 libxext6:i386 | |||
| # Fix 32bit bridge build | |||
| sudo ln -s /usr/lib/i386-linux-gnu/libX11.so.6 /usr/lib/i386-linux-gnu/libX11.so | |||
| sudo ln -s /usr/lib/i386-linux-gnu/libXext.so.6 /usr/lib/i386-linux-gnu/libXext.so | |||
| sudo ln -s /usr/lib/i386-linux-gnu/libfreetype.so.6 /usr/lib/i386-linux-gnu/libfreetype.so | |||
| sudo ln -s /usr/lib/i386-linux-gnu/libfontconfig.so.1 /usr/lib/i386-linux-gnu/libfontconfig.so | |||
| - name: make features | |||
| run: make features | |||
| - name: make | |||
| @@ -91,11 +92,12 @@ jobs: | |||
| sudo dpkg --add-architecture i386 | |||
| sudo apt-get update -qq | |||
| sudo apt-get install -yq libasound2-dev libfluidsynth-dev libgl1-mesa-dev liblo-dev libmagic-dev libpulse-dev libsndfile1-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev pkg-config pyqt5-dev-tools qtbase5-dev | |||
| sudo apt-get install -yq g++-multilib libfreetype6:i386 libx11-6:i386 libxext6:i386 | |||
| sudo apt-get install -yq g++-multilib libfreetype6:i386 libfontconfig1:i386 libx11-6:i386 libxext6:i386 | |||
| # Fix 32bit bridge build | |||
| sudo ln -s /usr/lib/i386-linux-gnu/libX11.so.6 /usr/lib/i386-linux-gnu/libX11.so | |||
| sudo ln -s /usr/lib/i386-linux-gnu/libXext.so.6 /usr/lib/i386-linux-gnu/libXext.so | |||
| sudo ln -s /usr/lib/i386-linux-gnu/libfreetype.so.6 /usr/lib/i386-linux-gnu/libfreetype.so | |||
| sudo ln -s /usr/lib/i386-linux-gnu/libfontconfig.so.1 /usr/lib/i386-linux-gnu/libfontconfig.so | |||
| - name: make features | |||
| run: make features | |||
| - name: make | |||
| @@ -88,6 +88,8 @@ ifeq ($(USING_RTAUDIO),true) | |||
| 3RD_LIBS += $(MODULEDIR)/rtmidi.a | |||
| endif | |||
| 3RD_LIBS += $(MODULEDIR)/ysfx.a | |||
| ALL_LIBS += $(3RD_LIBS) | |||
| 3rd: $(3RD_LIBS) | |||
| @@ -50,6 +50,7 @@ Possible formats: | |||
| - lv2 | |||
| - vst|vst2 | |||
| - vst3 | |||
| - jsfx | |||
| - sf2 | |||
| - sfz | |||
| @@ -97,7 +98,7 @@ if len(sys.argv) == arg: | |||
| # -------------------------------------------------------------------------------------------------------- | |||
| # Set format | |||
| if sys.argv[arg] in ("internal", "ladspa", "dssi", "lv2", "vst", "vst2", "vst3", "au", "sf2", "sfz"): | |||
| if sys.argv[arg] in ("internal", "ladspa", "dssi", "lv2", "vst", "vst2", "vst3", "au", "jsfx", "sf2", "sfz"): | |||
| FORMAT = sys.argv[arg] | |||
| arg += 1 | |||
| @@ -193,7 +194,7 @@ if ARCH not in ("native", "posix32", "posix64", "win32", "win64"): | |||
| print("Invalid arch") | |||
| sys.exit(1) | |||
| if FORMAT not in ("internal", "ladspa", "dssi", "lv2", "vst2", "vst3", "au", "sf2", "sfz"): | |||
| if FORMAT not in ("internal", "ladspa", "dssi", "lv2", "vst2", "vst3", "au", "jsfx", "sf2", "sfz"): | |||
| print("Invalid format") | |||
| sys.exit(1) | |||
| @@ -692,6 +692,13 @@ | |||
| </property> | |||
| </widget> | |||
| </item> | |||
| <item> | |||
| <widget class="QCheckBox" name="ch_jsfx"> | |||
| <property name="text"> | |||
| <string>JSFX</string> | |||
| </property> | |||
| </widget> | |||
| </item> | |||
| <item> | |||
| <widget class="QCheckBox" name="ch_kits"> | |||
| <property name="text"> | |||
| @@ -85,6 +85,13 @@ | |||
| </property> | |||
| </widget> | |||
| </item> | |||
| <item> | |||
| <widget class="QCheckBox" name="ch_jsfx"> | |||
| <property name="text"> | |||
| <string>JSFX</string> | |||
| </property> | |||
| </widget> | |||
| </item> | |||
| <item> | |||
| <widget class="Line" name="line_3"> | |||
| <property name="lineWidth"> | |||
| @@ -2154,6 +2154,11 @@ | |||
| <string>SFZ</string> | |||
| </property> | |||
| </item> | |||
| <item> | |||
| <property name="text"> | |||
| <string>JSFX</string> | |||
| </property> | |||
| </item> | |||
| </widget> | |||
| </item> | |||
| <item> | |||
| @@ -2358,6 +2363,28 @@ | |||
| </item> | |||
| </layout> | |||
| </widget> | |||
| <widget class="QWidget" name="tw_paths_jsfx"> | |||
| <layout class="QVBoxLayout" name="verticalLayout_24"> | |||
| <property name="spacing"> | |||
| <number>0</number> | |||
| </property> | |||
| <property name="leftMargin"> | |||
| <number>0</number> | |||
| </property> | |||
| <property name="topMargin"> | |||
| <number>0</number> | |||
| </property> | |||
| <property name="rightMargin"> | |||
| <number>0</number> | |||
| </property> | |||
| <property name="bottomMargin"> | |||
| <number>0</number> | |||
| </property> | |||
| <item> | |||
| <widget class="QListWidget" name="lw_jsfx"/> | |||
| </item> | |||
| </layout> | |||
| </widget> | |||
| </widget> | |||
| </item> | |||
| <item> | |||
| @@ -509,6 +509,14 @@ JUCE_GUI_BASICS_LIBS = -lgdi32 -limm32 -lcomdlg32 -lole32 | |||
| endif # USING_JUCE | |||
| endif # WIN32 | |||
| YSFX_FLAGS = -I$(CWD)/modules/ysfx/include | |||
| ifeq ($(MACOS),true) | |||
| YSFX_GRAPHICS_LIBS = -framework Cocoa -framework Carbon -framework Metal -framework Foundation | |||
| else ifneq ($(WIN32),true) | |||
| YSFX_GRAPHICS_FLAGS = $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --cflags freetype2 fontconfig) | |||
| YSFX_GRAPHICS_LIBS = $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --libs freetype2 fontconfig) | |||
| endif | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| ifeq ($(STATIC_PLUGIN_TARGET),true) | |||
| @@ -978,6 +978,7 @@ public: | |||
| static CarlaPluginPtr newVST2(const Initializer& init); | |||
| static CarlaPluginPtr newVST3(const Initializer& init); | |||
| static CarlaPluginPtr newAU(const Initializer& init); | |||
| static CarlaPluginPtr newJSFX(const Initializer& init); | |||
| static CarlaPluginPtr newJuce(const Initializer& init, const char* format); | |||
| static CarlaPluginPtr newFluidSynth(const Initializer& init, PluginType ptype, bool use16Outs); | |||
| @@ -64,6 +64,8 @@ STANDALONE_LIBS += $(MODULEDIR)/rtaudio.a | |||
| STANDALONE_LIBS += $(MODULEDIR)/rtmidi.a | |||
| endif | |||
| STANDALONE_LIBS += $(MODULEDIR)/ysfx.a | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| STANDALONE_LINK_FLAGS = $(HYLIA_LIBS) | |||
| @@ -97,6 +99,8 @@ STANDALONE_LINK_FLAGS += $(RTAUDIO_LIBS) | |||
| STANDALONE_LINK_FLAGS += $(RTMIDI_LIBS) | |||
| endif | |||
| STANDALONE_LINK_FLAGS += $(YSFX_GRAPHICS_LIBS) | |||
| ifeq ($(JACKBRIDGE_DIRECT),true) | |||
| STANDALONE_LINK_FLAGS += $(JACK_LIBS) | |||
| endif | |||
| @@ -596,6 +596,7 @@ bool CarlaEngine::addPlugin(const BinaryType btype, | |||
| && ptype != PLUGIN_GIG | |||
| && ptype != PLUGIN_SF2 | |||
| && ptype != PLUGIN_SFZ | |||
| && ptype != PLUGIN_JSFX | |||
| && ptype != PLUGIN_JACK; | |||
| // Prefer bridges for some specific plugins | |||
| @@ -705,10 +706,6 @@ bool CarlaEngine::addPlugin(const BinaryType btype, | |||
| plugin = CarlaPlugin::newAU(initializer); | |||
| break; | |||
| case PLUGIN_JSFX: | |||
| setLastError("Not implemented yet"); | |||
| break; | |||
| #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH | |||
| case PLUGIN_INTERNAL: | |||
| plugin = CarlaPlugin::newNative(initializer); | |||
| @@ -741,6 +738,10 @@ bool CarlaEngine::addPlugin(const BinaryType btype, | |||
| # endif | |||
| break; | |||
| case PLUGIN_JSFX: | |||
| plugin = CarlaPlugin::newJSFX(initializer); | |||
| break; | |||
| case PLUGIN_JACK: | |||
| # ifndef STATIC_PLUGIN_TARGET | |||
| plugin = CarlaPlugin::newJackApp(initializer); | |||
| @@ -754,6 +755,7 @@ bool CarlaEngine::addPlugin(const BinaryType btype, | |||
| case PLUGIN_GIG: | |||
| case PLUGIN_SF2: | |||
| case PLUGIN_SFZ: | |||
| case PLUGIN_JSFX: | |||
| case PLUGIN_JACK: | |||
| setLastError("Plugin bridges cannot handle this binary"); | |||
| break; | |||
| @@ -34,6 +34,7 @@ OBJS = \ | |||
| $(OBJDIR)/CarlaPluginVST2.cpp.o \ | |||
| $(OBJDIR)/CarlaPluginVST3.cpp.o \ | |||
| $(OBJDIR)/CarlaPluginAU.cpp.o \ | |||
| $(OBJDIR)/CarlaPluginJSFX.cpp.o \ | |||
| $(OBJDIR)/CarlaPluginJuce.cpp.o \ | |||
| $(OBJDIR)/CarlaPluginFluidSynth.cpp.o \ | |||
| $(OBJDIR)/CarlaPluginSFZero.cpp.o | |||
| @@ -72,6 +73,11 @@ $(OBJDIR)/CarlaPluginFluidSynth.cpp.o: CarlaPluginFluidSynth.cpp | |||
| @echo "Compiling $<" | |||
| $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(FLUIDSYNTH_FLAGS) -c -o $@ | |||
| $(OBJDIR)/CarlaPluginJSFX.cpp.o: CarlaPluginJSFX.cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $<" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) $(YSFX_FLAGS) -c -o $@ | |||
| ifeq ($(MACOS),true) | |||
| $(OBJDIR)/CarlaPluginVST2.cpp.o: CarlaPluginVST2.cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @@ -21,6 +21,7 @@ | |||
| #include "CarlaString.hpp" | |||
| #include "CarlaBackendUtils.hpp" | |||
| #include "CarlaLv2Utils.hpp" | |||
| #include "CarlaJsfxUtils.hpp" | |||
| #if defined(USING_JUCE) && defined(CARLA_OS_MAC) | |||
| # include "AppConfig.h" | |||
| @@ -96,6 +97,37 @@ static void findSFZs(const char* const sfzPaths) | |||
| } | |||
| } | |||
| } | |||
| // ------------------------------------------------------------------------------------------------------------------- | |||
| static water::Array<CarlaJsfxUnit> gJSFXs; | |||
| static void findJSFXs(const char* const jsfxPaths) | |||
| { | |||
| gJSFXs.clearQuick(); | |||
| CARLA_SAFE_ASSERT_RETURN(jsfxPaths != nullptr,); | |||
| if (jsfxPaths[0] == '\0') | |||
| return; | |||
| const water::StringArray splitPaths(water::StringArray::fromTokens(jsfxPaths, CARLA_OS_SPLIT_STR, "")); | |||
| for (water::String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it) | |||
| { | |||
| water::Array<water::File> results; | |||
| water::File path(*it); | |||
| if (path.findChildFiles(results, water::File::findFiles|water::File::ignoreHiddenFiles, true, "*") > 0) | |||
| { | |||
| for (const water::File& file : results) | |||
| { | |||
| water::String fileExt = file.getFileExtension(); | |||
| if (fileExt.isEmpty() || fileExt.equalsIgnoreCase(".jsfx")) | |||
| gJSFXs.add(CarlaJsfxUnit(path, file)); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| // ------------------------------------------------------------------------------------------------------------------- | |||
| @@ -630,6 +662,81 @@ static const CarlaCachedPluginInfo* get_cached_plugin_sfz(const water::File& fil | |||
| // ------------------------------------------------------------------------------------------------------------------- | |||
| static const CarlaCachedPluginInfo* get_cached_plugin_jsfx(const CarlaJsfxUnit& unit) | |||
| { | |||
| static CarlaCachedPluginInfo info; | |||
| ysfx_config_u config(ysfx_config_new()); | |||
| const water::String rootPath = unit.getRootPath().getFullPathName(); | |||
| const water::String filePath = unit.getFilePath().getFullPathName(); | |||
| ysfx_register_builtin_audio_formats(config.get()); | |||
| ysfx_set_import_root(config.get(), rootPath.toRawUTF8()); | |||
| ysfx_guess_file_roots(config.get(), filePath.toRawUTF8()); | |||
| ysfx_set_log_reporter(config.get(), &CarlaJsfxLogging::logErrorsOnly); | |||
| ysfx_u effect(ysfx_new(config.get())); | |||
| if (! ysfx_load_file(effect.get(), filePath.toRawUTF8(), 0)) | |||
| { | |||
| info.valid = false; | |||
| return &info; | |||
| } | |||
| // plugins with neither @block nor @sample are valid, but they are useless | |||
| // also use this as a sanity check against misdetected files | |||
| // since JSFX parsing is so permissive, it might accept lambda text files | |||
| if (! ysfx_has_section(effect.get(), ysfx_section_block) && | |||
| ! ysfx_has_section(effect.get(), ysfx_section_sample)) | |||
| { | |||
| info.valid = false; | |||
| return &info; | |||
| } | |||
| static CarlaString name, label, maker; | |||
| label = unit.getFileId().toRawUTF8(); | |||
| name = ysfx_get_name(effect.get()); | |||
| maker = ysfx_get_author(effect.get()); | |||
| info.valid = true; | |||
| info.category = CarlaJsfxCategories::getFromEffect(effect.get()); | |||
| info.audioIns = ysfx_get_num_inputs(effect.get()); | |||
| info.audioOuts = ysfx_get_num_outputs(effect.get()); | |||
| info.cvIns = 0; | |||
| info.cvOuts = 0; | |||
| info.midiIns = 1; | |||
| info.midiOuts = 1; | |||
| info.parameterIns = 0; | |||
| info.parameterOuts = 0; | |||
| for (uint32_t sliderIndex = 0; sliderIndex < ysfx_max_sliders; ++sliderIndex) | |||
| { | |||
| if (ysfx_slider_exists(effect.get(), sliderIndex)) | |||
| ++info.parameterIns; | |||
| } | |||
| info.hints = 0; | |||
| #if 0 // TODO(jsfx) when supporting custom graphics | |||
| if (ysfx_has_section(effect.get(), ysfx_section_gfx)) | |||
| info.hints |= CB::PLUGIN_HAS_CUSTOM_UI; | |||
| #endif | |||
| info.name = name.buffer(); | |||
| info.label = label.buffer(); | |||
| info.maker = maker.buffer(); | |||
| info.copyright = gCachedPluginsNullCharPtr; | |||
| return &info; | |||
| } | |||
| // ------------------------------------------------------------------------------------------------------------------- | |||
| uint carla_get_cached_plugin_count(CB::PluginType ptype, const char* pluginPath) | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(isCachedPluginType(ptype), 0); | |||
| @@ -661,6 +768,11 @@ uint carla_get_cached_plugin_count(CB::PluginType ptype, const char* pluginPath) | |||
| return static_cast<uint>(gSFZs.size()); | |||
| } | |||
| case CB::PLUGIN_JSFX: { | |||
| findJSFXs(pluginPath); | |||
| return static_cast<uint>(gJSFXs.size()); | |||
| } | |||
| default: | |||
| return 0; | |||
| } | |||
| @@ -706,6 +818,11 @@ const CarlaCachedPluginInfo* carla_get_cached_plugin_info(CB::PluginType ptype, | |||
| return get_cached_plugin_sfz(gSFZs[index]); | |||
| } | |||
| case CB::PLUGIN_JSFX: { | |||
| CARLA_SAFE_ASSERT_BREAK(index < static_cast<uint>(gJSFXs.size())); | |||
| return get_cached_plugin_jsfx(gJSFXs.getUnchecked(static_cast<int>(index))); | |||
| } | |||
| default: | |||
| break; | |||
| } | |||
| @@ -76,6 +76,8 @@ const char* carla_get_complete_license_text() | |||
| "<li>AU plugin support (using JUCE)</li>" | |||
| #endif | |||
| "<li>JSFX plugin support (using ysfx)</li>" | |||
| // Sample kit libraries | |||
| #if defined(HAVE_FLUIDSYNTH) && !defined(BUILD_BRIDGE_ALTERNATIVE_ARCH) | |||
| "<li>FluidSynth library v" FLUIDSYNTH_VERSION " for SF2/3 support</li>" | |||
| @@ -185,6 +187,9 @@ const char* const* carla_get_supported_file_extensions() | |||
| // SFZ | |||
| "sfz", | |||
| // JSFX | |||
| "jsfx", | |||
| // terminator | |||
| nullptr | |||
| }; | |||
| @@ -10,6 +10,7 @@ include ../Makefile.mk | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| BUILD_CXX_FLAGS += $(FLUIDSYNTH_FLAGS) | |||
| BUILD_CXX_FLAGS += $(YSFX_FLAGS) | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| @@ -62,6 +63,9 @@ LINK_FLAGS += $(JUCE_GUI_BASICS_LIBS) | |||
| LINK_FLAGS += $(JUCE_GUI_EXTRA_LIBS) | |||
| endif | |||
| LINK_FLAGS += $(MODULEDIR)/ysfx.a | |||
| LINK_FLAGS += $(YSFX_GRAPHICS_LIBS) | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| all: $(TARGETS) | |||
| @@ -147,6 +147,9 @@ LIBS_win64 += $(MODULEDIR)/juce_gui_extra.win64.a | |||
| LINK_FLAGS += $(JUCE_GUI_EXTRA_LIBS) | |||
| endif | |||
| LIBS_native += $(MODULEDIR)/ysfx.a | |||
| LINK_FLAGS += $(YSFX_GRAPHICS_LIBS) | |||
| ifeq ($(JACKBRIDGE_DIRECT),true) | |||
| LINK_FLAGS += $(JACK_LIBS) | |||
| endif | |||
| @@ -205,6 +208,7 @@ OBJS_native = \ | |||
| $(OBJDIR)/CarlaPluginVST2.cpp.o \ | |||
| $(OBJDIR)/CarlaPluginVST3.cpp.o \ | |||
| $(OBJDIR)/CarlaPluginAU.cpp.o \ | |||
| $(OBJDIR)/CarlaPluginJSFX.cpp.o \ | |||
| $(OBJDIR)/CarlaPluginJuce.cpp.o \ | |||
| $(OBJDIR)/CarlaPluginFluidSynth.cpp.o \ | |||
| $(OBJDIR)/CarlaPluginSFZero.cpp.o \ | |||
| @@ -306,6 +310,11 @@ $(BINDIR)/$(MODULENAME)-win64.exe: $(OBJS_win64) $(LIBS_win64) | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| # native | |||
| $(OBJDIR)/CarlaPluginJSFX.cpp.o: $(CWD)/backend/plugin/CarlaPluginJSFX.cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling CarlaPluginJSFX.cpp (bridge)" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) $(NATIVE_BUILD_FLAGS) $(YSFX_FLAGS) -c -o $@ | |||
| ifeq ($(MACOS),true) | |||
| $(OBJDIR)/CarlaPluginVST2.cpp.o: $(CWD)/backend/plugin/CarlaPluginVST2.cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @@ -69,6 +69,8 @@ NATIVE_BUILD_FLAGS += $(FLUIDSYNTH_FLAGS) | |||
| NATIVE_LINK_FLAGS += $(FLUIDSYNTH_LIBS) | |||
| endif | |||
| NATIVE_BUILD_FLAGS += $(YSFX_FLAGS) | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| LIBS_native = $(MODULEDIR)/lilv.a | |||
| @@ -152,6 +154,9 @@ LIBS_win64 += $(MODULEDIR)/juce_gui_extra.win64.a | |||
| LINK_FLAGS += $(JUCE_GUI_EXTRA_LIBS) | |||
| endif # USING_JUCE | |||
| LIBS_native += $(MODULEDIR)/ysfx.a | |||
| LINK_FLAGS += $(YSFX_GRAPHICS_LIBS) | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| OBJS_native = $(OBJDIR)/$(MODULENAME).cpp.o | |||
| @@ -18,6 +18,7 @@ | |||
| #include "CarlaBackendUtils.hpp" | |||
| #include "CarlaLibUtils.hpp" | |||
| #include "CarlaMathUtils.hpp" | |||
| #include "CarlaScopeUtils.hpp" | |||
| #include "CarlaMIDI.h" | |||
| #include "LinkedList.hpp" | |||
| @@ -54,6 +55,7 @@ | |||
| #ifndef BUILD_BRIDGE | |||
| # include "CarlaDssiUtils.cpp" | |||
| # include "CarlaJsfxUtils.hpp" | |||
| # include "../backend/utils/CachedPlugins.cpp" | |||
| #endif | |||
| @@ -2112,6 +2114,67 @@ static void do_fluidsynth_check(const char* const filename, const PluginType typ | |||
| #endif | |||
| } | |||
| // ------------------------------------------------------------------------------------------------------------------- | |||
| #ifndef BUILD_BRIDGE | |||
| static void do_jsfx_check(const char* const filename, bool doInit) | |||
| { | |||
| const water::File file = File(CharPointer_UTF8(filename)); | |||
| ysfx_config_u config(ysfx_config_new()); | |||
| ysfx_register_builtin_audio_formats(config.get()); | |||
| ysfx_guess_file_roots(config.get(), filename); | |||
| ysfx_set_log_reporter(config.get(), &CarlaJsfxLogging::logErrorsOnly); | |||
| ysfx_u effect(ysfx_new(config.get())); | |||
| uint hints = 0; | |||
| // do not attempt to compile it, because the import path is not known | |||
| (void)doInit; | |||
| if (! ysfx_load_file(effect.get(), filename, 0)) | |||
| { | |||
| DISCOVERY_OUT("error", "Cannot read the JSFX header"); | |||
| return; | |||
| } | |||
| const char* const name = ysfx_get_name(effect.get()); | |||
| // author and category are extracted from the pseudo-tags | |||
| const char* const author = ysfx_get_author(effect.get()); | |||
| const CB::PluginCategory category = CarlaJsfxCategories::getFromEffect(effect.get()); | |||
| const uint32_t audioIns = ysfx_get_num_inputs(effect.get()); | |||
| const uint32_t audioOuts = ysfx_get_num_outputs(effect.get()); | |||
| const uint32_t midiIns = 1; | |||
| const uint32_t midiOuts = 1; | |||
| uint32_t parameters = 0; | |||
| for (uint32_t sliderIndex = 0; sliderIndex < ysfx_max_sliders; ++sliderIndex) | |||
| { | |||
| if (ysfx_slider_exists(effect.get(), sliderIndex)) | |||
| ++parameters; | |||
| } | |||
| DISCOVERY_OUT("init", "-----------"); | |||
| DISCOVERY_OUT("build", BINARY_NATIVE); | |||
| DISCOVERY_OUT("hints", hints); | |||
| DISCOVERY_OUT("category", getPluginCategoryAsString(category)); | |||
| DISCOVERY_OUT("name", name); | |||
| DISCOVERY_OUT("maker", author); | |||
| DISCOVERY_OUT("label", filename); | |||
| DISCOVERY_OUT("audio.ins", audioIns); | |||
| DISCOVERY_OUT("audio.outs", audioOuts); | |||
| DISCOVERY_OUT("midi.ins", midiIns); | |||
| DISCOVERY_OUT("midi.outs", midiOuts); | |||
| DISCOVERY_OUT("parameters.ins", parameters); | |||
| DISCOVERY_OUT("end", "------------"); | |||
| } | |||
| #endif | |||
| // ------------------------------ main entry point ------------------------------ | |||
| int main(int argc, char* argv[]) | |||
| @@ -2282,6 +2345,12 @@ int main(int argc, char* argv[]) | |||
| #endif | |||
| break; | |||
| #ifndef BUILD_BRIDGE | |||
| case PLUGIN_JSFX: | |||
| do_jsfx_check(filename, doInit); | |||
| break; | |||
| #endif | |||
| case PLUGIN_DLS: | |||
| case PLUGIN_GIG: | |||
| case PLUGIN_SF2: | |||
| @@ -209,6 +209,7 @@ PluginRefreshW::PluginRefreshW(QWidget* const parent, const CarlaHost& host) | |||
| connect(self->ui.ch_au, SIGNAL(clicked()), SLOT(slot_checkTools())); | |||
| connect(self->ui.ch_sf2, SIGNAL(clicked()), SLOT(slot_checkTools())); | |||
| connect(self->ui.ch_sfz, SIGNAL(clicked()), SLOT(slot_checkTools())); | |||
| connect(self->ui.ch_jsfx, SIGNAL(clicked()), SLOT(slot_checkTools())); | |||
| connect(&self->fThread, SIGNAL(pluginLook(float, QString)), SLOT(slot_handlePluginLook(float, QString))); | |||
| connect(&self->fThread, SIGNAL(finished(int)), SLOT(slot_handlePluginThreadFinished())); | |||
| @@ -2676,6 +2676,7 @@ QString setEngineSettings(CarlaHost& host) | |||
| QStringList VST3_PATH = settings.valueStringList(CARLA_KEY_PATHS_VST3, CARLA_DEFAULT_VST3_PATH); | |||
| QStringList SF2_PATH = settings.valueStringList(CARLA_KEY_PATHS_SF2, CARLA_DEFAULT_SF2_PATH); | |||
| QStringList SFZ_PATH = settings.valueStringList(CARLA_KEY_PATHS_SFZ, CARLA_DEFAULT_SFZ_PATH); | |||
| QStringList JSFX_PATH = settings.valueStringList(CARLA_KEY_PATHS_JSFX, CARLA_DEFAULT_JSFX_PATH); | |||
| /* | |||
| // TODO | |||
| @@ -2686,6 +2687,7 @@ QString setEngineSettings(CarlaHost& host) | |||
| carla_set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_VST3, splitter.join(VST3_PATH)) | |||
| carla_set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_SF2, splitter.join(SF2_PATH)) | |||
| carla_set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_SFZ, splitter.join(SFZ_PATH)) | |||
| carla_set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_JSFX, splitter.join(JSFX_PATH)) | |||
| */ | |||
| //----------------------------------------------------------------------------------------------------------------- | |||
| @@ -426,7 +426,8 @@ enum PluginPathIndexes { | |||
| PLUGINPATH_INDEX_VST2, | |||
| PLUGINPATH_INDEX_VST3, | |||
| PLUGINPATH_INDEX_SF2, | |||
| PLUGINPATH_INDEX_SFZ | |||
| PLUGINPATH_INDEX_SFZ, | |||
| PLUGINPATH_INDEX_JSFX | |||
| }; | |||
| /* | |||
| @@ -662,6 +663,7 @@ struct CarlaSettingsW::PrivateData { | |||
| QStringList vst3s = settings.valueStringList(CARLA_KEY_PATHS_VST3, CARLA_DEFAULT_VST3_PATH); | |||
| QStringList sf2s = settings.valueStringList(CARLA_KEY_PATHS_SF2, CARLA_DEFAULT_SF2_PATH); | |||
| QStringList sfzs = settings.valueStringList(CARLA_KEY_PATHS_SFZ, CARLA_DEFAULT_SFZ_PATH); | |||
| QStringList jsfxs = settings.valueStringList(CARLA_KEY_PATHS_JSFX, CARLA_DEFAULT_JSFX_PATH); | |||
| ladspas.sort(); | |||
| dssis.sort(); | |||
| @@ -670,6 +672,7 @@ struct CarlaSettingsW::PrivateData { | |||
| vst3s.sort(); | |||
| sf2s.sort(); | |||
| sfzs.sort(); | |||
| jsfxs.sort(); | |||
| for (const QString& ladspa : ladspas) | |||
| { | |||
| @@ -713,6 +716,12 @@ struct CarlaSettingsW::PrivateData { | |||
| ui.lw_sfz->addItem(sfz); | |||
| } | |||
| for (const QString& jsfx : jsfxs) | |||
| { | |||
| if (jsfx.isEmpty()) continue; | |||
| ui.lw_jsfx->addItem(jsfx); | |||
| } | |||
| // ------------------------------------------------------------------------------------------------------------ | |||
| // Wine | |||
| @@ -883,6 +892,7 @@ struct CarlaSettingsW::PrivateData { | |||
| QStringList vst3s; | |||
| QStringList sf2s; | |||
| QStringList sfzs; | |||
| QStringList jsfxs; | |||
| for (int i=0; i < ui.lw_ladspa->count(); ++i) | |||
| ladspas.append(ui.lw_ladspa->item(i)->text()); | |||
| @@ -905,6 +915,9 @@ struct CarlaSettingsW::PrivateData { | |||
| for (int i=0; i < ui.lw_sfz->count(); ++i) | |||
| sfzs.append(ui.lw_sfz->item(i)->text()); | |||
| for (int i=0; i < ui.lw_jsfx->count(); ++i) | |||
| jsfxs.append(ui.lw_jsfx->item(i)->text()); | |||
| /* TODO | |||
| host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_LADSPA, splitter.join(ladspas)); | |||
| host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_DSSI, splitter.join(dssis)); | |||
| @@ -913,6 +926,7 @@ struct CarlaSettingsW::PrivateData { | |||
| host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_VST3, splitter.join(vst3s)); | |||
| host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_SF2, splitter.join(sf2s)); | |||
| host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_SFZ, splitter.join(sfzs)); | |||
| host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_JSFX, splitter.join(jsfxs)); | |||
| */ | |||
| settings.setValue(CARLA_KEY_PATHS_LADSPA, ladspas); | |||
| @@ -922,6 +936,7 @@ struct CarlaSettingsW::PrivateData { | |||
| settings.setValue(CARLA_KEY_PATHS_VST3, vst3s); | |||
| settings.setValue(CARLA_KEY_PATHS_SF2, sf2s); | |||
| settings.setValue(CARLA_KEY_PATHS_SFZ, sfzs); | |||
| settings.setValue(CARLA_KEY_PATHS_JSFX, jsfxs); | |||
| // ------------------------------------------------------------------------------------------------------------ | |||
| // Wine | |||
| @@ -1147,6 +1162,19 @@ struct CarlaSettingsW::PrivateData { | |||
| ui.lw_sfz->addItem(path); | |||
| } | |||
| break; | |||
| case PLUGINPATH_INDEX_JSFX: | |||
| paths = CARLA_DEFAULT_JSFX_PATH; | |||
| paths.sort(); | |||
| ui.lw_jsfx->clear(); | |||
| for (const auto& path : paths) | |||
| { | |||
| if (path.isEmpty()) | |||
| continue; | |||
| ui.lw_jsfx->addItem(path); | |||
| } | |||
| break; | |||
| } | |||
| break; | |||
| } | |||
| @@ -1310,6 +1338,7 @@ CarlaSettingsW::CarlaSettingsW(QWidget* const parent, CarlaHost& host, const boo | |||
| connect(self->ui.lw_vst3, SIGNAL(currentRowChanged(int)), SLOT(slot_pluginPathRowChanged(int))); | |||
| connect(self->ui.lw_sf2, SIGNAL(currentRowChanged(int)), SLOT(slot_pluginPathRowChanged(int))); | |||
| connect(self->ui.lw_sfz, SIGNAL(currentRowChanged(int)), SLOT(slot_pluginPathRowChanged(int))); | |||
| connect(self->ui.lw_jsfx, SIGNAL(currentRowChanged(int)), SLOT(slot_pluginPathRowChanged(int))); | |||
| connect(self->ui.b_filepaths_add, SIGNAL(clicked()), SLOT(slot_addFilePath())); | |||
| connect(self->ui.b_filepaths_remove, SIGNAL(clicked()), SLOT(slot_removeFilePath())); | |||
| @@ -1335,6 +1364,7 @@ CarlaSettingsW::CarlaSettingsW(QWidget* const parent, CarlaHost& host, const boo | |||
| self->ui.lw_vst3->setCurrentRow(0); | |||
| self->ui.lw_sf2->setCurrentRow(0); | |||
| self->ui.lw_sfz->setCurrentRow(0); | |||
| self->ui.lw_jsfx->setCurrentRow(0); | |||
| self->ui.lw_files_audio->setCurrentRow(0); | |||
| self->ui.lw_files_midi->setCurrentRow(0); | |||
| @@ -1485,6 +1515,9 @@ void CarlaSettingsW::slot_addPluginPath() | |||
| case PLUGINPATH_INDEX_SFZ: | |||
| self->ui.lw_sfz->addItem(newPath); | |||
| break; | |||
| case PLUGINPATH_INDEX_JSFX: | |||
| self->ui.lw_jsfx->addItem(newPath); | |||
| break; | |||
| } | |||
| } | |||
| @@ -1513,6 +1546,9 @@ void CarlaSettingsW::slot_removePluginPath() | |||
| case PLUGINPATH_INDEX_SFZ: | |||
| self->ui.lw_sfz->takeItem(self->ui.lw_sfz->currentRow()); | |||
| break; | |||
| case PLUGINPATH_INDEX_JSFX: | |||
| self->ui.lw_jsfx->takeItem(self->ui.lw_jsfx->currentRow()); | |||
| break; | |||
| } | |||
| } | |||
| @@ -1545,6 +1581,9 @@ void CarlaSettingsW::slot_changePluginPath() | |||
| case PLUGINPATH_INDEX_SFZ: | |||
| currentPath = self->ui.lw_sfz->currentItem()->text(); | |||
| break; | |||
| case PLUGINPATH_INDEX_JSFX: | |||
| currentPath = self->ui.lw_jsfx->currentItem()->text(); | |||
| break; | |||
| } | |||
| const QString newPath = QFileDialog::getExistingDirectory(this, tr("Add Path"), currentPath, QFileDialog::ShowDirsOnly); | |||
| @@ -1575,6 +1614,9 @@ void CarlaSettingsW::slot_changePluginPath() | |||
| case PLUGINPATH_INDEX_SFZ: | |||
| self->ui.lw_sfz->currentItem()->setText(newPath); | |||
| break; | |||
| case PLUGINPATH_INDEX_JSFX: | |||
| self->ui.lw_jsfx->currentItem()->setText(newPath); | |||
| break; | |||
| } | |||
| } | |||
| @@ -1607,6 +1649,9 @@ void CarlaSettingsW::slot_pluginPathTabChanged(const int index) | |||
| case PLUGINPATH_INDEX_SFZ: | |||
| row = self->ui.lw_sfz->currentRow(); | |||
| break; | |||
| case PLUGINPATH_INDEX_JSFX: | |||
| row = self->ui.lw_jsfx->currentRow(); | |||
| break; | |||
| default: | |||
| row = -1; | |||
| break; | |||
| @@ -172,6 +172,7 @@ static const char* const* const MIDI_CC_LIST = { | |||
| #define CARLA_KEY_PATHS_VST3 "Paths/VST3" | |||
| #define CARLA_KEY_PATHS_SF2 "Paths/SF2" | |||
| #define CARLA_KEY_PATHS_SFZ "Paths/SFZ" | |||
| #define CARLA_KEY_PATHS_JSFX "Paths/JSFX" | |||
| #define CARLA_KEY_WINE_EXECUTABLE "Wine/Executable" /* str */ | |||
| #define CARLA_KEY_WINE_AUTO_PREFIX "Wine/AutoPrefix" /* bool */ | |||
| @@ -299,6 +300,7 @@ static const char* const* const MIDI_CC_LIST = { | |||
| #define DEFAULT_VST3_PATH "" | |||
| #define DEFAULT_SF2_PATH "" | |||
| #define DEFAULT_SFZ_PATH "" | |||
| #define DEFAULT_JSFX_PATH "" | |||
| #ifdef CARLA_OS_WIN | |||
| # define CARLA_PATH_SPLITTER ";" | |||
| @@ -338,6 +340,7 @@ if WINDOWS: | |||
| # define CARLA_DEFAULT_VST3_PATH = std::getenv("VST3_PATH", DEFAULT_VST3_PATH).split(CARLA_PATH_SPLITTER) | |||
| # define CARLA_DEFAULT_SF2_PATH = std::getenv("SF2_PATH", DEFAULT_SF2_PATH).split(CARLA_PATH_SPLITTER) | |||
| # define CARLA_DEFAULT_SFZ_PATH = std::getenv("SFZ_PATH", DEFAULT_SFZ_PATH).split(CARLA_PATH_SPLITTER) | |||
| # define CARLA_DEFAULT_JSFX_PATH = std::getenv("JSFX_PATH", DEFAULT_JSFX_PATH).split(CARLA_PATH_SPLITTER) | |||
| #else | |||
| */ | |||
| # define CARLA_DEFAULT_LADSPA_PATH QString(DEFAULT_LADSPA_PATH).split(CARLA_PATH_SPLITTER) | |||
| @@ -347,6 +350,7 @@ if WINDOWS: | |||
| # define CARLA_DEFAULT_VST3_PATH QString(DEFAULT_VST3_PATH).split(CARLA_PATH_SPLITTER) | |||
| # define CARLA_DEFAULT_SF2_PATH QString(DEFAULT_SF2_PATH).split(CARLA_PATH_SPLITTER) | |||
| # define CARLA_DEFAULT_SFZ_PATH QString(DEFAULT_SFZ_PATH).split(CARLA_PATH_SPLITTER) | |||
| # define CARLA_DEFAULT_JSFX_PATH QString(DEFAULT_JSFX_PATH).split(CARLA_PATH_SPLITTER) | |||
| /* | |||
| #endif | |||
| */ | |||
| @@ -410,6 +410,7 @@ class SearchPluginsThread(QThread): | |||
| self.fCheckAU = False | |||
| self.fCheckSF2 = False | |||
| self.fCheckSFZ = False | |||
| self.fCheckJSFX = False | |||
| if WINDOWS: | |||
| toolNative = "carla-discovery-native.exe" | |||
| @@ -447,7 +448,7 @@ class SearchPluginsThread(QThread): | |||
| self.fCheckWin32 = win32 | |||
| self.fCheckWin64 = win64 | |||
| def setSearchPluginTypes(self, ladspa, dssi, lv2, vst2, vst3, au, sf2, sfz): | |||
| def setSearchPluginTypes(self, ladspa, dssi, lv2, vst2, vst3, au, sf2, sfz, jsfx): | |||
| self.fCheckLADSPA = ladspa | |||
| self.fCheckDSSI = dssi | |||
| self.fCheckLV2 = lv2 | |||
| @@ -456,6 +457,7 @@ class SearchPluginsThread(QThread): | |||
| self.fCheckAU = au and MACOS | |||
| self.fCheckSF2 = sf2 | |||
| self.fCheckSFZ = sfz | |||
| self.fCheckJSFX = jsfx | |||
| def stop(self): | |||
| self.fContinueChecking = False | |||
| @@ -522,6 +524,12 @@ class SearchPluginsThread(QThread): | |||
| else: | |||
| self.fCheckSF2 = False | |||
| if self.fCheckJSFX: | |||
| if self.fCheckNative: | |||
| self.fCurCount += 1 | |||
| else: | |||
| self.fCheckJSFX = False | |||
| if self.fCurCount == 0: | |||
| return | |||
| @@ -721,6 +729,11 @@ class SearchPluginsThread(QThread): | |||
| settingsDB.setValue("Plugins/SFZ", kits) | |||
| settingsDB.sync() | |||
| if self.fCheckJSFX: | |||
| kits = self._checkJsfxCached() | |||
| settingsDB.setValue("Plugins/JSFX", kits) | |||
| settingsDB.sync() | |||
| def _checkLADSPA(self, OS, tool, isWine=False): | |||
| ladspaBinaries = [] | |||
| ladspaPlugins = [] | |||
| @@ -994,6 +1007,36 @@ class SearchPluginsThread(QThread): | |||
| self.fLastCheckValue += self.fCurPercentValue | |||
| return sfzKits | |||
| def _checkJsfxCached(self): | |||
| settings = QSafeSettings("falkTX", "Carla2") | |||
| PLUG_PATH = splitter.join(settings.value(CARLA_KEY_PATHS_JSFX, CARLA_DEFAULT_JSFX_PATH, list)) | |||
| del settings | |||
| jsfxPlugins = [] | |||
| self._pluginLook(self.fLastCheckValue, "JSFX plugins...") | |||
| count = gCarla.utils.get_cached_plugin_count(PLUGIN_JSFX, PLUG_PATH) | |||
| if not self.fContinueChecking: | |||
| return jsfxPlugins | |||
| for i in range(count): | |||
| descInfo = gCarla.utils.get_cached_plugin_info(PLUGIN_JSFX, i) | |||
| percent = ( float(i) / count ) * self.fCurPercentValue | |||
| self._pluginLook(self.fLastCheckValue + percent, descInfo['label']) | |||
| if not descInfo['valid']: | |||
| continue | |||
| jsfxPlugins.append(checkPluginCached(descInfo, PLUGIN_JSFX)) | |||
| if not self.fContinueChecking: | |||
| break | |||
| self.fLastCheckValue += self.fCurPercentValue | |||
| return jsfxPlugins | |||
| def _pluginLook(self, percent, plugin): | |||
| self.pluginLook.emit(percent, plugin) | |||
| @@ -1227,6 +1270,7 @@ class PluginRefreshW(QDialog): | |||
| self.ui.ch_au.clicked.connect(self.slot_checkTools) | |||
| self.ui.ch_sf2.clicked.connect(self.slot_checkTools) | |||
| self.ui.ch_sfz.clicked.connect(self.slot_checkTools) | |||
| self.ui.ch_jsfx.clicked.connect(self.slot_checkTools) | |||
| self.fThread.pluginLook.connect(self.slot_handlePluginLook) | |||
| self.fThread.finished.connect(self.slot_handlePluginThreadFinished) | |||
| @@ -1267,6 +1311,9 @@ class PluginRefreshW(QDialog): | |||
| check = settings.value("PluginDatabase/SearchSFZ", False, bool) and self.ui.ch_sfz.isEnabled() | |||
| self.ui.ch_sfz.setChecked(check) | |||
| check = settings.value("PluginDatabase/SearchJSFX", True, bool) and self.ui.ch_jsfx.isEnabled() | |||
| self.ui.ch_jsfx.setChecked(check) | |||
| check = settings.value("PluginDatabase/SearchNative", True, bool) and self.ui.ch_native.isEnabled() | |||
| self.ui.ch_native.setChecked(check) | |||
| @@ -1297,6 +1344,7 @@ class PluginRefreshW(QDialog): | |||
| settings.setValue("PluginDatabase/SearchAU", self.ui.ch_au.isChecked()) | |||
| settings.setValue("PluginDatabase/SearchSF2", self.ui.ch_sf2.isChecked()) | |||
| settings.setValue("PluginDatabase/SearchSFZ", self.ui.ch_sfz.isChecked()) | |||
| settings.setValue("PluginDatabase/SearchJSFX", self.ui.ch_jsfx.isChecked()) | |||
| settings.setValue("PluginDatabase/SearchNative", self.ui.ch_native.isChecked()) | |||
| settings.setValue("PluginDatabase/SearchPOSIX32", self.ui.ch_posix32.isChecked()) | |||
| settings.setValue("PluginDatabase/SearchPOSIX64", self.ui.ch_posix64.isChecked()) | |||
| @@ -1326,13 +1374,14 @@ class PluginRefreshW(QDialog): | |||
| self.ui.ch_posix32.isChecked(), self.ui.ch_posix64.isChecked(), | |||
| self.ui.ch_win32.isChecked(), self.ui.ch_win64.isChecked()) | |||
| ladspa, dssi, lv2, vst, vst3, au, sf2, sfz = (self.ui.ch_ladspa.isChecked(), self.ui.ch_dssi.isChecked(), | |||
| self.ui.ch_lv2.isChecked(), self.ui.ch_vst.isChecked(), | |||
| self.ui.ch_vst3.isChecked(), self.ui.ch_au.isChecked(), | |||
| self.ui.ch_sf2.isChecked(), self.ui.ch_sfz.isChecked()) | |||
| ladspa, dssi, lv2, vst, vst3, au, sf2, sfz, jsfx = (self.ui.ch_ladspa.isChecked(), self.ui.ch_dssi.isChecked(), | |||
| self.ui.ch_lv2.isChecked(), self.ui.ch_vst.isChecked(), | |||
| self.ui.ch_vst3.isChecked(), self.ui.ch_au.isChecked(), | |||
| self.ui.ch_sf2.isChecked(), self.ui.ch_sfz.isChecked(), | |||
| self.ui.ch_jsfx.isChecked()) | |||
| self.fThread.setSearchBinaryTypes(native, posix32, posix64, win32, win64) | |||
| self.fThread.setSearchPluginTypes(ladspa, dssi, lv2, vst, vst3, au, sf2, sfz) | |||
| self.fThread.setSearchPluginTypes(ladspa, dssi, lv2, vst, vst3, au, sf2, sfz, jsfx) | |||
| self.fThread.start() | |||
| # ----------------------------------------------------------------------------------------------------------------- | |||
| @@ -1352,7 +1401,8 @@ class PluginRefreshW(QDialog): | |||
| enabled2 = bool(self.ui.ch_ladspa.isChecked() or self.ui.ch_dssi.isChecked() or | |||
| self.ui.ch_lv2.isChecked() or self.ui.ch_vst.isChecked() or | |||
| self.ui.ch_vst3.isChecked() or self.ui.ch_au.isChecked() or | |||
| self.ui.ch_sf2.isChecked() or self.ui.ch_sfz.isChecked()) | |||
| self.ui.ch_sf2.isChecked() or self.ui.ch_sfz.isChecked() or | |||
| self.ui.ch_jsfx.isChecked()) | |||
| self.ui.b_start.setEnabled(enabled1 and enabled2) | |||
| @@ -1512,6 +1562,7 @@ class PluginDatabaseW(QDialog): | |||
| self.ui.ch_vst.clicked.connect(self.slot_checkFilters) | |||
| self.ui.ch_vst3.clicked.connect(self.slot_checkFilters) | |||
| self.ui.ch_au.clicked.connect(self.slot_checkFilters) | |||
| self.ui.ch_jsfx.clicked.connect(self.slot_checkFilters) | |||
| self.ui.ch_kits.clicked.connect(self.slot_checkFilters) | |||
| self.ui.ch_effects.clicked.connect(self.slot_checkFilters) | |||
| self.ui.ch_instruments.clicked.connect(self.slot_checkFilters) | |||
| @@ -1704,6 +1755,7 @@ class PluginDatabaseW(QDialog): | |||
| self.ui.ch_dssi.setChecked(True) | |||
| self.ui.ch_lv2.setChecked(True) | |||
| self.ui.ch_vst.setChecked(True) | |||
| self.ui.ch_jsfx.setChecked(True) | |||
| self.ui.ch_kits.setChecked(True) | |||
| self.ui.ch_instruments.setChecked(True) | |||
| @@ -1762,6 +1814,7 @@ class PluginDatabaseW(QDialog): | |||
| settings.setValue("PluginDatabase/ShowVST2", self.ui.ch_vst.isChecked()) | |||
| settings.setValue("PluginDatabase/ShowVST3", self.ui.ch_vst3.isChecked()) | |||
| settings.setValue("PluginDatabase/ShowAU", self.ui.ch_au.isChecked()) | |||
| settings.setValue("PluginDatabase/ShowJSFX", self.ui.ch_jsfx.isChecked()) | |||
| settings.setValue("PluginDatabase/ShowKits", self.ui.ch_kits.isChecked()) | |||
| settings.setValue("PluginDatabase/ShowNative", self.ui.ch_native.isChecked()) | |||
| settings.setValue("PluginDatabase/ShowBridged", self.ui.ch_bridged.isChecked()) | |||
| @@ -1822,6 +1875,7 @@ class PluginDatabaseW(QDialog): | |||
| self.ui.ch_vst.setChecked(settings.value("PluginDatabase/ShowVST2", True, bool)) | |||
| self.ui.ch_vst3.setChecked(settings.value("PluginDatabase/ShowVST3", (MACOS or WINDOWS), bool)) | |||
| self.ui.ch_au.setChecked(settings.value("PluginDatabase/ShowAU", MACOS, bool)) | |||
| self.ui.ch_jsfx.setChecked(settings.value("PluginDatabase/ShowJSFX", True, bool)) | |||
| self.ui.ch_kits.setChecked(settings.value("PluginDatabase/ShowKits", True, bool)) | |||
| self.ui.ch_native.setChecked(settings.value("PluginDatabase/ShowNative", True, bool)) | |||
| self.ui.ch_bridged.setChecked(settings.value("PluginDatabase/ShowBridged", True, bool)) | |||
| @@ -1897,6 +1951,7 @@ class PluginDatabaseW(QDialog): | |||
| hideVST2 = not self.ui.ch_vst.isChecked() | |||
| hideVST3 = not self.ui.ch_vst3.isChecked() | |||
| hideAU = not self.ui.ch_au.isChecked() | |||
| hideJSFX = not self.ui.ch_jsfx.isChecked() | |||
| hideKits = not self.ui.ch_kits.isChecked() | |||
| hideNative = not self.ui.ch_native.isChecked() | |||
| @@ -1973,6 +2028,8 @@ class PluginDatabaseW(QDialog): | |||
| self.ui.tableWidget.hideRow(i) | |||
| elif hideAU and ptype == PLUGIN_AU: | |||
| self.ui.tableWidget.hideRow(i) | |||
| elif hideJSFX and ptype == PLUGIN_JSFX: | |||
| self.ui.tableWidget.hideRow(i) | |||
| elif hideNative and isNative: | |||
| self.ui.tableWidget.hideRow(i) | |||
| elif hideBridged and isBridged: | |||
| @@ -2012,7 +2069,7 @@ class PluginDatabaseW(QDialog): | |||
| def _addPluginToTable(self, plugin, ptype): | |||
| if plugin['API'] != PLUGIN_QUERY_API_VERSION: | |||
| return | |||
| if ptype in (self.tr("Internal"), "LV2", "SF2", "SFZ"): | |||
| if ptype in (self.tr("Internal"), "LV2", "SF2", "SFZ", "JSFX"): | |||
| plugin['build'] = BINARY_NATIVE | |||
| index = self.fLastTableIndex | |||
| @@ -2048,6 +2105,7 @@ class PluginDatabaseW(QDialog): | |||
| #elif ptype == PLUGIN_SFZ: | |||
| #ptypeStr = "SFZ" | |||
| #ptypeStrTr = ptypeStr | |||
| # TODO(jsfx) what to do here? | |||
| else: | |||
| return 0 | |||
| @@ -2159,6 +2217,11 @@ class PluginDatabaseW(QDialog): | |||
| auPlugins32 = settingsDB.value("Plugins/AU_posix32", [], list) if MACOS else [] | |||
| # ---------------------------------------------------------------------------------------------------- | |||
| # JSFX | |||
| jsfxPlugins = settingsDB.value("Plugins/JSFX", [], list) | |||
| # ---------------------------------------------------------------------------------------------------- | |||
| # Kits | |||
| @@ -2173,6 +2236,7 @@ class PluginDatabaseW(QDialog): | |||
| vstCount = 0 | |||
| vst3Count = 0 | |||
| au32Count = 0 | |||
| jsfxCount = len(jsfxPlugins) | |||
| sf2Count = 0 | |||
| sfzCount = len(sfzs) | |||
| @@ -2195,15 +2259,15 @@ class PluginDatabaseW(QDialog): | |||
| sf2Count += len(plugins) | |||
| self.ui.tableWidget.setRowCount(self.fLastTableIndex + | |||
| ladspaCount + dssiCount + vstCount + vst3Count + au32Count + | |||
| ladspaCount + dssiCount + vstCount + vst3Count + au32Count + jsfxCount + | |||
| sf2Count + sfzCount) | |||
| if MACOS: | |||
| self.ui.label.setText(self.tr("Have %i Internal, %i LADSPA, %i DSSI, %i LV2, %i VST2, %i VST3 and %i AudioUnit plugins, plus %i Sound Kits" % ( | |||
| internalCount, ladspaCount, dssiCount, lv2Count, vstCount, vst3Count, auCount+au32Count, sf2Count+sfzCount))) | |||
| self.ui.label.setText(self.tr("Have %i Internal, %i LADSPA, %i DSSI, %i LV2, %i VST2, %i VST3, %i AudioUnit plugins and %i JSFX plugins, plus %i Sound Kits" % ( | |||
| internalCount, ladspaCount, dssiCount, lv2Count, vstCount, vst3Count, auCount+au32Count, jsfxCount, sf2Count+sfzCount))) | |||
| else: | |||
| self.ui.label.setText(self.tr("Have %i Internal, %i LADSPA, %i DSSI, %i LV2, %i VST2 and %i VST3 plugins, plus %i Sound Kits" % ( | |||
| internalCount, ladspaCount, dssiCount, lv2Count, vstCount, vst3Count, sf2Count+sfzCount))) | |||
| self.ui.label.setText(self.tr("Have %i Internal, %i LADSPA, %i DSSI, %i LV2, %i VST2, %i VST3 plugins and %i JSFX plugins, plus %i Sound Kits" % ( | |||
| internalCount, ladspaCount, dssiCount, lv2Count, vstCount, vst3Count, jsfxCount, sf2Count+sfzCount))) | |||
| # ---------------------------------------------------------------------------------------------------- | |||
| # now add all plugins to the table | |||
| @@ -2228,6 +2292,9 @@ class PluginDatabaseW(QDialog): | |||
| for plugin in plugins: | |||
| self._addPluginToTable(plugin, "AU") | |||
| for plugin in jsfxPlugins: | |||
| self._addPluginToTable(plugin, "JSFX") | |||
| for sf2 in sf2s: | |||
| for sf2_i in sf2: | |||
| self._addPluginToTable(sf2_i, "SF2") | |||
| @@ -3393,6 +3393,7 @@ def setEngineSettings(host, oscPort = None): | |||
| VST3_PATH = settings.value(CARLA_KEY_PATHS_VST3, CARLA_DEFAULT_VST3_PATH, list) | |||
| SF2_PATH = settings.value(CARLA_KEY_PATHS_SF2, CARLA_DEFAULT_SF2_PATH, list) | |||
| SFZ_PATH = settings.value(CARLA_KEY_PATHS_SFZ, CARLA_DEFAULT_SFZ_PATH, list) | |||
| JSFX_PATH = settings.value(CARLA_KEY_PATHS_JSFX, CARLA_DEFAULT_JSFX_PATH, list) | |||
| host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_LADSPA, splitter.join(LADSPA_PATH)) | |||
| host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_DSSI, splitter.join(DSSI_PATH)) | |||
| @@ -3401,6 +3402,7 @@ def setEngineSettings(host, oscPort = None): | |||
| host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_VST3, splitter.join(VST3_PATH)) | |||
| host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_SF2, splitter.join(SF2_PATH)) | |||
| host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_SFZ, splitter.join(SFZ_PATH)) | |||
| host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_JSFX, splitter.join(JSFX_PATH)) | |||
| # -------------------------------------------------------------------------------------------------------- | |||
| # don't continue if plugin | |||
| @@ -45,7 +45,8 @@ from carla_backend import ( | |||
| PLUGIN_VST2, | |||
| PLUGIN_VST3, | |||
| PLUGIN_SF2, | |||
| PLUGIN_SFZ | |||
| PLUGIN_SFZ, | |||
| PLUGIN_JSFX | |||
| ) | |||
| from carla_shared import ( | |||
| @@ -97,6 +98,7 @@ from carla_shared import ( | |||
| CARLA_KEY_PATHS_VST3, | |||
| CARLA_KEY_PATHS_SF2, | |||
| CARLA_KEY_PATHS_SFZ, | |||
| CARLA_KEY_PATHS_JSFX, | |||
| CARLA_KEY_WINE_EXECUTABLE, | |||
| CARLA_KEY_WINE_AUTO_PREFIX, | |||
| CARLA_KEY_WINE_FALLBACK_PREFIX, | |||
| @@ -171,6 +173,7 @@ from carla_shared import ( | |||
| CARLA_DEFAULT_VST3_PATH, | |||
| CARLA_DEFAULT_SF2_PATH, | |||
| CARLA_DEFAULT_SFZ_PATH, | |||
| CARLA_DEFAULT_JSFX_PATH, | |||
| getAndSetPath, | |||
| getIcon, | |||
| fontMetricsHorizontalAdvance, | |||
| @@ -476,6 +479,7 @@ class CarlaSettingsW(QDialog): | |||
| PLUGINPATH_INDEX_VST3 = 4 | |||
| PLUGINPATH_INDEX_SF2 = 5 | |||
| PLUGINPATH_INDEX_SFZ = 6 | |||
| PLUGINPATH_INDEX_JSFX = 7 | |||
| # Single and Multiple client mode is only for JACK, | |||
| # but we still want to match QComboBox index to backend defines, | |||
| @@ -604,6 +608,7 @@ class CarlaSettingsW(QDialog): | |||
| self.ui.lw_vst3.currentRowChanged.connect(self.slot_pluginPathRowChanged) | |||
| self.ui.lw_sf2.currentRowChanged.connect(self.slot_pluginPathRowChanged) | |||
| self.ui.lw_sfz.currentRowChanged.connect(self.slot_pluginPathRowChanged) | |||
| self.ui.lw_jsfx.currentRowChanged.connect(self.slot_pluginPathRowChanged) | |||
| self.ui.b_filepaths_add.clicked.connect(self.slot_addFilePath) | |||
| self.ui.b_filepaths_remove.clicked.connect(self.slot_removeFilePath) | |||
| @@ -629,6 +634,7 @@ class CarlaSettingsW(QDialog): | |||
| self.ui.lw_vst3.setCurrentRow(0) | |||
| self.ui.lw_sf2.setCurrentRow(0) | |||
| self.ui.lw_sfz.setCurrentRow(0) | |||
| self.ui.lw_jsfx.setCurrentRow(0) | |||
| self.ui.lw_files_audio.setCurrentRow(0) | |||
| self.ui.lw_files_midi.setCurrentRow(0) | |||
| @@ -839,6 +845,7 @@ class CarlaSettingsW(QDialog): | |||
| vst3s = settings.value(CARLA_KEY_PATHS_VST3, CARLA_DEFAULT_VST3_PATH, list) | |||
| sf2s = settings.value(CARLA_KEY_PATHS_SF2, CARLA_DEFAULT_SF2_PATH, list) | |||
| sfzs = settings.value(CARLA_KEY_PATHS_SFZ, CARLA_DEFAULT_SFZ_PATH, list) | |||
| jsfxs = settings.value(CARLA_KEY_PATHS_JSFX, CARLA_DEFAULT_JSFX_PATH, list) | |||
| ladspas.sort() | |||
| dssis.sort() | |||
| @@ -847,6 +854,7 @@ class CarlaSettingsW(QDialog): | |||
| vst3s.sort() | |||
| sf2s.sort() | |||
| sfzs.sort() | |||
| jsfxs.sort() | |||
| for ladspa in ladspas: | |||
| if not ladspa: | |||
| @@ -883,6 +891,11 @@ class CarlaSettingsW(QDialog): | |||
| continue | |||
| self.ui.lw_sfz.addItem(sfz) | |||
| for jsfx in jsfxs: | |||
| if not jsfx: | |||
| continue | |||
| self.ui.lw_jsfx.addItem(jsfx) | |||
| # ------------------------------------------------------------------------------------------------------------- | |||
| # Wine | |||
| @@ -1053,6 +1066,7 @@ class CarlaSettingsW(QDialog): | |||
| vst3s = [] | |||
| sf2s = [] | |||
| sfzs = [] | |||
| jsfxs = [] | |||
| for i in range(self.ui.lw_ladspa.count()): | |||
| ladspas.append(self.ui.lw_ladspa.item(i).text()) | |||
| @@ -1075,6 +1089,9 @@ class CarlaSettingsW(QDialog): | |||
| for i in range(self.ui.lw_sfz.count()): | |||
| sfzs.append(self.ui.lw_sfz.item(i).text()) | |||
| for i in range(self.ui.lw_jsfx.count()): | |||
| jsfxs.append(self.ui.lw_jsfx.item(i).text()) | |||
| self.host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_LADSPA, splitter.join(ladspas)) | |||
| self.host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_DSSI, splitter.join(dssis)) | |||
| self.host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_LV2, splitter.join(lv2s)) | |||
| @@ -1082,6 +1099,7 @@ class CarlaSettingsW(QDialog): | |||
| self.host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_VST3, splitter.join(vst3s)) | |||
| self.host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_SF2, splitter.join(sf2s)) | |||
| self.host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_SFZ, splitter.join(sfzs)) | |||
| self.host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_JSFX, splitter.join(jsfxs)) | |||
| settings.setValue(CARLA_KEY_PATHS_LADSPA, ladspas) | |||
| settings.setValue(CARLA_KEY_PATHS_DSSI, dssis) | |||
| @@ -1090,6 +1108,7 @@ class CarlaSettingsW(QDialog): | |||
| settings.setValue(CARLA_KEY_PATHS_VST3, vst3s) | |||
| settings.setValue(CARLA_KEY_PATHS_SF2, sf2s) | |||
| settings.setValue(CARLA_KEY_PATHS_SFZ, sfzs) | |||
| settings.setValue(CARLA_KEY_PATHS_JSFX, jsfxs) | |||
| # ------------------------------------------------------------------------------------------------------------- | |||
| # Wine | |||
| @@ -1277,6 +1296,16 @@ class CarlaSettingsW(QDialog): | |||
| continue | |||
| self.ui.lw_sfz.addItem(path) | |||
| elif curIndex == self.PLUGINPATH_INDEX_JSFX: | |||
| paths = CARLA_DEFAULT_JSFX_PATH | |||
| paths.sort() | |||
| self.ui.lw_jsfx.clear() | |||
| for path in paths: | |||
| if not path: | |||
| continue | |||
| self.ui.lw_jsfx.addItem(path) | |||
| # ------------------------------------------------------------------------------------------------------------- | |||
| # Wine | |||
| @@ -1405,6 +1434,8 @@ class CarlaSettingsW(QDialog): | |||
| self.ui.lw_sf2.addItem(newPath) | |||
| elif curIndex == self.PLUGINPATH_INDEX_SFZ: | |||
| self.ui.lw_sfz.addItem(newPath) | |||
| elif curIndex == self.PLUGINPATH_INDEX_JSFX: | |||
| self.ui.lw_jsfx.addItem(newPath) | |||
| @pyqtSlot() | |||
| def slot_removePluginPath(self): | |||
| @@ -1424,6 +1455,8 @@ class CarlaSettingsW(QDialog): | |||
| self.ui.lw_sf2.takeItem(self.ui.lw_sf2.currentRow()) | |||
| elif curIndex == self.PLUGINPATH_INDEX_SFZ: | |||
| self.ui.lw_sfz.takeItem(self.ui.lw_sfz.currentRow()) | |||
| elif curIndex == self.PLUGINPATH_INDEX_JSFX: | |||
| self.ui.lw_jsfx.takeItem(self.ui.lw_jsfx.currentRow()) | |||
| @pyqtSlot() | |||
| def slot_changePluginPath(self): | |||
| @@ -1443,6 +1476,8 @@ class CarlaSettingsW(QDialog): | |||
| currentPath = self.ui.lw_sf2.currentItem().text() | |||
| elif curIndex == self.PLUGINPATH_INDEX_SFZ: | |||
| currentPath = self.ui.lw_sfz.currentItem().text() | |||
| elif curIndex == self.PLUGINPATH_INDEX_JSFX: | |||
| currentPath = self.ui.lw_jsfx.currentItem().text() | |||
| else: | |||
| currentPath = "" | |||
| @@ -1465,6 +1500,8 @@ class CarlaSettingsW(QDialog): | |||
| self.ui.lw_sf2.currentItem().setText(newPath) | |||
| elif curIndex == self.PLUGINPATH_INDEX_SFZ: | |||
| self.ui.lw_sfz.currentItem().setText(newPath) | |||
| elif curIndex == self.PLUGINPATH_INDEX_JSFX: | |||
| self.ui.lw_jsfx.currentItem().setText(newPath) | |||
| # ----------------------------------------------------------------------------------------------------------------- | |||
| @@ -1484,6 +1521,8 @@ class CarlaSettingsW(QDialog): | |||
| row = self.ui.lw_sf2.currentRow() | |||
| elif index == self.PLUGINPATH_INDEX_SFZ: | |||
| row = self.ui.lw_sfz.currentRow() | |||
| elif index == self.PLUGINPATH_INDEX_JSFX: | |||
| row = self.ui.lw_jsfx.currentRow() | |||
| else: | |||
| row = -1 | |||
| @@ -233,6 +233,7 @@ CARLA_KEY_PATHS_VST2 = "Paths/VST2" | |||
| CARLA_KEY_PATHS_VST3 = "Paths/VST3" | |||
| CARLA_KEY_PATHS_SF2 = "Paths/SF2" | |||
| CARLA_KEY_PATHS_SFZ = "Paths/SFZ" | |||
| CARLA_KEY_PATHS_JSFX = "Paths/JSFX" | |||
| CARLA_KEY_WINE_EXECUTABLE = "Wine/Executable" # str | |||
| CARLA_KEY_WINE_AUTO_PREFIX = "Wine/AutoPrefix" # bool | |||
| @@ -352,6 +353,7 @@ DEFAULT_VST2_PATH = "" | |||
| DEFAULT_VST3_PATH = "" | |||
| DEFAULT_SF2_PATH = "" | |||
| DEFAULT_SFZ_PATH = "" | |||
| DEFAULT_JSFX_PATH = "" | |||
| if WINDOWS: | |||
| splitter = ";" | |||
| @@ -388,6 +390,9 @@ if WINDOWS: | |||
| DEFAULT_VST2_PATH = PROGRAMFILES + "\\VstPlugins" | |||
| DEFAULT_VST2_PATH += ";" + PROGRAMFILES + "\\Steinberg\\VstPlugins" | |||
| DEFAULT_JSFX_PATH = APPDATA + "\\REAPER\Effects" | |||
| #DEFAULT_JSFX_PATH += ";" + PROGRAMFILES + "\\REAPER\\InstallData\\Effects" | |||
| if kIs64bit: | |||
| DEFAULT_VST2_PATH += ";" + COMMONPROGRAMFILES + "\\VST2" | |||
| @@ -402,6 +407,7 @@ if WINDOWS: | |||
| DEFAULT_DSSI_PATH += ";" + PROGRAMFILESx86 + "\\DSSI" | |||
| DEFAULT_VST2_PATH += ";" + PROGRAMFILESx86 + "\\VstPlugins" | |||
| DEFAULT_VST2_PATH += ";" + PROGRAMFILESx86 + "\\Steinberg\\VstPlugins" | |||
| #DEFAULT_JSFX_PATH += ";" + PROGRAMFILESx86 + "\\REAPER\\InstallData\\Effects" | |||
| if COMMONPROGRAMFILESx86: | |||
| DEFAULT_VST3_PATH += COMMONPROGRAMFILESx86 + "\\VST3" | |||
| @@ -444,7 +450,12 @@ elif MACOS: | |||
| DEFAULT_VST3_PATH = HOME + "/Library/Audio/Plug-Ins/VST3" | |||
| DEFAULT_VST3_PATH += ":/Library/Audio/Plug-Ins/VST3" | |||
| DEFAULT_JSFX_PATH = HOME + "/Library/Application Support/REAPER/Effects" | |||
| #DEFAULT_JSFX_PATH += ":/Applications/REAPER.app/Contents/InstallFiles/Effects" | |||
| else: | |||
| CONFIG_HOME = os.getenv("XDG_CONFIG_HOME", HOME + "/.config") | |||
| splitter = ":" | |||
| DEFAULT_LADSPA_PATH = HOME + "/.ladspa" | |||
| @@ -480,6 +491,9 @@ else: | |||
| DEFAULT_SFZ_PATH = HOME + "/.sounds/sfz" | |||
| DEFAULT_SFZ_PATH += ":/usr/share/sounds/sfz" | |||
| DEFAULT_JSFX_PATH = CONFIG_HOME + "/REAPER/Effects" | |||
| #DEFAULT_JSFX_PATH += ":" + "/opt/REAPER/InstallData/Effects" | |||
| if not WINDOWS: | |||
| winePrefix = os.getenv("WINEPREFIX") | |||
| @@ -527,6 +541,7 @@ if readEnvVars: | |||
| CARLA_DEFAULT_VST3_PATH = os.getenv("VST3_PATH", DEFAULT_VST3_PATH).split(splitter) | |||
| CARLA_DEFAULT_SF2_PATH = os.getenv("SF2_PATH", DEFAULT_SF2_PATH).split(splitter) | |||
| CARLA_DEFAULT_SFZ_PATH = os.getenv("SFZ_PATH", DEFAULT_SFZ_PATH).split(splitter) | |||
| CARLA_DEFAULT_JSFX_PATH = os.getenv("JSFX_PATH", DEFAULT_JSFX_PATH).split(splitter) | |||
| else: | |||
| CARLA_DEFAULT_LADSPA_PATH = DEFAULT_LADSPA_PATH.split(splitter) | |||
| @@ -536,6 +551,7 @@ else: | |||
| CARLA_DEFAULT_VST3_PATH = DEFAULT_VST3_PATH.split(splitter) | |||
| CARLA_DEFAULT_SF2_PATH = DEFAULT_SF2_PATH.split(splitter) | |||
| CARLA_DEFAULT_SFZ_PATH = DEFAULT_SFZ_PATH.split(splitter) | |||
| CARLA_DEFAULT_JSFX_PATH = DEFAULT_JSFX_PATH.split(splitter) | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| # Default Plugin Folders (cleanup) | |||
| @@ -43,6 +43,7 @@ from carla_backend import ( | |||
| PLUGIN_VST2, | |||
| PLUGIN_VST3, | |||
| PLUGIN_AU, | |||
| PLUGIN_JSFX, | |||
| PLUGIN_DLS, | |||
| PLUGIN_GIG, | |||
| PLUGIN_SF2, | |||
| @@ -85,6 +86,8 @@ def getPluginTypeAsString(ptype): | |||
| return "VST3" | |||
| if ptype == PLUGIN_AU: | |||
| return "AU" | |||
| if ptype == PLUGIN_JSFX: | |||
| return "JSFX" | |||
| if ptype == PLUGIN_DLS: | |||
| return "DLS" | |||
| if ptype == PLUGIN_GIG: | |||
| @@ -123,6 +126,8 @@ def getPluginTypeFromString(stype): | |||
| return PLUGIN_VST3 | |||
| if stype in ("au", "audiounit"): | |||
| return PLUGIN_AU | |||
| if stype == "jsfx": | |||
| return PLUGIN_JSFX | |||
| if stype == "dls": | |||
| return PLUGIN_DLS | |||
| if stype == "gig": | |||
| @@ -0,0 +1,13 @@ | |||
| Copyright 2021 Jean Pierre Cimalando | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
| you may not use this file except in compliance with the License. | |||
| You may obtain a copy of the License at | |||
| http://www.apache.org/licenses/LICENSE-2.0 | |||
| Unless required by applicable law or agreed to in writing, software | |||
| distributed under the License is distributed on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| See the License for the specific language governing permissions and | |||
| limitations under the License. | |||
| @@ -0,0 +1,202 @@ | |||
| Apache License | |||
| Version 2.0, January 2004 | |||
| http://www.apache.org/licenses/ | |||
| TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||
| 1. Definitions. | |||
| "License" shall mean the terms and conditions for use, reproduction, | |||
| and distribution as defined by Sections 1 through 9 of this document. | |||
| "Licensor" shall mean the copyright owner or entity authorized by | |||
| the copyright owner that is granting the License. | |||
| "Legal Entity" shall mean the union of the acting entity and all | |||
| other entities that control, are controlled by, or are under common | |||
| control with that entity. For the purposes of this definition, | |||
| "control" means (i) the power, direct or indirect, to cause the | |||
| direction or management of such entity, whether by contract or | |||
| otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||
| outstanding shares, or (iii) beneficial ownership of such entity. | |||
| "You" (or "Your") shall mean an individual or Legal Entity | |||
| exercising permissions granted by this License. | |||
| "Source" form shall mean the preferred form for making modifications, | |||
| including but not limited to software source code, documentation | |||
| source, and configuration files. | |||
| "Object" form shall mean any form resulting from mechanical | |||
| transformation or translation of a Source form, including but | |||
| not limited to compiled object code, generated documentation, | |||
| and conversions to other media types. | |||
| "Work" shall mean the work of authorship, whether in Source or | |||
| Object form, made available under the License, as indicated by a | |||
| copyright notice that is included in or attached to the work | |||
| (an example is provided in the Appendix below). | |||
| "Derivative Works" shall mean any work, whether in Source or Object | |||
| form, that is based on (or derived from) the Work and for which the | |||
| editorial revisions, annotations, elaborations, or other modifications | |||
| represent, as a whole, an original work of authorship. For the purposes | |||
| of this License, Derivative Works shall not include works that remain | |||
| separable from, or merely link (or bind by name) to the interfaces of, | |||
| the Work and Derivative Works thereof. | |||
| "Contribution" shall mean any work of authorship, including | |||
| the original version of the Work and any modifications or additions | |||
| to that Work or Derivative Works thereof, that is intentionally | |||
| submitted to Licensor for inclusion in the Work by the copyright owner | |||
| or by an individual or Legal Entity authorized to submit on behalf of | |||
| the copyright owner. For the purposes of this definition, "submitted" | |||
| means any form of electronic, verbal, or written communication sent | |||
| to the Licensor or its representatives, including but not limited to | |||
| communication on electronic mailing lists, source code control systems, | |||
| and issue tracking systems that are managed by, or on behalf of, the | |||
| Licensor for the purpose of discussing and improving the Work, but | |||
| excluding communication that is conspicuously marked or otherwise | |||
| designated in writing by the copyright owner as "Not a Contribution." | |||
| "Contributor" shall mean Licensor and any individual or Legal Entity | |||
| on behalf of whom a Contribution has been received by Licensor and | |||
| subsequently incorporated within the Work. | |||
| 2. Grant of Copyright License. Subject to the terms and conditions of | |||
| this License, each Contributor hereby grants to You a perpetual, | |||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
| copyright license to reproduce, prepare Derivative Works of, | |||
| publicly display, publicly perform, sublicense, and distribute the | |||
| Work and such Derivative Works in Source or Object form. | |||
| 3. Grant of Patent License. Subject to the terms and conditions of | |||
| this License, each Contributor hereby grants to You a perpetual, | |||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
| (except as stated in this section) patent license to make, have made, | |||
| use, offer to sell, sell, import, and otherwise transfer the Work, | |||
| where such license applies only to those patent claims licensable | |||
| by such Contributor that are necessarily infringed by their | |||
| Contribution(s) alone or by combination of their Contribution(s) | |||
| with the Work to which such Contribution(s) was submitted. If You | |||
| institute patent litigation against any entity (including a | |||
| cross-claim or counterclaim in a lawsuit) alleging that the Work | |||
| or a Contribution incorporated within the Work constitutes direct | |||
| or contributory patent infringement, then any patent licenses | |||
| granted to You under this License for that Work shall terminate | |||
| as of the date such litigation is filed. | |||
| 4. Redistribution. You may reproduce and distribute copies of the | |||
| Work or Derivative Works thereof in any medium, with or without | |||
| modifications, and in Source or Object form, provided that You | |||
| meet the following conditions: | |||
| (a) You must give any other recipients of the Work or | |||
| Derivative Works a copy of this License; and | |||
| (b) You must cause any modified files to carry prominent notices | |||
| stating that You changed the files; and | |||
| (c) You must retain, in the Source form of any Derivative Works | |||
| that You distribute, all copyright, patent, trademark, and | |||
| attribution notices from the Source form of the Work, | |||
| excluding those notices that do not pertain to any part of | |||
| the Derivative Works; and | |||
| (d) If the Work includes a "NOTICE" text file as part of its | |||
| distribution, then any Derivative Works that You distribute must | |||
| include a readable copy of the attribution notices contained | |||
| within such NOTICE file, excluding those notices that do not | |||
| pertain to any part of the Derivative Works, in at least one | |||
| of the following places: within a NOTICE text file distributed | |||
| as part of the Derivative Works; within the Source form or | |||
| documentation, if provided along with the Derivative Works; or, | |||
| within a display generated by the Derivative Works, if and | |||
| wherever such third-party notices normally appear. The contents | |||
| of the NOTICE file are for informational purposes only and | |||
| do not modify the License. You may add Your own attribution | |||
| notices within Derivative Works that You distribute, alongside | |||
| or as an addendum to the NOTICE text from the Work, provided | |||
| that such additional attribution notices cannot be construed | |||
| as modifying the License. | |||
| You may add Your own copyright statement to Your modifications and | |||
| may provide additional or different license terms and conditions | |||
| for use, reproduction, or distribution of Your modifications, or | |||
| for any such Derivative Works as a whole, provided Your use, | |||
| reproduction, and distribution of the Work otherwise complies with | |||
| the conditions stated in this License. | |||
| 5. Submission of Contributions. Unless You explicitly state otherwise, | |||
| any Contribution intentionally submitted for inclusion in the Work | |||
| by You to the Licensor shall be under the terms and conditions of | |||
| this License, without any additional terms or conditions. | |||
| Notwithstanding the above, nothing herein shall supersede or modify | |||
| the terms of any separate license agreement you may have executed | |||
| with Licensor regarding such Contributions. | |||
| 6. Trademarks. This License does not grant permission to use the trade | |||
| names, trademarks, service marks, or product names of the Licensor, | |||
| except as required for reasonable and customary use in describing the | |||
| origin of the Work and reproducing the content of the NOTICE file. | |||
| 7. Disclaimer of Warranty. Unless required by applicable law or | |||
| agreed to in writing, Licensor provides the Work (and each | |||
| Contributor provides its Contributions) on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||
| implied, including, without limitation, any warranties or conditions | |||
| of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||
| PARTICULAR PURPOSE. You are solely responsible for determining the | |||
| appropriateness of using or redistributing the Work and assume any | |||
| risks associated with Your exercise of permissions under this License. | |||
| 8. Limitation of Liability. In no event and under no legal theory, | |||
| whether in tort (including negligence), contract, or otherwise, | |||
| unless required by applicable law (such as deliberate and grossly | |||
| negligent acts) or agreed to in writing, shall any Contributor be | |||
| liable to You for damages, including any direct, indirect, special, | |||
| incidental, or consequential damages of any character arising as a | |||
| result of this License or out of the use or inability to use the | |||
| Work (including but not limited to damages for loss of goodwill, | |||
| work stoppage, computer failure or malfunction, or any and all | |||
| other commercial damages or losses), even if such Contributor | |||
| has been advised of the possibility of such damages. | |||
| 9. Accepting Warranty or Additional Liability. While redistributing | |||
| the Work or Derivative Works thereof, You may choose to offer, | |||
| and charge a fee for, acceptance of support, warranty, indemnity, | |||
| or other liability obligations and/or rights consistent with this | |||
| License. However, in accepting such obligations, You may act only | |||
| on Your own behalf and on Your sole responsibility, not on behalf | |||
| of any other Contributor, and only if You agree to indemnify, | |||
| defend, and hold each Contributor harmless for any liability | |||
| incurred by, or claims asserted against, such Contributor by reason | |||
| of your accepting any such warranty or additional liability. | |||
| END OF TERMS AND CONDITIONS | |||
| APPENDIX: How to apply the Apache License to your work. | |||
| To apply the Apache License to your work, attach the following | |||
| boilerplate notice, with the fields enclosed by brackets "[]" | |||
| replaced with your own identifying information. (Don't include | |||
| the brackets!) The text should be enclosed in the appropriate | |||
| comment syntax for the file format. We also recommend that a | |||
| file or class name and description of purpose be included on the | |||
| same "printed page" as the copyright notice for easier | |||
| identification within third-party archives. | |||
| Copyright [yyyy] [name of copyright owner] | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
| you may not use this file except in compliance with the License. | |||
| You may obtain a copy of the License at | |||
| http://www.apache.org/licenses/LICENSE-2.0 | |||
| Unless required by applicable law or agreed to in writing, software | |||
| distributed under the License is distributed on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| See the License for the specific language governing permissions and | |||
| limitations under the License. | |||
| @@ -0,0 +1,169 @@ | |||
| #!/usr/bin/make -f | |||
| # Makefile for eel2 # | |||
| # ----------------- # | |||
| # Created by falkTX | |||
| # | |||
| CWD=../.. | |||
| MODULENAME=ysfx | |||
| include ../Makefile.mk | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| BUILD_C_FLAGS += $(YSFX_FLAGS) | |||
| BUILD_C_FLAGS += $(YSFX_GRAPHICS_FLAGS) | |||
| BUILD_C_FLAGS += -Isources | |||
| BUILD_C_FLAGS += -Ithirdparty/WDL/source -Ithirdparty/dr_libs -Ithirdparty/stb | |||
| BUILD_C_FLAGS += -DYSFX_NO_STANDARD_MUTEX | |||
| BUILD_C_FLAGS += -DEELSCRIPT_NO_NET | |||
| BUILD_C_FLAGS += -DEELSCRIPT_NO_LICE | |||
| BUILD_C_FLAGS += -DWDL_FFT_REALSIZE=8 | |||
| BUILD_C_FLAGS += -DWDL_LINEPARSE_ATOF=ysfx_wdl_atof | |||
| BUILD_C_FLAGS += -DNSEEL_ATOF=ysfx_wdl_atof | |||
| BUILD_CXX_FLAGS += $(YSFX_FLAGS) | |||
| BUILD_CXX_FLAGS += $(YSFX_GRAPHICS_FLAGS) | |||
| BUILD_CXX_FLAGS += -Isources | |||
| BUILD_CXX_FLAGS += -Ithirdparty/WDL/source -Ithirdparty/dr_libs -Ithirdparty/stb | |||
| BUILD_CXX_FLAGS += -DYSFX_NO_STANDARD_MUTEX | |||
| BUILD_CXX_FLAGS += -DEELSCRIPT_NO_NET | |||
| BUILD_CXX_FLAGS += -DEELSCRIPT_NO_LICE | |||
| BUILD_CXX_FLAGS += -DWDL_FFT_REALSIZE=8 | |||
| BUILD_CXX_FLAGS += -DWDL_LINEPARSE_ATOF=ysfx_wdl_atof | |||
| BUILD_CXX_FLAGS += -DNSEEL_ATOF=ysfx_wdl_atof | |||
| ifneq ($(WIN32),true) | |||
| # NOTE: not compatible with MingGW, breaks win32_utf8 | |||
| BUILD_C_FLAGS += -D_FILE_OFFSET_BITS=64 | |||
| BUILD_CXX_FLAGS += -D_FILE_OFFSET_BITS=64 | |||
| endif | |||
| ifeq ($(WIN32),true) | |||
| BUILD_C_FLAGS += -DNOMINMAX | |||
| BUILD_CXX_FLAGS += -DNOMINMAX | |||
| endif | |||
| ifneq ($(WIN32),true) | |||
| ifneq ($(MACOS),true) | |||
| BUILD_C_FLAGS += -DSWELL_LICE_GDI -DSWELL_FONTCONFIG -DSWELL_FREETYPE | |||
| BUILD_CXX_FLAGS += -DSWELL_LICE_GDI -DSWELL_FONTCONFIG -DSWELL_FREETYPE | |||
| endif | |||
| endif | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| OBJS = $(OBJDIR)/sources/ysfx.cpp.o | |||
| OBJS += $(OBJDIR)/sources/ysfx_api_eel.cpp.o | |||
| OBJS += $(OBJDIR)/sources/ysfx_api_file.cpp.o | |||
| OBJS += $(OBJDIR)/sources/ysfx_api_gfx.cpp.o | |||
| OBJS += $(OBJDIR)/sources/ysfx_api_reaper.cpp.o | |||
| OBJS += $(OBJDIR)/sources/ysfx_audio_flac.cpp.o | |||
| OBJS += $(OBJDIR)/sources/ysfx_audio_wav.cpp.o | |||
| OBJS += $(OBJDIR)/sources/ysfx_config.cpp.o | |||
| OBJS += $(OBJDIR)/sources/ysfx_eel_utils.cpp.o | |||
| OBJS += $(OBJDIR)/sources/ysfx_midi.cpp.o | |||
| OBJS += $(OBJDIR)/sources/ysfx_parse.cpp.o | |||
| OBJS += $(OBJDIR)/sources/ysfx_reader.cpp.o | |||
| OBJS += $(OBJDIR)/sources/ysfx_utils.cpp.o | |||
| OBJS += $(OBJDIR)/sources/ysfx_utils_fts.cpp.o | |||
| OBJS += $(OBJDIR)/sources/eel2-gas/sources/asm-nseel-x64-sse.S.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/eel2/nseel-caltab.c.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/eel2/nseel-cfunc.c.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/eel2/nseel-compiler.c.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/eel2/nseel-eval.c.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/eel2/nseel-lextab.c.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/eel2/nseel-ram.c.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/eel2/nseel-yylex.c.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/fft.c.o | |||
| ifeq ($(WIN32),true) | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/win32_utf8.c.o | |||
| endif | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/lice/lice.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/lice/lice_arc.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/lice/lice_colorspace.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/lice/lice_image.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/lice/lice_line.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/lice/lice_palette.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/lice/lice_texgen.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/lice/lice_text.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/lice/lice_textnew.cpp.o | |||
| OBJS += $(OBJDIR)/sources/lice_stb/lice_stb_loaders.cpp.o | |||
| OBJS += $(OBJDIR)/sources/lice_stb/lice_stb_bmp.cpp.o | |||
| OBJS += $(OBJDIR)/sources/lice_stb/lice_stb_gif.cpp.o | |||
| OBJS += $(OBJDIR)/sources/lice_stb/lice_stb_jpg.cpp.o | |||
| OBJS += $(OBJDIR)/sources/lice_stb/lice_stb_png.cpp.o | |||
| OBJS += $(OBJDIR)/sources/lice_stb/lice_stb_write.cpp.o | |||
| ifneq ($(WIN32),true) | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-ini.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell.cpp.o | |||
| ifeq ($(MACOS),true) | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-appstub.mm.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-dlg.mm.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-gdi.mm.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-kb.mm.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-menu.mm.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-misc.mm.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-miscdlg.mm.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-modstub.mm.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-wnd.mm.o | |||
| else | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-appstub-generic.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-dlg-generic.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-gdi-generic.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-gdi-lice.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-generic-gdk.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-generic-headless.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-kb-generic.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-menu-generic.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-misc-generic.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-miscdlg-generic.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-modstub-generic.cpp.o | |||
| OBJS += $(OBJDIR)/thirdparty/WDL/source/WDL/swell/swell-wnd-generic.cpp.o | |||
| endif | |||
| endif | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| all: $(MODULEDIR)/$(MODULENAME).a | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| clean: | |||
| rm -f $(OBJS) $(MODULEDIR)/$(MODULENAME)*.a | |||
| debug: | |||
| $(MAKE) DEBUG=true | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| $(MODULEDIR)/$(MODULENAME).a: $(OBJS) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| $(OBJDIR)/%.c.o: %.c | |||
| -@mkdir -p $(dir $@) | |||
| @echo "Compiling $<" | |||
| @$(CC) $< $(BUILD_C_FLAGS) -c -o $@ | |||
| $(OBJDIR)/%.cpp.o: %.cpp | |||
| -@mkdir -p $(dir $@) | |||
| @echo "Compiling $<" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||
| $(OBJDIR)/%.mm.o: %.mm | |||
| -@mkdir -p $(dir $@) | |||
| @echo "Compiling $<" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||
| $(OBJDIR)/%.S.o: %.S | |||
| -@mkdir -p $(dir $@) | |||
| @echo "Compiling $<" | |||
| @$(CC) $< $(BUILD_ASM_FLAGS) -c -o $@ | |||
| -include $(OBJS:%.o=%.d) | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| @@ -0,0 +1,484 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| #if !defined(YSFX_INCLUDED_YSFX_H) | |||
| #define YSFX_INCLUDED_YSFX_H | |||
| #include <stdio.h> | |||
| #include <stddef.h> | |||
| #include <stdint.h> | |||
| #include <stdbool.h> | |||
| #if defined(_WIN32) | |||
| # include <wchar.h> | |||
| #endif | |||
| //------------------------------------------------------------------------------ | |||
| #ifdef __cplusplus | |||
| extern "C" { | |||
| #endif | |||
| #if !defined(YSFX_API) | |||
| # if defined(_WIN32) && defined(YSFX_DLL_BUILD) | |||
| # define YSFX_API __declspec(dllexport) | |||
| # elif defined(__GNUC__) | |||
| # define YSFX_API __attribute__((visibility("default"))) | |||
| # else | |||
| # define YSFX_API | |||
| # endif | |||
| #endif | |||
| //------------------------------------------------------------------------------ | |||
| // YSFX definitions | |||
| typedef double ysfx_real; | |||
| enum { | |||
| ysfx_max_sliders = 64, | |||
| ysfx_max_channels = 64, | |||
| ysfx_max_midi_buses = 16, | |||
| ysfx_max_triggers = 10, | |||
| }; | |||
| typedef enum ysfx_log_level_e { | |||
| ysfx_log_info, | |||
| ysfx_log_warning, | |||
| ysfx_log_error, | |||
| } ysfx_log_level; | |||
| typedef void (ysfx_log_reporter_t)(intptr_t userdata, ysfx_log_level level, const char *message); | |||
| //------------------------------------------------------------------------------ | |||
| // YSFX configuration | |||
| typedef struct ysfx_config_s ysfx_config_t; | |||
| typedef struct ysfx_audio_format_s ysfx_audio_format_t; | |||
| // create a new configuration | |||
| YSFX_API ysfx_config_t *ysfx_config_new(); | |||
| // delete a configuration | |||
| YSFX_API void ysfx_config_free(ysfx_config_t *config); | |||
| // increase the reference counter | |||
| YSFX_API void ysfx_config_add_ref(ysfx_config_t *config); | |||
| // set the path of the import root, a folder usually named "Effects" | |||
| YSFX_API void ysfx_set_import_root(ysfx_config_t *config, const char *root); | |||
| // set the path of the data root, a folder usually named "Data" | |||
| YSFX_API void ysfx_set_data_root(ysfx_config_t *config, const char *root); | |||
| // get the path of the import root, a folder usually named "Effects" | |||
| YSFX_API const char *ysfx_get_import_root(ysfx_config_t *config); | |||
| // get the path of the data root, a folder usually named "Data" | |||
| YSFX_API const char *ysfx_get_data_root(ysfx_config_t *config); | |||
| // guess the undefined root folders, based on the path to the JSFX file | |||
| YSFX_API void ysfx_guess_file_roots(ysfx_config_t *config, const char *sourcepath); | |||
| // register an audio format into the system | |||
| YSFX_API void ysfx_register_audio_format(ysfx_config_t *config, ysfx_audio_format_t *afmt); | |||
| // register the builtin audio formats (at least WAV file support) | |||
| YSFX_API void ysfx_register_builtin_audio_formats(ysfx_config_t *config); | |||
| // set the log reporting function | |||
| YSFX_API void ysfx_set_log_reporter(ysfx_config_t *config, ysfx_log_reporter_t *reporter); | |||
| // set the callback user data | |||
| YSFX_API void ysfx_set_user_data(ysfx_config_t *config, intptr_t userdata); | |||
| // get a string which textually represents the log level | |||
| YSFX_API const char *ysfx_log_level_string(ysfx_log_level level); | |||
| //------------------------------------------------------------------------------ | |||
| // YSFX effect | |||
| typedef struct ysfx_s ysfx_t; | |||
| // create a new effect, taking a reference to config | |||
| YSFX_API ysfx_t *ysfx_new(ysfx_config_t *config); | |||
| // delete an effect | |||
| YSFX_API void ysfx_free(ysfx_t *fx); | |||
| // increase the reference counter | |||
| YSFX_API void ysfx_add_ref(ysfx_t *fx); | |||
| // get the configuration | |||
| YSFX_API ysfx_config_t *ysfx_get_config(ysfx_t *fx); | |||
| typedef enum ysfx_load_option_e { | |||
| // skip imports; useful just for accessing header information and nothing else | |||
| ysfx_load_ignoring_imports = 1, | |||
| } ysfx_load_option_t; | |||
| // load the source code from file without compiling | |||
| YSFX_API bool ysfx_load_file(ysfx_t *fx, const char *filepath, uint32_t loadopts); | |||
| // unload the source code and any compiled code | |||
| YSFX_API void ysfx_unload(ysfx_t *fx); | |||
| // check whether the effect is loaded | |||
| YSFX_API bool ysfx_is_loaded(ysfx_t *fx); | |||
| // get the name of the effect | |||
| YSFX_API const char *ysfx_get_name(ysfx_t *fx); | |||
| // get the path of the file which is loaded | |||
| YSFX_API const char *ysfx_get_file_path(ysfx_t *fx); | |||
| // get the author of the effect | |||
| YSFX_API const char *ysfx_get_author(ysfx_t *fx); | |||
| // get the number of tags of the effect | |||
| #define ysfx_get_num_tags(fx) ysfx_get_tags((fx), NULL, 0) | |||
| // get the list of tags of the effect | |||
| YSFX_API uint32_t ysfx_get_tags(ysfx_t *fx, const char **dest, uint32_t destsize); | |||
| // get a single tag of the effect | |||
| YSFX_API const char *ysfx_get_tag(ysfx_t *fx, uint32_t index); | |||
| // get the number of inputs | |||
| YSFX_API uint32_t ysfx_get_num_inputs(ysfx_t *fx); | |||
| // get the number of outputs | |||
| YSFX_API uint32_t ysfx_get_num_outputs(ysfx_t *fx); | |||
| // get the name of the input | |||
| YSFX_API const char *ysfx_get_input_name(ysfx_t *fx, uint32_t index); | |||
| // get the name of the output | |||
| YSFX_API const char *ysfx_get_output_name(ysfx_t *fx, uint32_t index); | |||
| // get whether this effect wants metering | |||
| YSFX_API bool ysfx_wants_meters(ysfx_t *fx); | |||
| // get requested dimensions of the graphics area; 0 means host should decide | |||
| YSFX_API bool ysfx_get_gfx_dim(ysfx_t *fx, uint32_t dim[2]); | |||
| typedef enum ysfx_section_type_e { | |||
| ysfx_section_init = 1, | |||
| ysfx_section_slider = 2, | |||
| ysfx_section_block = 3, | |||
| ysfx_section_sample = 4, | |||
| ysfx_section_gfx = 5, | |||
| ysfx_section_serialize = 6, | |||
| } ysfx_section_type_t; | |||
| // get whether the source has the given section | |||
| YSFX_API bool ysfx_has_section(ysfx_t *fx, uint32_t type); | |||
| typedef struct ysfx_slider_range_s { | |||
| ysfx_real def; | |||
| ysfx_real min; | |||
| ysfx_real max; | |||
| ysfx_real inc; | |||
| } ysfx_slider_range_t; | |||
| // determine if slider exists; call from 0 to max-1 to scan available ones | |||
| YSFX_API bool ysfx_slider_exists(ysfx_t *fx, uint32_t index); | |||
| // get the name of a slider | |||
| YSFX_API const char *ysfx_slider_get_name(ysfx_t *fx, uint32_t index); | |||
| // get the range of a slider | |||
| YSFX_API bool ysfx_slider_get_range(ysfx_t *fx, uint32_t index, ysfx_slider_range_t *range); | |||
| // get whether the slider is an enumeration | |||
| YSFX_API bool ysfx_slider_is_enum(ysfx_t *fx, uint32_t index); | |||
| // get the number of labels for the enumeration slider | |||
| #define ysfx_slider_get_enum_size(fx, index) ysfx_slider_get_enum_names((fx), (index), NULL, 0) | |||
| // get the list of labels for the enumeration slider | |||
| YSFX_API uint32_t ysfx_slider_get_enum_names(ysfx_t *fx, uint32_t index, const char **dest, uint32_t destsize); | |||
| // get a single label for the enumeration slider | |||
| YSFX_API const char *ysfx_slider_get_enum_name(ysfx_t *fx, uint32_t slider_index, uint32_t enum_index); | |||
| // get whether the slider is a path (implies enumeration) | |||
| YSFX_API bool ysfx_slider_is_path(ysfx_t *fx, uint32_t index); | |||
| // get whether the slider is initially visible | |||
| YSFX_API bool ysfx_slider_is_initially_visible(ysfx_t *fx, uint32_t index); | |||
| // get the value of the slider | |||
| YSFX_API ysfx_real ysfx_slider_get_value(ysfx_t *fx, uint32_t index); | |||
| // set the value of the slider, and call @slider later if value has changed | |||
| YSFX_API void ysfx_slider_set_value(ysfx_t *fx, uint32_t index, ysfx_real value); | |||
| typedef enum ysfx_compile_option_e { | |||
| // skip compiling the @serialize section | |||
| ysfx_compile_no_serialize = 1 << 0, | |||
| // skip compiling the @gfx section | |||
| ysfx_compile_no_gfx = 1 << 1, | |||
| } ysfx_compile_option_t; | |||
| // compile the previously loaded source | |||
| YSFX_API bool ysfx_compile(ysfx_t *fx, uint32_t compileopts); | |||
| // check whether the effect is compiled | |||
| YSFX_API bool ysfx_is_compiled(ysfx_t *fx); | |||
| // get the block size | |||
| YSFX_API uint32_t ysfx_get_block_size(ysfx_t *fx); | |||
| // get the sample rate | |||
| YSFX_API ysfx_real ysfx_get_sample_rate(ysfx_t *fx); | |||
| // update the block size; don't forget to call @init | |||
| YSFX_API void ysfx_set_block_size(ysfx_t *fx, uint32_t blocksize); | |||
| // update the sample rate; don't forget to call @init | |||
| YSFX_API void ysfx_set_sample_rate(ysfx_t *fx, ysfx_real samplerate); | |||
| // set the capacity of the MIDI buffer | |||
| YSFX_API void ysfx_set_midi_capacity(ysfx_t *fx, uint32_t capacity, bool extensible); | |||
| // activate and invoke @init | |||
| YSFX_API void ysfx_init(ysfx_t *fx); | |||
| // get the output latency | |||
| YSFX_API ysfx_real ysfx_get_pdc_delay(ysfx_t *fx); | |||
| // get the range of channels where output latency applies (end not included) | |||
| YSFX_API void ysfx_get_pdc_channels(ysfx_t *fx, uint32_t channels[2]); | |||
| // get whether the output latency applies to MIDI as well | |||
| YSFX_API bool ysfx_get_pdc_midi(ysfx_t *fx); | |||
| typedef enum ysfx_playback_state_e { | |||
| ysfx_playback_error = 0, | |||
| ysfx_playback_playing = 1, | |||
| ysfx_playback_paused = 2, | |||
| ysfx_playback_recording = 5, | |||
| ysfx_playback_recording_paused = 6, | |||
| } ysfx_playback_state_t; | |||
| typedef struct ysfx_time_info_s { | |||
| // tempo in beats/minute | |||
| ysfx_real tempo; | |||
| // state of the playback (ysfx_playback_state_t) | |||
| uint32_t playback_state; | |||
| // time position in seconds | |||
| ysfx_real time_position; | |||
| // time position in beats | |||
| ysfx_real beat_position; | |||
| // time signature in fraction form | |||
| uint32_t time_signature[2]; | |||
| } ysfx_time_info_t; | |||
| // update time information; do this before processing the cycle | |||
| YSFX_API void ysfx_set_time_info(ysfx_t *fx, const ysfx_time_info_t *info); | |||
| typedef struct ysfx_midi_event_s { | |||
| // the bus number | |||
| uint32_t bus; | |||
| // the frame when it happens within the cycle | |||
| uint32_t offset; | |||
| // the size of the message | |||
| uint32_t size; | |||
| // the contents of the message | |||
| const uint8_t *data; | |||
| } ysfx_midi_event_t; | |||
| // send MIDI, it will be processed during the cycle | |||
| YSFX_API bool ysfx_send_midi(ysfx_t *fx, const ysfx_midi_event_t *event); | |||
| // receive MIDI, after having processed the cycle | |||
| YSFX_API bool ysfx_receive_midi(ysfx_t *fx, ysfx_midi_event_t *event); | |||
| // receive MIDI from a single bus (do not mix with API above, use either) | |||
| YSFX_API bool ysfx_receive_midi_from_bus(ysfx_t *fx, uint32_t bus, ysfx_midi_event_t *event); | |||
| // send a trigger, it will be processed during the cycle | |||
| YSFX_API bool ysfx_send_trigger(ysfx_t *fx, uint32_t index); | |||
| // get a bit mask of sliders whose values must be redisplayed, and clear it to zero | |||
| YSFX_API uint64_t ysfx_fetch_slider_changes(ysfx_t *fx); | |||
| // get a bit mask of sliders whose values must be automated, and clear it to zero | |||
| YSFX_API uint64_t ysfx_fetch_slider_automations(ysfx_t *fx); | |||
| // get a bit mask of sliders currently visible | |||
| YSFX_API uint64_t ysfx_get_slider_visibility(ysfx_t *fx); | |||
| // process a cycle in 32-bit float | |||
| YSFX_API void ysfx_process_float(ysfx_t *fx, const float *const *ins, float *const *outs, uint32_t num_ins, uint32_t num_outs, uint32_t num_frames); | |||
| // process a cycle in 64-bit float | |||
| YSFX_API void ysfx_process_double(ysfx_t *fx, const double *const *ins, double *const *outs, uint32_t num_ins, uint32_t num_outs, uint32_t num_frames); | |||
| typedef struct ysfx_state_slider_s { | |||
| // index of the slider | |||
| uint32_t index; | |||
| // value of the slider | |||
| ysfx_real value; | |||
| } ysfx_state_slider_t; | |||
| typedef struct ysfx_state_s { | |||
| // values of the sliders | |||
| ysfx_state_slider_t *sliders; | |||
| // number of sliders | |||
| uint32_t slider_count; | |||
| // serialized data | |||
| uint8_t *data; | |||
| // size of serialized data | |||
| size_t data_size; | |||
| } ysfx_state_t; | |||
| // load state | |||
| YSFX_API bool ysfx_load_state(ysfx_t *fx, ysfx_state_t *state); | |||
| // save current state; release this object when done | |||
| YSFX_API ysfx_state_t *ysfx_save_state(ysfx_t *fx); | |||
| // release a saved state object | |||
| YSFX_API void ysfx_state_free(ysfx_state_t *state); | |||
| // duplicate a state object | |||
| YSFX_API ysfx_state_t *ysfx_state_dup(ysfx_state_t *state); | |||
| typedef struct ysfx_preset_s { | |||
| // name of the preset | |||
| char *name; | |||
| // state of the preset | |||
| ysfx_state_t *state; | |||
| } ysfx_preset_t; | |||
| typedef struct ysfx_bank_s { | |||
| // name of the bank | |||
| char *name; | |||
| // list of presets | |||
| ysfx_preset_t *presets; | |||
| // number of programs | |||
| uint32_t preset_count; | |||
| } ysfx_bank_t; | |||
| // get the path of the RPL preset bank of the loaded JSFX, if present | |||
| YSFX_API const char *ysfx_get_bank_path(ysfx_t *fx); | |||
| // read a preset bank from RPL file | |||
| YSFX_API ysfx_bank_t *ysfx_load_bank(const char *path); | |||
| // free a preset bank | |||
| YSFX_API void ysfx_bank_free(ysfx_bank_t *bank); | |||
| // type of a function which can enumerate VM variables; returning 0 ends the search | |||
| typedef int (ysfx_enum_vars_callback_t)(const char *name, ysfx_real *var, void *userdata); | |||
| // enumerate all variables currently in the VM | |||
| YSFX_API void ysfx_enum_vars(ysfx_t *fx, ysfx_enum_vars_callback_t *callback, void *userdata); | |||
| // find a single variable in the VM | |||
| YSFX_API ysfx_real *ysfx_find_var(ysfx_t *fx, const char *name); | |||
| // read a chunk of virtual memory from the VM | |||
| YSFX_API void ysfx_read_vmem(ysfx_t *fx, uint32_t addr, ysfx_real *dest, uint32_t count); | |||
| //------------------------------------------------------------------------------ | |||
| // YSFX graphics | |||
| // NOTE: all `ysfx_gfx_*` functions must be invoked from a dedicated UI thread | |||
| typedef struct ysfx_gfx_config_s { | |||
| // opaque user data passed to callbacks | |||
| void *user_data; | |||
| // the width of the frame buffer (having the scale factor applied) | |||
| uint32_t pixel_width; | |||
| // the height of the frame buffer (having the scale factor applied) | |||
| uint32_t pixel_height; | |||
| // the distance in bytes between lines; if 0, it defaults to (4*width) | |||
| // currently it is required to be a multiple of 4 | |||
| uint32_t pixel_stride; | |||
| // the pixel data of the frame buffer, of size (stride*height) bytes | |||
| // the byte order in little-endian is 'BGRA', big-endian is 'ARGB' | |||
| uint8_t *pixels; | |||
| // the scale factor of the display; 1.0 or greater, 2.0 for Retina display | |||
| ysfx_real scale_factor; | |||
| // show a menu and run it synchronously; returns an item ID >= 1, or 0 if none | |||
| int32_t (*show_menu)(void *user_data, const char *menu_spec, int32_t xpos, int32_t ypos); | |||
| // change the cursor | |||
| void (*set_cursor)(void *user_data, int32_t cursor); | |||
| // if index is not -1, get the dropped file at this index (otherwise null) | |||
| // if index is -1, clear the list of dropped files, and return null | |||
| const char *(*get_drop_file)(void *user_data, int32_t index); | |||
| } ysfx_gfx_config_t; | |||
| // set up the graphics rendering | |||
| YSFX_API void ysfx_gfx_setup(ysfx_t *fx, ysfx_gfx_config_t *gc); | |||
| // get whether the current effect is requesting Retina support | |||
| YSFX_API bool ysfx_gfx_wants_retina(ysfx_t *fx); | |||
| // push a key to the input queue | |||
| YSFX_API void ysfx_gfx_add_key(ysfx_t *fx, uint32_t mods, uint32_t key, bool press); | |||
| // update mouse information; position is relative to canvas; wheel should be in steps normalized to ±1.0 | |||
| YSFX_API void ysfx_gfx_update_mouse(ysfx_t *fx, uint32_t mods, int32_t xpos, int32_t ypos, uint32_t buttons, ysfx_real wheel, ysfx_real hwheel); | |||
| // invoke @gfx to paint the graphics; returns whether the framer buffer is modified | |||
| YSFX_API bool ysfx_gfx_run(ysfx_t *fx); | |||
| //------------------------------------------------------------------------------ | |||
| // YSFX key map | |||
| // these key definitions match those of pugl | |||
| enum { | |||
| ysfx_mod_shift = 1 << 0, | |||
| ysfx_mod_ctrl = 1 << 1, | |||
| ysfx_mod_alt = 1 << 2, | |||
| ysfx_mod_super = 1 << 3, | |||
| }; | |||
| enum { | |||
| ysfx_key_backspace = 0x08, | |||
| ysfx_key_escape = 0x1b, | |||
| ysfx_key_delete = 0x7f, | |||
| ysfx_key_f1 = 0xe000, | |||
| ysfx_key_f2, | |||
| ysfx_key_f3, | |||
| ysfx_key_f4, | |||
| ysfx_key_f5, | |||
| ysfx_key_f6, | |||
| ysfx_key_f7, | |||
| ysfx_key_f8, | |||
| ysfx_key_f9, | |||
| ysfx_key_f10, | |||
| ysfx_key_f11, | |||
| ysfx_key_f12, | |||
| ysfx_key_left, | |||
| ysfx_key_up, | |||
| ysfx_key_right, | |||
| ysfx_key_down, | |||
| ysfx_key_page_up, | |||
| ysfx_key_page_down, | |||
| ysfx_key_home, | |||
| ysfx_key_end, | |||
| ysfx_key_insert, | |||
| }; | |||
| enum { | |||
| ysfx_button_left = 1 << 0, | |||
| ysfx_button_middle = 1 << 1, | |||
| ysfx_button_right = 1 << 2, | |||
| }; | |||
| //------------------------------------------------------------------------------ | |||
| // YSFX audio formats | |||
| typedef struct ysfx_audio_reader_s ysfx_audio_reader_t; | |||
| typedef struct ysfx_audio_file_info_s { | |||
| uint32_t channels; | |||
| ysfx_real sample_rate; | |||
| } ysfx_audio_file_info_t; | |||
| typedef struct ysfx_audio_format_s { | |||
| // quickly checks if this format would be able to handle the given file | |||
| bool (*can_handle)(const char *path); | |||
| // open an audio file of this format for reading | |||
| ysfx_audio_reader_t *(*open)(const char *path); | |||
| // close the audio file | |||
| void (*close)(ysfx_audio_reader_t *reader); | |||
| // get the sample rate and the channel count | |||
| ysfx_audio_file_info_t (*info)(ysfx_audio_reader_t *reader); | |||
| // get the number of samples left to read | |||
| uint64_t (*avail)(ysfx_audio_reader_t *reader); | |||
| // move the read pointer back to the beginning | |||
| void (*rewind)(ysfx_audio_reader_t *reader); | |||
| // read the next block of samples | |||
| uint64_t (*read)(ysfx_audio_reader_t *reader, ysfx_real *samples, uint64_t count); | |||
| } ysfx_audio_format_t; | |||
| //------------------------------------------------------------------------------ | |||
| #ifdef __cplusplus | |||
| } // extern "C" | |||
| #endif | |||
| //------------------------------------------------------------------------------ | |||
| // YSFX RAII helpers | |||
| #if defined(__cplusplus) && (__cplusplus >= 201103L || (defined(_MSC_VER) && _MSVC_LANG >= 201103L)) | |||
| #include <memory> | |||
| #define YSFX_DEFINE_AUTO_PTR(aptr, styp, freefn) \ | |||
| struct aptr##_deleter { \ | |||
| void operator()(styp *x) const noexcept { freefn(x); } \ | |||
| }; \ | |||
| using aptr = std::unique_ptr<styp, aptr##_deleter> | |||
| YSFX_DEFINE_AUTO_PTR(ysfx_config_u, ysfx_config_t, ysfx_config_free); | |||
| YSFX_DEFINE_AUTO_PTR(ysfx_u, ysfx_t, ysfx_free); | |||
| YSFX_DEFINE_AUTO_PTR(ysfx_state_u, ysfx_state_t, ysfx_state_free); | |||
| YSFX_DEFINE_AUTO_PTR(ysfx_bank_u, ysfx_bank_t, ysfx_bank_free); | |||
| #endif // defined(__cplusplus) && (__cplusplus >= 201103L || (defined(_MSC_VER) && _MSVC_LANG >= 201103L)) | |||
| //------------------------------------------------------------------------------ | |||
| #endif // !defined(YSFX_INCLUDED_YSFX_H) | |||
| @@ -0,0 +1,139 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2016 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2022 Jean Pierre Cimalando <jp-dev@inbox.ru> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| * permission notice appear in all copies. | |||
| * | |||
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||
| * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||
| * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| #pragma once | |||
| #include <cstdint> | |||
| #include <cstring> | |||
| #include <cassert> | |||
| #include <array> | |||
| #include <vector> | |||
| // ----------------------------------------------------------------------- | |||
| // base64 stuff, based on http://www.adp-gmbh.ch/cpp/common/base64.html | |||
| /* | |||
| Copyright (C) 2004-2008 René Nyffenegger | |||
| This source code is provided 'as-is', without any express or implied | |||
| warranty. In no event will the author be held liable for any damages | |||
| arising from the use of this software. | |||
| Permission is granted to anyone to use this software for any purpose, | |||
| including commercial applications, and to alter it and redistribute it | |||
| freely, subject to the following restrictions: | |||
| 1. The origin of this source code must not be misrepresented; you must not | |||
| claim that you wrote the original source code. If you use this source code | |||
| in a product, an acknowledgment in the product documentation would be | |||
| appreciated but is not required. | |||
| 2. Altered source versions must be plainly marked as such, and must not be | |||
| misrepresented as being the original source code. | |||
| 3. This notice may not be removed or altered from any source distribution. | |||
| René Nyffenegger rene.nyffenegger@adp-gmbh.ch | |||
| */ | |||
| // ----------------------------------------------------------------------- | |||
| // Helpers | |||
| #ifndef DOXYGEN | |||
| namespace DistrhoBase64Helpers { | |||
| static | |||
| std::array<int8_t, 256> createCharIndexTable() | |||
| { | |||
| std::array<int8_t, 256> table; | |||
| table.fill(-1); | |||
| int8_t index = 0; | |||
| for (uint8_t c = 'A'; c <= 'Z'; ++c) table[c] = index++; | |||
| for (uint8_t c = 'a'; c <= 'z'; ++c) table[c] = index++; | |||
| for (uint8_t c = '0'; c <= '9'; ++c) table[c] = index++; | |||
| table['+'] = index++; | |||
| table['/'] = index++; | |||
| return table; | |||
| } | |||
| static const std::array<int8_t, 256> kCharIndexTable = createCharIndexTable(); | |||
| } // namespace DistrhoBase64Helpers | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| static inline | |||
| std::vector<uint8_t> d_getChunkFromBase64String(const char* const base64string, std::size_t base64stringLen = ~std::size_t(0)) | |||
| { | |||
| std::vector<uint8_t> ret; | |||
| if (! base64string) | |||
| return ret; | |||
| uint32_t i=0, j=0; | |||
| uint32_t charArray3[3], charArray4[4]; | |||
| if (base64stringLen == ~std::size_t(0)) | |||
| base64stringLen = std::strlen(base64string); | |||
| ret.reserve(base64stringLen*3/4 + 4); | |||
| for (std::size_t l=0; l<base64stringLen; ++l) | |||
| { | |||
| const unsigned char c = base64string[l]; | |||
| if (c == '\0' || c == '=') | |||
| break; | |||
| if (DistrhoBase64Helpers::kCharIndexTable[c] == -1) | |||
| continue; | |||
| charArray4[i++] = static_cast<uint32_t>(c); | |||
| if (i == 4) | |||
| { | |||
| for (i=0; i<4; ++i) | |||
| charArray4[i] = static_cast<uint8_t>(DistrhoBase64Helpers::kCharIndexTable[charArray4[i]]); | |||
| charArray3[0] = (charArray4[0] << 2) + ((charArray4[1] & 0x30) >> 4); | |||
| charArray3[1] = ((charArray4[1] & 0xf) << 4) + ((charArray4[2] & 0x3c) >> 2); | |||
| charArray3[2] = ((charArray4[2] & 0x3) << 6) + charArray4[3]; | |||
| for (i=0; i<3; ++i) | |||
| ret.push_back(static_cast<uint8_t>(charArray3[i])); | |||
| i = 0; | |||
| } | |||
| } | |||
| if (i != 0) | |||
| { | |||
| for (j=0; j<i && j<4; ++j) | |||
| charArray4[j] = static_cast<uint8_t>(DistrhoBase64Helpers::kCharIndexTable[charArray4[j]]); | |||
| for (j=i; j<4; ++j) | |||
| charArray4[j] = 0; | |||
| charArray3[0] = (charArray4[0] << 2) + ((charArray4[1] & 0x30) >> 4); | |||
| charArray3[1] = ((charArray4[1] & 0xf) << 4) + ((charArray4[2] & 0x3c) >> 2); | |||
| charArray3[2] = ((charArray4[2] & 0x3) << 6) + charArray4[3]; | |||
| for (j=0; i>0 && j<i-1; j++) | |||
| ret.push_back(static_cast<uint8_t>(charArray3[j])); | |||
| } | |||
| return ret; | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| Copyright (C) 2005 and later Cockos Incorporated | |||
| Copyright (C) 2021 and later Jean Pierre Cimalando | |||
| Portions copyright other contributors, see each source file for more information | |||
| This software is provided 'as-is', without any express or implied | |||
| warranty. In no event will the authors be held liable for any damages | |||
| arising from the use of this software. | |||
| Permission is granted to anyone to use this software for any purpose, | |||
| including commercial applications, and to alter it and redistribute it | |||
| freely, subject to the following restrictions: | |||
| 1. The origin of this software must not be misrepresented; you must not | |||
| claim that you wrote the original software. If you use this software | |||
| in a product, an acknowledgment in the product documentation would be | |||
| appreciated but is not required. | |||
| 2. Altered source versions must be plainly marked as such, and must not be | |||
| misrepresented as being the original software. | |||
| 3. This notice may not be removed or altered from any source distribution. | |||
| @@ -0,0 +1 @@ | |||
| 157d18abd0ac1fe6c4cf59c6f8fbe2c146b39079cd2cc9987837835d4c998e03c2b4816d9e1de13144692081f171d66659b7d1ec735d995b64ee1b033d5f3801 | |||
| @@ -0,0 +1,62 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #define STBI_ONLY_BMP | |||
| #include "lice_stb_generic.hpp" | |||
| LICE_IBitmap *LICE_LoadBMP(const char *filename, LICE_IBitmap *bmp) | |||
| { | |||
| return LICE_LoadSTB(filename, bmp); | |||
| } | |||
| class LICE_stb_BMPLoader | |||
| { | |||
| _LICE_ImageLoader_rec rec; | |||
| public: | |||
| LICE_stb_BMPLoader() | |||
| { | |||
| rec.loadfunc = loadfunc; | |||
| rec.get_extlist = get_extlist; | |||
| rec._next = LICE_ImageLoader_list; | |||
| LICE_ImageLoader_list = &rec; | |||
| } | |||
| static LICE_IBitmap *loadfunc(const char *filename, bool checkFileName, LICE_IBitmap *bmpbase) | |||
| { | |||
| if (checkFileName) { | |||
| const char *p = filename; | |||
| while (*p) | |||
| p++; | |||
| while (p > filename && *p != '\\' && *p != '/' && *p != '.') | |||
| p--; | |||
| if (stricmp(p, ".bmp")) | |||
| return nullptr; | |||
| } | |||
| return LICE_LoadSTB(filename, bmpbase); | |||
| } | |||
| static const char *get_extlist() | |||
| { | |||
| return "BMP files (*.BMP)\0*.BMP\0"; | |||
| } | |||
| }; | |||
| void lice_stb_install_bmp_loader() | |||
| { | |||
| static LICE_stb_BMPLoader loader; | |||
| } | |||
| @@ -0,0 +1,76 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #define STB_IMAGE_IMPLEMENTATION | |||
| #define STB_IMAGE_STATIC | |||
| #define STBI_WINDOWS_UTF8 | |||
| #if defined(__GNUC__) | |||
| # pragma GCC diagnostic push | |||
| # pragma GCC diagnostic ignored "-Wunused-function" | |||
| #endif | |||
| #include "stb_image.h" | |||
| #if defined(__GNUC__) | |||
| # pragma GCC diagnostic pop | |||
| #endif | |||
| //------------------------------------------------------------------------------ | |||
| #include "WDL/lice/lice.h" | |||
| #include "WDL/wdltypes.h" | |||
| static LICE_IBitmap *LICE_LoadSTB(const char *filename, LICE_IBitmap *bmp) | |||
| { | |||
| LICE_IBitmap *delbmp = nullptr; | |||
| stbi_uc *srcpx = nullptr; | |||
| LICE_pixel *dstpx = nullptr; | |||
| bool dstflip = false; | |||
| unsigned dstspan = 0; | |||
| unsigned w = 0; | |||
| unsigned h = 0; | |||
| unsigned ch = 0; | |||
| srcpx = stbi_load(filename, (int *)&w, (int *)&h, (int *)&ch, 4); | |||
| if (!srcpx) | |||
| goto fail; | |||
| if (bmp) | |||
| bmp->resize(w, h); | |||
| else | |||
| bmp = delbmp = new WDL_NEW LICE_MemBitmap(w, h); | |||
| if (!bmp || (unsigned)bmp->getWidth() != w || (unsigned)bmp->getHeight() != h) | |||
| goto fail; | |||
| dstpx = bmp->getBits(); | |||
| dstflip = bmp->isFlipped(); | |||
| dstspan = bmp->getRowSpan(); | |||
| for (unsigned row = 0; row < h; ++row) { | |||
| const stbi_uc *src = srcpx + row * (4 * w); | |||
| LICE_pixel *dst = dstpx + dstspan * (dstflip ? (h - 1 - row) : row); | |||
| for (unsigned col = 0; col < w; ++col, src += 4, ++dst) | |||
| *dst = LICE_RGBA(src[0], src[1], src[2], src[3]); | |||
| } | |||
| stbi_image_free(srcpx); | |||
| return bmp; | |||
| fail: | |||
| delete delbmp; | |||
| stbi_image_free(srcpx); | |||
| return nullptr; | |||
| } | |||
| @@ -0,0 +1,67 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #define STBI_ONLY_GIF | |||
| #include "lice_stb_generic.hpp" | |||
| LICE_IBitmap *LICE_LoadGIF(const char *filename, LICE_IBitmap *bmp, int *nframes) | |||
| { | |||
| LICE_IBitmap *ret = LICE_LoadSTB(filename, bmp); | |||
| if (ret) { | |||
| if (nframes) // animation not implemented | |||
| *nframes = 1; | |||
| } | |||
| return ret; | |||
| } | |||
| class LICE_stb_GIFLoader | |||
| { | |||
| _LICE_ImageLoader_rec rec; | |||
| public: | |||
| LICE_stb_GIFLoader() | |||
| { | |||
| rec.loadfunc = loadfunc; | |||
| rec.get_extlist = get_extlist; | |||
| rec._next = LICE_ImageLoader_list; | |||
| LICE_ImageLoader_list = &rec; | |||
| } | |||
| static LICE_IBitmap *loadfunc(const char *filename, bool checkFileName, LICE_IBitmap *bmpbase) | |||
| { | |||
| if (checkFileName) { | |||
| const char *p = filename; | |||
| while (*p) | |||
| p++; | |||
| while (p > filename && *p != '\\' && *p != '/' && *p != '.') | |||
| p--; | |||
| if (stricmp(p, ".gif")) | |||
| return nullptr; | |||
| } | |||
| return LICE_LoadSTB(filename, bmpbase); | |||
| } | |||
| static const char *get_extlist() | |||
| { | |||
| return "GIF files (*.GIF)\0*.GIF\0"; | |||
| } | |||
| }; | |||
| void lice_stb_install_gif_loader() | |||
| { | |||
| static LICE_stb_GIFLoader loader; | |||
| } | |||
| @@ -0,0 +1,62 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #define STBI_ONLY_JPEG | |||
| #include "lice_stb_generic.hpp" | |||
| LICE_IBitmap *LICE_LoadJPG(const char *filename, LICE_IBitmap *bmp) | |||
| { | |||
| return LICE_LoadSTB(filename, bmp); | |||
| } | |||
| class LICE_stb_JPGLoader | |||
| { | |||
| _LICE_ImageLoader_rec rec; | |||
| public: | |||
| LICE_stb_JPGLoader() | |||
| { | |||
| rec.loadfunc = loadfunc; | |||
| rec.get_extlist = get_extlist; | |||
| rec._next = LICE_ImageLoader_list; | |||
| LICE_ImageLoader_list = &rec; | |||
| } | |||
| static LICE_IBitmap *loadfunc(const char *filename, bool checkFileName, LICE_IBitmap *jpgbase) | |||
| { | |||
| if (checkFileName) { | |||
| const char *p = filename; | |||
| while (*p) | |||
| p++; | |||
| while (p > filename && *p != '\\' && *p != '/' && *p != '.') | |||
| p--; | |||
| if (stricmp(p, ".jpg") && stricmp(p, ".jpeg") && stricmp(p, ".jfif")) | |||
| return nullptr; | |||
| } | |||
| return LICE_LoadSTB(filename, jpgbase); | |||
| } | |||
| static const char *get_extlist() | |||
| { | |||
| return "JPEG files (*.JPG;*.JPEG;*.JFIF)\0*.JPG;*.JPEG;*.JFIF\0"; | |||
| } | |||
| }; | |||
| void lice_stb_install_jpg_loader() | |||
| { | |||
| static LICE_stb_JPGLoader loader; | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #include "lice_stb_loaders.hpp" | |||
| void lice_stb_install_loaders() | |||
| { | |||
| lice_stb_install_bmp_loader(); | |||
| lice_stb_install_gif_loader(); | |||
| lice_stb_install_jpg_loader(); | |||
| lice_stb_install_png_loader(); | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| void lice_stb_install_loaders(); | |||
| void lice_stb_install_bmp_loader(); | |||
| void lice_stb_install_gif_loader(); | |||
| void lice_stb_install_jpg_loader(); | |||
| void lice_stb_install_png_loader(); | |||
| @@ -0,0 +1,62 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #define STBI_ONLY_PNG | |||
| #include "lice_stb_generic.hpp" | |||
| LICE_IBitmap *LICE_LoadPNG(const char *filename, LICE_IBitmap *bmp) | |||
| { | |||
| return LICE_LoadSTB(filename, bmp); | |||
| } | |||
| class LICE_stb_PNGLoader | |||
| { | |||
| _LICE_ImageLoader_rec rec; | |||
| public: | |||
| LICE_stb_PNGLoader() | |||
| { | |||
| rec.loadfunc = loadfunc; | |||
| rec.get_extlist = get_extlist; | |||
| rec._next = LICE_ImageLoader_list; | |||
| LICE_ImageLoader_list = &rec; | |||
| } | |||
| static LICE_IBitmap *loadfunc(const char *filename, bool checkFileName, LICE_IBitmap *bmpbase) | |||
| { | |||
| if (checkFileName) { | |||
| const char *p = filename; | |||
| while (*p) | |||
| p++; | |||
| while (p > filename && *p != '\\' && *p != '/' && *p != '.') | |||
| p--; | |||
| if (stricmp(p, ".png")) | |||
| return nullptr; | |||
| } | |||
| return LICE_LoadSTB(filename, bmpbase); | |||
| } | |||
| static const char *get_extlist() | |||
| { | |||
| return "PNG files (*.PNG)\0*.PNG\0"; | |||
| } | |||
| }; | |||
| void lice_stb_install_png_loader() | |||
| { | |||
| static LICE_stb_PNGLoader loader; | |||
| } | |||
| @@ -0,0 +1,101 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #define STBIW_WINDOWS_UTF8 | |||
| #define STB_IMAGE_WRITE_STATIC | |||
| #define STB_IMAGE_WRITE_IMPLEMENTATION | |||
| #if defined(__GNUC__) | |||
| # pragma GCC diagnostic push | |||
| # pragma GCC diagnostic ignored "-Wunused-function" | |||
| #endif | |||
| #include "stb_image_write.h" | |||
| #if defined(__GNUC__) | |||
| # pragma GCC diagnostic pop | |||
| #endif | |||
| //------------------------------------------------------------------------------ | |||
| #define WDL_NO_DEFINE_MINMAX | |||
| #include "WDL/lice/lice.h" | |||
| #include "WDL/wdltypes.h" | |||
| #include <memory> | |||
| static std::unique_ptr<unsigned char[]> bmp_to_stbi(LICE_IBitmap *bmp, unsigned ch) | |||
| { | |||
| unsigned w = (unsigned)bmp->getWidth(); | |||
| unsigned h = (unsigned)bmp->getHeight(); | |||
| unsigned srcspan = (unsigned)bmp->getRowSpan(); | |||
| bool srcflip = bmp->isFlipped(); | |||
| LICE_pixel *srcpx = bmp->getBits(); | |||
| std::unique_ptr<unsigned char[]> result; | |||
| if (ch == 4) { | |||
| unsigned char *dstpx = new unsigned char[4 * w * h]; | |||
| result.reset(dstpx); | |||
| for (unsigned row = 0; row < h; ++row) { | |||
| LICE_pixel *src = srcpx + srcspan * (srcflip ? (h - 1 - row) : row); | |||
| unsigned char *dst = dstpx + row * (4 * w); | |||
| for (unsigned col = 0; col < w; ++col, ++src, dst += 4) { | |||
| LICE_pixel px = *src; | |||
| dst[0] = LICE_GETR(px); | |||
| dst[1] = LICE_GETG(px); | |||
| dst[2] = LICE_GETB(px); | |||
| dst[3] = LICE_GETA(px); | |||
| } | |||
| } | |||
| } | |||
| else if (ch == 3) { | |||
| unsigned char *dstpx = new unsigned char[3 * w * h]; | |||
| result.reset(dstpx); | |||
| for (unsigned row = 0; row < h; ++row) { | |||
| LICE_pixel *src = srcpx + srcspan * (srcflip ? (h - 1 - row) : row); | |||
| unsigned char *dst = dstpx + row * (3 * w); | |||
| for (unsigned col = 0; col < w; ++col, ++src, dst += 3) { | |||
| LICE_pixel px = *src; | |||
| dst[0] = LICE_GETR(px); | |||
| dst[1] = LICE_GETG(px); | |||
| dst[2] = LICE_GETB(px); | |||
| } | |||
| } | |||
| } | |||
| return result; | |||
| } | |||
| bool LICE_WritePNG(const char *filename, LICE_IBitmap *bmp, bool wantalpha) | |||
| { | |||
| unsigned ch = wantalpha ? 4 : 3; | |||
| std::unique_ptr<unsigned char[]> data = bmp_to_stbi(bmp, ch); | |||
| if (!data) | |||
| return false; | |||
| return stbi_write_png(filename, bmp->getWidth(), bmp->getHeight(), (int)ch, data.get(), 0); | |||
| } | |||
| bool LICE_WriteJPG(const char *filename, LICE_IBitmap *bmp, int quality, bool force_baseline) | |||
| { | |||
| (void)force_baseline; // always baseline | |||
| unsigned ch = 4; | |||
| std::unique_ptr<unsigned char[]> data = bmp_to_stbi(bmp, ch); | |||
| if (!data) | |||
| return false; | |||
| return stbi_write_jpg(filename, bmp->getWidth(), bmp->getHeight(), (int)ch, data.get(), quality); | |||
| } | |||
| @@ -0,0 +1,169 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| #include <type_traits> | |||
| #include <atomic> | |||
| #include <cstdint> | |||
| namespace ysfx { | |||
| //------------------------------------------------------------------------------ | |||
| // sync_bitset64: A lock-free synchronized bitset of size 64 | |||
| // | |||
| // This is implemented by a single qword on 64-bit machine, otherwise a pair of | |||
| // dwords on 32-bit machine, which is to ensure lock-freedom. | |||
| // | |||
| // This bitset is synchronized but not atomic; a thread might see a | |||
| // partially updated set after a modification. Conceptually, it can be seen as | |||
| // atomic on individual bit flips (that is, masking operations do not race). | |||
| class sync_bitset64_single; | |||
| class sync_bitset64_dual; | |||
| // could use std::atomic::is_always_lock_free on C++17 and up | |||
| using sync_bitset64 = std::conditional< | |||
| sizeof(intptr_t) <= 4, sync_bitset64_dual, sync_bitset64_single>::type; | |||
| //------------------------------------------------------------------------------ | |||
| class sync_bitset64_single { | |||
| public: | |||
| uint64_t load() const | |||
| { | |||
| return bits_.load(std::memory_order_relaxed); | |||
| } | |||
| void store(uint64_t value) | |||
| { | |||
| bits_.store(value, std::memory_order_relaxed); | |||
| } | |||
| uint64_t exchange(uint64_t value) | |||
| { | |||
| return bits_.exchange(value, std::memory_order_relaxed); | |||
| } | |||
| uint64_t fetch_or(uint64_t value) | |||
| { | |||
| return bits_.fetch_or(value, std::memory_order_relaxed); | |||
| } | |||
| uint64_t fetch_and(uint64_t value) | |||
| { | |||
| return bits_.fetch_and(value, std::memory_order_relaxed); | |||
| } | |||
| uint64_t fetch_xor(uint64_t value) | |||
| { | |||
| return bits_.fetch_xor(value, std::memory_order_relaxed); | |||
| } | |||
| void operator|=(uint64_t value) | |||
| { | |||
| fetch_or(value); | |||
| } | |||
| void operator&=(uint64_t value) | |||
| { | |||
| fetch_and(value); | |||
| } | |||
| void operator^=(uint64_t value) | |||
| { | |||
| fetch_xor(value); | |||
| } | |||
| private: | |||
| std::atomic<uint64_t> bits_{0}; | |||
| }; | |||
| //------------------------------------------------------------------------------ | |||
| class sync_bitset64_dual { | |||
| public: | |||
| uint64_t load() const | |||
| { | |||
| return join(lobits_.load(std::memory_order_relaxed), | |||
| hibits_.load(std::memory_order_relaxed)); | |||
| } | |||
| void store(uint64_t value) | |||
| { | |||
| lobits_.store(lo(value), std::memory_order_relaxed); | |||
| hibits_.store(hi(value), std::memory_order_relaxed); | |||
| } | |||
| uint64_t exchange(uint64_t value) | |||
| { | |||
| return join(lobits_.exchange(lo(value), std::memory_order_relaxed), | |||
| hibits_.exchange(hi(value), std::memory_order_relaxed)); | |||
| } | |||
| uint64_t fetch_or(uint64_t value) | |||
| { | |||
| return join(lobits_.fetch_or(lo(value), std::memory_order_relaxed), | |||
| hibits_.fetch_or(hi(value), std::memory_order_relaxed)); | |||
| } | |||
| uint64_t fetch_and(uint64_t value) | |||
| { | |||
| return join(lobits_.fetch_and(lo(value), std::memory_order_relaxed), | |||
| hibits_.fetch_and(hi(value), std::memory_order_relaxed)); | |||
| } | |||
| uint64_t fetch_xor(uint64_t value) | |||
| { | |||
| return join(lobits_.fetch_xor(lo(value), std::memory_order_relaxed), | |||
| hibits_.fetch_xor(hi(value), std::memory_order_relaxed)); | |||
| } | |||
| void operator|=(uint64_t value) | |||
| { | |||
| fetch_or(value); | |||
| } | |||
| void operator&=(uint64_t value) | |||
| { | |||
| fetch_and(value); | |||
| } | |||
| void operator^=(uint64_t value) | |||
| { | |||
| fetch_xor(value); | |||
| } | |||
| private: | |||
| static constexpr uint64_t join(uint32_t lo, uint32_t hi) | |||
| { | |||
| return (uint64_t)lo | ((uint64_t)hi << 32); | |||
| } | |||
| static constexpr uint32_t lo(uint64_t value) | |||
| { | |||
| return (uint32_t)(value & 0xFFFFFFFFu); | |||
| } | |||
| static constexpr uint32_t hi(uint64_t value) | |||
| { | |||
| return (uint32_t)(value >> 32); | |||
| } | |||
| private: | |||
| std::atomic<uint32_t> lobits_{0}; | |||
| std::atomic<uint32_t> hibits_{0}; | |||
| }; | |||
| } // namespace ysfx | |||
| @@ -0,0 +1,192 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| #include "ysfx.h" | |||
| #include "ysfx_midi.hpp" | |||
| #include "ysfx_parse.hpp" | |||
| #include "ysfx_api_eel.hpp" | |||
| #include "ysfx_api_reaper.hpp" | |||
| #include "ysfx_api_file.hpp" | |||
| #include "ysfx_api_gfx.hpp" | |||
| #include "ysfx_utils.hpp" | |||
| #include "utility/sync_bitset.hpp" | |||
| #include "WDL/eel2/ns-eel.h" | |||
| #include "WDL/eel2/ns-eel-int.h" | |||
| #include <unordered_map> | |||
| #include <atomic> | |||
| YSFX_DEFINE_AUTO_PTR(NSEEL_VMCTX_u, void, NSEEL_VM_free); // NOTE: `NSEEL_VMCTX` is `void *` | |||
| YSFX_DEFINE_AUTO_PTR(NSEEL_CODEHANDLE_u, void, NSEEL_code_free); // NOTE: `NSEEL_CODEHANDLE` is `void *` | |||
| struct ysfx_source_unit_t { | |||
| ysfx_toplevel_t toplevel; | |||
| ysfx_header_t header; | |||
| }; | |||
| using ysfx_source_unit_u = std::unique_ptr< ysfx_source_unit_t>; | |||
| enum ysfx_file_type_t { | |||
| ysfx_file_type_none, | |||
| ysfx_file_type_txt, | |||
| ysfx_file_type_raw, | |||
| ysfx_file_type_audio, | |||
| }; | |||
| enum ysfx_thread_id_t { | |||
| ysfx_thread_id_none, | |||
| ysfx_thread_id_dsp, | |||
| ysfx_thread_id_gfx, | |||
| }; | |||
| struct ysfx_s { | |||
| ysfx_config_u config; | |||
| eel_string_context_state_u string_ctx; | |||
| ysfx::mutex string_mutex; | |||
| ysfx::mutex atomic_mutex; | |||
| NSEEL_VMCTX_u vm; | |||
| // some default values, these are not standard, just arbitrary | |||
| uint32_t block_size = 128; | |||
| ysfx_real sample_rate = 44100; | |||
| uint32_t valid_input_channels = 2; | |||
| bool is_freshly_compiled = false; | |||
| bool must_compute_init = false; | |||
| bool must_compute_slider = false; | |||
| std::unordered_map<ysfx_real *, uint32_t> slider_of_var; | |||
| // source | |||
| struct { | |||
| std::string main_file_path; | |||
| std::string bank_path; | |||
| ysfx_source_unit_u main; | |||
| std::vector<ysfx_source_unit_u> imports; | |||
| std::unordered_map<std::string, uint32_t> slider_alias; | |||
| } source; | |||
| // compilation | |||
| struct { | |||
| bool compiled = false; | |||
| std::vector<NSEEL_CODEHANDLE_u> init; | |||
| NSEEL_CODEHANDLE_u slider; | |||
| NSEEL_CODEHANDLE_u block; | |||
| NSEEL_CODEHANDLE_u sample; | |||
| NSEEL_CODEHANDLE_u gfx; | |||
| NSEEL_CODEHANDLE_u serialize; | |||
| } code; | |||
| // VM variables | |||
| struct { | |||
| EEL_F *spl[ysfx_max_channels] = {}; | |||
| EEL_F *slider[ysfx_max_sliders] = {}; | |||
| EEL_F *srate = nullptr; | |||
| EEL_F *num_ch = nullptr; | |||
| EEL_F *samplesblock = nullptr; | |||
| EEL_F *trigger = nullptr; | |||
| EEL_F *tempo = nullptr; | |||
| EEL_F *play_state = nullptr; | |||
| EEL_F *play_position = nullptr; | |||
| EEL_F *beat_position = nullptr; | |||
| EEL_F *ts_num = nullptr; | |||
| EEL_F *ts_denom = nullptr; | |||
| EEL_F *ext_noinit = nullptr; | |||
| EEL_F *ext_nodenorm = nullptr; | |||
| EEL_F *ext_midi_bus = nullptr; | |||
| EEL_F *midi_bus = nullptr; | |||
| EEL_F *pdc_delay = nullptr; | |||
| EEL_F *pdc_bot_ch = nullptr; | |||
| EEL_F *pdc_top_ch = nullptr; | |||
| EEL_F *pdc_midi = nullptr; | |||
| // gfx variables | |||
| EEL_F *gfx_r = nullptr; | |||
| EEL_F *gfx_g = nullptr; | |||
| EEL_F *gfx_b = nullptr; | |||
| EEL_F *gfx_a = nullptr; | |||
| EEL_F *gfx_a2 = nullptr; | |||
| EEL_F *gfx_w = nullptr; | |||
| EEL_F *gfx_h = nullptr; | |||
| EEL_F *gfx_x = nullptr; | |||
| EEL_F *gfx_y = nullptr; | |||
| EEL_F *gfx_mode = nullptr; | |||
| EEL_F *gfx_clear = nullptr; | |||
| EEL_F *gfx_texth = nullptr; | |||
| EEL_F *gfx_dest = nullptr; | |||
| EEL_F *gfx_ext_retina = nullptr; | |||
| EEL_F *mouse_x = nullptr; | |||
| EEL_F *mouse_y = nullptr; | |||
| EEL_F *mouse_cap = nullptr; | |||
| EEL_F *mouse_wheel = nullptr; | |||
| EEL_F *mouse_hwheel = nullptr; | |||
| // other | |||
| EEL_F ret_temp = 0; | |||
| } var; | |||
| // MIDI | |||
| struct { | |||
| ysfx_midi_buffer_u in; | |||
| ysfx_midi_buffer_u out; | |||
| } midi; | |||
| // Slider | |||
| struct { | |||
| ysfx::sync_bitset64 automate_mask; | |||
| ysfx::sync_bitset64 change_mask; | |||
| ysfx::sync_bitset64 visible_mask; | |||
| } slider; | |||
| // Triggers | |||
| uint32_t triggers = 0; | |||
| // Files | |||
| struct { | |||
| std::vector<ysfx_file_u> list; | |||
| ysfx::mutex list_mutex; | |||
| } file; | |||
| #if !defined(YSFX_NO_GFX) | |||
| // Graphics | |||
| struct { | |||
| ysfx_gfx_state_u state; | |||
| ysfx::mutex mutex; | |||
| volatile bool ready = false; | |||
| volatile bool wants_retina = false; | |||
| std::atomic<bool> must_init{false}; | |||
| } gfx; | |||
| #endif | |||
| std::atomic<uint32_t> ref_count{1}; | |||
| }; | |||
| ysfx_thread_id_t ysfx_get_thread_id(); | |||
| void ysfx_set_thread_id(ysfx_thread_id_t id); | |||
| void ysfx_unload_source(ysfx_t *fx); | |||
| void ysfx_unload_code(ysfx_t *fx); | |||
| void ysfx_first_init(ysfx_t *fx); | |||
| void ysfx_update_slider_visibility_mask(ysfx_t *fx); | |||
| void ysfx_fill_file_enums(ysfx_t *fx); | |||
| void ysfx_fix_invalid_enums(ysfx_t *fx); | |||
| ysfx_section_t *ysfx_search_section(ysfx_t *fx, uint32_t type, ysfx_toplevel_t **origin = nullptr); | |||
| std::string ysfx_resolve_import_path(ysfx_t *fx, const std::string &name, const std::string &origin); | |||
| uint32_t ysfx_current_midi_bus(ysfx_t *fx); | |||
| void ysfx_clear_files(ysfx_t *fx); | |||
| ysfx_file_t *ysfx_get_file(ysfx_t *fx, uint32_t handle, std::unique_lock<ysfx::mutex> &lock, std::unique_lock<ysfx::mutex> *list_lock = nullptr); | |||
| int32_t ysfx_insert_file(ysfx_t *fx, ysfx_file_t *file); | |||
| void ysfx_serialize(ysfx_t *fx); | |||
| uint32_t ysfx_get_slider_of_var(ysfx_t *fx, EEL_F *var); | |||
| bool ysfx_find_data_file(ysfx_t *fx, EEL_F *file, std::string &result); | |||
| ysfx_file_type_t ysfx_detect_file_type(ysfx_t *fx, const char *path, void **fmtobj); | |||
| @@ -0,0 +1,144 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #include "ysfx.hpp" | |||
| #include "ysfx_api_eel.hpp" | |||
| #include "ysfx_utils.hpp" | |||
| #include <cstring> | |||
| #include <cstdlib> | |||
| #include <cstddef> | |||
| #include "WDL/ptrlist.h" | |||
| #include "WDL/assocarray.h" | |||
| #include "WDL/mutex.h" | |||
| #ifndef EELSCRIPT_NO_STDIO | |||
| # define EEL_STRING_STDOUT_WRITE(x,len) { fwrite(x,len,1,stdout); fflush(stdout); } | |||
| #endif | |||
| //TODO: thread-safety considerations with strings | |||
| #define EEL_STRING_MAXUSERSTRING_LENGTH_HINT ysfx_string_max_length | |||
| static ysfx::mutex atomic_mutex; | |||
| #define EEL_ATOMIC_SET_SCOPE(opaque) ysfx::mutex *mutex = ((opaque) ? &((ysfx_t *)(opaque))->atomic_mutex : &atomic_mutex); | |||
| #define EEL_ATOMIC_ENTER mutex->lock() | |||
| #define EEL_ATOMIC_LEAVE mutex->unlock() | |||
| #include "WDL/eel2/eel_strings.h" | |||
| #include "WDL/eel2/eel_misc.h" | |||
| #include "WDL/eel2/eel_fft.h" | |||
| #include "WDL/eel2/eel_mdct.h" | |||
| #include "WDL/eel2/eel_atomic.h" | |||
| //------------------------------------------------------------------------------ | |||
| void ysfx_api_init_eel() | |||
| { | |||
| EEL_string_register(); | |||
| EEL_fft_register(); | |||
| EEL_mdct_register(); | |||
| EEL_string_register(); | |||
| EEL_misc_register(); | |||
| EEL_atomic_register(); | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| void ysfx_eel_string_initvm(NSEEL_VMCTX vm) | |||
| { | |||
| eel_string_initvm(vm); | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| eel_string_context_state *ysfx_eel_string_context_new() | |||
| { | |||
| return new eel_string_context_state; | |||
| } | |||
| void ysfx_eel_string_context_free(eel_string_context_state *state) | |||
| { | |||
| delete state; | |||
| } | |||
| void ysfx_eel_string_context_update_named_vars(eel_string_context_state *state, NSEEL_VMCTX vm) | |||
| { | |||
| state->update_named_vars(vm); | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| static_assert( | |||
| ysfx_string_max_length == EEL_STRING_MAXUSERSTRING_LENGTH_HINT, | |||
| "string max lengths do not match"); | |||
| bool ysfx_string_access(ysfx_t *fx, ysfx_real id, bool for_write, void (*access)(void *, WDL_FastString &), void *userdata) | |||
| { | |||
| void *opaque = fx; | |||
| eel_string_context_state *ctx = EEL_STRING_GET_CONTEXT_POINTER(opaque); | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| EEL_STRING_STORAGECLASS *wr = nullptr; | |||
| ctx->GetStringForIndex(id, &wr, for_write); | |||
| if (!wr) | |||
| return false; | |||
| access(userdata, *wr); | |||
| return true; | |||
| } | |||
| bool ysfx_string_get(ysfx_t *fx, ysfx_real id, std::string &txt) | |||
| { | |||
| return ysfx_string_access(fx, id, false, [](void *ud, WDL_FastString &str) { | |||
| ((std::string *)ud)->assign(str.Get(), (uint32_t)str.GetLength()); | |||
| }, &txt); | |||
| } | |||
| bool ysfx_string_set(ysfx_t *fx, ysfx_real id, const std::string &txt) | |||
| { | |||
| return ysfx_string_access(fx, id, true, [](void *ud, WDL_FastString &str) { | |||
| const std::string *txt = (const std::string *)ud; | |||
| size_t size = txt->size(); | |||
| if (size > ysfx_string_max_length) | |||
| size = ysfx_string_max_length; | |||
| str.SetRaw(txt->data(), (int)size); | |||
| }, (void *)&txt); | |||
| } | |||
| void ysfx_string_lock(ysfx_t *fx) | |||
| { | |||
| fx->string_mutex.lock(); | |||
| } | |||
| void ysfx_string_unlock(ysfx_t *fx) | |||
| { | |||
| fx->string_mutex.unlock(); | |||
| } | |||
| const char *ysfx_string_access_unlocked(ysfx_t *fx, ysfx_real id, WDL_FastString **fs, bool for_write) | |||
| { | |||
| return fx->string_ctx->GetStringForIndex(id, fs, for_write); | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| // NOTE(jpc) implement this? I guess probably not. | |||
| // DSP and UI should not mutex each other. | |||
| void NSEEL_HOSTSTUB_EnterMutex() | |||
| { | |||
| } | |||
| void NSEEL_HOSTSTUB_LeaveMutex() | |||
| { | |||
| } | |||
| @@ -0,0 +1,57 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| #include "ysfx.h" | |||
| #include <string> | |||
| typedef void *NSEEL_VMCTX; | |||
| class WDL_FastString; | |||
| //------------------------------------------------------------------------------ | |||
| void ysfx_api_init_eel(); | |||
| //------------------------------------------------------------------------------ | |||
| void ysfx_eel_string_initvm(NSEEL_VMCTX vm); | |||
| //------------------------------------------------------------------------------ | |||
| class eel_string_context_state; | |||
| eel_string_context_state *ysfx_eel_string_context_new(); | |||
| void ysfx_eel_string_context_free(eel_string_context_state *state); | |||
| void ysfx_eel_string_context_update_named_vars(eel_string_context_state *state, NSEEL_VMCTX vm); | |||
| YSFX_DEFINE_AUTO_PTR(eel_string_context_state_u, eel_string_context_state, ysfx_eel_string_context_free); | |||
| //------------------------------------------------------------------------------ | |||
| enum { ysfx_string_max_length = 1 << 16 }; | |||
| bool ysfx_string_access(ysfx_t *fx, ysfx_real id, bool for_write, void (*access)(void *, WDL_FastString &), void *userdata); | |||
| bool ysfx_string_get(ysfx_t *fx, ysfx_real id, std::string &txt); | |||
| bool ysfx_string_set(ysfx_t *fx, ysfx_real id, const std::string &txt); | |||
| void ysfx_string_lock(ysfx_t *fx); | |||
| void ysfx_string_unlock(ysfx_t *fx); | |||
| const char *ysfx_string_access_unlocked(ysfx_t *fx, ysfx_real id, WDL_FastString **fs, bool for_write); | |||
| struct ysfx_string_scoped_lock { | |||
| ysfx_string_scoped_lock(ysfx_t *fx) : m_fx(fx) { ysfx_string_lock(fx); } | |||
| ~ysfx_string_scoped_lock() { ysfx_string_unlock(m_fx); } | |||
| private: | |||
| ysfx_t *m_fx = nullptr; | |||
| }; | |||
| #define EEL_STRING_GET_CONTEXT_POINTER(opaque) (((ysfx_t *)(opaque))->string_ctx.get()) | |||
| #define EEL_STRING_GET_FOR_INDEX(x, wr) (ysfx_string_access_unlocked((ysfx_t *)(opaque), x, wr, false)) | |||
| #define EEL_STRING_GET_FOR_WRITE(x, wr) (ysfx_string_access_unlocked((ysfx_t *)(opaque), x, wr, true)) | |||
| #define EEL_STRING_MUTEXLOCK_SCOPE ysfx_string_scoped_lock lock{(ysfx_t *)(opaque)}; | |||
| @@ -0,0 +1,574 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #include "ysfx.hpp" | |||
| #include "ysfx_config.hpp" | |||
| #include "ysfx_api_file.hpp" | |||
| #include "ysfx_eel_utils.hpp" | |||
| #include <cstring> | |||
| #include <cstdio> | |||
| #include <cassert> | |||
| ysfx_raw_file_t::ysfx_raw_file_t(NSEEL_VMCTX vm, const char *filename) | |||
| : m_vm(vm), | |||
| m_stream(ysfx::fopen_utf8(filename, "rb")) | |||
| { | |||
| } | |||
| int32_t ysfx_raw_file_t::avail() | |||
| { | |||
| if (!m_stream) | |||
| return 0; | |||
| int64_t cur_off = ysfx::ftell_lfs(m_stream.get()); | |||
| if (cur_off == -1) | |||
| return 0; | |||
| if (ysfx::fseek_lfs(m_stream.get(), 0, SEEK_END) == -1) | |||
| return 0; | |||
| int64_t end_off = ysfx::ftell_lfs(m_stream.get()); | |||
| if (end_off == -1) | |||
| return 0; | |||
| if (ysfx::fseek_lfs(m_stream.get(), cur_off, SEEK_SET) == -1) | |||
| return 0; | |||
| if ((uint64_t)end_off < (uint64_t)cur_off) | |||
| return 0; | |||
| uint64_t byte_count = (uint64_t)end_off - (uint64_t)cur_off; | |||
| uint64_t f32_count = byte_count / 4; | |||
| return (f32_count > 0x7fffffff) ? 0x7fffffff : (uint32_t)f32_count; | |||
| } | |||
| void ysfx_raw_file_t::rewind() | |||
| { | |||
| if (!m_stream) | |||
| return; | |||
| ::rewind(m_stream.get()); | |||
| } | |||
| bool ysfx_raw_file_t::var(ysfx_real *var) | |||
| { | |||
| if (!m_stream) | |||
| return false; | |||
| uint8_t data[4]; | |||
| if (fread(data, 1, 4, m_stream.get()) != 4) | |||
| return false; | |||
| *var = (EEL_F)ysfx::unpack_f32le(data); | |||
| return true; | |||
| } | |||
| uint32_t ysfx_raw_file_t::mem(uint32_t offset, uint32_t length) | |||
| { | |||
| if (!m_stream) | |||
| return 0; | |||
| ysfx_eel_ram_writer writer{m_vm, offset}; | |||
| uint32_t read; | |||
| for (read = 0; read < length; ++read) { | |||
| ysfx_real value; | |||
| if (!var(&value)) | |||
| break; | |||
| writer.write_next(value); | |||
| } | |||
| return read; | |||
| } | |||
| uint32_t ysfx_raw_file_t::string(std::string &str) | |||
| { | |||
| if (!m_stream) | |||
| return 0; | |||
| uint8_t data[4]; | |||
| if (fread(data, 1, 4, m_stream.get()) != 4) | |||
| return 0; | |||
| str.clear(); | |||
| uint32_t srclen = ysfx::unpack_u32le(data); | |||
| str.reserve((srclen < ysfx_string_max_length) ? srclen : ysfx_string_max_length); | |||
| uint32_t count = 0; | |||
| for (int byte; count < srclen && (byte = fgetc(m_stream.get())) != EOF; ) { | |||
| if (str.size() < ysfx_string_max_length) | |||
| str.push_back((unsigned char)byte); | |||
| ++count; | |||
| } | |||
| return count; | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| ysfx_text_file_t::ysfx_text_file_t(NSEEL_VMCTX vm, const char *filename) | |||
| : m_vm(vm), | |||
| m_stream(ysfx::fopen_utf8(filename, "rb")) | |||
| { | |||
| m_buf.reserve(256); | |||
| } | |||
| int32_t ysfx_text_file_t::avail() | |||
| { | |||
| if (!m_stream || ferror(m_stream.get())) | |||
| return -1; | |||
| return (feof(m_stream.get()) == 0) ? 0 : 1; | |||
| } | |||
| void ysfx_text_file_t::rewind() | |||
| { | |||
| if (!m_stream) | |||
| return; | |||
| ::rewind(m_stream.get()); | |||
| } | |||
| bool ysfx_text_file_t::var(ysfx_real *var) | |||
| { | |||
| if (!m_stream) | |||
| return false; | |||
| //TODO support the expression language for arithmetic | |||
| int ch; | |||
| do { | |||
| // get the next number separated by newline or comma | |||
| // but skip invalid lines | |||
| m_buf.clear(); | |||
| while ((ch = fgetc(m_stream.get())) != EOF && ch != '\n' && ch != ',') | |||
| m_buf.push_back((unsigned char)ch); | |||
| const char *startp = m_buf.c_str(); | |||
| const char *endp = (char *)startp; | |||
| double value = ysfx::dot_strtod(startp, (char **)&endp); | |||
| if (endp != startp) { | |||
| *var = (EEL_F)value; | |||
| return true; | |||
| } | |||
| } while (ch != EOF); | |||
| return false; | |||
| } | |||
| uint32_t ysfx_text_file_t::mem(uint32_t offset, uint32_t length) | |||
| { | |||
| if (!m_stream) | |||
| return 0; | |||
| ysfx_eel_ram_writer writer{m_vm, offset}; | |||
| uint32_t read; | |||
| for (read = 0; read < length; ++read) { | |||
| ysfx_real value; | |||
| if (!var(&value)) | |||
| break; | |||
| writer.write_next(value); | |||
| } | |||
| return read; | |||
| } | |||
| uint32_t ysfx_text_file_t::string(std::string &str) | |||
| { | |||
| if (!m_stream) | |||
| return 0; | |||
| str.clear(); | |||
| str.reserve(256); | |||
| int ch; | |||
| do { | |||
| ch = fgetc(m_stream.get()); | |||
| if (ch != EOF && str.size() < ysfx_string_max_length) | |||
| str.push_back((unsigned char)ch); | |||
| } while (ch != EOF && ch != '\n'); | |||
| return (uint32_t)str.size(); | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| ysfx_audio_file_t::ysfx_audio_file_t(NSEEL_VMCTX vm, const ysfx_audio_format_t &fmt, const char *filename) | |||
| : m_vm(vm), | |||
| m_fmt(fmt), | |||
| m_reader(fmt.open(filename), fmt.close) | |||
| { | |||
| } | |||
| int32_t ysfx_audio_file_t::avail() | |||
| { | |||
| if (!m_reader) | |||
| return -1; | |||
| uint64_t avail = m_fmt.avail(m_reader.get()); | |||
| return (avail > 0x7fffffff) ? 0x7fffffff : (int32_t)avail; | |||
| } | |||
| void ysfx_audio_file_t::rewind() | |||
| { | |||
| if (!m_reader) | |||
| return; | |||
| m_fmt.rewind(m_reader.get()); | |||
| } | |||
| bool ysfx_audio_file_t::var(ysfx_real *var) | |||
| { | |||
| if (!m_reader) | |||
| return false; | |||
| return m_fmt.read(m_reader.get(), var, 1) == 1; | |||
| } | |||
| uint32_t ysfx_audio_file_t::mem(uint32_t offset, uint32_t length) | |||
| { | |||
| if (!m_reader) | |||
| return 0; | |||
| uint32_t numread = 0; | |||
| ysfx_real *buf = m_buf.get(); | |||
| ysfx_eel_ram_writer writer(m_vm, offset); | |||
| while (numread < length) { | |||
| uint32_t n = length - numread; | |||
| if (n > buffer_size) | |||
| n = buffer_size; | |||
| uint32_t m = (uint32_t)m_fmt.read(m_reader.get(), buf, n); | |||
| for (uint32_t i = 0; i < m; ++i) | |||
| writer.write_next(buf[i]); | |||
| numread += m; | |||
| if (m < n) | |||
| break; | |||
| } | |||
| return numread; | |||
| } | |||
| uint32_t ysfx_audio_file_t::string(std::string &str) | |||
| { | |||
| (void)str; | |||
| return 0; | |||
| } | |||
| bool ysfx_audio_file_t::riff(uint32_t &nch, ysfx_real &samplerate) | |||
| { | |||
| if (!m_reader) | |||
| return false; | |||
| ysfx_audio_file_info_t info = m_fmt.info(m_reader.get()); | |||
| nch = info.channels; | |||
| samplerate = info.sample_rate; | |||
| return true; | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| ysfx_serializer_t::ysfx_serializer_t(NSEEL_VMCTX vm) | |||
| : m_vm(vm) | |||
| { | |||
| } | |||
| void ysfx_serializer_t::begin(bool write, std::string &buffer) | |||
| { | |||
| m_write = (int)write; | |||
| m_buffer = &buffer; | |||
| m_pos = 0; | |||
| } | |||
| void ysfx_serializer_t::end() | |||
| { | |||
| m_write = -1; | |||
| m_buffer = nullptr; | |||
| } | |||
| int32_t ysfx_serializer_t::avail() | |||
| { | |||
| if (m_write) | |||
| return -1; | |||
| else | |||
| return 0; | |||
| } | |||
| void ysfx_serializer_t::rewind() | |||
| { | |||
| } | |||
| bool ysfx_serializer_t::var(ysfx_real *var) | |||
| { | |||
| if (m_write == 1) { | |||
| uint8_t buf[4]; | |||
| ysfx::pack_f32le((float)*var, buf); | |||
| m_buffer->append((char *)buf, 4); | |||
| return true; | |||
| } | |||
| else if (m_write == 0) { | |||
| if (m_pos + 4 > m_buffer->size()) { | |||
| m_pos = m_buffer->size(); | |||
| *var = 0; | |||
| return false; | |||
| } | |||
| *var = (EEL_F)ysfx::unpack_f32le((uint8_t *)&(*m_buffer)[m_pos]); | |||
| m_pos += 4; | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| uint32_t ysfx_serializer_t::mem(uint32_t offset, uint32_t length) | |||
| { | |||
| if (m_write == 1) { | |||
| ysfx_eel_ram_reader reader{m_vm, offset}; | |||
| for (uint32_t i = 0; i < length; ++i) { | |||
| ysfx_real value = reader.read_next(); | |||
| if (!var(&value)) | |||
| return i; | |||
| } | |||
| return length; | |||
| } | |||
| else if (m_write == 0) { | |||
| ysfx_eel_ram_writer writer{m_vm, offset}; | |||
| for (uint32_t i = 0; i < length; ++i) { | |||
| ysfx_real value{}; | |||
| if (!var(&value)) | |||
| return i; | |||
| writer.write_next(value); | |||
| } | |||
| return length; | |||
| } | |||
| return 0; | |||
| } | |||
| uint32_t ysfx_serializer_t::string(std::string &str) | |||
| { | |||
| // TODO implement me; docs claim support in Reaper 4.59+ but it seems | |||
| // non-working (as of Reaper 6.40) | |||
| return 0; | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_file_open(void *opaque, EEL_F *file_) | |||
| { | |||
| ysfx_t *fx = (ysfx_t *)opaque; | |||
| std::string filepath; | |||
| if (!ysfx_find_data_file(fx, file_, filepath)) | |||
| return -1; | |||
| void *fmtobj = nullptr; | |||
| ysfx_file_type_t ftype = ysfx_detect_file_type(fx, filepath.c_str(), &fmtobj); | |||
| ysfx_file_u file; | |||
| switch (ftype) { | |||
| case ysfx_file_type_txt: | |||
| file.reset(new ysfx_text_file_t(fx->vm.get(), filepath.c_str())); | |||
| break; | |||
| case ysfx_file_type_raw: | |||
| file.reset(new ysfx_raw_file_t(fx->vm.get(), filepath.c_str())); | |||
| break; | |||
| case ysfx_file_type_audio: | |||
| file.reset(new ysfx_audio_file_t(fx->vm.get(), *(ysfx_audio_format_t *)fmtobj, filepath.c_str())); | |||
| break; | |||
| case ysfx_file_type_none: | |||
| break; | |||
| default: | |||
| assert(false); | |||
| } | |||
| if (file) { | |||
| int32_t handle = ysfx_insert_file(fx, file.get()); | |||
| if (handle == -1) | |||
| return -1; | |||
| (void)file.release(); | |||
| return (EEL_F)(uint32_t)handle; | |||
| } | |||
| return -1; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_file_close(void *opaque, EEL_F *handle_) | |||
| { | |||
| int32_t handle = ysfx_eel_round<int32_t>(*handle_); | |||
| if (handle <= 0) //NOTE: cannot close the serializer handle (0) | |||
| return -1; | |||
| ysfx_t *fx = (ysfx_t *)opaque; | |||
| std::unique_ptr<ysfx::mutex> file_mutex; | |||
| std::unique_lock<ysfx::mutex> lock; | |||
| std::unique_lock<ysfx::mutex> list_lock; | |||
| // hold both locks to protect file and list access during removal | |||
| if (!ysfx_get_file(fx, (uint32_t)handle, lock, &list_lock)) | |||
| return -1; | |||
| // preserve the locked mutex of the object being removed | |||
| file_mutex = std::move(fx->file.list[(uint32_t)handle]->m_mutex); | |||
| fx->file.list[(uint32_t)handle].reset(); | |||
| return 0; | |||
| } | |||
| static EEL_F *NSEEL_CGEN_CALL ysfx_api_file_rewind(void *opaque, EEL_F *handle_) | |||
| { | |||
| int32_t handle = ysfx_eel_round<int32_t>(*handle_); | |||
| if (handle < 0) | |||
| return handle_; | |||
| ysfx_t *fx = (ysfx_t *)opaque; | |||
| std::unique_lock<ysfx::mutex> lock; | |||
| ysfx_file_t *file = ysfx_get_file(fx, (uint32_t)handle, lock); | |||
| if (!file) | |||
| return 0; | |||
| file->rewind(); | |||
| return handle_; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_file_var(void *opaque, EEL_F *handle_, EEL_F *var) | |||
| { | |||
| int32_t handle = ysfx_eel_round<int32_t>(*handle_); | |||
| if (handle < 0) | |||
| return 0; | |||
| ysfx_t *fx = (ysfx_t *)opaque; | |||
| std::unique_lock<ysfx::mutex> lock; | |||
| ysfx_file_t *file = ysfx_get_file(fx, (uint32_t)handle, lock); | |||
| if (!file) | |||
| return 0; | |||
| if (!file->var(var)) | |||
| return 0; | |||
| return 1; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_file_mem(void *opaque, EEL_F *handle_, EEL_F *offset_, EEL_F *length_) | |||
| { | |||
| int32_t handle = ysfx_eel_round<int32_t>(*handle_); | |||
| int32_t offset = ysfx_eel_round<int32_t>(*offset_); | |||
| int32_t length = ysfx_eel_round<int32_t>(*length_); | |||
| if (handle < 0 || offset < 0 || length <= 0) | |||
| return 0; | |||
| ysfx_t *fx = (ysfx_t *)opaque; | |||
| std::unique_lock<ysfx::mutex> lock; | |||
| ysfx_file_t *file = ysfx_get_file(fx, (uint32_t)handle, lock); | |||
| if (!file) | |||
| return 0; | |||
| return (EEL_F)file->mem((uint32_t)offset, (uint32_t)length); | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_file_avail(void *opaque, EEL_F *handle_) | |||
| { | |||
| int32_t handle = ysfx_eel_round<int32_t>(*handle_); | |||
| if (handle < 0) | |||
| return 0; | |||
| ysfx_t *fx = (ysfx_t *)opaque; | |||
| std::unique_lock<ysfx::mutex> lock; | |||
| ysfx_file_t *file = ysfx_get_file(fx, (uint32_t)handle, lock); | |||
| if (!file) | |||
| return 0; | |||
| return file->avail(); | |||
| } | |||
| static EEL_F *NSEEL_CGEN_CALL ysfx_api_file_riff(void *opaque, EEL_F *handle_, EEL_F *nch_, EEL_F *samplerate_) | |||
| { | |||
| int32_t handle = ysfx_eel_round<int32_t>(*handle_); | |||
| if (handle < 0) | |||
| return 0; | |||
| ysfx_t *fx = (ysfx_t *)opaque; | |||
| std::unique_lock<ysfx::mutex> lock; | |||
| ysfx_file_t *file = ysfx_get_file(fx, (uint32_t)handle, lock); | |||
| if (!file) { | |||
| *nch_ = 0; | |||
| *samplerate_ = 0; | |||
| return nch_; | |||
| } | |||
| uint32_t nch = 0; | |||
| ysfx_real samplerate = 0; | |||
| if (!file->riff(nch, samplerate)) { | |||
| *nch_ = 0; | |||
| *samplerate_ = 0; | |||
| return nch_; | |||
| } | |||
| *nch_ = (EEL_F)nch; | |||
| *samplerate_ = samplerate; | |||
| return nch_; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_file_text(void *opaque, EEL_F *handle_) | |||
| { | |||
| int32_t handle = ysfx_eel_round<int32_t>(*handle_); | |||
| if (handle < 0) | |||
| return 0; | |||
| ysfx_t *fx = (ysfx_t *)opaque; | |||
| std::unique_lock<ysfx::mutex> lock; | |||
| ysfx_file_t *file = ysfx_get_file(fx, (uint32_t)handle, lock); | |||
| if (!file) | |||
| return 0; | |||
| return (EEL_F)file->is_text(); | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_file_string(void *opaque, EEL_F *handle_, EEL_F *string_) | |||
| { | |||
| int32_t handle = ysfx_eel_round<int32_t>(*handle_); | |||
| if (handle < 0) | |||
| return 0; | |||
| ysfx_t *fx = (ysfx_t *)opaque; | |||
| std::unique_lock<ysfx::mutex> lock; | |||
| ysfx_file_t *file = ysfx_get_file(fx, (uint32_t)handle, lock); | |||
| if (!file) | |||
| return 0; | |||
| std::string txt; | |||
| uint32_t count; | |||
| if (!file->is_in_write_mode()) { | |||
| count = file->string(txt); | |||
| ysfx_string_set(fx, *string_, txt); | |||
| } | |||
| else { | |||
| ysfx_string_get(fx, *string_, txt); | |||
| count = file->string(txt); | |||
| } | |||
| return (EEL_F)count; | |||
| } | |||
| void ysfx_api_init_file() | |||
| { | |||
| NSEEL_addfunc_retval("file_open", 1, NSEEL_PProc_THIS, &ysfx_api_file_open); | |||
| NSEEL_addfunc_retval("file_close", 1, NSEEL_PProc_THIS, &ysfx_api_file_close); | |||
| NSEEL_addfunc_retptr("file_rewind", 1, NSEEL_PProc_THIS, &ysfx_api_file_rewind); | |||
| NSEEL_addfunc_retval("file_var", 2, NSEEL_PProc_THIS, &ysfx_api_file_var); | |||
| NSEEL_addfunc_retval("file_mem", 3, NSEEL_PProc_THIS, &ysfx_api_file_mem); | |||
| NSEEL_addfunc_retval("file_avail", 1, NSEEL_PProc_THIS, &ysfx_api_file_avail); | |||
| NSEEL_addfunc_retptr("file_riff", 3, NSEEL_PProc_THIS, &ysfx_api_file_riff); | |||
| NSEEL_addfunc_retval("file_text", 1, NSEEL_PProc_THIS, &ysfx_api_file_text); | |||
| NSEEL_addfunc_retval("file_string", 2, NSEEL_PProc_THIS, &ysfx_api_file_string); | |||
| } | |||
| @@ -0,0 +1,127 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| #include "ysfx.h" | |||
| #include "ysfx_utils.hpp" | |||
| #include "WDL/eel2/ns-eel.h" | |||
| #include "WDL/eel2/ns-eel-int.h" | |||
| #include <vector> | |||
| #include <memory> | |||
| struct ysfx_file_t { | |||
| virtual ~ysfx_file_t() {} | |||
| virtual int32_t avail() = 0; | |||
| virtual void rewind() = 0; | |||
| virtual bool var(ysfx_real *var) = 0; | |||
| virtual uint32_t mem(uint32_t offset, uint32_t length) = 0; | |||
| virtual uint32_t string(std::string &str) = 0; | |||
| virtual bool riff(uint32_t &nch, ysfx_real &samplerate) = 0; | |||
| virtual bool is_text() = 0; | |||
| virtual bool is_in_write_mode() = 0; | |||
| std::unique_ptr<ysfx::mutex> m_mutex{new ysfx::mutex}; | |||
| }; | |||
| using ysfx_file_u = std::unique_ptr<ysfx_file_t>; | |||
| //------------------------------------------------------------------------------ | |||
| struct ysfx_raw_file_t final : ysfx_file_t { | |||
| ysfx_raw_file_t(NSEEL_VMCTX vm, const char *filename); | |||
| int32_t avail() override; | |||
| void rewind() override; | |||
| bool var(ysfx_real *var) override; | |||
| uint32_t mem(uint32_t offset, uint32_t length) override; | |||
| uint32_t string(std::string &str) override; | |||
| bool riff(uint32_t &, ysfx_real &) override { return false; } | |||
| bool is_text() override { return false; } | |||
| bool is_in_write_mode() override { return false; } | |||
| NSEEL_VMCTX m_vm = nullptr; | |||
| ysfx::FILE_u m_stream; | |||
| }; | |||
| //------------------------------------------------------------------------------ | |||
| struct ysfx_text_file_t final : ysfx_file_t { | |||
| ysfx_text_file_t(NSEEL_VMCTX vm, const char *filename); | |||
| int32_t avail() override; | |||
| void rewind() override; | |||
| bool var(ysfx_real *var) override; | |||
| uint32_t mem(uint32_t offset, uint32_t length) override; | |||
| uint32_t string(std::string &str) override; | |||
| bool riff(uint32_t &, ysfx_real &) override { return false; } | |||
| bool is_text() override { return true; } | |||
| bool is_in_write_mode() override { return false; } | |||
| NSEEL_VMCTX m_vm = nullptr; | |||
| ysfx::FILE_u m_stream; | |||
| std::string m_buf; | |||
| }; | |||
| //------------------------------------------------------------------------------ | |||
| struct ysfx_audio_file_t final : ysfx_file_t { | |||
| ysfx_audio_file_t(NSEEL_VMCTX vm, const ysfx_audio_format_t &fmt, const char *filename); | |||
| int32_t avail() override; | |||
| void rewind() override; | |||
| bool var(ysfx_real *var) override; | |||
| uint32_t mem(uint32_t offset, uint32_t length) override; | |||
| uint32_t string(std::string &str) override; | |||
| bool riff(uint32_t &nch, ysfx_real &samplerate) override; | |||
| bool is_text() override { return false; } | |||
| bool is_in_write_mode() override { return false; } | |||
| NSEEL_VMCTX m_vm = nullptr; | |||
| ysfx_audio_format_t m_fmt{}; | |||
| std::unique_ptr<ysfx_audio_reader_t, void (*)(ysfx_audio_reader_t *)> m_reader; | |||
| enum { buffer_size = 256 }; | |||
| std::unique_ptr<ysfx_real[]> m_buf{new ysfx_real[buffer_size]}; | |||
| }; | |||
| //------------------------------------------------------------------------------ | |||
| struct ysfx_serializer_t final : ysfx_file_t { | |||
| explicit ysfx_serializer_t(NSEEL_VMCTX vm); | |||
| void begin(bool write, std::string &buffer); | |||
| void end(); | |||
| int32_t avail() override; | |||
| void rewind() override; | |||
| bool var(ysfx_real *var) override; | |||
| uint32_t mem(uint32_t offset, uint32_t length) override; | |||
| uint32_t string(std::string &str) override; | |||
| bool riff(uint32_t &, ysfx_real &) override { return false; } | |||
| bool is_text() override { return false; } | |||
| bool is_in_write_mode() override { return m_write == 1; } | |||
| NSEEL_VMCTX m_vm{}; | |||
| int m_write = -1; | |||
| std::string *m_buffer = nullptr; | |||
| size_t m_pos = 0; | |||
| }; | |||
| using ysfx_serializer_u = std::unique_ptr<ysfx_serializer_t>; | |||
| //------------------------------------------------------------------------------ | |||
| void ysfx_api_init_file(); | |||
| @@ -0,0 +1,470 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #include "ysfx.hpp" | |||
| #include "ysfx_config.hpp" | |||
| #include "ysfx_api_gfx.hpp" | |||
| #include "ysfx_eel_utils.hpp" | |||
| #if !defined(YSFX_NO_GFX) | |||
| # include "lice_stb/lice_stb_loaders.hpp" | |||
| # define WDL_NO_DEFINE_MINMAX | |||
| # include "WDL/swell/swell.h" | |||
| # include "WDL/lice/lice.h" | |||
| # include "WDL/lice/lice_text.h" | |||
| # include "WDL/wdlstring.h" | |||
| #endif | |||
| #include <vector> | |||
| #include <queue> | |||
| #include <unordered_set> | |||
| #include <memory> | |||
| #include <atomic> | |||
| #include <cassert> | |||
| #if !defined(YSFX_NO_GFX) | |||
| #define GFX_GET_CONTEXT(opaque) (((opaque)) ? ysfx_gfx_get_context((ysfx_t *)(opaque)) : nullptr) | |||
| enum { | |||
| ysfx_gfx_max_images = 1024, | |||
| ysfx_gfx_max_fonts = 128, | |||
| ysfx_gfx_max_input = 1024, | |||
| }; | |||
| class eel_lice_state; | |||
| struct ysfx_gfx_state_t { | |||
| ysfx_gfx_state_t(ysfx_t *fx); | |||
| ~ysfx_gfx_state_t(); | |||
| std::unique_ptr<eel_lice_state> lice; | |||
| std::queue<uint32_t> input_queue; | |||
| std::unordered_set<uint32_t> keys_pressed; | |||
| ysfx_real scale = 0.0; | |||
| void *callback_data = nullptr; | |||
| int (*show_menu)(void *, const char *, int32_t, int32_t) = nullptr; | |||
| void (*set_cursor)(void *, int32_t) = nullptr; | |||
| const char *(*get_drop_file)(void *user_data, int32_t index) = nullptr; | |||
| }; | |||
| //------------------------------------------------------------------------------ | |||
| #if !defined(YSFX_NO_GFX) | |||
| static bool eel_lice_get_filename_for_string(void *opaque, EEL_F idx, WDL_FastString *fs, int iswrite) | |||
| { | |||
| if (iswrite) | |||
| return false; // this is neither supported nor used | |||
| ysfx_t *fx = (ysfx_t *)opaque; | |||
| std::string filepath; | |||
| if (!ysfx_find_data_file(fx, &idx, filepath)) | |||
| return false; | |||
| if (fs) fs->Set(filepath.data(), (uint32_t)filepath.size()); | |||
| return true; | |||
| } | |||
| #define EEL_LICE_GET_FILENAME_FOR_STRING(idx, fs, p) \ | |||
| eel_lice_get_filename_for_string(opaque, (idx), (fs), (p)) | |||
| #endif | |||
| //------------------------------------------------------------------------------ | |||
| #if !defined(YSFX_NO_GFX) | |||
| # include "ysfx_api_gfx_lice.hpp" | |||
| #else | |||
| # include "ysfx_api_gfx_dummy.hpp" | |||
| #endif | |||
| //------------------------------------------------------------------------------ | |||
| #if !defined(YSFX_NO_GFX) | |||
| static bool translate_special_key(uint32_t uni_key, uint32_t &jsfx_key) | |||
| { | |||
| auto key_c = [](uint8_t a, uint8_t b, uint8_t c, uint8_t d) -> uint32_t { | |||
| return a | (b << 8) | (c << 16) | (d << 24); | |||
| }; | |||
| switch (uni_key) { | |||
| default: return false; | |||
| case ysfx_key_delete: jsfx_key = key_c('d', 'e', 'l', 0); break; | |||
| case ysfx_key_f1: jsfx_key = key_c('f', '1', 0, 0); break; | |||
| case ysfx_key_f2: jsfx_key = key_c('f', '2', 0, 0); break; | |||
| case ysfx_key_f3: jsfx_key = key_c('f', '3', 0, 0); break; | |||
| case ysfx_key_f4: jsfx_key = key_c('f', '4', 0, 0); break; | |||
| case ysfx_key_f5: jsfx_key = key_c('f', '5', 0, 0); break; | |||
| case ysfx_key_f6: jsfx_key = key_c('f', '6', 0, 0); break; | |||
| case ysfx_key_f7: jsfx_key = key_c('f', '7', 0, 0); break; | |||
| case ysfx_key_f8: jsfx_key = key_c('f', '8', 0, 0); break; | |||
| case ysfx_key_f9: jsfx_key = key_c('f', '9', 0, 0); break; | |||
| case ysfx_key_f10: jsfx_key = key_c('f', '1', '0', 0); break; | |||
| case ysfx_key_f11: jsfx_key = key_c('f', '1', '1', 0); break; | |||
| case ysfx_key_f12: jsfx_key = key_c('f', '1', '2', 0); break; | |||
| case ysfx_key_left: jsfx_key = key_c('l', 'e', 'f', 't'); break; | |||
| case ysfx_key_up: jsfx_key = key_c('u', 'p', 0, 0); break; | |||
| case ysfx_key_right: jsfx_key = key_c('r', 'g', 'h', 't'); break; | |||
| case ysfx_key_down: jsfx_key = key_c('d', 'o', 'w', 'n'); break; | |||
| case ysfx_key_page_up: jsfx_key = key_c('p', 'g', 'u', 'p'); break; | |||
| case ysfx_key_page_down: jsfx_key = key_c('p', 'g', 'd', 'n'); break; | |||
| case ysfx_key_home: jsfx_key = key_c('h', 'o', 'm', 'e'); break; | |||
| case ysfx_key_end: jsfx_key = key_c('e', 'n', 'd', 0); break; | |||
| case ysfx_key_insert: jsfx_key = key_c('i', 'n', 's', 0); break; | |||
| } | |||
| return true; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_gfx_getchar(void *opaque, EEL_F *p) | |||
| { | |||
| ysfx_gfx_state_t *state = GFX_GET_CONTEXT(opaque); | |||
| if (!state) | |||
| return 0; | |||
| if (*p >= 1/*2*/) { // NOTE(jpc) this is 2.0 originally, which seems wrong | |||
| if (*p == 65536) { | |||
| // TODO implement window flags | |||
| return 0; | |||
| } | |||
| // current key down status | |||
| uint32_t key = (uint32_t)*p; | |||
| uint32_t key_id; | |||
| if (translate_special_key(key, key)) | |||
| key_id = key; | |||
| else if (key < 256) | |||
| key_id = ysfx::latin1_tolower(key); | |||
| else // support the Latin-1 character set only | |||
| return 0; | |||
| return (EEL_F)(state->keys_pressed.find(key_id) != state->keys_pressed.end()); | |||
| } | |||
| if (!state->input_queue.empty()) { | |||
| uint32_t key = state->input_queue.front(); | |||
| state->input_queue.pop(); | |||
| return (EEL_F)key; | |||
| } | |||
| return 0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_gfx_showmenu(void *opaque, INT_PTR nparms, EEL_F **parms) | |||
| { | |||
| ysfx_gfx_state_t *state = GFX_GET_CONTEXT(opaque); | |||
| if (!state || !state->show_menu) | |||
| return 0; | |||
| ysfx_t *fx = (ysfx_t *)state->lice->m_user_ctx; | |||
| std::string desc; | |||
| if (!ysfx_string_get(fx, *parms[0], desc) || desc.empty()) | |||
| return 0; | |||
| int32_t x = (int32_t)*fx->var.gfx_x; | |||
| int32_t y = (int32_t)*fx->var.gfx_y; | |||
| return state->show_menu(state->callback_data, desc.c_str(), x, y); | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_gfx_setcursor(void *opaque, INT_PTR nparms, EEL_F **parms) | |||
| { | |||
| ysfx_gfx_state_t *state = GFX_GET_CONTEXT(opaque); | |||
| if (!state || !state->set_cursor) | |||
| return 0; | |||
| int32_t id = (int32_t)*parms[0]; | |||
| state->set_cursor(state->callback_data, id); | |||
| return 0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_gfx_getdropfile(void *opaque, INT_PTR np, EEL_F **parms) | |||
| { | |||
| ysfx_gfx_state_t *state = GFX_GET_CONTEXT(opaque); | |||
| if (!state || !state->get_drop_file) | |||
| return 0; | |||
| const int32_t idx = (int)*parms[0]; | |||
| if (idx < 0) { | |||
| state->get_drop_file(state->callback_data, -1); | |||
| return 0; | |||
| } | |||
| const char *file = state->get_drop_file(state->callback_data, idx); | |||
| if (!file) | |||
| return 0; | |||
| if (np > 1) { | |||
| ysfx_t *fx = (ysfx_t *)state->lice->m_user_ctx; | |||
| ysfx_string_set(fx, *parms[1], file); | |||
| } | |||
| return 1; | |||
| } | |||
| #endif | |||
| //------------------------------------------------------------------------------ | |||
| #if !defined(YSFX_NO_GFX) | |||
| ysfx_gfx_state_t::ysfx_gfx_state_t(ysfx_t *fx) | |||
| : lice{new eel_lice_state{fx->vm.get(), fx, ysfx_gfx_max_images, ysfx_gfx_max_fonts}} | |||
| { | |||
| lice->m_framebuffer = new LICE_WrapperBitmap{nullptr, 0, 0, 0, false}; | |||
| } | |||
| ysfx_gfx_state_t::~ysfx_gfx_state_t() | |||
| { | |||
| } | |||
| #endif | |||
| ysfx_gfx_state_t *ysfx_gfx_state_new(ysfx_t *fx) | |||
| { | |||
| return new ysfx_gfx_state_t{fx}; | |||
| } | |||
| void ysfx_gfx_state_free(ysfx_gfx_state_t *state) | |||
| { | |||
| delete state; | |||
| } | |||
| void ysfx_gfx_state_set_bitmap(ysfx_gfx_state_t *state, uint8_t *data, uint32_t w, uint32_t h, uint32_t stride) | |||
| { | |||
| if (stride == 0) | |||
| stride = 4 * w; | |||
| eel_lice_state *lice = state->lice.get(); | |||
| assert(stride % 4 == 0); | |||
| *static_cast<LICE_WrapperBitmap *>(lice->m_framebuffer) = LICE_WrapperBitmap{(LICE_pixel *)data, (int)w, (int)h, (int)(stride / 4), false}; | |||
| } | |||
| void ysfx_gfx_state_set_scale_factor(ysfx_gfx_state_t *state, ysfx_real scale) | |||
| { | |||
| state->scale = scale; | |||
| } | |||
| void ysfx_gfx_state_set_callback_data(ysfx_gfx_state_t *state, void *callback_data) | |||
| { | |||
| state->callback_data = callback_data; | |||
| } | |||
| void ysfx_gfx_state_set_show_menu_callback(ysfx_gfx_state_t *state, int (*callback)(void *, const char *, int32_t, int32_t)) | |||
| { | |||
| state->show_menu = callback; | |||
| } | |||
| void ysfx_gfx_state_set_set_cursor_callback(ysfx_gfx_state_t *state, void (*callback)(void *, int32_t)) | |||
| { | |||
| state->set_cursor = callback; | |||
| } | |||
| void ysfx_gfx_state_set_get_drop_file_callback(ysfx_gfx_state_t *state, const char *(*callback)(void *, int32_t)) | |||
| { | |||
| state->get_drop_file = callback; | |||
| } | |||
| bool ysfx_gfx_state_is_dirty(ysfx_gfx_state_t *state) | |||
| { | |||
| return state->lice->m_framebuffer_dirty; | |||
| } | |||
| void ysfx_gfx_state_add_key(ysfx_gfx_state_t *state, uint32_t mods, uint32_t key, bool press) | |||
| { | |||
| if (key < 1) | |||
| return; | |||
| uint32_t key_id; | |||
| if (translate_special_key(key, key)) | |||
| key_id = key; | |||
| else if (key < 256) | |||
| key_id = ysfx::latin1_tolower(key); | |||
| else // support the Latin-1 character set only | |||
| return; | |||
| uint32_t key_with_mod = key; | |||
| if (key_id >= 'a' && key_id <= 'z') { | |||
| uint32_t off = (uint32_t)(key_id - 'a'); | |||
| if (mods & (ysfx_mod_ctrl|ysfx_mod_alt)) | |||
| key_with_mod = off + 257; | |||
| else if (mods & ysfx_mod_ctrl) | |||
| key_with_mod = off + 1; | |||
| else if (mods & ysfx_mod_alt) | |||
| key_with_mod = off + 321; | |||
| } | |||
| if (press && key_with_mod > 0) { | |||
| while (state->input_queue.size() >= ysfx_gfx_max_input) | |||
| state->input_queue.pop(); | |||
| state->input_queue.push(key_with_mod); | |||
| } | |||
| if (press) | |||
| state->keys_pressed.insert(key_id); | |||
| else | |||
| state->keys_pressed.erase(key_id); | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| void ysfx_gfx_enter(ysfx_t *fx, bool doinit) | |||
| { | |||
| fx->gfx.mutex.lock(); | |||
| ysfx_gfx_state_t *state = fx->gfx.state.get(); | |||
| if (doinit) { | |||
| if (fx->gfx.must_init.exchange(false, std::memory_order_acquire)) { | |||
| *fx->var.gfx_r = 1.0; | |||
| *fx->var.gfx_g = 1.0; | |||
| *fx->var.gfx_b = 1.0; | |||
| *fx->var.gfx_a = 1.0; | |||
| *fx->var.gfx_a2 = 1.0; | |||
| *fx->var.gfx_dest = -1.0; | |||
| *fx->var.mouse_wheel = 0.0; | |||
| *fx->var.mouse_hwheel = 0.0; | |||
| // NOTE the above are according to eel_lice.h `resetVarsToStock` | |||
| // it helps to reset a few more, especially for clearing | |||
| *fx->var.gfx_mode = 0; | |||
| *fx->var.gfx_clear = 0; | |||
| *fx->var.gfx_texth = 0; | |||
| *fx->var.mouse_cap = 0; | |||
| // reset key state | |||
| state->input_queue = {}; | |||
| state->keys_pressed = {}; | |||
| // reset lice | |||
| eel_lice_state *lice = state->lice.get(); | |||
| LICE_WrapperBitmap framebuffer = *static_cast<LICE_WrapperBitmap *>(lice->m_framebuffer); | |||
| state->lice.reset(); | |||
| lice = new eel_lice_state{fx->vm.get(), fx, ysfx_gfx_max_images, ysfx_gfx_max_fonts}; | |||
| state->lice.reset(lice); | |||
| lice->m_framebuffer = new LICE_WrapperBitmap(framebuffer); | |||
| // load images from filenames | |||
| uint32_t numfiles = (uint32_t)fx->source.main->header.filenames.size(); | |||
| for (uint32_t i = 0; i < numfiles; ++i) | |||
| lice->gfx_loadimg(fx, (int32_t)i, (EEL_F)i); | |||
| fx->gfx.ready = true; | |||
| } | |||
| } | |||
| ysfx_set_thread_id(ysfx_thread_id_gfx); | |||
| } | |||
| void ysfx_gfx_leave(ysfx_t *fx) | |||
| { | |||
| ysfx_set_thread_id(ysfx_thread_id_none); | |||
| fx->gfx.mutex.unlock(); | |||
| } | |||
| ysfx_gfx_state_t *ysfx_gfx_get_context(ysfx_t *fx) | |||
| { | |||
| if (!fx) | |||
| return nullptr; | |||
| // NOTE: make sure that this will be used from the @gfx thread only | |||
| if (ysfx_get_thread_id() != ysfx_thread_id_gfx) | |||
| return nullptr; | |||
| return fx->gfx.state.get(); | |||
| } | |||
| void ysfx_gfx_prepare(ysfx_t *fx) | |||
| { | |||
| ysfx_gfx_state_t *state = ysfx_gfx_get_context(fx); | |||
| eel_lice_state *lice = state->lice.get(); | |||
| lice->m_framebuffer_dirty = false; | |||
| // set variables `gfx_w` and `gfx_h` | |||
| ysfx_real gfx_w = (ysfx_real)lice->m_framebuffer->getWidth(); | |||
| ysfx_real gfx_h = (ysfx_real)lice->m_framebuffer->getHeight(); | |||
| if (state->scale > 1.0) { | |||
| gfx_w *= state->scale; | |||
| gfx_h *= state->scale; | |||
| *fx->var.gfx_ext_retina = state->scale; | |||
| } | |||
| *fx->var.gfx_w = gfx_w; | |||
| *fx->var.gfx_h = gfx_h; | |||
| } | |||
| #endif | |||
| //------------------------------------------------------------------------------ | |||
| void ysfx_api_init_gfx() | |||
| { | |||
| #if !defined(YSFX_NO_GFX) | |||
| lice_stb_install_loaders(); | |||
| #endif | |||
| NSEEL_addfunc_retptr("gfx_lineto", 3, NSEEL_PProc_THIS, &ysfx_api_gfx_lineto); | |||
| NSEEL_addfunc_retptr("gfx_lineto", 2, NSEEL_PProc_THIS, &ysfx_api_gfx_lineto2); | |||
| NSEEL_addfunc_retptr("gfx_rectto", 2, NSEEL_PProc_THIS, &ysfx_api_gfx_rectto); | |||
| NSEEL_addfunc_varparm("gfx_rect", 4, NSEEL_PProc_THIS, &ysfx_api_gfx_rect); | |||
| NSEEL_addfunc_varparm("gfx_line", 4, NSEEL_PProc_THIS, &ysfx_api_gfx_line); | |||
| NSEEL_addfunc_varparm("gfx_gradrect", 8, NSEEL_PProc_THIS, &ysfx_api_gfx_gradrect); | |||
| NSEEL_addfunc_varparm("gfx_muladdrect", 7, NSEEL_PProc_THIS, &ysfx_api_gfx_muladdrect); | |||
| NSEEL_addfunc_varparm("gfx_deltablit", 9, NSEEL_PProc_THIS, &ysfx_api_gfx_deltablit); | |||
| NSEEL_addfunc_exparms("gfx_transformblit", 8, NSEEL_PProc_THIS, &ysfx_api_gfx_transformblit); | |||
| NSEEL_addfunc_varparm("gfx_circle", 3, NSEEL_PProc_THIS, &ysfx_api_gfx_circle); | |||
| NSEEL_addfunc_varparm("gfx_triangle", 6, NSEEL_PProc_THIS, &ysfx_api_gfx_triangle); | |||
| NSEEL_addfunc_varparm("gfx_roundrect", 5, NSEEL_PProc_THIS, &ysfx_api_gfx_roundrect); | |||
| NSEEL_addfunc_varparm("gfx_arc", 5, NSEEL_PProc_THIS, &ysfx_api_gfx_arc); | |||
| NSEEL_addfunc_retptr("gfx_blurto", 2, NSEEL_PProc_THIS, &ysfx_api_gfx_blurto); | |||
| NSEEL_addfunc_exparms("gfx_showmenu", 1, NSEEL_PProc_THIS, &ysfx_api_gfx_showmenu); | |||
| NSEEL_addfunc_varparm("gfx_setcursor", 1, NSEEL_PProc_THIS, &ysfx_api_gfx_setcursor); | |||
| NSEEL_addfunc_retptr("gfx_drawnumber", 2, NSEEL_PProc_THIS, &ysfx_api_gfx_drawnumber); | |||
| NSEEL_addfunc_retptr("gfx_drawchar", 1, NSEEL_PProc_THIS, &ysfx_api_gfx_drawchar); | |||
| NSEEL_addfunc_varparm("gfx_drawstr", 1, NSEEL_PProc_THIS, &ysfx_api_gfx_drawstr); | |||
| NSEEL_addfunc_retptr("gfx_measurestr", 3, NSEEL_PProc_THIS, &ysfx_api_gfx_measurestr); | |||
| NSEEL_addfunc_retptr("gfx_measurechar", 3, NSEEL_PProc_THIS, &ysfx_api_gfx_measurechar); | |||
| NSEEL_addfunc_varparm("gfx_printf", 1, NSEEL_PProc_THIS, &ysfx_api_gfx_printf); | |||
| NSEEL_addfunc_retptr("gfx_setpixel", 3, NSEEL_PProc_THIS, &ysfx_api_gfx_setpixel); | |||
| NSEEL_addfunc_retptr("gfx_getpixel", 3, NSEEL_PProc_THIS, &ysfx_api_gfx_getpixel); | |||
| NSEEL_addfunc_retptr("gfx_getimgdim", 3, NSEEL_PProc_THIS, &ysfx_api_gfx_getimgdim); | |||
| NSEEL_addfunc_retval("gfx_setimgdim", 3, NSEEL_PProc_THIS, &ysfx_api_gfx_setimgdim); | |||
| NSEEL_addfunc_retval("gfx_loadimg", 2, NSEEL_PProc_THIS, &ysfx_api_gfx_loadimg); | |||
| NSEEL_addfunc_retptr("gfx_blit", 3, NSEEL_PProc_THIS, &ysfx_api_gfx_blit); | |||
| NSEEL_addfunc_retptr("gfx_blitext", 3, NSEEL_PProc_THIS, &ysfx_api_gfx_blitext); | |||
| NSEEL_addfunc_varparm("gfx_blit", 4, NSEEL_PProc_THIS, &ysfx_api_gfx_blit2); | |||
| NSEEL_addfunc_varparm("gfx_setfont", 1, NSEEL_PProc_THIS, &ysfx_api_gfx_setfont); | |||
| NSEEL_addfunc_varparm("gfx_getfont", 1, NSEEL_PProc_THIS, &ysfx_api_gfx_getfont); | |||
| NSEEL_addfunc_varparm("gfx_set", 1, NSEEL_PProc_THIS, &ysfx_api_gfx_set); | |||
| NSEEL_addfunc_varparm("gfx_getdropfile", 1, NSEEL_PProc_THIS, &ysfx_api_gfx_getdropfile); | |||
| NSEEL_addfunc_varparm("gfx_getsyscol", 0, NSEEL_PProc_THIS, &ysfx_api_gfx_getsyscol); | |||
| NSEEL_addfunc_retval("gfx_getchar", 1, NSEEL_PProc_THIS, &ysfx_api_gfx_getchar); | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| // SWELL helpers | |||
| #if !defined(YSFX_NO_GFX) | |||
| // implement these GL functions on SWELL targets where they are missing | |||
| #if !defined(SWELL_TARGET_GDK) && !defined(SWELL_TARGET_OSX) | |||
| void SWELL_SetViewGL(HWND h, char wantGL) | |||
| { | |||
| } | |||
| bool SWELL_GetViewGL(HWND h) | |||
| { | |||
| return false; | |||
| } | |||
| bool SWELL_SetGLContextToView(HWND h) | |||
| { | |||
| return false; | |||
| } | |||
| #endif // !defined(SWELL_TARGET_GDK) && !defined(SWELL_TARGET_OSX) | |||
| #endif | |||
| @@ -0,0 +1,50 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| #include "ysfx.h" | |||
| #if !defined(YSFX_NO_GFX) | |||
| struct ysfx_gfx_state_t; | |||
| ysfx_gfx_state_t *ysfx_gfx_state_new(ysfx_t *fx); | |||
| void ysfx_gfx_state_free(ysfx_gfx_state_t *state); | |||
| YSFX_DEFINE_AUTO_PTR(ysfx_gfx_state_u, ysfx_gfx_state_t, ysfx_gfx_state_free); | |||
| void ysfx_gfx_state_set_bitmap(ysfx_gfx_state_t *state, uint8_t *data, uint32_t w, uint32_t h, uint32_t stride); | |||
| void ysfx_gfx_state_set_scale_factor(ysfx_gfx_state_t *state, ysfx_real scale); | |||
| void ysfx_gfx_state_set_callback_data(ysfx_gfx_state_t *state, void *callback_data); | |||
| void ysfx_gfx_state_set_show_menu_callback(ysfx_gfx_state_t *state, int (*callback)(void *, const char *, int32_t, int32_t)); | |||
| void ysfx_gfx_state_set_set_cursor_callback(ysfx_gfx_state_t *state, void (*callback)(void *, int32_t)); | |||
| void ysfx_gfx_state_set_get_drop_file_callback(ysfx_gfx_state_t *state, const char *(*callback)(void *, int32_t)); | |||
| bool ysfx_gfx_state_is_dirty(ysfx_gfx_state_t *state); | |||
| void ysfx_gfx_state_add_key(ysfx_gfx_state_t *state, uint32_t mods, uint32_t key, bool press); | |||
| void ysfx_gfx_state_update_mouse(ysfx_gfx_state_t *state, uint32_t mods, int xpos, int ypos, uint32_t buttons, int wheel, int hwheel); | |||
| //------------------------------------------------------------------------------ | |||
| void ysfx_gfx_enter(ysfx_t *fx, bool doinit); | |||
| void ysfx_gfx_leave(ysfx_t *fx); | |||
| ysfx_gfx_state_t *ysfx_gfx_get_context(ysfx_t *fx); | |||
| void ysfx_gfx_prepare(ysfx_t *fx); | |||
| struct ysfx_scoped_gfx_t { | |||
| ysfx_scoped_gfx_t(ysfx_t *fx, bool doinit) : m_fx(fx) { ysfx_gfx_enter(fx, doinit); } | |||
| ~ysfx_scoped_gfx_t() { ysfx_gfx_leave(m_fx); } | |||
| ysfx_t *m_fx = nullptr; | |||
| }; | |||
| #endif | |||
| //------------------------------------------------------------------------------ | |||
| void ysfx_api_init_gfx(); | |||
| @@ -0,0 +1,91 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| #include "ysfx_eel_utils.hpp" | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_gfx_stub_varparm(void *, INT_PTR, EEL_F **) | |||
| { | |||
| return 0; | |||
| } | |||
| static EEL_F *NSEEL_CGEN_CALL ysfx_api_gfx_stub_retptr1(void *, EEL_F *arg) | |||
| { | |||
| return arg; | |||
| } | |||
| static EEL_F *NSEEL_CGEN_CALL ysfx_api_gfx_stub_retptr2(void *, EEL_F *arg, EEL_F *) | |||
| { | |||
| return arg; | |||
| } | |||
| static EEL_F *NSEEL_CGEN_CALL ysfx_api_gfx_stub_retptr3(void *, EEL_F *arg, EEL_F *, EEL_F *) | |||
| { | |||
| return arg; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_gfx_stub_retval1(void *, EEL_F *) | |||
| { | |||
| return 0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_gfx_stub_retval2(void *, EEL_F *, EEL_F *) | |||
| { | |||
| return 0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_gfx_stub_retval3(void *, EEL_F *, EEL_F *, EEL_F *) | |||
| { | |||
| return 0; | |||
| } | |||
| #define ysfx_api_gfx_lineto ysfx_api_gfx_stub_retptr3 | |||
| #define ysfx_api_gfx_lineto2 ysfx_api_gfx_stub_retptr2 | |||
| #define ysfx_api_gfx_rectto ysfx_api_gfx_stub_retptr2 | |||
| #define ysfx_api_gfx_rect ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_line ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_gradrect ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_muladdrect ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_deltablit ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_transformblit ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_circle ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_triangle ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_roundrect ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_arc ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_blurto ysfx_api_gfx_stub_retptr2 | |||
| #define ysfx_api_gfx_showmenu ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_setcursor ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_drawnumber ysfx_api_gfx_stub_retptr2 | |||
| #define ysfx_api_gfx_drawchar ysfx_api_gfx_stub_retptr1 | |||
| #define ysfx_api_gfx_drawstr ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_measurestr ysfx_api_gfx_stub_retptr3 | |||
| #define ysfx_api_gfx_measurechar ysfx_api_gfx_stub_retptr3 | |||
| #define ysfx_api_gfx_printf ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_setpixel ysfx_api_gfx_stub_retptr3 | |||
| #define ysfx_api_gfx_getpixel ysfx_api_gfx_stub_retptr3 | |||
| #define ysfx_api_gfx_getimgdim ysfx_api_gfx_stub_retptr3 | |||
| #define ysfx_api_gfx_setimgdim ysfx_api_gfx_stub_retval3 | |||
| #define ysfx_api_gfx_loadimg ysfx_api_gfx_stub_retval2 | |||
| #define ysfx_api_gfx_blit ysfx_api_gfx_stub_retptr3 | |||
| #define ysfx_api_gfx_blitext ysfx_api_gfx_stub_retptr3 | |||
| #define ysfx_api_gfx_blit2 ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_setfont ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_getfont ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_set ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_getdropfile ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_getsyscol ysfx_api_gfx_stub_varparm | |||
| #define ysfx_api_gfx_getchar ysfx_api_gfx_stub_retval1 | |||
| @@ -0,0 +1,439 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #include "ysfx.hpp" | |||
| #include "ysfx_api_reaper.hpp" | |||
| #include "ysfx_api_eel.hpp" | |||
| #include "ysfx_eel_utils.hpp" | |||
| #include "ysfx_utils.hpp" | |||
| #include <cmath> | |||
| #include <cstring> | |||
| #include <cassert> | |||
| #include "WDL/wdlstring.h" | |||
| #define REAPER_GET_INTERFACE(opaque) ((opaque) ? (ysfx_t *)(opaque) : nullptr) | |||
| static EEL_F *NSEEL_CGEN_CALL ysfx_api_spl(void *opaque, EEL_F *n_) | |||
| { | |||
| //NOTE: callable from @gfx thread | |||
| ysfx_t *fx = REAPER_GET_INTERFACE(opaque); | |||
| int32_t n = ysfx_eel_round<int32_t>(*n_); | |||
| if (n < 0 || n >= ysfx_max_channels) { | |||
| fx->var.ret_temp = 0; | |||
| return &fx->var.ret_temp; | |||
| } | |||
| return fx->var.spl[(uint32_t)n]; | |||
| } | |||
| static EEL_F *NSEEL_CGEN_CALL ysfx_api_slider(void *opaque, EEL_F *n_) | |||
| { | |||
| //NOTE: callable from @gfx thread | |||
| ysfx_t *fx = REAPER_GET_INTERFACE(opaque); | |||
| int32_t n = ysfx_eel_round<int32_t>(*n_); | |||
| if (n < 1 || n > ysfx_max_sliders) { | |||
| fx->var.ret_temp = 0; | |||
| return &fx->var.ret_temp; | |||
| } | |||
| n -= 1; | |||
| return fx->var.slider[(uint32_t)n]; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_slider_next_chg(void *opaque, EEL_F *index_, EEL_F *val_) | |||
| { | |||
| //TODO frame-accurate slider changes | |||
| (void)opaque; | |||
| (void)index_; | |||
| (void)val_; | |||
| return -1; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_slider_automate(void *opaque, EEL_F *mask_or_slider_) | |||
| { | |||
| //NOTE: callable from @gfx thread | |||
| ysfx_t *fx = REAPER_GET_INTERFACE(opaque); | |||
| uint32_t slider = ysfx_get_slider_of_var(fx, mask_or_slider_); | |||
| uint64_t mask; | |||
| if (slider < ysfx_max_sliders) | |||
| mask = (uint64_t)1 << slider; | |||
| else | |||
| mask = ysfx_eel_round<uint64_t>(std::fabs(*mask_or_slider_)); | |||
| fx->slider.automate_mask |= mask; | |||
| fx->slider.change_mask |= mask; | |||
| return 0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_sliderchange(void *opaque, EEL_F *mask_or_slider_) | |||
| { | |||
| //NOTE: callable from @gfx thread | |||
| ysfx_t *fx = REAPER_GET_INTERFACE(opaque); | |||
| uint32_t slider = ysfx_get_slider_of_var(fx, mask_or_slider_); | |||
| uint64_t mask; | |||
| if (slider < ysfx_max_sliders) | |||
| mask = (uint64_t)1 << slider; | |||
| else | |||
| mask = ysfx_eel_round<uint64_t>(std::fabs(*mask_or_slider_)); | |||
| fx->slider.change_mask |= mask; | |||
| return 0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_slider_show(void *opaque, EEL_F *mask_or_slider_, EEL_F *value_) | |||
| { | |||
| //NOTE: callable from @gfx thread | |||
| ysfx_t *fx = REAPER_GET_INTERFACE(opaque); | |||
| uint32_t slider = ysfx_get_slider_of_var(fx, mask_or_slider_); | |||
| uint64_t mask; | |||
| if (slider < ysfx_max_sliders) | |||
| mask = (uint64_t)1 << slider; | |||
| else | |||
| mask = ysfx_eel_round<uint64_t>(std::fabs(*mask_or_slider_)); | |||
| if (*value_ >= (EEL_F)+0.5) { | |||
| // show | |||
| fx->slider.visible_mask |= mask; | |||
| } | |||
| else if (*value_ >= (EEL_F)-0.5) { | |||
| // hide | |||
| mask = ~mask; | |||
| fx->slider.visible_mask &= mask; | |||
| } | |||
| else { | |||
| // toggle | |||
| mask = fx->slider.visible_mask.fetch_xor(mask) ^ mask; | |||
| } | |||
| return (EEL_F)mask; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_midisend(void *opaque, INT_PTR np, EEL_F **parms) | |||
| { | |||
| if (ysfx_get_thread_id() != ysfx_thread_id_dsp) | |||
| return 0; | |||
| int32_t offset; | |||
| uint8_t msg1; | |||
| uint8_t msg2; | |||
| uint8_t msg3; | |||
| switch (np) { | |||
| case 3: | |||
| { | |||
| offset = ysfx_eel_round<int32_t>(*parms[0]); | |||
| msg1 = (uint8_t)ysfx_eel_round<int32_t>(*parms[1]); | |||
| const uint32_t msg23 = ysfx_eel_round<int32_t>(*parms[2]); | |||
| msg2 = (uint8_t)(msg23 & 0xff); | |||
| msg3 = (uint8_t)(msg23 >> 8); | |||
| break; | |||
| } | |||
| case 4: | |||
| offset = ysfx_eel_round<int32_t>(*parms[0]); | |||
| msg1 = (uint8_t)ysfx_eel_round<int32_t>(*parms[1]); | |||
| msg2 = (uint8_t)ysfx_eel_round<int32_t>(*parms[2]); | |||
| msg3 = (uint8_t)ysfx_eel_round<int32_t>(*parms[3]); | |||
| break; | |||
| default: | |||
| assert(false); | |||
| return 0; | |||
| } | |||
| if (offset < 0) | |||
| offset = 0; | |||
| // NOTE(jpc) correct the length of the message | |||
| // in case it should be less than 3 bytes | |||
| uint32_t length = ysfx_midi_sizeof(msg1); | |||
| if (length == 0) // don't know what message this is | |||
| length = 3; | |||
| ysfx_t *fx = REAPER_GET_INTERFACE(opaque); | |||
| ysfx_midi_event_t event; | |||
| const uint8_t data[] = {msg1, msg2, msg3}; | |||
| event.bus = ysfx_current_midi_bus(fx); | |||
| event.offset = (uint32_t)offset; | |||
| event.size = length; | |||
| event.data = data; | |||
| if (!ysfx_midi_push(fx->midi.out.get(), &event)) | |||
| return 0; | |||
| return msg1; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_midisend_buf(void *opaque, EEL_F *offset_, EEL_F *buf_, EEL_F *len_) | |||
| { | |||
| if (ysfx_get_thread_id() != ysfx_thread_id_dsp) | |||
| return 0; | |||
| int32_t offset = ysfx_eel_round<int32_t>(*offset_); | |||
| int32_t buf = ysfx_eel_round<int32_t>(*buf_); | |||
| int32_t len = ysfx_eel_round<int32_t>(*len_); | |||
| if (len <= 0) | |||
| return 0; | |||
| if (offset < 0) | |||
| offset = 0; | |||
| ysfx_t *fx = REAPER_GET_INTERFACE(opaque); | |||
| ysfx_midi_push_t mp; | |||
| if (!ysfx_midi_push_begin(fx->midi.out.get(), ysfx_current_midi_bus(fx), (uint32_t)offset, &mp)) | |||
| return 0; | |||
| ysfx_eel_ram_reader reader{fx->vm.get(), buf}; | |||
| for (uint32_t i = 0; i < (uint32_t)len; ++i) { | |||
| uint8_t byte = (uint8_t)ysfx_eel_round<int32_t>(reader.read_next()); | |||
| if (!ysfx_midi_push_data(&mp, &byte, 1)) | |||
| break; | |||
| } | |||
| if (!ysfx_midi_push_end(&mp)) | |||
| return 0; | |||
| return len; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_midisend_str(void *opaque, EEL_F *offset_, EEL_F *str_) | |||
| { | |||
| if (ysfx_get_thread_id() != ysfx_thread_id_dsp) | |||
| return 0; | |||
| int32_t offset = ysfx_eel_round<int32_t>(*offset_); | |||
| if (offset < 0) | |||
| offset = 0; | |||
| ysfx_t *fx = REAPER_GET_INTERFACE(opaque); | |||
| /// | |||
| struct process_data { | |||
| ysfx_t *fx = nullptr; | |||
| uint32_t offset = 0; | |||
| uint32_t result = 0; | |||
| }; | |||
| process_data pdata; | |||
| pdata.offset = (uint32_t)offset; | |||
| pdata.fx = fx; | |||
| auto process_str = [](void *userdata, WDL_FastString &str) { | |||
| process_data *pdata = (process_data *)userdata; | |||
| ysfx_t *fx = pdata->fx; | |||
| ysfx_midi_event_t event; | |||
| event.bus = ysfx_current_midi_bus(fx); | |||
| event.offset = pdata->offset; | |||
| event.size = (uint32_t)str.GetLength(); | |||
| event.data = (const uint8_t *)str.Get(); | |||
| pdata->result = ysfx_midi_push(fx->midi.out.get(), &event) ? event.size : 0; | |||
| }; | |||
| /// | |||
| if (!ysfx_string_access(fx, *str_, false, +process_str, &pdata)) | |||
| return 0; | |||
| return pdata.result; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_midisyx(void *opaque, EEL_F *offset_, EEL_F *buf_, EEL_F *len_) | |||
| { | |||
| if (ysfx_get_thread_id() != ysfx_thread_id_dsp) | |||
| return 0; | |||
| int32_t offset = ysfx_eel_round<int32_t>(*offset_); | |||
| int32_t buf = ysfx_eel_round<int32_t>(*buf_); | |||
| int32_t len = ysfx_eel_round<int32_t>(*len_); | |||
| if (len <= 0) | |||
| return 0; | |||
| if (offset < 0) | |||
| offset = 0; | |||
| ysfx_t *fx = REAPER_GET_INTERFACE(opaque); | |||
| ysfx_midi_push_t mp; | |||
| if (!ysfx_midi_push_begin(fx->midi.out.get(), ysfx_current_midi_bus(fx), (uint32_t)offset, &mp)) | |||
| return 0; | |||
| ysfx_eel_ram_reader reader{fx->vm.get(), buf}; | |||
| for (uint32_t i = 0; i < (uint32_t)len; ++i) { | |||
| uint8_t byte = (uint8_t)ysfx_eel_round<int32_t>(reader.read_next()); | |||
| const uint8_t head = 0xf0; | |||
| const uint8_t tail = 0xf7; | |||
| if (i == 0 && byte != head) { | |||
| if (!ysfx_midi_push_data(&mp, &head, 1)) | |||
| break; | |||
| } | |||
| if (!ysfx_midi_push_data(&mp, &byte, 1)) | |||
| break; | |||
| if (i + 1 == (uint32_t)len && byte != tail) { | |||
| if (!ysfx_midi_push_data(&mp, &tail, 1)) | |||
| break; | |||
| } | |||
| } | |||
| if (!ysfx_midi_push_end(&mp)) | |||
| return 0; | |||
| return len; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_midirecv(void *opaque, INT_PTR np, EEL_F **parms) | |||
| { | |||
| if (ysfx_get_thread_id() != ysfx_thread_id_dsp) | |||
| return 0; | |||
| ysfx_t *fx = REAPER_GET_INTERFACE(opaque); | |||
| uint32_t bus = ysfx_current_midi_bus(fx); | |||
| ysfx_midi_event_t event; | |||
| bool have_event = ysfx_midi_get_next_from_bus(fx->midi.in.get(), bus, &event); | |||
| // pass through the sysex events | |||
| while (have_event && event.size > 3) { | |||
| ysfx_midi_push(fx->midi.out.get(), &event); | |||
| have_event = ysfx_midi_get_next_from_bus(fx->midi.in.get(), bus, &event); | |||
| } | |||
| if (!have_event) | |||
| return 0; | |||
| uint8_t msg1 = 0; | |||
| uint8_t msg2 = 0; | |||
| uint8_t msg3 = 0; | |||
| switch (event.size) { | |||
| case 3: msg3 = event.data[2]; // fall through | |||
| case 2: msg2 = event.data[1]; // fall through | |||
| case 1: msg1 = event.data[0]; break; | |||
| } | |||
| *parms[0] = (EEL_F)event.offset; | |||
| *parms[1] = (EEL_F)msg1; | |||
| switch (np) { | |||
| case 4: | |||
| *parms[2] = (EEL_F)msg2; | |||
| *parms[3] = (EEL_F)msg3; | |||
| break; | |||
| case 3: | |||
| *parms[2] = (EEL_F)(msg2 + (msg3 << 8)); | |||
| break; | |||
| default: | |||
| assert(false); | |||
| return 0; | |||
| } | |||
| return 1; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_midirecv_buf(void *opaque, EEL_F *offset_, EEL_F *buf_, EEL_F *recvlen_) | |||
| { | |||
| if (ysfx_get_thread_id() != ysfx_thread_id_dsp) | |||
| return 0; | |||
| ysfx_t *fx = REAPER_GET_INTERFACE(opaque); | |||
| NSEEL_VMCTX vm = fx->vm.get(); | |||
| int32_t buf = ysfx_eel_round<int32_t>(*buf_); | |||
| int32_t recvlen = ysfx_eel_round<int32_t>(*recvlen_); | |||
| if (recvlen < 0) | |||
| recvlen = 0; | |||
| uint32_t bus = ysfx_current_midi_bus(fx); | |||
| ysfx_midi_event_t event; | |||
| bool have_event = ysfx_midi_get_next_from_bus(fx->midi.in.get(), bus, &event); | |||
| // pass through the events larger than the buffer | |||
| while (have_event && event.size > (uint32_t)recvlen) { | |||
| ysfx_midi_push(fx->midi.out.get(), &event); | |||
| have_event = ysfx_midi_get_next_from_bus(fx->midi.in.get(), bus, &event); | |||
| } | |||
| if (!have_event) | |||
| return 0; | |||
| *offset_ = (EEL_F)event.offset; | |||
| ysfx_eel_ram_writer writer{vm, buf}; | |||
| for (uint32_t i = 0; i < event.size; ++i) | |||
| writer.write_next(event.data[i]); | |||
| return event.size; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL ysfx_api_midirecv_str(void *opaque, EEL_F *offset_, EEL_F *str_) | |||
| { | |||
| if (ysfx_get_thread_id() != ysfx_thread_id_dsp) | |||
| return 0; | |||
| ysfx_t *fx = REAPER_GET_INTERFACE(opaque); | |||
| uint32_t bus = ysfx_current_midi_bus(fx); | |||
| ysfx_midi_event_t event; | |||
| bool have_event = ysfx_midi_get_next_from_bus(fx->midi.in.get(), bus, &event); | |||
| // pass through the events larger than the maximum string | |||
| while (have_event && event.size > ysfx_string_max_length) { | |||
| ysfx_midi_push(fx->midi.out.get(), &event); | |||
| have_event = ysfx_midi_get_next_from_bus(fx->midi.in.get(), bus, &event); | |||
| } | |||
| if (!have_event) | |||
| return 0; | |||
| auto process_str = [](void *userdata, WDL_FastString &str) { | |||
| ysfx_midi_event_t *event = (ysfx_midi_event_t *)userdata; | |||
| str.SetRaw((const char *)event->data, (int32_t)event->size); | |||
| }; | |||
| /// | |||
| if (!ysfx_string_access(fx, *str_, true, +process_str, &event)) | |||
| return 0; | |||
| *offset_ = (EEL_F)event.offset; | |||
| return event.size; | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| void ysfx_api_init_reaper() | |||
| { | |||
| NSEEL_addfunc_retptr("spl", 1, NSEEL_PProc_THIS, &ysfx_api_spl); | |||
| NSEEL_addfunc_retptr("slider", 1, NSEEL_PProc_THIS, &ysfx_api_slider); | |||
| NSEEL_addfunc_retval("slider_next_chg", 2, NSEEL_PProc_THIS, &ysfx_api_slider_next_chg); | |||
| NSEEL_addfunc_retval("slider_automate", 1, NSEEL_PProc_THIS, &ysfx_api_slider_automate); | |||
| NSEEL_addfunc_retval("sliderchange", 1, NSEEL_PProc_THIS, &ysfx_api_sliderchange); | |||
| NSEEL_addfunc_retval("slider_show", 2, NSEEL_PProc_THIS, &ysfx_api_slider_show); | |||
| NSEEL_addfunc_exparms("midisend", 3, NSEEL_PProc_THIS, &ysfx_api_midisend); | |||
| NSEEL_addfunc_exparms("midisend", 4, NSEEL_PProc_THIS, &ysfx_api_midisend); | |||
| NSEEL_addfunc_retval("midisend_buf", 3, NSEEL_PProc_THIS, &ysfx_api_midisend_buf); | |||
| NSEEL_addfunc_retval("midisend_str", 2, NSEEL_PProc_THIS, &ysfx_api_midisend_str); | |||
| NSEEL_addfunc_exparms("midirecv", 3, NSEEL_PProc_THIS, &ysfx_api_midirecv); | |||
| NSEEL_addfunc_exparms("midirecv", 4, NSEEL_PProc_THIS, &ysfx_api_midirecv); | |||
| NSEEL_addfunc_retval("midirecv_buf", 3, NSEEL_PProc_THIS, &ysfx_api_midirecv_buf); | |||
| NSEEL_addfunc_retval("midirecv_str", 2, NSEEL_PProc_THIS, &ysfx_api_midirecv_str); | |||
| NSEEL_addfunc_retval("midisyx", 3, NSEEL_PProc_THIS, &ysfx_api_midisyx); | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| void ysfx_api_init_reaper(); | |||
| @@ -0,0 +1,166 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #include "ysfx_audio_flac.hpp" | |||
| #include "ysfx_utils.hpp" | |||
| #include <memory> | |||
| #include <cstring> | |||
| #if defined(__GNUC__) | |||
| # pragma GCC diagnostic push | |||
| # pragma GCC diagnostic ignored "-Wunused-function" | |||
| #endif | |||
| #define DR_FLAC_IMPLEMENTATION | |||
| #define DRFLAC_API static | |||
| #define DRFLAC_PRIVATE static | |||
| #include "dr_flac.h" | |||
| #if defined(__GNUC__) | |||
| # pragma GCC diagnostic pop | |||
| #endif | |||
| struct drflac_u_deleter { | |||
| void operator()(drflac *x) const noexcept { drflac_close(x); } | |||
| }; | |||
| using drflac_u = std::unique_ptr<drflac, drflac_u_deleter>; | |||
| /// | |||
| struct ysfx_flac_reader_t { | |||
| drflac_u flac; | |||
| uint32_t nbuff = 0; | |||
| std::unique_ptr<float[]> buff; | |||
| }; | |||
| static bool ysfx_flac_can_handle(const char *path) | |||
| { | |||
| return ysfx::path_has_suffix(path, "flac"); | |||
| } | |||
| static ysfx_audio_reader_t *ysfx_flac_open(const char *path) | |||
| { | |||
| #if !defined(_WIN32) | |||
| drflac_u flac{drflac_open_file(path, NULL)}; | |||
| #else | |||
| drflac_u flac{drflac_open_file_w(ysfx::widen(path).c_str(), NULL)}; | |||
| #endif | |||
| if (!flac) | |||
| return nullptr; | |||
| std::unique_ptr<ysfx_flac_reader_t> reader{new ysfx_flac_reader_t}; | |||
| reader->flac = std::move(flac); | |||
| reader->buff.reset(new float[reader->flac->channels]); | |||
| return (ysfx_audio_reader_t *)reader.release(); | |||
| } | |||
| static void ysfx_flac_close(ysfx_audio_reader_t *reader_) | |||
| { | |||
| ysfx_flac_reader_t *reader = (ysfx_flac_reader_t *)reader_; | |||
| delete reader; | |||
| } | |||
| static ysfx_audio_file_info_t ysfx_flac_info(ysfx_audio_reader_t *reader_) | |||
| { | |||
| ysfx_flac_reader_t *reader = (ysfx_flac_reader_t *)reader_; | |||
| ysfx_audio_file_info_t info; | |||
| info.channels = reader->flac->channels; | |||
| info.sample_rate = (ysfx_real)reader->flac->sampleRate; | |||
| return info; | |||
| } | |||
| static uint64_t ysfx_flac_avail(ysfx_audio_reader_t *reader_) | |||
| { | |||
| ysfx_flac_reader_t *reader = (ysfx_flac_reader_t *)reader_; | |||
| return reader->nbuff + reader->flac->channels * (reader->flac->totalPCMFrameCount - reader->flac->currentPCMFrame); | |||
| } | |||
| static void ysfx_flac_rewind(ysfx_audio_reader_t *reader_) | |||
| { | |||
| ysfx_flac_reader_t *reader = (ysfx_flac_reader_t *)reader_; | |||
| drflac_seek_to_pcm_frame(reader->flac.get(), 0); | |||
| reader->nbuff = 0; | |||
| } | |||
| static uint64_t ysfx_flac_unload_buffer(ysfx_audio_reader_t *reader_, ysfx_real *samples, uint64_t count) | |||
| { | |||
| ysfx_flac_reader_t *reader = (ysfx_flac_reader_t *)reader_; | |||
| uint32_t nbuff = reader->nbuff; | |||
| if (nbuff > count) | |||
| nbuff = (uint32_t)count; | |||
| if (nbuff == 0) | |||
| return 0; | |||
| const float *src = &reader->buff[reader->flac->channels - reader->nbuff]; | |||
| for (uint32_t i = 0; i < nbuff; ++i) | |||
| samples[i] = src[i]; | |||
| reader->nbuff -= nbuff; | |||
| return nbuff; | |||
| } | |||
| static uint64_t ysfx_flac_read(ysfx_audio_reader_t *reader_, ysfx_real *samples, uint64_t count) | |||
| { | |||
| ysfx_flac_reader_t *reader = (ysfx_flac_reader_t *)reader_; | |||
| uint32_t channels = reader->flac->channels; | |||
| uint64_t readtotal = 0; | |||
| if (count == 0) | |||
| return readtotal; | |||
| else { | |||
| uint64_t copied = ysfx_flac_unload_buffer(reader_, samples, count); | |||
| samples += copied; | |||
| count -= copied; | |||
| readtotal += copied; | |||
| } | |||
| if (count == 0) | |||
| return readtotal; | |||
| else { | |||
| float *f32buf = (float *)samples; | |||
| uint64_t readframes = drflac_read_pcm_frames_f32(reader->flac.get(), count / channels, f32buf); | |||
| uint64_t readsamples = channels * readframes; | |||
| // f32->f64 | |||
| for (uint64_t i = readsamples; i-- > 0; ) | |||
| samples[i] = f32buf[i]; | |||
| samples += readsamples; | |||
| count -= readsamples; | |||
| readtotal += readsamples; | |||
| } | |||
| if (count == 0) | |||
| return readtotal; | |||
| else if (drflac_read_pcm_frames_f32(reader->flac.get(), 1, reader->buff.get()) == 1) { | |||
| reader->nbuff = channels; | |||
| uint64_t copied = ysfx_flac_unload_buffer(reader_, samples, count); | |||
| samples += copied; | |||
| count -= copied; | |||
| readtotal += copied; | |||
| } | |||
| return readtotal; | |||
| } | |||
| const ysfx_audio_format_t ysfx_audio_format_flac = { | |||
| &ysfx_flac_can_handle, | |||
| &ysfx_flac_open, | |||
| &ysfx_flac_close, | |||
| &ysfx_flac_info, | |||
| &ysfx_flac_avail, | |||
| &ysfx_flac_rewind, | |||
| &ysfx_flac_read, | |||
| }; | |||
| @@ -0,0 +1,21 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| #include "ysfx.h" | |||
| extern const ysfx_audio_format_t ysfx_audio_format_flac; | |||
| @@ -0,0 +1,162 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #include "ysfx_audio_wav.hpp" | |||
| #include "ysfx_utils.hpp" | |||
| #include <memory> | |||
| #include <cstring> | |||
| #if defined(__GNUC__) | |||
| # pragma GCC diagnostic push | |||
| # pragma GCC diagnostic ignored "-Wunused-function" | |||
| #endif | |||
| #define DR_WAV_IMPLEMENTATION | |||
| #define DRWAV_API static | |||
| #define DRWAV_PRIVATE static | |||
| #include "dr_wav.h" | |||
| #if defined(__GNUC__) | |||
| # pragma GCC diagnostic pop | |||
| #endif | |||
| struct ysfx_wav_reader_t { | |||
| ~ysfx_wav_reader_t() { drwav_uninit(wav.get()); } | |||
| std::unique_ptr<drwav> wav; | |||
| uint32_t nbuff = 0; | |||
| std::unique_ptr<float[]> buff; | |||
| }; | |||
| static bool ysfx_wav_can_handle(const char *path) | |||
| { | |||
| return ysfx::path_has_suffix(path, "wav"); | |||
| } | |||
| static ysfx_audio_reader_t *ysfx_wav_open(const char *path) | |||
| { | |||
| std::unique_ptr<drwav> wav{new drwav}; | |||
| #if !defined(_WIN32) | |||
| drwav_bool32 initok = drwav_init_file(wav.get(), path, nullptr); | |||
| #else | |||
| drwav_bool32 initok = drwav_init_file_w(wav.get(), ysfx::widen(path).c_str(), nullptr); | |||
| #endif | |||
| if (!initok) | |||
| return nullptr; | |||
| std::unique_ptr<ysfx_wav_reader_t> reader{new ysfx_wav_reader_t}; | |||
| reader->wav = std::move(wav); | |||
| reader->buff.reset(new float[reader->wav->channels]); | |||
| return (ysfx_audio_reader_t *)reader.release(); | |||
| } | |||
| static void ysfx_wav_close(ysfx_audio_reader_t *reader_) | |||
| { | |||
| ysfx_wav_reader_t *reader = (ysfx_wav_reader_t *)reader_; | |||
| delete reader; | |||
| } | |||
| static ysfx_audio_file_info_t ysfx_wav_info(ysfx_audio_reader_t *reader_) | |||
| { | |||
| ysfx_wav_reader_t *reader = (ysfx_wav_reader_t *)reader_; | |||
| ysfx_audio_file_info_t info; | |||
| info.channels = reader->wav->channels; | |||
| info.sample_rate = (ysfx_real)reader->wav->sampleRate; | |||
| return info; | |||
| } | |||
| static uint64_t ysfx_wav_avail(ysfx_audio_reader_t *reader_) | |||
| { | |||
| ysfx_wav_reader_t *reader = (ysfx_wav_reader_t *)reader_; | |||
| return reader->nbuff + reader->wav->channels * (reader->wav->totalPCMFrameCount - reader->wav->readCursorInPCMFrames); | |||
| } | |||
| static void ysfx_wav_rewind(ysfx_audio_reader_t *reader_) | |||
| { | |||
| ysfx_wav_reader_t *reader = (ysfx_wav_reader_t *)reader_; | |||
| drwav_seek_to_pcm_frame(reader->wav.get(), 0); | |||
| reader->nbuff = 0; | |||
| } | |||
| static uint64_t ysfx_wav_unload_buffer(ysfx_audio_reader_t *reader_, ysfx_real *samples, uint64_t count) | |||
| { | |||
| ysfx_wav_reader_t *reader = (ysfx_wav_reader_t *)reader_; | |||
| uint32_t nbuff = reader->nbuff; | |||
| if (nbuff > count) | |||
| nbuff = (uint32_t)count; | |||
| if (nbuff == 0) | |||
| return 0; | |||
| const float *src = &reader->buff[reader->wav->channels - reader->nbuff]; | |||
| for (uint32_t i = 0; i < nbuff; ++i) | |||
| samples[i] = src[i]; | |||
| reader->nbuff -= nbuff; | |||
| return nbuff; | |||
| } | |||
| static uint64_t ysfx_wav_read(ysfx_audio_reader_t *reader_, ysfx_real *samples, uint64_t count) | |||
| { | |||
| ysfx_wav_reader_t *reader = (ysfx_wav_reader_t *)reader_; | |||
| uint32_t channels = reader->wav->channels; | |||
| uint64_t readtotal = 0; | |||
| if (count == 0) | |||
| return readtotal; | |||
| else { | |||
| uint64_t copied = ysfx_wav_unload_buffer(reader_, samples, count); | |||
| samples += copied; | |||
| count -= copied; | |||
| readtotal += copied; | |||
| } | |||
| if (count == 0) | |||
| return readtotal; | |||
| else { | |||
| float *f32buf = (float *)samples; | |||
| uint64_t readframes = drwav_read_pcm_frames_f32(reader->wav.get(), count / channels, f32buf); | |||
| uint64_t readsamples = channels * readframes; | |||
| // f32->f64 | |||
| for (uint64_t i = readsamples; i-- > 0; ) | |||
| samples[i] = f32buf[i]; | |||
| samples += readsamples; | |||
| count -= readsamples; | |||
| readtotal += readsamples; | |||
| } | |||
| if (count == 0) | |||
| return readtotal; | |||
| else if (drwav_read_pcm_frames_f32(reader->wav.get(), 1, reader->buff.get()) == 1) { | |||
| reader->nbuff = channels; | |||
| uint64_t copied = ysfx_wav_unload_buffer(reader_, samples, count); | |||
| samples += copied; | |||
| count -= copied; | |||
| readtotal += copied; | |||
| } | |||
| return readtotal; | |||
| } | |||
| const ysfx_audio_format_t ysfx_audio_format_wav = { | |||
| &ysfx_wav_can_handle, | |||
| &ysfx_wav_open, | |||
| &ysfx_wav_close, | |||
| &ysfx_wav_info, | |||
| &ysfx_wav_avail, | |||
| &ysfx_wav_rewind, | |||
| &ysfx_wav_read, | |||
| }; | |||
| @@ -0,0 +1,21 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| #include "ysfx.h" | |||
| extern const ysfx_audio_format_t ysfx_audio_format_wav; | |||
| @@ -0,0 +1,159 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #include "ysfx_config.hpp" | |||
| #include "ysfx_utils.hpp" | |||
| #include "ysfx_audio_wav.hpp" | |||
| #include "ysfx_audio_flac.hpp" | |||
| #include <cassert> | |||
| ysfx_config_t *ysfx_config_new() | |||
| { | |||
| return new ysfx_config_t; | |||
| } | |||
| void ysfx_config_free(ysfx_config_t *config) | |||
| { | |||
| if (!config) | |||
| return; | |||
| if (config->ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1) | |||
| delete config; | |||
| } | |||
| void ysfx_config_add_ref(ysfx_config_t *config) | |||
| { | |||
| config->ref_count.fetch_add(1, std::memory_order_relaxed); | |||
| } | |||
| void ysfx_set_import_root(ysfx_config_t *config, const char *root) | |||
| { | |||
| config->import_root = ysfx::path_ensure_final_separator(root ? root : ""); | |||
| } | |||
| void ysfx_set_data_root(ysfx_config_t *config, const char *root) | |||
| { | |||
| config->data_root = ysfx::path_ensure_final_separator(root ? root : ""); | |||
| } | |||
| const char *ysfx_get_import_root(ysfx_config_t *config) | |||
| { | |||
| return config->import_root.c_str(); | |||
| } | |||
| const char *ysfx_get_data_root(ysfx_config_t *config) | |||
| { | |||
| return config->data_root.c_str(); | |||
| } | |||
| void ysfx_guess_file_roots(ysfx_config_t *config, const char *sourcepath) | |||
| { | |||
| if (config->import_root.empty()) { | |||
| bool stop = false; | |||
| const std::string sourcedir = ysfx::path_directory(sourcepath); | |||
| std::string cur_dir = sourcedir + "../"; | |||
| ysfx::file_uid cur_uid; | |||
| if (!ysfx::get_file_uid(cur_dir.c_str(), cur_uid)) | |||
| stop = true; | |||
| while (!stop) { | |||
| bool match = | |||
| ysfx::exists((cur_dir + "Effects/").c_str()) && | |||
| ysfx::exists((cur_dir + "Data/").c_str()); | |||
| if (match) { | |||
| stop = true; | |||
| config->import_root = cur_dir + "Effects/"; | |||
| } | |||
| else { | |||
| cur_dir += "../"; | |||
| ysfx::file_uid old_uid = cur_uid; | |||
| if (!ysfx::get_file_uid(cur_dir.c_str(), cur_uid) || old_uid == cur_uid) | |||
| stop = true; | |||
| } | |||
| } | |||
| } | |||
| if (config->data_root.empty() && !config->import_root.empty()) { | |||
| const std::string datadir = config->import_root + "../Data/"; | |||
| bool match = ysfx::exists(datadir.c_str()); | |||
| if (match) | |||
| config->data_root = datadir; | |||
| } | |||
| } | |||
| void ysfx_register_audio_format(ysfx_config_t *config, ysfx_audio_format_t *afmt) | |||
| { | |||
| config->audio_formats.push_back(*afmt); | |||
| } | |||
| void ysfx_register_builtin_audio_formats(ysfx_config_t *config) | |||
| { | |||
| config->audio_formats.push_back(ysfx_audio_format_wav); | |||
| config->audio_formats.push_back(ysfx_audio_format_flac); | |||
| } | |||
| void ysfx_set_log_reporter(ysfx_config_t *config, ysfx_log_reporter_t *reporter) | |||
| { | |||
| config->log_reporter = reporter; | |||
| } | |||
| void ysfx_set_user_data(ysfx_config_t *config, intptr_t userdata) | |||
| { | |||
| config->userdata = userdata; | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| const char *ysfx_log_level_string(ysfx_log_level level) | |||
| { | |||
| switch (level) { | |||
| case ysfx_log_info: | |||
| return "info"; | |||
| case ysfx_log_warning: | |||
| return "warning"; | |||
| case ysfx_log_error: | |||
| return "error"; | |||
| default: | |||
| assert(false); | |||
| return "?"; | |||
| } | |||
| } | |||
| void ysfx_log(ysfx_config_t &conf, ysfx_log_level level, const char *message) | |||
| { | |||
| if (conf.log_reporter) | |||
| conf.log_reporter(conf.userdata, level, message); | |||
| else | |||
| fprintf(stderr, "[ysfx] %s: %s\n", ysfx_log_level_string(level), message); | |||
| } | |||
| void ysfx_logfv(ysfx_config_t &conf, ysfx_log_level level, const char *format, va_list ap) | |||
| { | |||
| char buf[256]; | |||
| vsnprintf(buf, sizeof(buf), format, ap); | |||
| buf[sizeof(buf)-1] = '\0'; | |||
| ysfx_log(conf, level, buf); | |||
| } | |||
| void ysfx_logf(ysfx_config_t &conf, ysfx_log_level level, const char *format, ...) | |||
| { | |||
| va_list ap; | |||
| va_start(ap, format); | |||
| ysfx_logfv(conf, level, format, ap); | |||
| va_end(ap); | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| #include "ysfx.h" | |||
| #include <vector> | |||
| #include <string> | |||
| #include <atomic> | |||
| #include <cstdarg> | |||
| struct ysfx_config_s { | |||
| std::string import_root; | |||
| std::string data_root; | |||
| std::vector<ysfx_audio_format_t> audio_formats; | |||
| ysfx_log_reporter_t *log_reporter = nullptr; | |||
| intptr_t userdata = 0; | |||
| std::atomic<uint32_t> ref_count{1}; | |||
| }; | |||
| void ysfx_log(ysfx_config_t &conf, ysfx_log_level level, const char *message); | |||
| void ysfx_logfv(ysfx_config_t &conf, ysfx_log_level level, const char *format, va_list ap); | |||
| #if defined(__GNUC__) | |||
| __attribute__((format(printf, 3, 4))) | |||
| #endif | |||
| void ysfx_logf(ysfx_config_t &conf, ysfx_log_level level, const char *format, ...); | |||
| @@ -0,0 +1,66 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #include "ysfx_eel_utils.hpp" | |||
| ysfx_eel_ram_reader::ysfx_eel_ram_reader(NSEEL_VMCTX vm, int64_t addr) | |||
| : m_vm(vm), | |||
| m_addr(addr) | |||
| { | |||
| } | |||
| EEL_F ysfx_eel_ram_reader::read_next() | |||
| { | |||
| if (m_block_avail == 0) { | |||
| m_block = (m_addr < 0 || m_addr > 0xFFFFFFFFu) ? nullptr : | |||
| NSEEL_VM_getramptr_noalloc(m_vm, (uint32_t)m_addr, (int32_t *)&m_block_avail); | |||
| if (m_block) | |||
| m_addr += m_block_avail; | |||
| else { | |||
| m_addr += 1; | |||
| m_block_avail = 1; | |||
| } | |||
| } | |||
| EEL_F value = m_block ? *m_block++ : 0; | |||
| m_block_avail -= 1; | |||
| return value; | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| ysfx_eel_ram_writer::ysfx_eel_ram_writer(NSEEL_VMCTX vm, int64_t addr) | |||
| : m_vm(vm), | |||
| m_addr(addr) | |||
| { | |||
| } | |||
| bool ysfx_eel_ram_writer::write_next(EEL_F value) | |||
| { | |||
| if (m_block_avail == 0) { | |||
| m_block = (m_addr < 0 || m_addr > 0xFFFFFFFFu) ? nullptr : | |||
| NSEEL_VM_getramptr(m_vm, (uint32_t)m_addr, (int32_t *)&m_block_avail); | |||
| if (m_block) | |||
| m_addr += m_block_avail; | |||
| else { | |||
| m_addr += 1; | |||
| m_block_avail = 1; | |||
| } | |||
| } | |||
| if (m_block) | |||
| *m_block++ = value; | |||
| m_block_avail -= 1; | |||
| return true; | |||
| } | |||
| @@ -0,0 +1,57 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| #include "WDL/eel2/ns-eel.h" | |||
| #include "WDL/eel2/ns-eel-int.h" | |||
| #include <type_traits> | |||
| #include <cstdint> | |||
| template <class T> | |||
| inline typename std::enable_if<std::is_integral<T>::value, T>::type | |||
| ysfx_eel_round(EEL_F value) | |||
| { | |||
| return (T)(value + (EEL_F)0.0001); // same one as used in eel2 everywhere | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| class ysfx_eel_ram_reader { | |||
| public: | |||
| ysfx_eel_ram_reader() = default; | |||
| ysfx_eel_ram_reader(NSEEL_VMCTX vm, int64_t addr); | |||
| EEL_F read_next(); | |||
| private: | |||
| NSEEL_VMCTX m_vm{}; | |||
| int64_t m_addr = 0; | |||
| const EEL_F *m_block = nullptr; | |||
| uint32_t m_block_avail = 0; | |||
| }; | |||
| //------------------------------------------------------------------------------ | |||
| class ysfx_eel_ram_writer { | |||
| public: | |||
| ysfx_eel_ram_writer() = default; | |||
| ysfx_eel_ram_writer(NSEEL_VMCTX vm, int64_t addr); | |||
| bool write_next(EEL_F value); | |||
| private: | |||
| NSEEL_VMCTX m_vm{}; | |||
| int64_t m_addr = 0; | |||
| EEL_F *m_block = nullptr; | |||
| uint32_t m_block_avail = 0; | |||
| }; | |||
| @@ -0,0 +1,214 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #include "ysfx_midi.hpp" | |||
| #include <cstring> | |||
| #include <cassert> | |||
| void ysfx_midi_reserve(ysfx_midi_buffer_t *midi, uint32_t capacity, bool extensible) | |||
| { | |||
| std::vector<uint8_t> data; | |||
| data.reserve(capacity); | |||
| std::swap(data, midi->data); | |||
| midi->extensible = extensible; | |||
| ysfx_midi_rewind(midi); | |||
| } | |||
| void ysfx_midi_clear(ysfx_midi_buffer_t *midi) | |||
| { | |||
| midi->data.clear(); | |||
| ysfx_midi_rewind(midi); | |||
| } | |||
| bool ysfx_midi_push(ysfx_midi_buffer_t *midi, const ysfx_midi_event_t *event) | |||
| { | |||
| if (event->size > ysfx_midi_message_max_size) | |||
| return false; | |||
| if (event->bus >= ysfx_max_midi_buses) | |||
| return false; | |||
| ysfx_midi_header_t header; | |||
| if (!midi->extensible) { | |||
| size_t writable = midi->data.capacity() - midi->data.size(); | |||
| if (writable < sizeof(header) + event->size) | |||
| return false; | |||
| } | |||
| const uint8_t *data = event->data; | |||
| const uint8_t *headp = (const uint8_t *)&header; | |||
| header.bus = event->bus; | |||
| header.offset = event->offset; | |||
| header.size = event->size; | |||
| midi->data.insert(midi->data.end(), headp, headp + sizeof(header)); | |||
| midi->data.insert(midi->data.end(), data, data + header.size); | |||
| return true; | |||
| } | |||
| void ysfx_midi_rewind(ysfx_midi_buffer_t *midi) | |||
| { | |||
| midi->read_pos = 0; | |||
| for (uint32_t i = 0; i < ysfx_max_midi_buses; ++i) | |||
| midi->read_pos_for_bus[i] = 0; | |||
| } | |||
| bool ysfx_midi_get_next(ysfx_midi_buffer_t *midi, ysfx_midi_event_t *event) | |||
| { | |||
| size_t *pos_ptr = &midi->read_pos; | |||
| size_t pos = *pos_ptr; | |||
| size_t avail = midi->data.size() - pos; | |||
| ysfx_midi_header_t header; | |||
| if (avail == 0) | |||
| return false; | |||
| assert(avail >= sizeof(header)); | |||
| memcpy(&header, &midi->data[pos], sizeof(header)); | |||
| assert(avail >= sizeof(header) + header.size); | |||
| event->bus = header.bus; | |||
| event->offset = header.offset; | |||
| event->size = header.size; | |||
| event->data = &midi->data[pos + sizeof(header)]; | |||
| *pos_ptr = pos + (sizeof(header) + header.size); | |||
| return true; | |||
| } | |||
| bool ysfx_midi_get_next_from_bus(ysfx_midi_buffer_t *midi, uint32_t bus, ysfx_midi_event_t *event) | |||
| { | |||
| if (bus >= ysfx_max_midi_buses) | |||
| return false; | |||
| size_t *pos_ptr = &midi->read_pos_for_bus[bus]; | |||
| size_t pos = *pos_ptr; | |||
| size_t avail = midi->data.size() - pos; | |||
| ysfx_midi_header_t header; | |||
| bool found = false; | |||
| while (!found && avail > 0) { | |||
| assert(avail >= sizeof(header)); | |||
| memcpy(&header, &midi->data[pos], sizeof(header)); | |||
| assert(avail >= sizeof(header) + header.size); | |||
| found = header.bus == bus; | |||
| if (!found) { | |||
| pos += sizeof(header) + header.size; | |||
| avail -= sizeof(header) + header.size; | |||
| } | |||
| } | |||
| if (!found) { | |||
| *pos_ptr = pos; | |||
| return false; | |||
| } | |||
| event->bus = header.bus; | |||
| event->offset = header.offset; | |||
| event->size = header.size; | |||
| event->data = &midi->data[pos + sizeof(header)]; | |||
| *pos_ptr = pos + (sizeof(header) + header.size); | |||
| return true; | |||
| } | |||
| bool ysfx_midi_push_begin(ysfx_midi_buffer_t *midi, uint32_t bus, uint32_t offset, ysfx_midi_push_t *mp) | |||
| { | |||
| ysfx_midi_header_t header; | |||
| mp->midi = midi; | |||
| mp->start = midi->data.size(); | |||
| mp->count = 0; | |||
| mp->eob = false; | |||
| if (!midi->extensible) { | |||
| size_t writable = midi->data.capacity() - midi->data.size(); | |||
| if (writable < sizeof(header)) { | |||
| mp->eob = true; | |||
| return false; | |||
| } | |||
| } | |||
| header.bus = bus; | |||
| header.offset = offset; | |||
| header.size = 0; | |||
| const uint8_t *headp = (const uint8_t *)&header; | |||
| midi->data.insert(midi->data.end(), headp, headp + sizeof(header)); | |||
| return true; | |||
| } | |||
| bool ysfx_midi_push_data(ysfx_midi_push_t *mp, const uint8_t *data, uint32_t size) | |||
| { | |||
| if (mp->eob) | |||
| return false; | |||
| if (size > ysfx_midi_message_max_size || mp->count + size > ysfx_midi_message_max_size) { | |||
| mp->eob = true; | |||
| return false; | |||
| } | |||
| ysfx_midi_buffer_t *midi = mp->midi; | |||
| if (!midi->extensible) { | |||
| size_t writable = midi->data.capacity() - midi->data.size(); | |||
| if (writable < size) { | |||
| mp->eob = true; | |||
| return false; | |||
| } | |||
| } | |||
| midi->data.insert(midi->data.end(), data, data + size); | |||
| mp->count += size; | |||
| return true; | |||
| } | |||
| bool ysfx_midi_push_end(ysfx_midi_push_t *mp) | |||
| { | |||
| if (mp->eob) { | |||
| mp->midi->data.resize(mp->start); | |||
| return false; | |||
| } | |||
| ysfx_midi_header_t header; | |||
| uint8_t *headp = &mp->midi->data[mp->start]; | |||
| memcpy(&header, headp, sizeof(header)); | |||
| header.size = mp->count; | |||
| memcpy(headp, &header, sizeof(header)); | |||
| return true; | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| uint32_t ysfx_midi_sizeof(uint8_t id) | |||
| { | |||
| if ((id >> 7) == 0) { | |||
| return 0; | |||
| } | |||
| else if ((id >> 4) != 0b1111) { | |||
| static const uint8_t sizetable[8] = { | |||
| 3, 3, 3, 3, 2, 2, 3, | |||
| }; | |||
| return sizetable[(id >> 4) & 0b111]; | |||
| } | |||
| else { | |||
| static const uint8_t sizetable[16] = { | |||
| 0, 2, 3, 2, 1, 1, 1, 0, | |||
| 1, 1, 1, 1, 1, 1, 1, 1, | |||
| }; | |||
| return sizetable[id & 0b1111]; | |||
| } | |||
| } | |||
| @@ -0,0 +1,72 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| #include "ysfx.h" | |||
| #include <vector> | |||
| #include <memory> | |||
| struct ysfx_midi_header_t { | |||
| uint32_t bus; | |||
| uint32_t offset; | |||
| uint32_t size; | |||
| }; | |||
| struct ysfx_midi_buffer_t { | |||
| std::vector<uint8_t> data; | |||
| size_t read_pos = 0; | |||
| size_t read_pos_for_bus[ysfx_max_midi_buses] = {}; | |||
| bool extensible = false; | |||
| }; | |||
| using ysfx_midi_buffer_u = std::unique_ptr<ysfx_midi_buffer_t>; | |||
| enum { | |||
| ysfx_midi_message_max_size = 1 << 24, | |||
| }; | |||
| // NOTE: regarding buses, | |||
| // The buffer keeps 2 kinds of read positions: global, and per-bus. | |||
| // | |||
| // These are tracked separately, so use either global/per-bus reading API, | |||
| // but not both mixed in the same piece of code. | |||
| // | |||
| // The JSFX API `midi*` implementations should always use per-bus access: | |||
| // if `ext_midi_bus` is true, use the bus defined by `midi_bus`, otherwise 0. | |||
| void ysfx_midi_reserve(ysfx_midi_buffer_t *midi, uint32_t capacity, bool extensible); | |||
| void ysfx_midi_clear(ysfx_midi_buffer_t *midi); | |||
| bool ysfx_midi_push(ysfx_midi_buffer_t *midi, const ysfx_midi_event_t *event); | |||
| void ysfx_midi_rewind(ysfx_midi_buffer_t *midi); | |||
| bool ysfx_midi_get_next(ysfx_midi_buffer_t *midi, ysfx_midi_event_t *event); | |||
| bool ysfx_midi_get_next_from_bus(ysfx_midi_buffer_t *midi, uint32_t bus, ysfx_midi_event_t *event); | |||
| // incremental writer into a midi buffer | |||
| struct ysfx_midi_push_t { | |||
| ysfx_midi_buffer_t *midi = nullptr; | |||
| size_t start = 0; | |||
| uint32_t count = 0; | |||
| bool eob = false; | |||
| }; | |||
| bool ysfx_midi_push_begin(ysfx_midi_buffer_t *midi, uint32_t bus, uint32_t offset, ysfx_midi_push_t *mp); | |||
| bool ysfx_midi_push_data(ysfx_midi_push_t *mp, const uint8_t *data, uint32_t size); | |||
| bool ysfx_midi_push_end(ysfx_midi_push_t *mp); | |||
| //------------------------------------------------------------------------------ | |||
| // determine the length of a midi message according to its status byte | |||
| // if length is dynamic, returns 0 | |||
| uint32_t ysfx_midi_sizeof(uint8_t id); | |||
| @@ -0,0 +1,380 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #include "ysfx_parse.hpp" | |||
| #include "ysfx_utils.hpp" | |||
| #include <cstdlib> | |||
| #include <cstring> | |||
| bool ysfx_parse_toplevel(ysfx::text_reader &reader, ysfx_toplevel_t &toplevel, ysfx_parse_error *error) | |||
| { | |||
| toplevel = ysfx_toplevel_t{}; | |||
| ysfx_section_t *current = new ysfx_section_t; | |||
| toplevel.header.reset(current); | |||
| std::string line; | |||
| uint32_t lineno = 0; | |||
| line.reserve(256); | |||
| while (reader.read_next_line(line)) { | |||
| const char *linep = line.c_str(); | |||
| if (linep[0] == '@') { | |||
| // a new section starts | |||
| ysfx::string_list tokens = ysfx::split_strings_noempty(linep, &ysfx::ascii_isspace); | |||
| current = new ysfx_section_t; | |||
| if (tokens[0] == "@init") | |||
| toplevel.init.reset(current); | |||
| else if (tokens[0] == "@slider") | |||
| toplevel.slider.reset(current); | |||
| else if (tokens[0] == "@block") | |||
| toplevel.block.reset(current); | |||
| else if (tokens[0] == "@sample") | |||
| toplevel.sample.reset(current); | |||
| else if (tokens[0] == "@serialize") | |||
| toplevel.serialize.reset(current); | |||
| else if (tokens[0] == "@gfx") { | |||
| toplevel.gfx.reset(current); | |||
| long gfx_w = 0; | |||
| long gfx_h = 0; | |||
| if (tokens.size() > 1) | |||
| gfx_w = (long)ysfx::dot_atof(tokens[1].c_str()); | |||
| if (tokens.size() > 2) | |||
| gfx_h = (long)ysfx::dot_atof(tokens[2].c_str()); | |||
| toplevel.gfx_w = (gfx_w > 0) ? (uint32_t)gfx_w : 0; | |||
| toplevel.gfx_h = (gfx_h > 0) ? (uint32_t)gfx_h : 0; | |||
| } | |||
| else { | |||
| delete current; | |||
| if (error) { | |||
| error->line = lineno; | |||
| error->message = std::string("Invalid section: ") + line; | |||
| } | |||
| return false; | |||
| } | |||
| current->line_offset = lineno + 1; | |||
| } | |||
| else { | |||
| current->text.append(line); | |||
| current->text.push_back('\n'); | |||
| } | |||
| ++lineno; | |||
| } | |||
| return true; | |||
| } | |||
| void ysfx_parse_header(ysfx_section_t *section, ysfx_header_t &header) | |||
| { | |||
| header = ysfx_header_t{}; | |||
| ysfx::string_text_reader reader(section->text.c_str()); | |||
| std::string line; | |||
| //uint32_t lineno = section->line_offset; | |||
| line.reserve(256); | |||
| /// | |||
| auto unprefix = [](const char *text, const char **restp, const char *prefix) -> bool { | |||
| size_t len = strlen(prefix); | |||
| if (strncmp(text, prefix, len)) | |||
| return false; | |||
| if (restp) | |||
| *restp = text + len; | |||
| return true; | |||
| }; | |||
| //-------------------------------------------------------------------------- | |||
| // pass 1: regular metadata | |||
| while (reader.read_next_line(line)) { | |||
| const char *linep = line.c_str(); | |||
| const char *rest = nullptr; | |||
| ysfx_slider_t slider; | |||
| ysfx_parsed_filename_t filename; | |||
| /// | |||
| if (unprefix(linep, &rest, "desc:")) { | |||
| if (header.desc.empty()) | |||
| header.desc = ysfx::trim(rest, &ysfx::ascii_isspace); | |||
| } | |||
| else if (unprefix(linep, &rest, "author:")) { | |||
| if (header.author.empty()) | |||
| header.author = ysfx::trim(rest, &ysfx::ascii_isspace); | |||
| } | |||
| else if (unprefix(linep, &rest, "tags:")) { | |||
| if (header.tags.empty()) { | |||
| for (const std::string &tag : ysfx::split_strings_noempty(rest, &ysfx::ascii_isspace)) | |||
| header.tags.push_back(tag); | |||
| } | |||
| } | |||
| else if (unprefix(linep, &rest, "in_pin:")) { | |||
| header.explicit_pins = true; | |||
| header.in_pins.push_back(ysfx::trim(rest, &ysfx::ascii_isspace)); | |||
| } | |||
| else if (unprefix(linep, &rest, "out_pin:")) { | |||
| header.explicit_pins = true; | |||
| header.out_pins.push_back(ysfx::trim(rest, &ysfx::ascii_isspace)); | |||
| } | |||
| else if (unprefix(linep, &rest, "options:")) { | |||
| for (const std::string &opt : ysfx::split_strings_noempty(rest, &ysfx::ascii_isspace)) { | |||
| size_t pos = opt.find('='); | |||
| std::string name = (pos == opt.npos) ? opt : opt.substr(0, pos); | |||
| std::string value = (pos == opt.npos) ? std::string{} : opt.substr(pos + 1); | |||
| if (name == "gmem") | |||
| header.options.gmem = value; | |||
| else if (name == "maxmem") { | |||
| int32_t maxmem = (int32_t)ysfx::dot_atof(value.c_str()); | |||
| header.options.maxmem = (maxmem < 0) ? 0 : (uint32_t)maxmem; | |||
| } | |||
| else if (name == "want_all_kb") | |||
| header.options.want_all_kb = true; | |||
| else if (name == "no_meter") | |||
| header.options.no_meter = true; | |||
| } | |||
| } | |||
| else if (unprefix(linep, &rest, "import") && ysfx::ascii_isspace(rest[0])) | |||
| header.imports.push_back(ysfx::trim(rest + 1, &ysfx::ascii_isspace)); | |||
| else if (ysfx_parse_slider(linep, slider)) { | |||
| if (slider.id >= ysfx_max_sliders) | |||
| continue; | |||
| slider.exists = true; | |||
| header.sliders[slider.id] = slider; | |||
| } | |||
| else if (ysfx_parse_filename(linep, filename)) { | |||
| if (filename.index != header.filenames.size()) | |||
| continue; | |||
| header.filenames.push_back(std::move(filename.filename)); | |||
| } | |||
| //++lineno; | |||
| } | |||
| //-------------------------------------------------------------------------- | |||
| // pass 2: comments | |||
| reader = ysfx::string_text_reader{section->text.c_str()}; | |||
| while (reader.read_next_line(line)) { | |||
| const char *linep = line.c_str(); | |||
| const char *rest = nullptr; | |||
| // some files contain metadata in the form of comments | |||
| // this is not part of spec, but we'll take this info regardless | |||
| if (unprefix(linep, &rest, "//author:")) { | |||
| if (header.author.empty()) | |||
| header.author = ysfx::trim(rest, &ysfx::ascii_isspace); | |||
| } | |||
| else if (unprefix(linep, &rest, "//tags:")) { | |||
| if (header.tags.empty()) { | |||
| for (const std::string &tag : ysfx::split_strings_noempty(rest, &ysfx::ascii_isspace)) | |||
| header.tags.push_back(tag); | |||
| } | |||
| } | |||
| } | |||
| //-------------------------------------------------------------------------- | |||
| if (header.in_pins.size() == 1 && !ysfx::ascii_casecmp(header.in_pins.front().c_str(), "none")) | |||
| header.in_pins.clear(); | |||
| if (header.out_pins.size() == 1 && !ysfx::ascii_casecmp(header.out_pins.front().c_str(), "none")) | |||
| header.out_pins.clear(); | |||
| if (header.in_pins.size() > ysfx_max_channels) | |||
| header.in_pins.resize(ysfx_max_channels); | |||
| if (header.out_pins.size() > ysfx_max_channels) | |||
| header.out_pins.resize(ysfx_max_channels); | |||
| } | |||
| bool ysfx_parse_slider(const char *line, ysfx_slider_t &slider) | |||
| { | |||
| // NOTE this parser is intentionally very permissive, | |||
| // in order to match the reference behavior | |||
| slider = ysfx_slider_t{}; | |||
| #define PARSE_FAIL do { \ | |||
| /*fprintf(stderr, "parse error (line %d): `%s`\n", __LINE__, line);*/ \ | |||
| return false; \ | |||
| } while (0) | |||
| const char *cur = line; | |||
| for (const char *p = "slider"; *p; ++p) { | |||
| if (*cur++ != *p) | |||
| PARSE_FAIL; | |||
| } | |||
| // parse ID (1-index) | |||
| unsigned long id = strtoul(cur, (char **)&cur, 10); | |||
| if (id < 1 || id > ysfx_max_sliders) | |||
| PARSE_FAIL; | |||
| slider.id = (uint32_t)--id; | |||
| // semicolon | |||
| if (*cur++ != ':') | |||
| PARSE_FAIL; | |||
| // search if there is an '=' sign prior to any '<' or ',' | |||
| // if there is, it's a custom variable | |||
| { | |||
| const char *pos = cur; | |||
| for (char c; pos && (c = *pos) && c != '='; ) | |||
| pos = (c == '<' || c == ',') ? nullptr : (pos + 1); | |||
| if (pos && *pos) { | |||
| slider.var.assign(cur, pos); | |||
| cur = pos + 1; | |||
| } | |||
| else | |||
| slider.var = "slider" + std::to_string(id + 1); | |||
| } | |||
| // a regular slider | |||
| if (*cur != '/') { | |||
| slider.def = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur); | |||
| while (*cur && *cur != ',' && *cur != '<') ++cur; | |||
| if (!*cur) PARSE_FAIL; | |||
| // no range specification | |||
| if (*cur == ',') | |||
| ++cur; | |||
| // range specification | |||
| else if (*cur == '<') { | |||
| ++cur; | |||
| // min | |||
| slider.min = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur); | |||
| while (*cur && *cur != ',' && *cur != '>') ++cur; | |||
| if (!*cur) PARSE_FAIL; | |||
| // max | |||
| if (*cur == ',') { | |||
| ++cur; | |||
| slider.max = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur); | |||
| while (*cur && *cur != ',' && *cur != '>') ++cur; | |||
| if (!*cur) PARSE_FAIL; | |||
| } | |||
| // inc | |||
| if (*cur == ',') { | |||
| ++cur; | |||
| slider.inc = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur); | |||
| while (*cur && *cur != '{' && *cur != ',' && *cur != '>') ++cur; | |||
| if (!*cur) PARSE_FAIL; | |||
| // enumeration values | |||
| if (*cur == '{') { | |||
| const char *pos = ++cur; | |||
| while (*cur && *cur != '}' && *cur != '>') ++cur; | |||
| if (!*cur) PARSE_FAIL; | |||
| slider.is_enum = true; | |||
| slider.enum_names = ysfx::split_strings_noempty( | |||
| std::string(pos, cur).c_str(), | |||
| [](char c) -> bool { return c == ','; }); | |||
| for (std::string &name : slider.enum_names) | |||
| name = ysfx::trim(name.c_str(), &ysfx::ascii_isspace); | |||
| } | |||
| } | |||
| while (*cur && *cur != '>') ++cur; | |||
| if (!*cur) PARSE_FAIL; | |||
| ++cur; | |||
| } | |||
| else | |||
| PARSE_FAIL; | |||
| // NOTE: skip ',' and whitespace. not sure why, it's how it is | |||
| while (*cur && (*cur == ',' || ysfx::ascii_isspace(*cur))) ++cur; | |||
| if (!*cur) PARSE_FAIL; | |||
| } | |||
| // a path slider | |||
| else | |||
| { | |||
| const char *pos = cur; | |||
| while (*cur && *cur != ':') ++cur; | |||
| if (!*cur) PARSE_FAIL; | |||
| slider.path.assign(pos, cur); | |||
| ++cur; | |||
| slider.def = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur); | |||
| slider.inc = 1; | |||
| slider.is_enum = true; | |||
| while (*cur && *cur != ':') ++cur; | |||
| if (!*cur) PARSE_FAIL; | |||
| ++cur; | |||
| } | |||
| // description and visibility | |||
| while (ysfx::ascii_isspace(*cur)) | |||
| ++cur; | |||
| slider.initially_visible = true; | |||
| if (*cur == '-') { | |||
| ++cur; | |||
| slider.initially_visible = false; | |||
| } | |||
| slider.desc = ysfx::trim(cur, &ysfx::ascii_isspace); | |||
| if (slider.desc.empty()) | |||
| PARSE_FAIL; | |||
| // | |||
| #undef PARSE_FAIL | |||
| return true; | |||
| } | |||
| bool ysfx_parse_filename(const char *line, ysfx_parsed_filename_t &filename) | |||
| { | |||
| filename = ysfx_parsed_filename_t{}; | |||
| const char *cur = line; | |||
| for (const char *p = "filename:"; *p; ++p) { | |||
| if (*cur++ != *p) | |||
| return false; | |||
| } | |||
| int64_t index = (int64_t)ysfx::dot_strtod(cur, (char **)&cur); | |||
| if (index < 0 || index > ~(uint32_t)0) | |||
| return false; | |||
| while (*cur && *cur != ',') ++cur; | |||
| if (!*cur) return false;; | |||
| ++cur; | |||
| filename.index = (uint32_t)index; | |||
| filename.filename.assign(cur); | |||
| return true; | |||
| } | |||
| @@ -0,0 +1,101 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| #include "ysfx.h" | |||
| #include "ysfx_reader.hpp" | |||
| #include "ysfx_utils.hpp" | |||
| #include <string> | |||
| #include <vector> | |||
| #include <cstdint> | |||
| struct ysfx_section_t; | |||
| struct ysfx_toplevel_t; | |||
| struct ysfx_slider_t; | |||
| struct ysfx_header_t; | |||
| using ysfx_section_u = std::unique_ptr<ysfx_section_t>; | |||
| using ysfx_toplevel_u = std::unique_ptr<ysfx_toplevel_t>; | |||
| using ysfx_slider_u = std::unique_ptr<ysfx_slider_t>; | |||
| using ysfx_header_u = std::unique_ptr<ysfx_header_t>; | |||
| struct ysfx_section_t { | |||
| uint32_t line_offset = 0; | |||
| std::string text; | |||
| }; | |||
| struct ysfx_toplevel_t { | |||
| ysfx_section_u header; | |||
| ysfx_section_u init; | |||
| ysfx_section_u slider; | |||
| ysfx_section_u block; | |||
| ysfx_section_u sample; | |||
| ysfx_section_u serialize; | |||
| ysfx_section_u gfx; | |||
| uint32_t gfx_w = 0; | |||
| uint32_t gfx_h = 0; | |||
| }; | |||
| struct ysfx_parse_error { | |||
| uint32_t line = 0; | |||
| std::string message; | |||
| explicit operator bool() { return !message.empty(); } | |||
| }; | |||
| struct ysfx_slider_t { | |||
| uint32_t id = 0; | |||
| bool exists = false; | |||
| ysfx_real def = 0; | |||
| ysfx_real min = 0; | |||
| ysfx_real max = 0; | |||
| ysfx_real inc = 0; | |||
| std::string var; | |||
| std::string path; | |||
| bool is_enum = false; | |||
| ysfx::string_list enum_names; | |||
| std::string desc; | |||
| bool initially_visible = false; | |||
| }; | |||
| struct ysfx_options_t { | |||
| std::string gmem; | |||
| uint32_t maxmem = 0; | |||
| bool want_all_kb = false; | |||
| bool no_meter = false; | |||
| }; | |||
| struct ysfx_header_t { | |||
| std::string desc; | |||
| std::string author; | |||
| ysfx::string_list tags; | |||
| ysfx::string_list imports; | |||
| ysfx::string_list in_pins; | |||
| ysfx::string_list out_pins; | |||
| bool explicit_pins = false; | |||
| ysfx::string_list filenames; | |||
| ysfx_options_t options; | |||
| ysfx_slider_t sliders[ysfx_max_sliders]; | |||
| }; | |||
| struct ysfx_parsed_filename_t { | |||
| uint32_t index; | |||
| std::string filename; | |||
| }; | |||
| bool ysfx_parse_toplevel(ysfx::text_reader &reader, ysfx_toplevel_t &toplevel, ysfx_parse_error *error); | |||
| bool ysfx_parse_slider(const char *line, ysfx_slider_t &slider); | |||
| bool ysfx_parse_filename(const char *line, ysfx_parsed_filename_t &filename); | |||
| void ysfx_parse_header(ysfx_section_t *section, ysfx_header_t &header); | |||
| @@ -0,0 +1,171 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #include "ysfx.h" | |||
| #include "ysfx_preset.hpp" | |||
| #include "ysfx_utils.hpp" | |||
| #include <vector> | |||
| #include <string> | |||
| #include <cstring> | |||
| #include "WDL/lineparse.h" | |||
| static void ysfx_preset_clear(ysfx_preset_t *preset); | |||
| static ysfx_bank_t *ysfx_load_bank_from_rpl_text(const std::string &text); | |||
| static void ysfx_parse_preset_from_rpl_blob(ysfx_preset_t *preset, const char *name, const std::vector<uint8_t> &data); | |||
| ysfx_bank_t *ysfx_load_bank(const char *path) | |||
| { | |||
| ysfx::FILE_u stream{fopen(path, "rb")}; | |||
| if (!stream) | |||
| return nullptr; | |||
| std::string input; | |||
| constexpr uint32_t max_input = 1u << 24; | |||
| input.reserve(1u << 16); | |||
| for (int ch; input.size() < max_input && (ch = fgetc(stream.get())) != EOF; ) { | |||
| ch = (ch == '\r' || ch == '\n') ? ' ' : ch; | |||
| input.push_back((unsigned char)ch); | |||
| } | |||
| if (ferror(stream.get())) | |||
| return nullptr; | |||
| stream.reset(); | |||
| return ysfx_load_bank_from_rpl_text(input); | |||
| } | |||
| void ysfx_bank_free(ysfx_bank_t *bank) | |||
| { | |||
| if (!bank) | |||
| return; | |||
| delete[] bank->name; | |||
| if (ysfx_preset_t *presets = bank->presets) { | |||
| uint32_t count = bank->preset_count; | |||
| for (uint32_t i = 0; i < count; ++i) | |||
| ysfx_preset_clear(&presets[i]); | |||
| delete[] presets; | |||
| } | |||
| delete bank; | |||
| } | |||
| static void ysfx_preset_clear(ysfx_preset_t *preset) | |||
| { | |||
| delete[] preset->name; | |||
| preset->name = nullptr; | |||
| ysfx_state_free(preset->state); | |||
| preset->state = nullptr; | |||
| } | |||
| static ysfx_bank_t *ysfx_load_bank_from_rpl_text(const std::string &text) | |||
| { | |||
| LineParser parser; | |||
| if (parser.parse(text.c_str()) < 0) | |||
| return nullptr; | |||
| /// | |||
| std::vector<ysfx_preset_t> preset_list; | |||
| preset_list.reserve(256); | |||
| auto list_cleanup = ysfx::defer([&preset_list]() { | |||
| for (ysfx_preset_t &pst : preset_list) | |||
| ysfx_preset_clear(&pst); | |||
| }); | |||
| /// | |||
| int ntok = parser.getnumtokens(); | |||
| int itok = 0; | |||
| if (strcmp("<REAPER_PRESET_LIBRARY", parser.gettoken_str(itok++)) != 0) | |||
| return nullptr; | |||
| const char *bank_name = parser.gettoken_str(itok++); | |||
| std::string base64; | |||
| base64.reserve(1024); | |||
| while (itok < ntok) { | |||
| if (strcmp("<PRESET", parser.gettoken_str(itok++)) == 0) { | |||
| const char *preset_name = parser.gettoken_str(itok++); | |||
| base64.clear(); | |||
| for (const char *part; itok < ntok && | |||
| strcmp(">", (part = parser.gettoken_str(itok++))) != 0; ) | |||
| base64.append(part); | |||
| preset_list.emplace_back(); | |||
| ysfx_preset_t &preset = preset_list.back(); | |||
| ysfx_parse_preset_from_rpl_blob( | |||
| &preset, preset_name, | |||
| ysfx::decode_base64(base64.data(), base64.size())); | |||
| } | |||
| } | |||
| /// | |||
| ysfx_bank_u bank{new ysfx_bank_t{}}; | |||
| bank->name = ysfx::strdup_using_new(bank_name); | |||
| bank->presets = new ysfx_preset_t[(uint32_t)preset_list.size()]{}; | |||
| bank->preset_count = (uint32_t)preset_list.size(); | |||
| for (uint32_t i = (uint32_t)preset_list.size(); i-- > 0; ) { | |||
| bank->presets[i] = preset_list[i]; | |||
| preset_list.pop_back(); | |||
| } | |||
| return bank.release(); | |||
| } | |||
| static void ysfx_parse_preset_from_rpl_blob(ysfx_preset_t *preset, const char *name, const std::vector<uint8_t> &data) | |||
| { | |||
| ysfx_state_t state{}; | |||
| std::vector<ysfx_state_slider_t> sliders; | |||
| size_t len = data.size(); | |||
| size_t pos = 0; | |||
| while (pos < len && data[pos] != 0) ++pos; | |||
| if (pos++ < len) { | |||
| state.data = const_cast<uint8_t *>(&data[pos]); | |||
| state.data_size = len - pos; | |||
| LineParser parser; | |||
| if (parser.parse((const char *)data.data()) >= 0) { | |||
| sliders.reserve(ysfx_max_sliders); | |||
| for (uint32_t i = 0; i < 64; ++i) { | |||
| int success = false; | |||
| ysfx_state_slider_t slider{}; | |||
| slider.index = i; | |||
| slider.value = (ysfx_real)parser.gettoken_float(i, &success); | |||
| if (success) | |||
| sliders.push_back(slider); | |||
| } | |||
| state.sliders = sliders.data(); | |||
| state.slider_count = (uint32_t)sliders.size(); | |||
| } | |||
| } | |||
| preset->name = ysfx::strdup_using_new(name); | |||
| preset->state = ysfx_state_dup(&state); | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| @@ -0,0 +1,98 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #include "ysfx_reader.hpp" | |||
| namespace ysfx { | |||
| //------------------------------------------------------------------------------ | |||
| bool text_reader::read_next_line(std::string &line) | |||
| { | |||
| line.clear(); | |||
| char next = read_next_char(); | |||
| if (next == '\0') | |||
| return false; | |||
| while (next != '\0' && next != '\r' && next != '\n') { | |||
| line.push_back(next); | |||
| next = read_next_char(); | |||
| } | |||
| if (next == '\r') { | |||
| next = peek_next_char(); | |||
| if (next == '\n') | |||
| read_next_char(); | |||
| } | |||
| return true; | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| char string_text_reader::read_next_char() | |||
| { | |||
| const char *ptr = m_char_ptr; | |||
| if (!ptr || *ptr == '\0') | |||
| return '\0'; | |||
| char next = *ptr; | |||
| m_char_ptr = ptr + 1; | |||
| return next; | |||
| } | |||
| char string_text_reader::peek_next_char() | |||
| { | |||
| const char *ptr = m_char_ptr; | |||
| if (!ptr) | |||
| return '\0'; | |||
| return *ptr; | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| char stdio_text_reader::read_next_char() | |||
| { | |||
| FILE *stream = m_stream; | |||
| if (!stream) | |||
| return '\0'; | |||
| int next = fgetc(stream); | |||
| if (next == EOF) | |||
| return '\0'; | |||
| return (unsigned char)next; | |||
| } | |||
| char stdio_text_reader::peek_next_char() | |||
| { | |||
| FILE *stream = m_stream; | |||
| if (!stream) | |||
| return '\0'; | |||
| int next = fgetc(stream); | |||
| if (next == EOF) | |||
| return '\0'; | |||
| ungetc(next, stream); | |||
| return (unsigned char)next; | |||
| } | |||
| } // namespace ysfx | |||
| @@ -0,0 +1,56 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| #include <string> | |||
| #include <cstdio> | |||
| #include <cstddef> | |||
| namespace ysfx { | |||
| class text_reader | |||
| { | |||
| public: | |||
| virtual ~text_reader() = default; | |||
| virtual char read_next_char() = 0; | |||
| virtual char peek_next_char() = 0; | |||
| bool read_next_line(std::string &line); | |||
| }; | |||
| //------------------------------------------------------------------------------ | |||
| class string_text_reader : public text_reader | |||
| { | |||
| public: | |||
| explicit string_text_reader(const char *text) : m_char_ptr(text) {} | |||
| char read_next_char() override; | |||
| char peek_next_char() override; | |||
| private: | |||
| const char *m_char_ptr = nullptr; | |||
| }; | |||
| //------------------------------------------------------------------------------ | |||
| class stdio_text_reader : public text_reader | |||
| { | |||
| public: | |||
| explicit stdio_text_reader(FILE *stream) : m_stream(stream) {} | |||
| char read_next_char() override; | |||
| char peek_next_char() override; | |||
| private: | |||
| FILE *m_stream = nullptr; | |||
| }; | |||
| } // namespace ysfx | |||
| @@ -0,0 +1,730 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #include "ysfx_utils.hpp" | |||
| #include "base64/Base64.hpp" | |||
| #include <system_error> | |||
| #include <algorithm> | |||
| #include <deque> | |||
| #include <clocale> | |||
| #include <cstring> | |||
| #include <cassert> | |||
| #if !defined(_WIN32) | |||
| # include <sys/stat.h> | |||
| # include <sys/types.h> | |||
| # include <unistd.h> | |||
| # include <dirent.h> | |||
| # include <fcntl.h> | |||
| #else | |||
| # include <windows.h> | |||
| # include <io.h> | |||
| #endif | |||
| namespace ysfx { | |||
| #if !defined(_WIN32) | |||
| static_assert(sizeof(off_t) == 8, "64-bit large file support is not enabled"); | |||
| #endif | |||
| FILE *fopen_utf8(const char *path, const char *mode) | |||
| { | |||
| #if defined(_WIN32) | |||
| return _wfopen(widen(path).c_str(), widen(mode).c_str()); | |||
| #else | |||
| return fopen(path, mode); | |||
| #endif | |||
| } | |||
| int64_t fseek_lfs(FILE *stream, int64_t off, int whence) | |||
| { | |||
| #if defined(_WIN32) | |||
| return _fseeki64(stream, off, whence); | |||
| #else | |||
| return fseeko(stream, off, whence); | |||
| #endif | |||
| } | |||
| int64_t ftell_lfs(FILE *stream) | |||
| { | |||
| #if defined(_WIN32) | |||
| return _ftelli64(stream); | |||
| #else | |||
| return ftello(stream); | |||
| #endif | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| namespace { | |||
| struct scoped_c_locale | |||
| { | |||
| scoped_c_locale(int lc, const char *name); | |||
| ~scoped_c_locale(); | |||
| c_locale_t m_loc{}; | |||
| scoped_c_locale(const scoped_c_locale &) = delete; | |||
| scoped_c_locale &operator=(const scoped_c_locale &) = delete; | |||
| }; | |||
| scoped_c_locale::scoped_c_locale(int lc, const char *name) | |||
| { | |||
| #if defined(_WIN32) | |||
| m_loc = _create_locale(lc, name); | |||
| #else | |||
| switch (lc) { | |||
| case LC_ALL: | |||
| m_loc = newlocale(LC_ALL_MASK, name, c_locale_t{}); | |||
| break; | |||
| case LC_CTYPE: | |||
| m_loc = newlocale(LC_CTYPE_MASK, name, c_locale_t{}); | |||
| break; | |||
| case LC_COLLATE: | |||
| m_loc = newlocale(LC_COLLATE_MASK, name, c_locale_t{}); | |||
| break; | |||
| case LC_MONETARY: | |||
| m_loc = newlocale(LC_MONETARY_MASK, name, c_locale_t{}); | |||
| break; | |||
| case LC_NUMERIC: | |||
| m_loc = newlocale(LC_NUMERIC_MASK, name, c_locale_t{}); | |||
| break; | |||
| case LC_TIME: | |||
| m_loc = newlocale(LC_TIME_MASK, name, c_locale_t{}); | |||
| break; | |||
| case LC_MESSAGES: | |||
| m_loc = newlocale(LC_MESSAGES_MASK, name, c_locale_t{}); | |||
| break; | |||
| default: | |||
| errno = EINVAL; | |||
| break; | |||
| } | |||
| #endif | |||
| if (!m_loc) | |||
| throw std::system_error(errno, std::generic_category()); | |||
| } | |||
| scoped_c_locale::~scoped_c_locale() | |||
| { | |||
| if (!m_loc) return; | |||
| #if !defined(_WIN32) | |||
| freelocale(m_loc); | |||
| #else | |||
| _free_locale(m_loc); | |||
| #endif | |||
| } | |||
| #if !defined(_WIN32) | |||
| struct scoped_posix_uselocale { | |||
| explicit scoped_posix_uselocale(c_locale_t loc); | |||
| ~scoped_posix_uselocale(); | |||
| c_locale_t m_loc{}; | |||
| c_locale_t m_old{}; | |||
| scoped_posix_uselocale(const scoped_posix_uselocale &) = delete; | |||
| scoped_posix_uselocale &operator=(const scoped_posix_uselocale &) = delete; | |||
| }; | |||
| scoped_posix_uselocale::scoped_posix_uselocale(c_locale_t loc) | |||
| { | |||
| if (loc) | |||
| { | |||
| m_loc = loc; | |||
| m_old = uselocale(loc); | |||
| } | |||
| } | |||
| scoped_posix_uselocale::~scoped_posix_uselocale() | |||
| { | |||
| if (m_loc) | |||
| uselocale(m_old); | |||
| } | |||
| #endif | |||
| } // namespace | |||
| //------------------------------------------------------------------------------ | |||
| c_locale_t c_numeric_locale() | |||
| { | |||
| static scoped_c_locale loc(LC_NUMERIC, "C"); | |||
| return loc.m_loc; | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| double c_atof(const char *text, c_locale_t loc) | |||
| { | |||
| #if defined(_WIN32) | |||
| return _atof_l(text, loc); | |||
| #else | |||
| scoped_posix_uselocale use(loc); | |||
| return atof(text); | |||
| #endif | |||
| } | |||
| double c_strtod(const char *text, char **endp, c_locale_t loc) | |||
| { | |||
| #if defined(_WIN32) | |||
| return _strtod_l(text, endp, loc); | |||
| #else | |||
| scoped_posix_uselocale use(loc); | |||
| return strtod(text, endp); | |||
| #endif | |||
| } | |||
| double dot_atof(const char *text) | |||
| { | |||
| return c_atof(text, c_numeric_locale()); | |||
| } | |||
| double dot_strtod(const char *text, char **endp) | |||
| { | |||
| return c_strtod(text, endp, c_numeric_locale()); | |||
| } | |||
| bool ascii_isspace(char c) | |||
| { | |||
| switch (c) { | |||
| case ' ': case '\f': case '\n': case '\r': case '\t': case '\v': | |||
| return true; | |||
| default: | |||
| return false; | |||
| } | |||
| } | |||
| bool ascii_isalpha(char c) | |||
| { | |||
| return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); | |||
| } | |||
| char ascii_tolower(char c) | |||
| { | |||
| return (c >= 'A' && c <= 'Z') ? (c - 'A' + 'a') : c; | |||
| } | |||
| char ascii_toupper(char c) | |||
| { | |||
| return (c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c; | |||
| } | |||
| int ascii_casecmp(const char *a, const char *b) | |||
| { | |||
| for (char ca, cb; (ca = *a++) | (cb = *b++); ) { | |||
| ca = ascii_tolower(ca); | |||
| cb = ascii_tolower(cb); | |||
| if (ca < cb) return -1; | |||
| if (ca > cb) return +1; | |||
| } | |||
| return 0; | |||
| } | |||
| uint32_t latin1_toupper(uint32_t c) | |||
| { | |||
| if (c >= 'a' && c <= 'z') | |||
| return c - 'a' + 'A'; | |||
| if ((c >= 0xe0 && c <= 0xf6) || (c >= 0xf8 && c <= 0xfe)) | |||
| return c - 0x20; | |||
| return c; | |||
| } | |||
| uint32_t latin1_tolower(uint32_t c) | |||
| { | |||
| if (c >= 'A' && c <= 'Z') | |||
| return c - 'A' + 'a'; | |||
| if ((c >= 0xc0 && c <= 0xd6) || (c >= 0xd8 && c <= 0xde)) | |||
| return c + 0x20; | |||
| return c; | |||
| } | |||
| char *strdup_using_new(const char *src) | |||
| { | |||
| size_t len = strlen(src); | |||
| char *dst = new char[len + 1]; | |||
| memcpy(dst, src, len + 1); | |||
| return dst; | |||
| } | |||
| string_list split_strings_noempty(const char *input, bool(*pred)(char)) | |||
| { | |||
| string_list list; | |||
| if (input) { | |||
| std::string acc; | |||
| acc.reserve(256); | |||
| for (char c; (c = *input++) != '\0'; ) { | |||
| if (!pred(c)) | |||
| acc.push_back(c); | |||
| else { | |||
| if (!acc.empty()) { | |||
| list.push_back(acc); | |||
| acc.clear(); | |||
| } | |||
| } | |||
| } | |||
| if (!acc.empty()) | |||
| list.push_back(acc); | |||
| } | |||
| return list; | |||
| } | |||
| std::string trim(const char *input, bool(*pred)(char)) | |||
| { | |||
| const char *start = input; | |||
| while (*start != '\0' && pred(*start)) | |||
| ++start; | |||
| const char *end = start + strlen(start); | |||
| while (end > start && pred(*(end - 1))) | |||
| --end; | |||
| return std::string(start, end); | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| void pack_u32le(uint32_t value, uint8_t data[4]) | |||
| { | |||
| data[0] = value & 0xff; | |||
| data[1] = (value >> 8) & 0xff; | |||
| data[2] = (value >> 16) & 0xff; | |||
| data[3] = value >> 24; | |||
| } | |||
| void pack_f32le(float value, uint8_t data[4]) | |||
| { | |||
| uint32_t u; | |||
| memcpy(&u, &value, 4); | |||
| pack_u32le(u, data); | |||
| } | |||
| uint32_t unpack_u32le(const uint8_t data[4]) | |||
| { | |||
| return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); | |||
| } | |||
| float unpack_f32le(const uint8_t data[4]) | |||
| { | |||
| float value; | |||
| uint32_t u = unpack_u32le(data); | |||
| memcpy(&value, &u, 4); | |||
| return value; | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| std::vector<uint8_t> decode_base64(const char *text, size_t len) | |||
| { | |||
| return d_getChunkFromBase64String(text, len); | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| bool get_file_uid(const char *path, file_uid &uid) | |||
| { | |||
| #ifdef _WIN32 | |||
| HANDLE handle = CreateFileW(widen(path).c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); | |||
| if (handle == INVALID_HANDLE_VALUE) | |||
| return false; | |||
| bool success = get_handle_file_uid((void *)handle, uid); | |||
| CloseHandle(handle); | |||
| return success; | |||
| #else | |||
| int fd = open(path, O_RDONLY); | |||
| if (fd == -1) | |||
| return false; | |||
| bool success = get_descriptor_file_uid(fd, uid); | |||
| close(fd); | |||
| return success; | |||
| #endif | |||
| } | |||
| bool get_stream_file_uid(FILE *stream, file_uid &uid) | |||
| { | |||
| #if !defined(_WIN32) | |||
| int fd = fileno(stream); | |||
| if (fd == -1) | |||
| return false; | |||
| #else | |||
| int fd = _fileno(stream); | |||
| if (fd == -1) | |||
| return false; | |||
| #endif | |||
| return get_descriptor_file_uid(fd, uid); | |||
| } | |||
| bool get_descriptor_file_uid(int fd, file_uid &uid) | |||
| { | |||
| #if !defined(_WIN32) | |||
| struct stat st; | |||
| if (fstat(fd, &st) != 0) | |||
| return false; | |||
| uid.first = (uint64_t)st.st_dev; | |||
| uid.second = (uint64_t)st.st_ino; | |||
| return true; | |||
| #else | |||
| HANDLE handle = (HANDLE)_get_osfhandle(fd); | |||
| if (handle == INVALID_HANDLE_VALUE) | |||
| return false; | |||
| return get_handle_file_uid((void *)handle, uid); | |||
| #endif | |||
| } | |||
| #if defined(_WIN32) | |||
| bool get_handle_file_uid(void *handle, file_uid &uid) | |||
| { | |||
| BY_HANDLE_FILE_INFORMATION info; | |||
| if (!GetFileInformationByHandle((HANDLE)handle, &info)) | |||
| return false; | |||
| uid.first = info.dwVolumeSerialNumber; | |||
| uid.second = (uint64_t)info.nFileIndexLow | ((uint64_t)info.nFileIndexHigh << 32); | |||
| return true; | |||
| } | |||
| #endif | |||
| //------------------------------------------------------------------------------ | |||
| bool is_path_separator(char ch) | |||
| { | |||
| #if !defined(_WIN32) | |||
| return ch == '/'; | |||
| #else | |||
| return ch == '/' || ch == '\\'; | |||
| #endif | |||
| } | |||
| split_path_t split_path(const char *path) | |||
| { | |||
| split_path_t sp; | |||
| #if !defined(_WIN32) | |||
| size_t npos = ~(size_t)0; | |||
| size_t pos = npos; | |||
| for (size_t i = 0; path[i] != '\0'; ++i) { | |||
| if (is_path_separator(path[i])) | |||
| pos = i; | |||
| } | |||
| if (pos == npos) | |||
| sp.file.assign(path); | |||
| else { | |||
| sp.dir.assign(path, pos + 1); | |||
| sp.file.assign(path + pos + 1); | |||
| } | |||
| #else | |||
| std::wstring wpath = widen(path); | |||
| std::unique_ptr<wchar_t[]> drive{new wchar_t[wpath.size() + 1]{}}; | |||
| std::unique_ptr<wchar_t[]> dir{new wchar_t[wpath.size() + 1]{}}; | |||
| std::unique_ptr<wchar_t[]> fname{new wchar_t[wpath.size() + 1]{}}; | |||
| std::unique_ptr<wchar_t[]> ext{new wchar_t[wpath.size() + 1]{}}; | |||
| _wsplitpath(wpath.c_str(), drive.get(), dir.get(), fname.get(), ext.get()); | |||
| sp.drive = narrow(drive.get()); | |||
| if (!drive[0] || dir[0] == L'/' || dir[0] == L'\\') | |||
| sp.dir = narrow(dir.get()); | |||
| else | |||
| sp.dir = narrow(L'\\' + std::wstring(dir.get())); | |||
| sp.file = narrow(std::wstring(fname.get()) + std::wstring(ext.get())); | |||
| #endif | |||
| return sp; | |||
| } | |||
| std::string path_file_name(const char *path) | |||
| { | |||
| return split_path(path).file; | |||
| } | |||
| std::string path_directory(const char *path) | |||
| { | |||
| split_path_t sp = split_path(path); | |||
| return sp.dir.empty() ? std::string("./") : (sp.drive + sp.dir); | |||
| } | |||
| std::string path_ensure_final_separator(const char *path) | |||
| { | |||
| std::string result(path); | |||
| if (!result.empty() && !is_path_separator(result.back())) | |||
| result.push_back('/'); | |||
| return result; | |||
| } | |||
| bool path_has_suffix(const char *path, const char *suffix) | |||
| { | |||
| if (*suffix == '.') | |||
| ++suffix; | |||
| size_t plen = strlen(path); | |||
| size_t slen = strlen(suffix); | |||
| if (plen < slen + 2) | |||
| return false; | |||
| return path[plen - slen - 1] == '.' && | |||
| ascii_casecmp(suffix, &path[plen - slen]) == 0; | |||
| } | |||
| bool path_is_relative(const char *path) | |||
| { | |||
| #if !defined(_WIN32) | |||
| return !is_path_separator(path[0]); | |||
| #else | |||
| return !is_path_separator(split_path(path).dir.c_str()[0]); | |||
| #endif | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| #if !defined(_WIN32) | |||
| bool exists(const char *path) | |||
| { | |||
| return access(path, F_OK) == 0; | |||
| } | |||
| string_list list_directory(const char *path) | |||
| { | |||
| string_list list; | |||
| DIR *dir = opendir(path); | |||
| if (!dir) | |||
| return list; | |||
| auto dir_cleanup = defer([dir]() { closedir(dir); }); | |||
| list.reserve(256); | |||
| std::string pathbuf; | |||
| pathbuf.reserve(1024); | |||
| while (dirent *ent = readdir(dir)) { | |||
| const char *name = ent->d_name; | |||
| if (!strcmp(name, ".") || !strcmp(name, "..")) | |||
| continue; | |||
| pathbuf.assign(name); | |||
| if (ent->d_type == DT_DIR) | |||
| pathbuf.push_back('/'); | |||
| list.push_back(pathbuf); | |||
| } | |||
| std::sort(list.begin(), list.end()); | |||
| return list; | |||
| } | |||
| // void visit_directories(const char *rootpath, bool (*visit)(const std::string &, void *), void *data); | |||
| // NOTE: implemented in separate file `ysfx_utils_fts.cpp` | |||
| #else | |||
| bool exists(const char *path) | |||
| { | |||
| return _waccess(widen(path).c_str(), 0) == 0; | |||
| } | |||
| string_list list_directory(const char *path) | |||
| { | |||
| string_list list; | |||
| std::wstring pattern = widen(path) + L"\\*"; | |||
| WIN32_FIND_DATAW fd; | |||
| HANDLE handle = FindFirstFileW(pattern.c_str(), &fd); | |||
| if (handle == INVALID_HANDLE_VALUE) | |||
| return list; | |||
| auto handle_cleanup = defer([handle]() { FindClose(handle); }); | |||
| list.reserve(256); | |||
| do { | |||
| const wchar_t *name = fd.cFileName; | |||
| if (!wcscmp(name, L".") || !wcscmp(name, L"..")) | |||
| continue; | |||
| std::string entry = narrow(name); | |||
| if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !(fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) | |||
| entry.push_back('/'); | |||
| list.push_back(std::move(entry)); | |||
| } while (FindNextFileW(handle, &fd)); | |||
| std::sort(list.begin(), list.end()); | |||
| return list; | |||
| } | |||
| void visit_directories(const char *rootpath, bool (*visit)(const std::string &, void *), void *data) | |||
| { | |||
| std::deque<std::wstring> dirs; | |||
| dirs.push_back(widen(path_ensure_final_separator(rootpath))); | |||
| std::wstring pathbuf; | |||
| pathbuf.reserve(1024); | |||
| std::vector<std::wstring> entries; | |||
| entries.reserve(256); | |||
| while (!dirs.empty()) { | |||
| std::wstring dir = std::move(dirs.front()); | |||
| dirs.pop_front(); | |||
| if (!visit(narrow(dir), data)) | |||
| return; | |||
| pathbuf.assign(dir); | |||
| pathbuf.append(L"\\*"); | |||
| WIN32_FIND_DATAW fd; | |||
| HANDLE handle = FindFirstFileW(pathbuf.c_str(), &fd); | |||
| if (handle == INVALID_HANDLE_VALUE) | |||
| continue; | |||
| auto handle_cleanup = defer([handle]() { FindClose(handle); }); | |||
| entries.clear(); | |||
| do { | |||
| if (fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) | |||
| continue; | |||
| if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { | |||
| const wchar_t *name = fd.cFileName; | |||
| if (!wcscmp(name, L".") || !wcscmp(name, L"..")) | |||
| continue; | |||
| pathbuf.assign(dir); | |||
| pathbuf.append(name); | |||
| pathbuf.push_back(L'\\'); | |||
| entries.push_back(pathbuf); | |||
| } | |||
| } while (FindNextFileW(handle, &fd)); | |||
| std::sort(entries.begin(), entries.end()); | |||
| for (size_t n = entries.size(); n-- > 0; ) | |||
| dirs.push_front(std::move(entries[n])); | |||
| } | |||
| } | |||
| #endif | |||
| int case_resolve(const char *root_, const char *fragment, std::string &result) | |||
| { | |||
| if (fragment[0] == '\0') | |||
| return 0; | |||
| std::string root = path_ensure_final_separator(root_); | |||
| std::string pathbuf; | |||
| pathbuf.reserve(1024); | |||
| pathbuf.assign(root); | |||
| pathbuf.append(fragment); | |||
| if (exists(pathbuf.c_str())) { | |||
| result = std::move(pathbuf); | |||
| return 1; | |||
| } | |||
| struct Item { | |||
| std::string root; | |||
| string_list components; | |||
| }; | |||
| std::deque<Item> worklist; | |||
| { | |||
| Item item; | |||
| item.root = root; | |||
| item.components = split_strings_noempty(fragment, &is_path_separator); | |||
| if (item.components.empty()) | |||
| return 0; | |||
| for (size_t i = 0; i + 1 < item.components.size(); ++i) | |||
| item.components[i].push_back('/'); | |||
| if (is_path_separator(fragment[strlen(fragment) - 1])) | |||
| item.components.back().push_back('/'); | |||
| worklist.push_back(std::move(item)); | |||
| } | |||
| while (!worklist.empty()) { | |||
| Item item = std::move(worklist.front()); | |||
| worklist.pop_front(); | |||
| for (const std::string &entry : list_directory(item.root.c_str())) { | |||
| if (ascii_casecmp(entry.c_str(), item.components[0].c_str()) != 0) | |||
| continue; | |||
| if (item.components.size() == 1) { | |||
| pathbuf.assign(item.root); | |||
| pathbuf.append(entry); | |||
| if (exists(pathbuf.c_str())) { | |||
| result = std::move(pathbuf); | |||
| return 2; | |||
| } | |||
| } | |||
| else { | |||
| assert(item.components.size() > 1); | |||
| Item newitem; | |||
| newitem.root = item.root + entry; | |||
| newitem.components.assign(item.components.begin() + 1, item.components.end()); | |||
| worklist.push_front(std::move(newitem)); | |||
| } | |||
| } | |||
| } | |||
| return 0; | |||
| } | |||
| //------------------------------------------------------------------------------ | |||
| #if defined(_WIN32) | |||
| std::wstring widen(const std::string &u8str) | |||
| { | |||
| return widen(u8str.data(), u8str.size()); | |||
| } | |||
| std::wstring widen(const char *u8data, size_t u8len) | |||
| { | |||
| if (u8len == ~(size_t)0) | |||
| u8len = strlen(u8data); | |||
| std::wstring wstr; | |||
| int wch = MultiByteToWideChar(CP_UTF8, 0, u8data, (int)u8len, nullptr, 0); | |||
| if (wch != 0) { | |||
| wstr.resize((size_t)wch); | |||
| MultiByteToWideChar(CP_UTF8, 0, u8data, (int)u8len, &wstr[0], wch); | |||
| } | |||
| return wstr; | |||
| } | |||
| std::string narrow(const std::wstring &wstr) | |||
| { | |||
| return narrow(wstr.data(), wstr.size()); | |||
| } | |||
| std::string narrow(const wchar_t *wdata, size_t wlen) | |||
| { | |||
| if (wlen == ~(size_t)0) | |||
| wlen = wcslen(wdata); | |||
| std::string u8str; | |||
| int u8ch = WideCharToMultiByte(CP_UTF8, 0, wdata, (int)wlen, nullptr, 0, nullptr, nullptr); | |||
| if (u8ch != 0) { | |||
| u8str.resize((size_t)u8ch); | |||
| WideCharToMultiByte(CP_UTF8, 0, wdata, (int)wlen, &u8str[0], u8ch, nullptr, nullptr); | |||
| } | |||
| return u8str; | |||
| } | |||
| #endif | |||
| } // namespace ysfx | |||
| //------------------------------------------------------------------------------ | |||
| // WDL helpers | |||
| // our replacement `atof` for WDL, which is unaffected by current locale | |||
| extern "C" double ysfx_wdl_atof(const char *text) | |||
| { | |||
| return ysfx::dot_atof(text); | |||
| } | |||
| @@ -0,0 +1,185 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #pragma once | |||
| #include "ysfx.h" | |||
| #include <string> | |||
| #include <vector> | |||
| #include <mutex> | |||
| #include <cstdio> | |||
| #include <cstdint> | |||
| #include <clocale> | |||
| #if defined(__APPLE__) | |||
| # include <xlocale.h> | |||
| #endif | |||
| #if !defined(_WIN32) | |||
| # include <alloca.h> | |||
| #else | |||
| # include <malloc.h> | |||
| #endif | |||
| #if defined(YSFX_NO_STANDARD_MUTEX) | |||
| # include "WDL/mutex.h" | |||
| #endif | |||
| namespace ysfx { | |||
| YSFX_DEFINE_AUTO_PTR(FILE_u, FILE, fclose); | |||
| FILE *fopen_utf8(const char *path, const char *mode); | |||
| int64_t fseek_lfs(FILE *stream, int64_t off, int whence); | |||
| int64_t ftell_lfs(FILE *stream); | |||
| //------------------------------------------------------------------------------ | |||
| #if !defined(_WIN32) | |||
| using c_locale_t = locale_t; | |||
| #else | |||
| using c_locale_t = _locale_t; | |||
| #endif | |||
| c_locale_t c_numeric_locale(); | |||
| //------------------------------------------------------------------------------ | |||
| #if !defined(_WIN32) | |||
| # define ysfx_alloca(n) alloca((n)) | |||
| #else | |||
| # define ysfx_alloca(n) _malloca((n)) | |||
| #endif | |||
| //------------------------------------------------------------------------------ | |||
| #if !defined(YSFX_NO_STANDARD_MUTEX) | |||
| using mutex = std::mutex; | |||
| #else | |||
| class mutex | |||
| { | |||
| public: | |||
| void lock() { m_mutex.Enter(); } | |||
| void unlock() { m_mutex.Leave(); } | |||
| private: | |||
| WDL_Mutex m_mutex; | |||
| }; | |||
| #endif | |||
| //------------------------------------------------------------------------------ | |||
| using string_list = std::vector<std::string>; | |||
| double c_atof(const char *text, c_locale_t loc); | |||
| double c_strtod(const char *text, char **endp, c_locale_t loc); | |||
| double dot_atof(const char *text); | |||
| double dot_strtod(const char *text, char **endp); | |||
| bool ascii_isspace(char c); | |||
| bool ascii_isalpha(char c); | |||
| char ascii_tolower(char c); | |||
| char ascii_toupper(char c); | |||
| int ascii_casecmp(const char *a, const char *b); | |||
| uint32_t latin1_toupper(uint32_t c); | |||
| uint32_t latin1_tolower(uint32_t c); | |||
| char *strdup_using_new(const char *src); | |||
| string_list split_strings_noempty(const char *input, bool(*pred)(char)); | |||
| std::string trim(const char *input, bool(*pred)(char)); | |||
| //------------------------------------------------------------------------------ | |||
| void pack_u32le(uint32_t value, uint8_t data[4]); | |||
| void pack_f32le(float value, uint8_t data[4]); | |||
| uint32_t unpack_u32le(const uint8_t data[4]); | |||
| float unpack_f32le(const uint8_t data[4]); | |||
| //------------------------------------------------------------------------------ | |||
| std::vector<uint8_t> decode_base64(const char *text, size_t len = ~(size_t)0); | |||
| //------------------------------------------------------------------------------ | |||
| using file_uid = std::pair<uint64_t, uint64_t>; | |||
| bool get_file_uid(const char *path, file_uid &uid); | |||
| bool get_stream_file_uid(FILE *stream, file_uid &uid); | |||
| bool get_descriptor_file_uid(int fd, file_uid &uid); | |||
| #if defined(_WIN32) | |||
| bool get_handle_file_uid(void *handle, file_uid &uid); | |||
| #endif | |||
| //------------------------------------------------------------------------------ | |||
| struct split_path_t { | |||
| std::string drive; | |||
| std::string dir; | |||
| std::string file; | |||
| }; | |||
| // check if the character is a path separator | |||
| bool is_path_separator(char ch); | |||
| // break down a path into individual components | |||
| split_path_t split_path(const char *path); | |||
| // get the file name part (all after the final '/' separator) | |||
| std::string path_file_name(const char *path); | |||
| // get the directory part (all up to the '/' separator, inclusive) | |||
| std::string path_directory(const char *path); | |||
| // add the final '/' separator if absent; if empty, does nothing | |||
| std::string path_ensure_final_separator(const char *path); | |||
| // compare the tail of the path with the suffix, case-insensitively | |||
| bool path_has_suffix(const char *path, const char *suffix); | |||
| // check whether the path is relative | |||
| bool path_is_relative(const char *path); | |||
| //------------------------------------------------------------------------------ | |||
| // check whether a file exists on disk | |||
| bool exists(const char *path); | |||
| // list the elements of a directory; directories are distinguished with a final '/' | |||
| string_list list_directory(const char *path); | |||
| // visit the root and subdirectories in depth-first order | |||
| void visit_directories(const char *rootpath, bool (*visit)(const std::string &, void *), void *data); | |||
| // resolve a path which matches root/fragment, where fragment is case-insensitive (0=failed, 1=exact, 2=inexact) | |||
| int case_resolve(const char *root, const char *fragment, std::string &result); | |||
| //------------------------------------------------------------------------------ | |||
| #if defined(_WIN32) | |||
| std::wstring widen(const std::string &u8str); | |||
| std::wstring widen(const char *u8data, size_t u8len = ~(size_t)0); | |||
| std::string narrow(const std::wstring &wstr); | |||
| std::string narrow(const wchar_t *wdata, size_t wlen = ~(size_t)0); | |||
| #endif | |||
| //------------------------------------------------------------------------------ | |||
| template <class F> | |||
| class scope_guard { | |||
| public: | |||
| explicit scope_guard(F &&f) : f(std::forward<F>(f)), a(true) {} | |||
| scope_guard(scope_guard &&o) : f(std::move(o.f)), a(o.a) { o.a = false; } | |||
| ~scope_guard() { if (a) f(); } | |||
| void disarm() { a = false; } | |||
| private: | |||
| F f; | |||
| bool a; | |||
| scope_guard(const scope_guard &) = delete; | |||
| scope_guard &operator=(const scope_guard &) = delete; | |||
| }; | |||
| template <class F> scope_guard<F> defer(F &&f) | |||
| { | |||
| return scope_guard<F>(std::forward<F>(f)); | |||
| } | |||
| } // namespace ysfx | |||
| @@ -0,0 +1,60 @@ | |||
| // Copyright 2021 Jean Pierre Cimalando | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // | |||
| #if !defined(_WIN32) | |||
| // fts lacks large file support in glibc < 2.23 | |||
| #if defined(YSFX_FTS_LACKS_LFS_SUPPORT) | |||
| # undef _FILE_OFFSET_BITS | |||
| #endif | |||
| #include "ysfx_utils.hpp" | |||
| #include <fts.h> | |||
| #include <string> | |||
| #include <cstring> | |||
| namespace ysfx { | |||
| void visit_directories(const char *rootpath, bool (*visit)(const std::string &, void *), void *data) | |||
| { | |||
| char *argv[] = {(char *)rootpath, nullptr}; | |||
| auto compar = [](const FTSENT **a, const FTSENT **b) -> int { | |||
| return strcmp((*a)->fts_name, (*b)->fts_name); | |||
| }; | |||
| FTS *fts = fts_open(argv, FTS_NOCHDIR|FTS_PHYSICAL, +compar); | |||
| if (!fts) | |||
| return; | |||
| auto fts_cleanup = defer([fts]() { fts_close(fts); }); | |||
| std::string pathbuf; | |||
| pathbuf.reserve(1024); | |||
| while (FTSENT *ent = fts_read(fts)) { | |||
| if (ent->fts_info == FTS_D) { | |||
| pathbuf.assign(ent->fts_path); | |||
| pathbuf.push_back('/'); | |||
| if (!visit(pathbuf, data)) | |||
| return; | |||
| } | |||
| } | |||
| } | |||
| } // namespace ysfx | |||
| #endif // !defined(_WIN32) | |||
| @@ -0,0 +1,19 @@ | |||
| Copyright (C) 2005 and later Cockos Incorporated | |||
| Portions copyright other contributors, see each source file for more information | |||
| This software is provided 'as-is', without any express or implied | |||
| warranty. In no event will the authors be held liable for any damages | |||
| arising from the use of this software. | |||
| Permission is granted to anyone to use this software for any purpose, | |||
| including commercial applications, and to alter it and redistribute it | |||
| freely, subject to the following restrictions: | |||
| 1. The origin of this software must not be misrepresented; you must not | |||
| claim that you wrote the original software. If you use this software | |||
| in a product, an acknowledgment in the product documentation would be | |||
| appreciated but is not required. | |||
| 2. Altered source versions must be plainly marked as such, and must not be | |||
| misrepresented as being the original software. | |||
| 3. This notice may not be removed or altered from any source distribution. | |||
| @@ -0,0 +1,482 @@ | |||
| #ifndef _WDL_ASSOCARRAY_H_ | |||
| #define _WDL_ASSOCARRAY_H_ | |||
| #include "heapbuf.h" | |||
| #include "mergesort.h" | |||
| // on all of these, if valdispose is set, the array will dispose of values as needed. | |||
| // if keydup/keydispose are set, copies of (any) key data will be made/destroyed as necessary | |||
| // WDL_AssocArrayImpl can be used on its own, and can contain structs for keys or values | |||
| template <class KEY, class VAL> class WDL_AssocArrayImpl | |||
| { | |||
| WDL_AssocArrayImpl(const WDL_AssocArrayImpl &cp) { CopyContents(cp); } | |||
| WDL_AssocArrayImpl &operator=(const WDL_AssocArrayImpl &cp) { CopyContents(cp); return *this; } | |||
| public: | |||
| explicit WDL_AssocArrayImpl(int (*keycmp)(KEY *k1, KEY *k2), KEY (*keydup)(KEY)=0, void (*keydispose)(KEY)=0, void (*valdispose)(VAL)=0) | |||
| { | |||
| m_keycmp = keycmp; | |||
| m_keydup = keydup; | |||
| m_keydispose = keydispose; | |||
| m_valdispose = valdispose; | |||
| } | |||
| ~WDL_AssocArrayImpl() | |||
| { | |||
| DeleteAll(); | |||
| } | |||
| VAL* GetPtr(KEY key, KEY *keyPtrOut=NULL) const | |||
| { | |||
| bool ismatch = false; | |||
| int i = LowerBound(key, &ismatch); | |||
| if (ismatch) | |||
| { | |||
| KeyVal* kv = m_data.Get()+i; | |||
| if (keyPtrOut) *keyPtrOut = kv->key; | |||
| return &(kv->val); | |||
| } | |||
| return 0; | |||
| } | |||
| bool Exists(KEY key) const | |||
| { | |||
| bool ismatch = false; | |||
| LowerBound(key, &ismatch); | |||
| return ismatch; | |||
| } | |||
| int Insert(KEY key, VAL val) | |||
| { | |||
| bool ismatch = false; | |||
| int i = LowerBound(key, &ismatch); | |||
| if (ismatch) | |||
| { | |||
| KeyVal* kv = m_data.Get()+i; | |||
| if (m_valdispose) m_valdispose(kv->val); | |||
| kv->val = val; | |||
| } | |||
| else | |||
| { | |||
| KeyVal* kv = m_data.Resize(m_data.GetSize()+1)+i; | |||
| memmove(kv+1, kv, (m_data.GetSize()-i-1)*(unsigned int)sizeof(KeyVal)); | |||
| if (m_keydup) key = m_keydup(key); | |||
| kv->key = key; | |||
| kv->val = val; | |||
| } | |||
| return i; | |||
| } | |||
| void Delete(KEY key) | |||
| { | |||
| bool ismatch = false; | |||
| int i = LowerBound(key, &ismatch); | |||
| if (ismatch) | |||
| { | |||
| KeyVal* kv = m_data.Get()+i; | |||
| if (m_keydispose) m_keydispose(kv->key); | |||
| if (m_valdispose) m_valdispose(kv->val); | |||
| m_data.Delete(i); | |||
| } | |||
| } | |||
| void DeleteByIndex(int idx) | |||
| { | |||
| if (idx >= 0 && idx < m_data.GetSize()) | |||
| { | |||
| KeyVal* kv = m_data.Get()+idx; | |||
| if (m_keydispose) m_keydispose(kv->key); | |||
| if (m_valdispose) m_valdispose(kv->val); | |||
| m_data.Delete(idx); | |||
| } | |||
| } | |||
| void DeleteAll(bool resizedown=false) | |||
| { | |||
| if (m_keydispose || m_valdispose) | |||
| { | |||
| int i; | |||
| for (i = 0; i < m_data.GetSize(); ++i) | |||
| { | |||
| KeyVal* kv = m_data.Get()+i; | |||
| if (m_keydispose) m_keydispose(kv->key); | |||
| if (m_valdispose) m_valdispose(kv->val); | |||
| } | |||
| } | |||
| m_data.Resize(0, resizedown); | |||
| } | |||
| int GetSize() const | |||
| { | |||
| return m_data.GetSize(); | |||
| } | |||
| VAL* EnumeratePtr(int i, KEY* key=0) const | |||
| { | |||
| if (i >= 0 && i < m_data.GetSize()) | |||
| { | |||
| KeyVal* kv = m_data.Get()+i; | |||
| if (key) *key = kv->key; | |||
| return &(kv->val); | |||
| } | |||
| return 0; | |||
| } | |||
| KEY* ReverseLookupPtr(VAL val) const | |||
| { | |||
| int i; | |||
| for (i = 0; i < m_data.GetSize(); ++i) | |||
| { | |||
| KeyVal* kv = m_data.Get()+i; | |||
| if (kv->val == val) return &kv->key; | |||
| } | |||
| return 0; | |||
| } | |||
| void ChangeKey(KEY oldkey, KEY newkey) | |||
| { | |||
| bool ismatch=false; | |||
| int i=LowerBound(oldkey, &ismatch); | |||
| if (ismatch) ChangeKeyByIndex(i, newkey, true); | |||
| } | |||
| void ChangeKeyByIndex(int idx, KEY newkey, bool needsort) | |||
| { | |||
| if (idx >= 0 && idx < m_data.GetSize()) | |||
| { | |||
| KeyVal* kv=m_data.Get()+idx; | |||
| if (!needsort) | |||
| { | |||
| if (m_keydispose) m_keydispose(kv->key); | |||
| if (m_keydup) newkey=m_keydup(newkey); | |||
| kv->key=newkey; | |||
| } | |||
| else | |||
| { | |||
| VAL val=kv->val; | |||
| m_data.Delete(idx); | |||
| Insert(newkey, val); | |||
| } | |||
| } | |||
| } | |||
| // fast add-block mode | |||
| void AddUnsorted(KEY key, VAL val) | |||
| { | |||
| int i=m_data.GetSize(); | |||
| KeyVal* kv = m_data.Resize(i+1)+i; | |||
| if (m_keydup) key = m_keydup(key); | |||
| kv->key = key; | |||
| kv->val = val; | |||
| } | |||
| void Resort(int (*new_keycmp)(KEY *k1, KEY *k2)=NULL) | |||
| { | |||
| if (new_keycmp) m_keycmp = new_keycmp; | |||
| if (m_data.GetSize() > 1 && m_keycmp) | |||
| { | |||
| qsort(m_data.Get(), m_data.GetSize(), sizeof(KeyVal), | |||
| (int(*)(const void*, const void*))m_keycmp); | |||
| if (!new_keycmp) | |||
| RemoveDuplicateKeys(); | |||
| } | |||
| } | |||
| void ResortStable() | |||
| { | |||
| if (m_data.GetSize() > 1 && m_keycmp) | |||
| { | |||
| char *tmp=(char*)malloc(m_data.GetSize()*sizeof(KeyVal)); | |||
| if (WDL_NORMALLY(tmp)) | |||
| { | |||
| WDL_mergesort(m_data.Get(), m_data.GetSize(), sizeof(KeyVal), | |||
| (int(*)(const void*, const void*))m_keycmp, tmp); | |||
| free(tmp); | |||
| } | |||
| else | |||
| { | |||
| qsort(m_data.Get(), m_data.GetSize(), sizeof(KeyVal), | |||
| (int(*)(const void*, const void*))m_keycmp); | |||
| } | |||
| RemoveDuplicateKeys(); | |||
| } | |||
| } | |||
| int LowerBound(KEY key, bool* ismatch) const | |||
| { | |||
| int a = 0; | |||
| int c = m_data.GetSize(); | |||
| while (a != c) | |||
| { | |||
| int b = (a+c)/2; | |||
| KeyVal* kv=m_data.Get()+b; | |||
| int cmp = m_keycmp(&key, &kv->key); | |||
| if (cmp > 0) a = b+1; | |||
| else if (cmp < 0) c = b; | |||
| else | |||
| { | |||
| *ismatch = true; | |||
| return b; | |||
| } | |||
| } | |||
| *ismatch = false; | |||
| return a; | |||
| } | |||
| int GetIdx(KEY key) const | |||
| { | |||
| bool ismatch=false; | |||
| int i = LowerBound(key, &ismatch); | |||
| if (ismatch) return i; | |||
| return -1; | |||
| } | |||
| void SetGranul(int gran) | |||
| { | |||
| m_data.SetGranul(gran); | |||
| } | |||
| void CopyContents(const WDL_AssocArrayImpl &cp) | |||
| { | |||
| m_data=cp.m_data; | |||
| m_keycmp = cp.m_keycmp; | |||
| m_keydup = cp.m_keydup; | |||
| m_keydispose = m_keydup ? cp.m_keydispose : NULL; | |||
| m_valdispose = NULL; // avoid disposing of values twice, since we don't have a valdup, we can't have a fully valid copy | |||
| if (m_keydup) | |||
| { | |||
| int x; | |||
| const int n=m_data.GetSize(); | |||
| for (x=0;x<n;x++) | |||
| { | |||
| KeyVal *kv=m_data.Get()+x; | |||
| if (kv->key) kv->key = m_keydup(kv->key); | |||
| } | |||
| } | |||
| } | |||
| void CopyContentsAsReference(const WDL_AssocArrayImpl &cp) | |||
| { | |||
| DeleteAll(true); | |||
| m_keycmp = cp.m_keycmp; | |||
| m_keydup = NULL; // this no longer can own any data | |||
| m_keydispose = NULL; | |||
| m_valdispose = NULL; | |||
| m_data=cp.m_data; | |||
| } | |||
| // private data, but exposed in case the caller wants to manipulate at its own risk | |||
| struct KeyVal | |||
| { | |||
| KEY key; | |||
| VAL val; | |||
| }; | |||
| WDL_TypedBuf<KeyVal> m_data; | |||
| protected: | |||
| int (*m_keycmp)(KEY *k1, KEY *k2); | |||
| KEY (*m_keydup)(KEY); | |||
| void (*m_keydispose)(KEY); | |||
| void (*m_valdispose)(VAL); | |||
| private: | |||
| void RemoveDuplicateKeys() // after resorting | |||
| { | |||
| const int sz = m_data.GetSize(); | |||
| int cnt = 1; | |||
| KeyVal *rd = m_data.Get() + 1, *wr = rd; | |||
| for (int x = 1; x < sz; x ++) | |||
| { | |||
| if (m_keycmp(&rd->key, &wr[-1].key)) | |||
| { | |||
| if (rd != wr) *wr=*rd; | |||
| wr++; | |||
| cnt++; | |||
| } | |||
| else | |||
| { | |||
| if (m_keydispose) m_keydispose(rd->key); | |||
| if (m_valdispose) m_valdispose(rd->val); | |||
| } | |||
| rd++; | |||
| } | |||
| if (cnt < sz) m_data.Resize(cnt,false); | |||
| } | |||
| }; | |||
| // WDL_AssocArray adds useful functions but cannot contain structs for keys or values | |||
| template <class KEY, class VAL> class WDL_AssocArray : public WDL_AssocArrayImpl<KEY, VAL> | |||
| { | |||
| public: | |||
| explicit WDL_AssocArray(int (*keycmp)(KEY *k1, KEY *k2), KEY (*keydup)(KEY)=0, void (*keydispose)(KEY)=0, void (*valdispose)(VAL)=0) | |||
| : WDL_AssocArrayImpl<KEY, VAL>(keycmp, keydup, keydispose, valdispose) | |||
| { | |||
| } | |||
| VAL Get(KEY key, VAL notfound=0) const | |||
| { | |||
| VAL* p = this->GetPtr(key); | |||
| if (p) return *p; | |||
| return notfound; | |||
| } | |||
| VAL Enumerate(int i, KEY* key=0, VAL notfound=0) const | |||
| { | |||
| VAL* p = this->EnumeratePtr(i, key); | |||
| if (p) return *p; | |||
| return notfound; | |||
| } | |||
| KEY ReverseLookup(VAL val, KEY notfound=0) const | |||
| { | |||
| KEY* p=this->ReverseLookupPtr(val); | |||
| if (p) return *p; | |||
| return notfound; | |||
| } | |||
| }; | |||
| template <class VAL> class WDL_IntKeyedArray : public WDL_AssocArray<int, VAL> | |||
| { | |||
| public: | |||
| explicit WDL_IntKeyedArray(void (*valdispose)(VAL)=0) : WDL_AssocArray<int, VAL>(cmpint, NULL, NULL, valdispose) {} | |||
| ~WDL_IntKeyedArray() {} | |||
| private: | |||
| static int cmpint(int *i1, int *i2) { return *i1-*i2; } | |||
| }; | |||
| template <class VAL> class WDL_IntKeyedArray2 : public WDL_AssocArrayImpl<int, VAL> | |||
| { | |||
| public: | |||
| explicit WDL_IntKeyedArray2(void (*valdispose)(VAL)=0) : WDL_AssocArrayImpl<int, VAL>(cmpint, NULL, NULL, valdispose) {} | |||
| ~WDL_IntKeyedArray2() {} | |||
| private: | |||
| static int cmpint(int *i1, int *i2) { return *i1-*i2; } | |||
| }; | |||
| template <class VAL> class WDL_StringKeyedArray : public WDL_AssocArray<const char *, VAL> | |||
| { | |||
| public: | |||
| explicit WDL_StringKeyedArray(bool caseSensitive=true, void (*valdispose)(VAL)=0) : WDL_AssocArray<const char*, VAL>(caseSensitive?cmpstr:cmpistr, dupstr, freestr, valdispose) {} | |||
| ~WDL_StringKeyedArray() { } | |||
| static const char *dupstr(const char *s) { return strdup(s); } // these might not be necessary but depending on the libc maybe... | |||
| static int cmpstr(const char **s1, const char **s2) { return strcmp(*s1, *s2); } | |||
| static int cmpistr(const char **a, const char **b) { return stricmp(*a,*b); } | |||
| static void freestr(const char* s) { free((void*)s); } | |||
| static void freecharptr(char *p) { free(p); } | |||
| }; | |||
| template <class VAL> class WDL_StringKeyedArray2 : public WDL_AssocArrayImpl<const char *, VAL> | |||
| { | |||
| public: | |||
| explicit WDL_StringKeyedArray2(bool caseSensitive=true, void (*valdispose)(VAL)=0) : WDL_AssocArrayImpl<const char*, VAL>(caseSensitive?cmpstr:cmpistr, dupstr, freestr, valdispose) {} | |||
| ~WDL_StringKeyedArray2() { } | |||
| static const char *dupstr(const char *s) { return strdup(s); } // these might not be necessary but depending on the libc maybe... | |||
| static int cmpstr(const char **s1, const char **s2) { return strcmp(*s1, *s2); } | |||
| static int cmpistr(const char **a, const char **b) { return stricmp(*a,*b); } | |||
| static void freestr(const char* s) { free((void*)s); } | |||
| static void freecharptr(char *p) { free(p); } | |||
| }; | |||
| // sorts text as text, sorts anything that looks like a number as a number | |||
| template <class VAL> class WDL_LogicalSortStringKeyedArray : public WDL_StringKeyedArray<VAL> | |||
| { | |||
| public: | |||
| explicit WDL_LogicalSortStringKeyedArray(bool caseSensitive=true, void (*valdispose)(VAL)=0) : WDL_StringKeyedArray<VAL>(caseSensitive, valdispose) | |||
| { | |||
| WDL_StringKeyedArray<VAL>::m_keycmp = caseSensitive?cmpstr:cmpistr; // override | |||
| } | |||
| ~WDL_LogicalSortStringKeyedArray() { } | |||
| static int cmpstr(const char **a, const char **b) { return _cmpstr(*a, *b, true); } | |||
| static int cmpistr(const char **a, const char **b) { return _cmpstr(*a, *b, false); } | |||
| private: | |||
| static int _cmpstr(const char *s1, const char *s2, bool case_sensitive) | |||
| { | |||
| // this also exists as WDL_strcmp_logical in wdlcstring.h | |||
| for (;;) | |||
| { | |||
| if (*s1 >= '0' && *s1 <= '9' && *s2 >= '0' && *s2 <= '9') | |||
| { | |||
| int lzdiff=0, len1=0, len2=0; | |||
| while (*s1 == '0') { s1++; lzdiff--; } | |||
| while (*s2 == '0') { s2++; lzdiff++; } | |||
| while (s1[len1] >= '0' && s1[len1] <= '9') len1++; | |||
| while (s2[len2] >= '0' && s2[len2] <= '9') len2++; | |||
| if (len1 != len2) return len1-len2; | |||
| while (len1--) | |||
| { | |||
| const int d = *s1++ - *s2++; | |||
| if (d) return d; | |||
| } | |||
| if (lzdiff) return lzdiff; | |||
| } | |||
| else | |||
| { | |||
| char c1 = *s1++, c2 = *s2++; | |||
| if (c1 != c2) | |||
| { | |||
| if (case_sensitive) return c1-c2; | |||
| if (c1>='a' && c1<='z') c1+='A'-'a'; | |||
| if (c2>='a' && c2<='z') c2+='A'-'a'; | |||
| if (c1 != c2) return c1-c2; | |||
| } | |||
| else if (!c1) return 0; | |||
| } | |||
| } | |||
| } | |||
| }; | |||
| template <class VAL> class WDL_PtrKeyedArray : public WDL_AssocArray<INT_PTR, VAL> | |||
| { | |||
| public: | |||
| explicit WDL_PtrKeyedArray(void (*valdispose)(VAL)=0) : WDL_AssocArray<INT_PTR, VAL>(cmpptr, 0, 0, valdispose) {} | |||
| ~WDL_PtrKeyedArray() {} | |||
| private: | |||
| static int cmpptr(INT_PTR* a, INT_PTR* b) { const INT_PTR d = *a - *b; return d<0?-1:(d!=0); } | |||
| }; | |||
| #endif | |||
| @@ -0,0 +1,248 @@ | |||
| #ifndef _WDL_DENORMAL_H_ | |||
| #define _WDL_DENORMAL_H_ | |||
| #include <string.h> | |||
| #include "wdltypes.h" | |||
| // note: the _aggressive versions filter out anything less than around 1.0e-16 or so (approximately) to 0.0, including -0.0 (becomes 0.0) | |||
| // note: new! the _aggressive versions also filter inf and NaN to 0.0 | |||
| #ifdef __cplusplus | |||
| #define WDL_DENORMAL_INLINE inline | |||
| #elif defined(_MSC_VER) | |||
| #define WDL_DENORMAL_INLINE __inline | |||
| #else | |||
| #ifdef WDL_STATICFUNC_UNUSED | |||
| #define WDL_DENORMAL_INLINE WDL_STATICFUNC_UNUSED | |||
| #else | |||
| #define WDL_DENORMAL_INLINE | |||
| #endif | |||
| #endif | |||
| static WDL_DENORMAL_INLINE unsigned int WDL_DENORMAL_FLOAT_W(const float *a) { unsigned int v; memcpy(&v,a,sizeof(v)); return v; } | |||
| static WDL_DENORMAL_INLINE unsigned int WDL_DENORMAL_DOUBLE_HW(const double *a) { WDL_UINT64 v; memcpy(&v,(char*)a,sizeof(v)); return (unsigned int) (v>>32); } | |||
| #define WDL_DENORMAL_DOUBLE_AGGRESSIVE_CUTOFF 0x3cA00000 // 0x3B8000000 maybe instead? that's 10^-5 smaller or so | |||
| #define WDL_DENORMAL_FLOAT_AGGRESSIVE_CUTOFF 0x25000000 | |||
| // define WDL_DENORMAL_WANTS_SCOPED_FTZ, and then use a WDL_denormal_ftz_scope in addition to denormal_*(), then | |||
| // if FTZ is available it will be used instead... | |||
| // | |||
| #ifdef WDL_DENORMAL_WANTS_SCOPED_FTZ | |||
| #if defined(__SSE2__) || _M_IX86_FP >= 2 || defined(_WIN64) | |||
| #define WDL_DENORMAL_FTZMODE | |||
| #define WDL_DENORMAL_FTZSTATE_TYPE unsigned int | |||
| #ifdef _MSC_VER | |||
| #include <intrin.h> | |||
| #else | |||
| #include <xmmintrin.h> | |||
| #endif | |||
| #define wdl_denorm_mm_getcsr() _mm_getcsr() | |||
| #define wdl_denorm_mm_setcsr(x) _mm_setcsr(x) | |||
| #if defined(__SSE3__) | |||
| #define wdl_denorm_mm_csr_mask ((1<<15)|(1<<11) | (1<<8) | (1<<6)) // FTZ, underflow, denormal mask, DAZ | |||
| #else | |||
| #define wdl_denorm_mm_csr_mask ((1<<15)|(1<<11)) // FTZ and underflow only (target SSE2) | |||
| #endif | |||
| #elif defined(__arm__) || defined(__aarch64__) | |||
| #define WDL_DENORMAL_FTZMODE | |||
| #define WDL_DENORMAL_FTZSTATE_TYPE unsigned long | |||
| static unsigned long __attribute__((unused)) wdl_denorm_mm_getcsr() | |||
| { | |||
| unsigned long rv; | |||
| #ifdef __aarch64__ | |||
| asm volatile ( "mrs %0, fpcr" : "=r" (rv)); | |||
| #else | |||
| asm volatile ( "fmrx %0, fpscr" : "=r" (rv)); | |||
| #endif | |||
| return rv; | |||
| } | |||
| static void __attribute__((unused)) wdl_denorm_mm_setcsr(unsigned long v) | |||
| { | |||
| #ifdef __aarch64__ | |||
| asm volatile ( "msr fpcr, %0" :: "r"(v)); | |||
| #else | |||
| asm volatile ( "fmxr fpscr, %0" :: "r"(v)); | |||
| #endif | |||
| } | |||
| #define wdl_denorm_mm_csr_mask (1<<24) | |||
| #endif | |||
| class WDL_denormal_ftz_scope | |||
| { | |||
| public: | |||
| WDL_denormal_ftz_scope() | |||
| { | |||
| #ifdef WDL_DENORMAL_FTZMODE | |||
| const WDL_DENORMAL_FTZSTATE_TYPE b = wdl_denorm_mm_csr_mask; | |||
| old_state = wdl_denorm_mm_getcsr(); | |||
| if ((need_restore = (old_state & b) != b)) | |||
| wdl_denorm_mm_setcsr(old_state|b); | |||
| #endif | |||
| } | |||
| ~WDL_denormal_ftz_scope() | |||
| { | |||
| #ifdef WDL_DENORMAL_FTZMODE | |||
| if (need_restore) wdl_denorm_mm_setcsr(old_state); | |||
| #endif | |||
| } | |||
| #ifdef WDL_DENORMAL_FTZMODE | |||
| WDL_DENORMAL_FTZSTATE_TYPE old_state; | |||
| bool need_restore; | |||
| #endif | |||
| }; | |||
| #endif | |||
| #if !defined(WDL_DENORMAL_FTZMODE) && !defined(WDL_DENORMAL_DO_NOT_FILTER) | |||
| static double WDL_DENORMAL_INLINE denormal_filter_double(double a) | |||
| { | |||
| return (WDL_DENORMAL_DOUBLE_HW(&a)&0x7ff00000) ? a : 0.0; | |||
| } | |||
| static double WDL_DENORMAL_INLINE denormal_filter_double2(double a) | |||
| { | |||
| return ((WDL_DENORMAL_DOUBLE_HW(&a)+0x100000)&0x7ff00000) > 0x100000 ? a : 0.0; | |||
| } | |||
| static double WDL_DENORMAL_INLINE denormal_filter_double_aggressive(double a) | |||
| { | |||
| return ((WDL_DENORMAL_DOUBLE_HW(&a)+0x100000)&0x7ff00000) >= WDL_DENORMAL_DOUBLE_AGGRESSIVE_CUTOFF ? a : 0.0; | |||
| } | |||
| static float WDL_DENORMAL_INLINE denormal_filter_float(float a) | |||
| { | |||
| return (WDL_DENORMAL_FLOAT_W(&a)&0x7f800000) ? a : 0.0f; | |||
| } | |||
| static float WDL_DENORMAL_INLINE denormal_filter_float2(float a) | |||
| { | |||
| return ((WDL_DENORMAL_FLOAT_W(&a)+0x800000)&0x7f800000) > 0x800000 ? a : 0.0f; | |||
| } | |||
| static float WDL_DENORMAL_INLINE denormal_filter_float_aggressive(float a) | |||
| { | |||
| return ((WDL_DENORMAL_FLOAT_W(&a)+0x800000)&0x7f800000) >= WDL_DENORMAL_FLOAT_AGGRESSIVE_CUTOFF ? a : 0.0f; | |||
| } | |||
| static void WDL_DENORMAL_INLINE denormal_fix_double(double *a) | |||
| { | |||
| if (!(WDL_DENORMAL_DOUBLE_HW(a)&0x7ff00000)) *a=0.0; | |||
| } | |||
| static void WDL_DENORMAL_INLINE denormal_fix_double_aggressive(double *a) | |||
| { | |||
| if (((WDL_DENORMAL_DOUBLE_HW(a)+0x100000)&0x7ff00000) < WDL_DENORMAL_DOUBLE_AGGRESSIVE_CUTOFF) *a=0.0; | |||
| } | |||
| static void WDL_DENORMAL_INLINE denormal_fix_float(float *a) | |||
| { | |||
| if (!(WDL_DENORMAL_FLOAT_W(a)&0x7f800000)) *a=0.0f; | |||
| } | |||
| static void WDL_DENORMAL_INLINE denormal_fix_float_aggressive(float *a) | |||
| { | |||
| if (((WDL_DENORMAL_FLOAT_W(a)+0x800000)&0x7f800000) < WDL_DENORMAL_FLOAT_AGGRESSIVE_CUTOFF) *a=0.0f; | |||
| } | |||
| #ifdef __cplusplus // automatic typed versions (though one should probably use the explicit versions... | |||
| static double WDL_DENORMAL_INLINE denormal_filter(double a) | |||
| { | |||
| return (WDL_DENORMAL_DOUBLE_HW(&a)&0x7ff00000) ? a : 0.0; | |||
| } | |||
| static double WDL_DENORMAL_INLINE denormal_filter_aggressive(double a) | |||
| { | |||
| return ((WDL_DENORMAL_DOUBLE_HW(&a)+0x100000)&0x7ff00000) >= WDL_DENORMAL_DOUBLE_AGGRESSIVE_CUTOFF ? a : 0.0; | |||
| } | |||
| static float WDL_DENORMAL_INLINE denormal_filter(float a) | |||
| { | |||
| return (WDL_DENORMAL_FLOAT_W(&a)&0x7f800000) ? a : 0.0f; | |||
| } | |||
| static float WDL_DENORMAL_INLINE denormal_filter_aggressive(float a) | |||
| { | |||
| return ((WDL_DENORMAL_FLOAT_W(&a)+0x800000)&0x7f800000) >= WDL_DENORMAL_FLOAT_AGGRESSIVE_CUTOFF ? a : 0.0f; | |||
| } | |||
| static void WDL_DENORMAL_INLINE denormal_fix(double *a) | |||
| { | |||
| if (!(WDL_DENORMAL_DOUBLE_HW(a)&0x7ff00000)) *a=0.0; | |||
| } | |||
| static void WDL_DENORMAL_INLINE denormal_fix_aggressive(double *a) | |||
| { | |||
| if (((WDL_DENORMAL_DOUBLE_HW(a)+0x100000)&0x7ff00000) < WDL_DENORMAL_DOUBLE_AGGRESSIVE_CUTOFF) *a=0.0; | |||
| } | |||
| static void WDL_DENORMAL_INLINE denormal_fix(float *a) | |||
| { | |||
| if (!(WDL_DENORMAL_FLOAT_W(a)&0x7f800000)) *a=0.0f; | |||
| } | |||
| static void WDL_DENORMAL_INLINE denormal_fix_aggressive(float *a) | |||
| { | |||
| if (((WDL_DENORMAL_FLOAT_W(a)+0x800000)&0x7f800000) < WDL_DENORMAL_FLOAT_AGGRESSIVE_CUTOFF) *a=0.0f; | |||
| } | |||
| #endif // cplusplus versions | |||
| #else // end of !WDL_DENORMAL_DO_NOT_FILTER (and other platform-specific checks) | |||
| #define denormal_filter(x) (x) | |||
| #define denormal_filter2(x) (x) | |||
| #define denormal_filter_double(x) (x) | |||
| #define denormal_filter_double2(x) (x) | |||
| #define denormal_filter_double_aggressive(x) (x) | |||
| #define denormal_filter_float(x) (x) | |||
| #define denormal_filter_float2(x) (x) | |||
| #define denormal_filter_float_aggressive(x) (x) | |||
| #define denormal_filter_aggressive(x) (x) | |||
| #define denormal_fix(x) do { } while(0) | |||
| #define denormal_fix_aggressive(x) do { } while(0) | |||
| #define denormal_fix_double(x) do { } while(0) | |||
| #define denormal_fix_double_aggressive(x) do { } while(0) | |||
| #define denormal_fix_float(x) do { } while(0) | |||
| #define denormal_fix_float_aggressive(x) do { } while(0) | |||
| #endif | |||
| //////////////////// | |||
| // this isnt a denormal function but it is similar, so we'll put it here as a bonus | |||
| static void WDL_DENORMAL_INLINE GetDoubleMaxAbsValue(double *out, const double *in) // note: the value pointed to by "out" must be >=0.0, __NOT__ <= -0.0 | |||
| { | |||
| WDL_UINT64 i, o; | |||
| memcpy(&i,in,sizeof(i)); | |||
| memcpy(&o,out,sizeof(o)); | |||
| i &= WDL_UINT64_CONST(0x7fffffffffffffff); | |||
| if (i > o) memcpy(out,&i,sizeof(i)); | |||
| } | |||
| static void WDL_DENORMAL_INLINE GetFloatMaxAbsValue(float *out, const float *in) // note: the value pointed to by "out" must be >=0.0, __NOT__ <= -0.0 | |||
| { | |||
| unsigned int i, o; | |||
| memcpy(&i, in, sizeof(i)); | |||
| memcpy(&o, out, sizeof(o)); | |||
| i &= 0x7fffffff; | |||
| if (i > o) memcpy(out, &i, sizeof(i)); | |||
| } | |||
| #ifdef __cplusplus | |||
| static void WDL_DENORMAL_INLINE GetFloatMaxAbsValue(double *out, const double *in) // note: the value pointed to by "out" must be >=0.0, __NOT__ <= -0.0 | |||
| { | |||
| GetDoubleMaxAbsValue(out,in); | |||
| } | |||
| #endif | |||
| #endif | |||
| @@ -0,0 +1,329 @@ | |||
| /* | |||
| WDL - dirscan.h | |||
| Copyright (C) 2005 and later Cockos Incorporated | |||
| This software is provided 'as-is', without any express or implied | |||
| warranty. In no event will the authors be held liable for any damages | |||
| arising from the use of this software. | |||
| Permission is granted to anyone to use this software for any purpose, | |||
| including commercial applications, and to alter it and redistribute it | |||
| freely, subject to the following restrictions: | |||
| 1. The origin of this software must not be misrepresented; you must not | |||
| claim that you wrote the original software. If you use this software | |||
| in a product, an acknowledgment in the product documentation would be | |||
| appreciated but is not required. | |||
| 2. Altered source versions must be plainly marked as such, and must not be | |||
| misrepresented as being the original software. | |||
| 3. This notice may not be removed or altered from any source distribution. | |||
| */ | |||
| /* | |||
| This file provides the interface and implementation for WDL_DirScan, a simple | |||
| (and somewhat portable) directory reading class. On non-Win32 systems it wraps | |||
| opendir()/readdir()/etc. On Win32, it uses FindFirst*, and supports wildcards as | |||
| well. | |||
| */ | |||
| #ifndef _WDL_DIRSCAN_H_ | |||
| #define _WDL_DIRSCAN_H_ | |||
| #include "wdlstring.h" | |||
| #ifndef _WIN32 | |||
| #include <sys/types.h> | |||
| #include <sys/stat.h> | |||
| #include <dirent.h> | |||
| extern struct stat wdl_stat_chk; | |||
| // if this fails on linux, use CFLAGS += -D_FILE_OFFSET_BITS=64 | |||
| typedef char wdl_dirscan_assert_failed_stat_not_64[sizeof(wdl_stat_chk.st_size)!=8 ? -1 : 1]; | |||
| #endif | |||
| class WDL_DirScan | |||
| { | |||
| public: | |||
| WDL_DirScan() : | |||
| #ifdef _WIN32 | |||
| m_h(INVALID_HANDLE_VALUE) | |||
| #ifndef WDL_NO_SUPPORT_UTF8 | |||
| , m_wcmode(false) | |||
| #endif | |||
| #else | |||
| m_h(NULL), m_ent(NULL) | |||
| #endif | |||
| { | |||
| } | |||
| ~WDL_DirScan() | |||
| { | |||
| Close(); | |||
| } | |||
| int First(const char *dirname | |||
| #ifdef _WIN32 | |||
| , int isExactSpec=0 | |||
| #endif | |||
| ) // returns 0 if success | |||
| { | |||
| WDL_FastString scanstr(dirname); | |||
| const int l = scanstr.GetLength(); | |||
| if (l < 1) return -1; | |||
| #ifdef _WIN32 | |||
| if (!isExactSpec) | |||
| { | |||
| if (dirname[l-1] == '\\' || dirname[l-1] == '/') scanstr.SetLen(l-1); | |||
| m_leading_path = scanstr; | |||
| scanstr.Append("\\*"); | |||
| } | |||
| else | |||
| { | |||
| m_leading_path = scanstr; | |||
| // remove trailing wildcards and directory separator from m_leading_path | |||
| const char *sp = m_leading_path.Get(); | |||
| int idx = m_leading_path.GetLength() - 1; | |||
| while (idx > 0 && sp[idx] != '/' && sp[idx] != '\\') idx--; | |||
| if (idx > 0) m_leading_path.SetLen(idx); | |||
| } | |||
| #else | |||
| if (dirname[l-1] == '\\' || dirname[l-1] == '/') scanstr.SetLen(l-1); | |||
| m_leading_path = scanstr; | |||
| if (!scanstr.GetLength()) scanstr.Set("/"); // fix for scanning / | |||
| #endif | |||
| Close(); | |||
| #ifdef _WIN32 | |||
| #ifndef WDL_NO_SUPPORT_UTF8 | |||
| m_h=INVALID_HANDLE_VALUE; | |||
| #ifdef WDL_SUPPORT_WIN9X | |||
| m_wcmode = GetVersion()< 0x80000000; | |||
| #else | |||
| m_wcmode = true; | |||
| #endif | |||
| if (m_wcmode) | |||
| { | |||
| int reqbuf = MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,scanstr.Get(),-1,NULL,0); | |||
| if (reqbuf > 1000) | |||
| { | |||
| WDL_TypedBuf<WCHAR> tmp; | |||
| tmp.Resize(reqbuf+20); | |||
| if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,scanstr.Get(),-1,tmp.Get(),tmp.GetSize()-10)) | |||
| { | |||
| correctlongpath(tmp.Get()); | |||
| m_h=FindFirstFileW(tmp.Get(),&m_fd); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| WCHAR wfilename[1024]; | |||
| if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,scanstr.Get(),-1,wfilename,1024-10)) | |||
| { | |||
| correctlongpath(wfilename); | |||
| m_h=FindFirstFileW(wfilename,&m_fd); | |||
| } | |||
| } | |||
| } | |||
| if (m_h==INVALID_HANDLE_VALUE) m_wcmode=false; | |||
| if (m_h==INVALID_HANDLE_VALUE) | |||
| #endif | |||
| m_h=FindFirstFile(scanstr.Get(),(WIN32_FIND_DATA*)&m_fd); | |||
| return (m_h == INVALID_HANDLE_VALUE); | |||
| #else | |||
| m_ent=0; | |||
| m_h=opendir(scanstr.Get()); | |||
| return !m_h || Next(); | |||
| #endif | |||
| } | |||
| int Next() // returns 0 on success | |||
| { | |||
| #ifdef _WIN32 | |||
| if (m_h == INVALID_HANDLE_VALUE) return -1; | |||
| #ifndef WDL_NO_SUPPORT_UTF8 | |||
| if (m_wcmode) return !FindNextFileW(m_h,&m_fd); | |||
| #endif | |||
| return !FindNextFile(m_h,(WIN32_FIND_DATA*)&m_fd); | |||
| #else | |||
| if (!m_h) return -1; | |||
| return !(m_ent=readdir(m_h)); | |||
| #endif | |||
| } | |||
| void Close() | |||
| { | |||
| #ifdef _WIN32 | |||
| if (m_h != INVALID_HANDLE_VALUE) FindClose(m_h); | |||
| m_h=INVALID_HANDLE_VALUE; | |||
| #else | |||
| if (m_h) closedir(m_h); | |||
| m_h=0; m_ent=0; | |||
| #endif | |||
| } | |||
| #ifdef _WIN32 | |||
| const char *GetCurrentFN() | |||
| { | |||
| #ifndef WDL_NO_SUPPORT_UTF8 | |||
| if (m_wcmode) | |||
| { | |||
| if (!WideCharToMultiByte(CP_UTF8,0,m_fd.cFileName,-1,m_tmpbuf,sizeof(m_tmpbuf),NULL,NULL)) | |||
| m_tmpbuf[0]=0; | |||
| return m_tmpbuf; | |||
| } | |||
| #endif | |||
| return ((WIN32_FIND_DATA *)&m_fd)->cFileName; | |||
| } | |||
| #else | |||
| const char *GetCurrentFN() const { return m_ent?m_ent->d_name : ""; } | |||
| #endif | |||
| template<class T> void GetCurrentFullFN(T *str) | |||
| { | |||
| str->Set(m_leading_path.Get()); | |||
| #ifdef _WIN32 | |||
| str->Append("\\"); | |||
| #else | |||
| str->Append("/"); | |||
| #endif | |||
| str->Append(GetCurrentFN()); | |||
| } | |||
| int GetCurrentIsDirectory() const // returns 1 if dir, 2 if symlink to dir, 4 if possibly-recursive symlink to dir | |||
| { | |||
| #ifdef _WIN32 | |||
| return !!(m_fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); | |||
| #else | |||
| char tmp[2048]; | |||
| if (m_ent) switch (m_ent->d_type) | |||
| { | |||
| case DT_DIR: return 1; | |||
| case DT_LNK: | |||
| { | |||
| snprintf(tmp,sizeof(tmp),"%s/%s",m_leading_path.Get(),m_ent->d_name); | |||
| char *rp = realpath(tmp,NULL); | |||
| if (!rp) return 0; | |||
| struct stat sb; | |||
| int ret = (!stat(rp,&sb) && (sb.st_mode & S_IFMT) == S_IFDIR) ? 2 : 0; | |||
| if (ret) | |||
| { | |||
| // treat symlinks of /path/to/foo -> /path from being resolved (avoiding obvious feedback loops) | |||
| const int rpl = (int) strlen(rp); | |||
| if ( | |||
| #ifdef __APPLE__ | |||
| !strnicmp(rp,m_leading_path.Get(),rpl) | |||
| #else | |||
| !strncmp(rp,m_leading_path.Get(),rpl) | |||
| #endif | |||
| && (m_leading_path.Get()[rpl] == '/' || m_leading_path.Get()[rpl] == 0) | |||
| ) ret = 4; | |||
| } | |||
| free(rp); | |||
| return ret; | |||
| } | |||
| case DT_UNKNOWN: | |||
| { | |||
| snprintf(tmp,sizeof(tmp),"%s/%s",m_leading_path.Get(),m_ent->d_name); | |||
| DIR *d = opendir(tmp); | |||
| if (d) { closedir(d); return 1; } | |||
| return 0; | |||
| } | |||
| } | |||
| return 0; | |||
| #endif | |||
| } | |||
| // these are somewhat windows specific calls, eh | |||
| #ifdef _WIN32 | |||
| DWORD GetCurrentFileSize(DWORD *HighWord=NULL) const { if (HighWord) *HighWord = m_fd.nFileSizeHigh; return m_fd.nFileSizeLow; } | |||
| void GetCurrentLastWriteTime(FILETIME *ft) const { *ft = m_fd.ftLastWriteTime; } | |||
| void GetCurrentLastAccessTime(FILETIME *ft) const { *ft = m_fd.ftLastAccessTime; } | |||
| void GetCurrentCreationTime(FILETIME *ft) const { *ft = m_fd.ftCreationTime; } | |||
| DWORD GetFileAttributes() const { return m_fd.dwFileAttributes; } | |||
| #elif defined(_WDL_SWELL_H_) | |||
| void GetCurrentCreationTime(FILETIME *ft) | |||
| { | |||
| char tmp[2048]; | |||
| snprintf(tmp,sizeof(tmp),"%s/%s",m_leading_path.Get(),GetCurrentFN()); | |||
| struct stat st={0,}; | |||
| stat(tmp,&st); | |||
| unsigned long long a=(unsigned long long)st.st_ctime; // seconds since january 1st, 1970 | |||
| a+=11644473600ull; // 1601->1970 | |||
| a*=10000000; // seconds to 1/10th microseconds (100 nanoseconds) | |||
| ft->dwLowDateTime=a & 0xffffffff; | |||
| ft->dwHighDateTime=a>>32; | |||
| } | |||
| void GetCurrentLastWriteTime(FILETIME *ft) | |||
| { | |||
| char tmp[2048]; | |||
| snprintf(tmp,sizeof(tmp),"%s/%s",m_leading_path.Get(),GetCurrentFN()); | |||
| struct stat st={0,}; | |||
| stat(tmp,&st); | |||
| unsigned long long a=(unsigned long long)st.st_mtime; // seconds since january 1st, 1970 | |||
| a+=11644473600ull; // 1601->1970 | |||
| a*=10000000; // seconds to 1/10th microseconds (100 nanoseconds) | |||
| ft->dwLowDateTime=a & 0xffffffff; | |||
| ft->dwHighDateTime=a>>32; | |||
| } | |||
| DWORD GetCurrentFileSize(DWORD *HighWord=NULL) | |||
| { | |||
| char tmp[2048]; | |||
| snprintf(tmp,sizeof(tmp),"%s/%s",m_leading_path.Get(),GetCurrentFN()); | |||
| struct stat st={0,}; | |||
| stat(tmp,&st); | |||
| if (HighWord) *HighWord = (DWORD)(st.st_size>>32); | |||
| return (DWORD)(st.st_size&0xffffffff); | |||
| } | |||
| #endif | |||
| private: | |||
| #ifdef _WIN32 | |||
| #ifndef WDL_NO_SUPPORT_UTF8 | |||
| bool m_wcmode; | |||
| WIN32_FIND_DATAW m_fd; | |||
| char m_tmpbuf[MAX_PATH*5]; // even if each byte gets encoded as 4 utf-8 bytes this should be plenty ;) | |||
| #else | |||
| WIN32_FIND_DATA m_fd; | |||
| #endif | |||
| HANDLE m_h; | |||
| #else | |||
| DIR *m_h; | |||
| struct dirent *m_ent; | |||
| #endif | |||
| WDL_FastString m_leading_path; | |||
| #ifdef _WIN32 | |||
| static void correctlongpath(WCHAR *buf) // this also exists as wdl_utf8_correctlongpath | |||
| { | |||
| const WCHAR *insert; | |||
| WCHAR *wr; | |||
| int skip = 0; | |||
| if (!buf || !buf[0] || wcslen(buf) < 256) return; | |||
| if (buf[1] == ':') insert=L"\\\\?\\"; | |||
| else if (buf[0] == '\\' && buf[1] == '\\') { insert = L"\\\\?\\UNC\\"; skip=2; } | |||
| else return; | |||
| wr = buf + wcslen(insert); | |||
| memmove(wr, buf + skip, (wcslen(buf+skip)+1)*2); | |||
| memmove(buf,insert,wcslen(insert)*2); | |||
| while (*wr) | |||
| { | |||
| if (*wr == '/') *wr = '\\'; | |||
| wr++; | |||
| } | |||
| } | |||
| #endif | |||
| } WDL_FIXALIGN; | |||
| #endif | |||
| @@ -0,0 +1,113 @@ | |||
| %option reentrant | |||
| %option prefix="nseel" | |||
| %option bison-bridge | |||
| %option bison-locations | |||
| %option noyywrap | |||
| %option never-interactive | |||
| %option batch | |||
| %option nounput | |||
| %{ | |||
| #include <stdlib.h> | |||
| #include <stdio.h> | |||
| #define YY_USER_ACTION yylloc->first_line = yylineno; | |||
| #define YY_FATAL_ERROR(msg) { ((struct yyguts_t*)yyscanner)->yyextra_r->errVar=1; } | |||
| #define YY_INPUT(buf,result,max_size) { (result) = nseel_gets(yyextra,(buf),max_size); } | |||
| #define YY_EXTRA_TYPE compileContext * | |||
| #undef YY_BUF_SIZE | |||
| #define YY_BUF_SIZE (NSEEL_MAX_VARIABLE_NAMELEN*2) | |||
| #undef YY_READ_BUF_SIZE | |||
| #define YY_READ_BUF_SIZE (NSEEL_MAX_VARIABLE_NAMELEN) | |||
| #include "y.tab.h" | |||
| #ifdef _WIN32 | |||
| #define YY_NO_UNISTD_H | |||
| #endif | |||
| #include "ns-eel-int.h" | |||
| int nseel_gets(compileContext *ctx, char *buf, size_t sz); | |||
| #define PARSENUM *yylval = nseel_translate(yyextra,yytext, 0); return VALUE; | |||
| #define EEL_ACTION(x) return x; | |||
| #ifdef stdin | |||
| #undef stdin | |||
| #endif | |||
| #define stdin (0) | |||
| #ifdef stdout | |||
| #undef stdout | |||
| #endif | |||
| #define stdout (0) | |||
| static int g_fake_errno; | |||
| #ifdef errno | |||
| #undef errno | |||
| #endif | |||
| #define errno g_fake_errno | |||
| static void comment(yyscan_t yyscanner); | |||
| %} | |||
| %% | |||
| [0-9]+\.?[0-9]* PARSENUM; | |||
| \.[0-9]+ PARSENUM; | |||
| 0[xX][0-9a-fA-F]* PARSENUM; | |||
| \$[xX][0-9a-fA-F]* PARSENUM; | |||
| \$\~[0-9]* PARSENUM; | |||
| \$[Ee] PARSENUM; | |||
| \$[Pp][Ii] PARSENUM; | |||
| \$[Pp][Hh][Ii] PARSENUM; | |||
| \$\'.\' PARSENUM; | |||
| \#[a-zA-Z0-9\._]* *yylval = nseel_translate(yyextra,yytext, 0); return STRING_IDENTIFIER; | |||
| \<\< return TOKEN_SHL; | |||
| \>\> return TOKEN_SHR; | |||
| \<= return TOKEN_LTE; | |||
| \>= return TOKEN_GTE; | |||
| == return TOKEN_EQ; | |||
| === return TOKEN_EQ_EXACT; | |||
| \!= return TOKEN_NE; | |||
| \!== return TOKEN_NE_EXACT; | |||
| \&\& return TOKEN_LOGICAL_AND; | |||
| \|\| return TOKEN_LOGICAL_OR; | |||
| \+= return TOKEN_ADD_OP; | |||
| -= return TOKEN_SUB_OP; | |||
| %= return TOKEN_MOD_OP; | |||
| \|= return TOKEN_OR_OP; | |||
| \&= return TOKEN_AND_OP; | |||
| \~= return TOKEN_XOR_OP; | |||
| \/= return TOKEN_DIV_OP; | |||
| \*= return TOKEN_MUL_OP; | |||
| \^= return TOKEN_POW_OP; | |||
| [a-zA-Z_][a-zA-Z0-9\._]* &yylval = nseel_createCompiledValuePtr((compileContext *)yyextra, NULL, yytext); return IDENTIFIER; | |||
| [ \t\r\n]+ /* whitespace */ | |||
| \/\/.*$ /* comment */ | |||
| "/*" { comment(yyscanner); } | |||
| . return (int)yytext[0]; | |||
| %% | |||
| static void comment(yyscan_t yyscanner) | |||
| { | |||
| int c,lc=0; | |||
| while (0 != (c = input(yyscanner))) | |||
| { | |||
| if (c == '/' && lc == '*') return; | |||
| lc = c; | |||
| } | |||
| // end of file, ignore for now | |||
| } | |||
| @@ -0,0 +1,376 @@ | |||
| %pure-parser | |||
| %name-prefix="nseel" | |||
| %parse-param { compileContext* context } | |||
| %lex-param { void* scanner } | |||
| /* this will prevent y.tab.c from ever calling yydestruct(), since we do not use it and it is a waste */ | |||
| %destructor { | |||
| #define yydestruct(a,b,c,d,e) | |||
| } VALUE | |||
| %{ | |||
| #ifdef _WIN32 | |||
| #include <windows.h> | |||
| #endif | |||
| #include <stdlib.h> | |||
| #include <stdio.h> | |||
| #include <string.h> | |||
| #include <math.h> | |||
| #include "y.tab.h" | |||
| #include "ns-eel-int.h" | |||
| #define scanner context->scanner | |||
| #define YY_(x) ("") | |||
| %} | |||
| %token VALUE IDENTIFIER TOKEN_SHL TOKEN_SHR | |||
| %token TOKEN_LTE TOKEN_GTE TOKEN_EQ TOKEN_EQ_EXACT TOKEN_NE TOKEN_NE_EXACT TOKEN_LOGICAL_AND TOKEN_LOGICAL_OR | |||
| %token TOKEN_ADD_OP TOKEN_SUB_OP TOKEN_MOD_OP TOKEN_OR_OP TOKEN_AND_OP TOKEN_XOR_OP TOKEN_DIV_OP TOKEN_MUL_OP TOKEN_POW_OP | |||
| %token STRING_LITERAL STRING_IDENTIFIER | |||
| %expect 75 | |||
| %start program | |||
| %% | |||
| more_params: | |||
| expression | |||
| | expression ',' more_params | |||
| { | |||
| $$ = nseel_createMoreParametersOpcode(context,$1,$3); | |||
| } | |||
| ; | |||
| string: | |||
| STRING_LITERAL | |||
| | STRING_LITERAL string | |||
| { | |||
| ((struct eelStringSegmentRec *)$1)->_next = (struct eelStringSegmentRec *)$2; | |||
| $$ = $1; | |||
| } | |||
| ; | |||
| assignable_value: | |||
| IDENTIFIER | |||
| { | |||
| if (!($$ = nseel_resolve_named_symbol(context, $1, -1, NULL))) /* convert from purely named to namespace-relative, etc */ | |||
| { | |||
| yyerror(&yyloc, context, ""); | |||
| YYERROR; | |||
| } | |||
| } | |||
| /* we used to have VALUE in here rather than rvalue, to allow 1=1 1+=2 etc, but silly to, | |||
| though this breaks Vmorph, which does 1=1 for a nop, and Jonas DrumReaplacer, which does x = 0 = y = 0 */ | |||
| | '(' expression ')' | |||
| { | |||
| $$ = $2; | |||
| } | |||
| | IDENTIFIER '(' expression ')' '(' expression ')' | |||
| { | |||
| int err; | |||
| if (!($$ = nseel_setCompiledFunctionCallParameters(context,$1, $3, 0, 0, $6, &err))) | |||
| { | |||
| if (err == -1) yyerror(&yylsp[-2], context, ""); | |||
| else if (err == 0) yyerror(&yylsp[-6], context, ""); | |||
| else yyerror(&yylsp[-3], context, ""); // parameter count wrong | |||
| YYERROR; | |||
| } | |||
| } | |||
| | IDENTIFIER '(' expression ')' | |||
| { | |||
| int err; | |||
| if (!($$ = nseel_setCompiledFunctionCallParameters(context,$1, $3, 0, 0, 0, &err))) | |||
| { | |||
| if (err == 0) yyerror(&yylsp[-3], context, ""); | |||
| else yyerror(&yylsp[0], context, ""); // parameter count wrong | |||
| YYERROR; | |||
| } | |||
| } | |||
| | IDENTIFIER '(' ')' | |||
| { | |||
| int err; | |||
| if (!($$ = nseel_setCompiledFunctionCallParameters(context,$1, nseel_createCompiledValue(context,0.0), 0, 0, 0,&err))) | |||
| { | |||
| if (err == 0) yyerror(&yylsp[-2], context, ""); // function not found | |||
| else yyerror(&yylsp[0], context, ""); // parameter count wrong | |||
| YYERROR; | |||
| } | |||
| } | |||
| | IDENTIFIER '(' expression ',' expression ')' | |||
| { | |||
| int err; | |||
| if (!($$ = nseel_setCompiledFunctionCallParameters(context,$1, $3, $5, 0, 0,&err))) | |||
| { | |||
| if (err == 0) yyerror(&yylsp[-5], context, ""); | |||
| else if (err == 2) yyerror(&yylsp[0], context, ""); // needs more than 2 parameters | |||
| else yyerror(&yylsp[-2], context, ""); // less than 2 | |||
| YYERROR; | |||
| } | |||
| } | |||
| | IDENTIFIER '(' expression ',' expression ',' more_params ')' | |||
| { | |||
| int err; | |||
| if (!($$ = nseel_setCompiledFunctionCallParameters(context,$1, $3, $5, $7, 0, &err))) | |||
| { | |||
| if (err == 0) yyerror(&yylsp[-7], context, ""); | |||
| else if (err==2) yyerror(&yylsp[0], context, ""); // needs more parameters | |||
| else if (err==4) yyerror(&yylsp[-4], context, ""); // needs single parameter | |||
| else yyerror(&yylsp[-2], context, ""); // less parm | |||
| YYERROR; | |||
| } | |||
| } | |||
| | rvalue '[' ']' | |||
| { | |||
| $$ = nseel_createMemoryAccess(context,$1,0); | |||
| } | |||
| | rvalue '[' expression ']' | |||
| { | |||
| $$ = nseel_createMemoryAccess(context,$1,$3); | |||
| } | |||
| ; | |||
| rvalue: | |||
| VALUE | |||
| | STRING_IDENTIFIER | |||
| | string | |||
| { | |||
| $$ = nseel_eelMakeOpcodeFromStringSegments(context,(struct eelStringSegmentRec *)$1); | |||
| } | |||
| | assignable_value | |||
| ; | |||
| assignment: | |||
| rvalue | |||
| | assignable_value '=' if_else_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_ASSIGN,2,$1,$3); | |||
| } | |||
| | assignable_value TOKEN_ADD_OP if_else_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_ADD_OP,2,$1,$3); | |||
| } | |||
| | assignable_value TOKEN_SUB_OP if_else_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_SUB_OP,2,$1,$3); | |||
| } | |||
| | assignable_value TOKEN_MOD_OP if_else_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_MOD_OP,2,$1,$3); | |||
| } | |||
| | assignable_value TOKEN_OR_OP if_else_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_OR_OP,2,$1,$3); | |||
| } | |||
| | assignable_value TOKEN_AND_OP if_else_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_AND_OP,2,$1,$3); | |||
| } | |||
| | assignable_value TOKEN_XOR_OP if_else_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_XOR_OP,2,$1,$3); | |||
| } | |||
| | assignable_value TOKEN_DIV_OP if_else_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_DIV_OP,2,$1,$3); | |||
| } | |||
| | assignable_value TOKEN_MUL_OP if_else_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_MUL_OP,2,$1,$3); | |||
| } | |||
| | assignable_value TOKEN_POW_OP if_else_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_POW_OP,2,$1,$3); | |||
| } | |||
| | STRING_IDENTIFIER '=' if_else_expr | |||
| { | |||
| $$ = nseel_createFunctionByName(context,"strcpy",2,$1,$3,NULL); | |||
| } | |||
| | STRING_IDENTIFIER TOKEN_ADD_OP if_else_expr | |||
| { | |||
| $$ = nseel_createFunctionByName(context,"strcat",2,$1,$3,NULL); | |||
| } | |||
| ; | |||
| unary_expr: | |||
| assignment | |||
| | '+' unary_expr | |||
| { | |||
| $$ = $2; | |||
| } | |||
| | '-' unary_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_UMINUS,1,$2,0); | |||
| } | |||
| | '!' unary_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_NOT,1,$2,0); | |||
| } | |||
| ; | |||
| pow_expr: | |||
| unary_expr | |||
| | pow_expr '^' unary_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_POW,2,$1,$3); | |||
| } | |||
| ; | |||
| mod_expr: | |||
| pow_expr | |||
| | mod_expr '%' pow_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_MOD,2,$1,$3); | |||
| } | |||
| | mod_expr TOKEN_SHL pow_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_SHL,2,$1,$3); | |||
| } | |||
| | mod_expr TOKEN_SHR pow_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_SHR,2,$1,$3); | |||
| } | |||
| ; | |||
| div_expr: | |||
| mod_expr | |||
| | div_expr '/' mod_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_DIVIDE,2,$1,$3); | |||
| } | |||
| ; | |||
| mul_expr: | |||
| div_expr | |||
| | mul_expr '*' div_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_MULTIPLY,2,$1,$3); | |||
| } | |||
| ; | |||
| sub_expr: | |||
| mul_expr | |||
| | sub_expr '-' mul_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_SUB,2,$1,$3); | |||
| } | |||
| ; | |||
| add_expr: | |||
| sub_expr | |||
| | add_expr '+' sub_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_ADD,2,$1,$3); | |||
| } | |||
| ; | |||
| andor_expr: | |||
| add_expr | |||
| | andor_expr '&' add_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_AND,2,$1,$3); | |||
| } | |||
| | andor_expr '|' add_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_OR,2,$1,$3); | |||
| } | |||
| | andor_expr '~' add_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_XOR,2,$1,$3); | |||
| } | |||
| ; | |||
| cmp_expr: | |||
| andor_expr | |||
| | cmp_expr '<' andor_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_LT,2,$1,$3); | |||
| } | |||
| | cmp_expr '>' andor_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_GT,2,$1,$3); | |||
| } | |||
| | cmp_expr TOKEN_LTE andor_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_LTE,2,$1,$3); | |||
| } | |||
| | cmp_expr TOKEN_GTE andor_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_GTE,2,$1,$3); | |||
| } | |||
| | cmp_expr TOKEN_EQ andor_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_EQ,2,$1,$3); | |||
| } | |||
| | cmp_expr TOKEN_EQ_EXACT andor_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_EQ_EXACT,2,$1,$3); | |||
| } | |||
| | cmp_expr TOKEN_NE andor_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_NE,2,$1,$3); | |||
| } | |||
| | cmp_expr TOKEN_NE_EXACT andor_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_NE_EXACT,2,$1,$3); | |||
| } | |||
| ; | |||
| logical_and_or_expr: | |||
| cmp_expr | |||
| | logical_and_or_expr TOKEN_LOGICAL_AND cmp_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_LOGICAL_AND,2,$1,$3); | |||
| } | |||
| | logical_and_or_expr TOKEN_LOGICAL_OR cmp_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_LOGICAL_OR,2,$1,$3); | |||
| } | |||
| ; | |||
| if_else_expr: | |||
| logical_and_or_expr | |||
| | logical_and_or_expr '?' if_else_expr ':' if_else_expr | |||
| { | |||
| $$ = nseel_createIfElse(context, $1, $3, $5); | |||
| } | |||
| | logical_and_or_expr '?' ':' if_else_expr | |||
| { | |||
| $$ = nseel_createIfElse(context, $1, 0, $4); | |||
| } | |||
| | logical_and_or_expr '?' if_else_expr | |||
| { | |||
| $$ = nseel_createIfElse(context, $1, $3, 0); | |||
| } | |||
| ; | |||
| expression: | |||
| if_else_expr | |||
| | expression ';' if_else_expr | |||
| { | |||
| $$ = nseel_createSimpleCompiledFunction(context,FN_JOIN_STATEMENTS,2,$1,$3); | |||
| } | |||
| | expression ';' | |||
| { | |||
| $$ = $1; | |||
| } | |||
| ; | |||
| program: | |||
| expression | |||
| { | |||
| if (@1.first_line) { } | |||
| context->result = $1; | |||
| } | |||
| ; | |||
| %% | |||
| @@ -0,0 +1,71 @@ | |||
| #ifndef __EEL_ATOMIC_H__ | |||
| #define __EEL_ATOMIC_H__ | |||
| // requires these to be defined | |||
| //#define EEL_ATOMIC_SET_SCOPE(opaque) WDL_Mutex *mutex = (opaque?&((effectProcessor *)opaque)->m_atomic_mutex:&atomic_mutex); | |||
| //#define EEL_ATOMIC_ENTER mutex->Enter() | |||
| //#define EEL_ATOMIC_LEAVE mutex->Leave() | |||
| static EEL_F NSEEL_CGEN_CALL atomic_setifeq(void *opaque, EEL_F *a, EEL_F *cmp, EEL_F *nd) | |||
| { | |||
| EEL_F ret; | |||
| EEL_ATOMIC_SET_SCOPE(opaque) | |||
| EEL_ATOMIC_ENTER; | |||
| ret = *a; | |||
| if (fabs(ret - *cmp) < NSEEL_CLOSEFACTOR) *a = *nd; | |||
| EEL_ATOMIC_LEAVE; | |||
| return ret; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL atomic_exch(void *opaque, EEL_F *a, EEL_F *b) | |||
| { | |||
| EEL_F tmp; | |||
| EEL_ATOMIC_SET_SCOPE(opaque) | |||
| EEL_ATOMIC_ENTER; | |||
| tmp = *b; | |||
| *b = *a; | |||
| *a = tmp; | |||
| EEL_ATOMIC_LEAVE; | |||
| return tmp; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL atomic_add(void *opaque, EEL_F *a, EEL_F *b) | |||
| { | |||
| EEL_F tmp; | |||
| EEL_ATOMIC_SET_SCOPE(opaque) | |||
| EEL_ATOMIC_ENTER; | |||
| tmp = (*a += *b); | |||
| EEL_ATOMIC_LEAVE; | |||
| return tmp; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL atomic_set(void *opaque, EEL_F *a, EEL_F *b) | |||
| { | |||
| EEL_F tmp; | |||
| EEL_ATOMIC_SET_SCOPE(opaque) | |||
| EEL_ATOMIC_ENTER; | |||
| tmp = *a = *b; | |||
| EEL_ATOMIC_LEAVE; | |||
| return tmp; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL atomic_get(void *opaque, EEL_F *a) | |||
| { | |||
| EEL_F tmp; | |||
| EEL_ATOMIC_SET_SCOPE(opaque) | |||
| EEL_ATOMIC_ENTER; | |||
| tmp = *a; | |||
| EEL_ATOMIC_LEAVE; | |||
| return tmp; | |||
| } | |||
| static void EEL_atomic_register() | |||
| { | |||
| NSEEL_addfunc_retval("atomic_setifequal",3, NSEEL_PProc_THIS, &atomic_setifeq); | |||
| NSEEL_addfunc_retval("atomic_exch",2, NSEEL_PProc_THIS, &atomic_exch); | |||
| NSEEL_addfunc_retval("atomic_add",2, NSEEL_PProc_THIS, &atomic_add); | |||
| NSEEL_addfunc_retval("atomic_set",2, NSEEL_PProc_THIS, &atomic_set); | |||
| NSEEL_addfunc_retval("atomic_get",1, NSEEL_PProc_THIS, &atomic_get); | |||
| } | |||
| #endif | |||
| @@ -0,0 +1,81 @@ | |||
| #ifndef _EEL_EVAL_H_ | |||
| #define _EEL_EVAL_H_ | |||
| #ifndef EEL_EVAL_GET_CACHED | |||
| #define EEL_EVAL_GET_CACHED(str, ch) (NULL) | |||
| #endif | |||
| #ifndef EEL_EVAL_SET_CACHED | |||
| #define EEL_EVAL_SET_CACHED(sv, ch) { NSEEL_code_free(ch); free(sv); } | |||
| #endif | |||
| #ifndef EEL_EVAL_SCOPE_ENTER | |||
| #define EEL_EVAL_SCOPE_ENTER 1 | |||
| #define EEL_EVAL_SCOPE_LEAVE | |||
| #endif | |||
| static EEL_F NSEEL_CGEN_CALL _eel_eval(void *opaque, EEL_F *s) | |||
| { | |||
| NSEEL_VMCTX r = EEL_EVAL_GET_VMCTX(opaque); | |||
| NSEEL_CODEHANDLE ch = NULL; | |||
| char *sv=NULL; | |||
| if (r) | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| const char *str=EEL_STRING_GET_FOR_INDEX(*s,NULL); | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| if (!str) | |||
| { | |||
| EEL_STRING_DEBUGOUT("eval() passed invalid string handle %f",*s); | |||
| } | |||
| #endif | |||
| if (str && *str) | |||
| { | |||
| sv=EEL_EVAL_GET_CACHED(str,ch); | |||
| if (!sv) sv=strdup(str); | |||
| } | |||
| } | |||
| if (sv) | |||
| { | |||
| if (!ch) ch = NSEEL_code_compile(r,sv,0); | |||
| if (ch) | |||
| { | |||
| if (EEL_EVAL_SCOPE_ENTER) | |||
| { | |||
| NSEEL_code_execute(ch); | |||
| EEL_EVAL_SCOPE_LEAVE | |||
| } | |||
| else | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("eval() reentrancy limit reached"); | |||
| #endif | |||
| } | |||
| EEL_EVAL_SET_CACHED(sv,ch); | |||
| return 1.0; | |||
| } | |||
| else | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| const char *err=NSEEL_code_getcodeerror(r); | |||
| if (err) EEL_STRING_DEBUGOUT("eval() error: %s",err); | |||
| #endif | |||
| } | |||
| free(sv); | |||
| } | |||
| return 0.0; | |||
| } | |||
| void EEL_eval_register() | |||
| { | |||
| NSEEL_addfunc_retval("eval",1,NSEEL_PProc_THIS,&_eel_eval); | |||
| } | |||
| #ifdef EEL_WANT_DOCUMENTATION | |||
| static const char *eel_eval_function_reference = | |||
| "eval\t\"code\"\tExecutes code passed in. Code can use functions, but functions created in code can't be used elsewhere.\0" | |||
| ; | |||
| #endif | |||
| #endif | |||
| @@ -0,0 +1,396 @@ | |||
| #ifndef __EEL_FFT_H_ | |||
| #define __EEL_FFT_H_ | |||
| #include "../fft.h" | |||
| #if WDL_FFT_REALSIZE != EEL_F_SIZE | |||
| #error WDL_FFT_REALSIZE -- EEL_F_SIZE size mismatch | |||
| #endif | |||
| #ifndef EEL_FFT_MINBITLEN | |||
| #define EEL_FFT_MINBITLEN 4 | |||
| #endif | |||
| #ifndef EEL_FFT_MAXBITLEN | |||
| #define EEL_FFT_MAXBITLEN 15 | |||
| #endif | |||
| #ifndef EEL_FFT_MINBITLEN_REORDER | |||
| #define EEL_FFT_MINBITLEN_REORDER (EEL_FFT_MINBITLEN-1) | |||
| #endif | |||
| //#define EEL_SUPER_FAST_FFT_REORDERING // quite a bit faster (50-100%) than "normal", but uses a 256kb lookup | |||
| //#define EEL_SLOW_FFT_REORDERING // 20%-80% slower than normal, alloca() use, no reason to ever use this | |||
| #ifdef EEL_SUPER_FAST_FFT_REORDERING | |||
| static int *fft_reorder_table_for_bitsize(int bitsz) | |||
| { | |||
| static int s_tab[ (2 << EEL_FFT_MAXBITLEN) + 24*(EEL_FFT_MAXBITLEN-EEL_FFT_MINBITLEN_REORDER+1) ]; // big 256kb table, ugh | |||
| if (bitsz<=EEL_FFT_MINBITLEN_REORDER) return s_tab; | |||
| return s_tab + (1<<bitsz) + (bitsz-EEL_FFT_MINBITLEN_REORDER) * 24; | |||
| } | |||
| static void fft_make_reorder_table(int bitsz, int *tab) | |||
| { | |||
| const int fft_sz=1<<bitsz; | |||
| char flag[1<<EEL_FFT_MAXBITLEN]; | |||
| int x; | |||
| int *tabstart = tab; | |||
| memset(flag,0,fft_sz); | |||
| for (x=0;x<fft_sz;x++) | |||
| { | |||
| int fx; | |||
| if (!flag[x] && (fx=WDL_fft_permute(fft_sz,x))!=x) | |||
| { | |||
| flag[x]=1; | |||
| *tab++ = x; | |||
| do | |||
| { | |||
| flag[fx]=1; | |||
| *tab++ = fx; | |||
| fx = WDL_fft_permute(fft_sz, fx); | |||
| } | |||
| while (fx != x); | |||
| *tab++ = 0; // delimit a run | |||
| } | |||
| else flag[x]=1; | |||
| } | |||
| *tab++ = 0; // doublenull terminated | |||
| } | |||
| static void fft_reorder_buffer(int bitsz, WDL_FFT_COMPLEX *data, int fwd) | |||
| { | |||
| const int *tab=fft_reorder_table_for_bitsize(bitsz); | |||
| if (!fwd) | |||
| { | |||
| while (*tab) | |||
| { | |||
| const int sidx=*tab++; | |||
| WDL_FFT_COMPLEX a=data[sidx]; | |||
| for (;;) | |||
| { | |||
| WDL_FFT_COMPLEX ta; | |||
| const int idx=*tab++; | |||
| if (!idx) break; | |||
| ta=data[idx]; | |||
| data[idx]=a; | |||
| a=ta; | |||
| } | |||
| data[sidx] = a; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| while (*tab) | |||
| { | |||
| const int sidx=*tab++; | |||
| int lidx = sidx; | |||
| const WDL_FFT_COMPLEX sta=data[lidx]; | |||
| for (;;) | |||
| { | |||
| const int idx=*tab++; | |||
| if (!idx) break; | |||
| data[lidx]=data[idx]; | |||
| lidx=idx; | |||
| } | |||
| data[lidx] = sta; | |||
| } | |||
| } | |||
| return 1; | |||
| } | |||
| #else | |||
| #ifndef EEL_SLOW_FFT_REORDERING | |||
| // moderate speed mode, minus the big 256k table | |||
| static void fft_reorder_buffer(int bitsz, WDL_FFT_COMPLEX *data, int fwd) | |||
| { | |||
| // this is a good compromise, quite a bit faster than out of place reordering, but no separate 256kb lookup required | |||
| /* | |||
| these generated via: | |||
| static void fft_make_reorder_table(int bitsz) | |||
| { | |||
| int fft_sz=1<<bitsz,x; | |||
| char flag[65536]={0,}; | |||
| printf("static const int tab%d[]={ ",fft_sz); | |||
| for (x=0;x<fft_sz;x++) | |||
| { | |||
| int fx; | |||
| if (!flag[x] && (fx=WDL_fft_permute(fft_sz,x))!=x) | |||
| { | |||
| printf("%d, ",x); | |||
| do { flag[fx]=1; fx = WDL_fft_permute(fft_sz, fx); } while (fx != x); | |||
| } | |||
| flag[x]=1; | |||
| } | |||
| printf(" 0 };\n"); | |||
| } | |||
| */ | |||
| static const int tab4_8_32[]={ 1, 0 }; | |||
| static const int tab16[]={ 1, 3, 0 }; | |||
| static const int tab64[]={ 1, 3, 9, 0 }; | |||
| static const int tab128[]={ 1, 3, 4, 9, 14, 0 }; | |||
| static const int tab256[]={ 1, 3, 6, 12, 13, 14, 19, 0 }; | |||
| static const int tab512[]={ 1, 4, 7, 9, 18, 50, 115, 0 }; | |||
| static const int tab1024[]={ 1, 3, 4, 25, 26, 77, 79, 0 }; | |||
| static const int tab2048[]={ 1, 58, 59, 106, 135, 206, 210, 212, 0 }; | |||
| static const int tab4096[]={ 1, 3, 12, 25, 54, 221, 313, 431, 453, 0 }; | |||
| static const int tab8192[]={ 1, 12, 18, 26, 30, 100, 101, 106, 113, 144, 150, 237, 244, 247, 386, 468, 513, 1210, 4839, 0 }; | |||
| static const int tab16384[]={ 1, 3, 6, 24, 1219, 0 }; | |||
| static const int tab32768[]={ 1, 3, 4, 7, 13, 18, 31, 64, 113, 145, 203, 246, 594, 956, 1871, 2439, 4959, 19175, 0 }; | |||
| const int *tab; | |||
| switch (bitsz) | |||
| { | |||
| case 1: return; // no reorder necessary | |||
| case 2: | |||
| case 3: | |||
| case 5: tab = tab4_8_32; break; | |||
| case 4: tab=tab16; break; | |||
| case 6: tab=tab64; break; | |||
| case 7: tab=tab128; break; | |||
| case 8: tab=tab256; break; | |||
| case 9: tab=tab512; break; | |||
| case 10: tab=tab1024; break; | |||
| case 11: tab=tab2048; break; | |||
| case 12: tab=tab4096; break; | |||
| case 13: tab=tab8192; break; | |||
| case 14: tab=tab16384; break; | |||
| case 15: tab=tab32768; break; | |||
| default: return; // no reorder possible | |||
| } | |||
| const int fft_sz=1<<bitsz; | |||
| const int *tb2 = WDL_fft_permute_tab(fft_sz); | |||
| if (!tb2) return; // ugh | |||
| if (!fwd) | |||
| { | |||
| while (*tab) | |||
| { | |||
| const int sidx=*tab++; | |||
| WDL_FFT_COMPLEX a=data[sidx]; | |||
| int idx=sidx; | |||
| for (;;) | |||
| { | |||
| WDL_FFT_COMPLEX ta; | |||
| idx=tb2[idx]; | |||
| if (idx==sidx) break; | |||
| ta=data[idx]; | |||
| data[idx]=a; | |||
| a=ta; | |||
| } | |||
| data[sidx] = a; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| while (*tab) | |||
| { | |||
| const int sidx=*tab++; | |||
| int lidx = sidx; | |||
| const WDL_FFT_COMPLEX sta=data[lidx]; | |||
| for (;;) | |||
| { | |||
| const int idx=tb2[lidx]; | |||
| if (idx==sidx) break; | |||
| data[lidx]=data[idx]; | |||
| lidx=idx; | |||
| } | |||
| data[lidx] = sta; | |||
| } | |||
| } | |||
| } | |||
| #endif // not fast ,not slow, just right | |||
| #endif | |||
| //#define TIMING | |||
| //#include "../timing.h" | |||
| // 0=fw, 1=iv, 2=fwreal, 3=ireal, 4=permutec, 6=permuter | |||
| // low bit: is inverse | |||
| // second bit: was isreal, but no longer used | |||
| // third bit: is permute | |||
| static void FFT(int sizebits, EEL_F *data, int dir) | |||
| { | |||
| if (dir >= 4 && dir < 8) | |||
| { | |||
| if (dir == 4 || dir == 5) | |||
| { | |||
| //timingEnter(0); | |||
| #if defined(EEL_SUPER_FAST_FFT_REORDERING) || !defined(EEL_SLOW_FFT_REORDERING) | |||
| fft_reorder_buffer(sizebits,(WDL_FFT_COMPLEX*)data,dir==4); | |||
| #else | |||
| // old blech | |||
| const int flen=1<<sizebits; | |||
| int x; | |||
| EEL_F *tmp=(EEL_F*)alloca(sizeof(EEL_F)*flen*2); | |||
| const int flen2=flen+flen; | |||
| // reorder entries, now | |||
| memcpy(tmp,data,sizeof(EEL_F)*flen*2); | |||
| if (dir == 4) | |||
| { | |||
| for (x = 0; x < flen2; x += 2) | |||
| { | |||
| int y=WDL_fft_permute(flen,x/2)*2; | |||
| data[x]=tmp[y]; | |||
| data[x+1]=tmp[y+1]; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for (x = 0; x < flen2; x += 2) | |||
| { | |||
| int y=WDL_fft_permute(flen,x/2)*2; | |||
| data[y]=tmp[x]; | |||
| data[y+1]=tmp[x+1]; | |||
| } | |||
| } | |||
| #endif | |||
| //timingLeave(0); | |||
| } | |||
| } | |||
| else if (dir >= 0 && dir < 2) | |||
| { | |||
| WDL_fft((WDL_FFT_COMPLEX*)data,1<<sizebits,dir&1); | |||
| } | |||
| else if (dir >= 2 && dir < 4) | |||
| { | |||
| WDL_real_fft((WDL_FFT_REAL*)data,1<<sizebits,dir&1); | |||
| } | |||
| } | |||
| static EEL_F * fft_func(int dir, EEL_F **blocks, EEL_F *start, EEL_F *length) | |||
| { | |||
| const int offs = (int)(*start + 0.0001); | |||
| const int itemSizeShift=(dir&2)?0:1; | |||
| int l=(int)(*length + 0.0001); | |||
| int bitl=0; | |||
| int ilen; | |||
| EEL_F *ptr; | |||
| while (l>1 && bitl < EEL_FFT_MAXBITLEN) | |||
| { | |||
| bitl++; | |||
| l>>=1; | |||
| } | |||
| if (bitl < ((dir&4) ? EEL_FFT_MINBITLEN_REORDER : EEL_FFT_MINBITLEN)) // smallest FFT is 16 item, smallest reorder is 8 item | |||
| { | |||
| return start; | |||
| } | |||
| ilen=1<<bitl; | |||
| // check to make sure we don't cross a boundary | |||
| if (offs/NSEEL_RAM_ITEMSPERBLOCK != (offs + (ilen<<itemSizeShift) - 1)/NSEEL_RAM_ITEMSPERBLOCK) | |||
| { | |||
| return start; | |||
| } | |||
| ptr=__NSEEL_RAMAlloc(blocks,offs); | |||
| if (!ptr || ptr==&nseel_ramalloc_onfail) | |||
| { | |||
| return start; | |||
| } | |||
| FFT(bitl,ptr,dir); | |||
| return start; | |||
| } | |||
| static EEL_F * NSEEL_CGEN_CALL eel_fft(EEL_F **blocks, EEL_F *start, EEL_F *length) | |||
| { | |||
| return fft_func(0,blocks,start,length); | |||
| } | |||
| static EEL_F * NSEEL_CGEN_CALL eel_ifft(EEL_F **blocks, EEL_F *start, EEL_F *length) | |||
| { | |||
| return fft_func(1,blocks,start,length); | |||
| } | |||
| static EEL_F * NSEEL_CGEN_CALL eel_fft_real(EEL_F **blocks, EEL_F *start, EEL_F *length) | |||
| { | |||
| return fft_func(2,blocks,start,length); | |||
| } | |||
| static EEL_F * NSEEL_CGEN_CALL eel_ifft_real(EEL_F **blocks, EEL_F *start, EEL_F *length) | |||
| { | |||
| return fft_func(3,blocks,start,length); | |||
| } | |||
| static EEL_F * NSEEL_CGEN_CALL eel_fft_permute(EEL_F **blocks, EEL_F *start, EEL_F *length) | |||
| { | |||
| return fft_func(4,blocks,start,length); | |||
| } | |||
| static EEL_F * NSEEL_CGEN_CALL eel_ifft_permute(EEL_F **blocks, EEL_F *start, EEL_F *length) | |||
| { | |||
| return fft_func(5,blocks,start,length); | |||
| } | |||
| static EEL_F * NSEEL_CGEN_CALL eel_convolve_c(EEL_F **blocks,EEL_F *dest, EEL_F *src, EEL_F *lenptr) | |||
| { | |||
| const int dest_offs = (int)(*dest + 0.0001); | |||
| const int src_offs = (int)(*src + 0.0001); | |||
| const int len = ((int)(*lenptr + 0.0001)) * 2; | |||
| EEL_F *srcptr,*destptr; | |||
| if (len < 1 || len > NSEEL_RAM_ITEMSPERBLOCK || dest_offs < 0 || src_offs < 0 || | |||
| dest_offs >= NSEEL_RAM_BLOCKS*NSEEL_RAM_ITEMSPERBLOCK || src_offs >= NSEEL_RAM_BLOCKS*NSEEL_RAM_ITEMSPERBLOCK) return dest; | |||
| if ((dest_offs&(NSEEL_RAM_ITEMSPERBLOCK-1)) + len > NSEEL_RAM_ITEMSPERBLOCK) return dest; | |||
| if ((src_offs&(NSEEL_RAM_ITEMSPERBLOCK-1)) + len > NSEEL_RAM_ITEMSPERBLOCK) return dest; | |||
| srcptr = __NSEEL_RAMAlloc(blocks,src_offs); | |||
| if (!srcptr || srcptr==&nseel_ramalloc_onfail) return dest; | |||
| destptr = __NSEEL_RAMAlloc(blocks,dest_offs); | |||
| if (!destptr || destptr==&nseel_ramalloc_onfail) return dest; | |||
| WDL_fft_complexmul((WDL_FFT_COMPLEX*)destptr,(WDL_FFT_COMPLEX*)srcptr,(len/2)&~1); | |||
| return dest; | |||
| } | |||
| void EEL_fft_register() | |||
| { | |||
| WDL_fft_init(); | |||
| #if defined(EEL_SUPER_FAST_FFT_REORDERING) | |||
| if (!fft_reorder_table_for_bitsize(EEL_FFT_MINBITLEN_REORDER)[0]) | |||
| { | |||
| int x; | |||
| for (x=EEL_FFT_MINBITLEN_REORDER;x<=EEL_FFT_MAXBITLEN;x++) fft_make_reorder_table(x,fft_reorder_table_for_bitsize(x)); | |||
| } | |||
| #endif | |||
| NSEEL_addfunc_retptr("convolve_c",3,NSEEL_PProc_RAM,&eel_convolve_c); | |||
| NSEEL_addfunc_retptr("fft",2,NSEEL_PProc_RAM,&eel_fft); | |||
| NSEEL_addfunc_retptr("ifft",2,NSEEL_PProc_RAM,&eel_ifft); | |||
| NSEEL_addfunc_retptr("fft_real",2,NSEEL_PProc_RAM,&eel_fft_real); | |||
| NSEEL_addfunc_retptr("ifft_real",2,NSEEL_PProc_RAM,&eel_ifft_real); | |||
| NSEEL_addfunc_retptr("fft_permute",2,NSEEL_PProc_RAM,&eel_fft_permute); | |||
| NSEEL_addfunc_retptr("fft_ipermute",2,NSEEL_PProc_RAM,&eel_ifft_permute); | |||
| } | |||
| #ifdef EEL_WANT_DOCUMENTATION | |||
| static const char *eel_fft_function_reference = | |||
| "convolve_c\tdest,src,size\tMultiplies each of size complex pairs in dest by the complex pairs in src. Often used for convolution.\0" | |||
| "fft\tbuffer,size\tPerforms a FFT on the data in the local memory buffer at the offset specified by the first parameter. The size of the FFT is specified " | |||
| "by the second parameter, which must be 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, or 32768. The outputs are permuted, so if " | |||
| "you plan to use them in-order, call fft_permute(buffer, size) before and fft_ipermute(buffer,size) after your in-order use. Your inputs or " | |||
| "outputs will need to be scaled down by 1/size, if used.\n" | |||
| "Note that fft()/ifft() require real / imaginary input pairs, so a 256 point FFT actually works with 512 items.\n" | |||
| "Note that fft()/ifft() must NOT cross a 65,536 item boundary, so be sure to specify the offset accordingly.\0" | |||
| "ifft\tbuffer,size\tPerform an inverse FFT. For more information see fft().\0" | |||
| "fft_real\tbuffer,size\tPerforms an FFT, but takes size input samples and produces size/2 complex output pairs. Usually used along with fft_permute(size/2). Inputs/outputs will need to be scaled by 0.5/size.\0" | |||
| "ifft_real\tbuffer,size\tPerforms an inverse FFT, but takes size/2 complex input pairs and produces size real output values. Usually used along with fft_ipermute(size/2).\0" | |||
| "fft_permute\tbuffer,size\tPermute the output of fft() to have bands in-order. See fft() for more information.\0" | |||
| "fft_ipermute\tbuffer,size\tPermute the input for ifft(), taking bands from in-order to the order ifft() requires. See fft() for more information.\0" | |||
| ; | |||
| #endif | |||
| #endif | |||
| @@ -0,0 +1,262 @@ | |||
| #ifndef __EEL_FILES_H__ | |||
| #define __EEL_FILES_H__ | |||
| // should include eel_strings.h before this, probably | |||
| //#define EEL_FILE_OPEN(fn,mode) ((instance)opaque)->OpenFile(fn,mode) | |||
| //#define EEL_FILE_GETFP(fp) ((instance)opaque)->GetFileFP(fp) | |||
| //#define EEL_FILE_CLOSE(fpindex) ((instance)opaque)->CloseFile(fpindex) | |||
| static EEL_F NSEEL_CGEN_CALL _eel_fopen(void *opaque, EEL_F *fn_index, EEL_F *mode_index) | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| const char *fn = EEL_STRING_GET_FOR_INDEX(*fn_index,NULL); | |||
| const char *mode = EEL_STRING_GET_FOR_INDEX(*mode_index,NULL); | |||
| if (!fn || !mode) return 0; | |||
| return (EEL_F) EEL_FILE_OPEN(fn,mode); | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_fclose(void *opaque, EEL_F *fpp) | |||
| { | |||
| EEL_F ret=EEL_FILE_CLOSE((int)*fpp); | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| if (ret < 0) EEL_STRING_DEBUGOUT("fclose(): file handle %f not valid",*fpp); | |||
| #endif | |||
| return ret; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_fgetc(void *opaque, EEL_F *fpp) | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| FILE *fp = EEL_FILE_GETFP((int)*fpp); | |||
| if (fp) return (EEL_F)fgetc(fp); | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("fgetc(): file handle %f not valid",*fpp); | |||
| #endif | |||
| return -1.0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_ftell(void *opaque, EEL_F *fpp) | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| FILE *fp = EEL_FILE_GETFP((int)*fpp); | |||
| if (fp) return (EEL_F)ftell(fp); | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("ftell(): file handle %f not valid",*fpp); | |||
| #endif | |||
| return -1.0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_fflush(void *opaque, EEL_F *fpp) | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| FILE *fp = EEL_FILE_GETFP((int)*fpp); | |||
| if (fp) { fflush(fp); return 0.0; } | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("fflush(): file handle %f not valid",*fpp); | |||
| #endif | |||
| return -1.0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_feof(void *opaque, EEL_F *fpp) | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| FILE *fp = EEL_FILE_GETFP((int)*fpp); | |||
| if (fp) return feof(fp) ? 1.0 : 0.0; | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("feof(): file handle %f not valid",*fpp); | |||
| #endif | |||
| return -1.0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_fseek(void *opaque, EEL_F *fpp, EEL_F *offset, EEL_F *wh) | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| FILE *fp = EEL_FILE_GETFP((int)*fpp); | |||
| if (fp) return fseek(fp, (int) *offset, *wh<0 ? SEEK_SET : *wh > 0 ? SEEK_END : SEEK_CUR); | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("fseek(): file handle %f not valid",*fpp); | |||
| #endif | |||
| return -1.0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_fgets(void *opaque, EEL_F *fpp, EEL_F *strOut) | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| EEL_STRING_STORAGECLASS *wr=NULL; | |||
| EEL_STRING_GET_FOR_WRITE(*strOut, &wr); | |||
| FILE *fp = EEL_FILE_GETFP((int)*fpp); | |||
| if (!fp) | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("fgets(): file handle %f not valid",*fpp); | |||
| #endif | |||
| if (wr) wr->Set(""); | |||
| return 0.0; | |||
| } | |||
| char buf[16384]; | |||
| buf[0]=0; | |||
| fgets(buf,sizeof(buf),fp); | |||
| if (wr) | |||
| { | |||
| wr->Set(buf); | |||
| return (EEL_F)wr->GetLength(); | |||
| } | |||
| else | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("fgets: bad destination specifier passed %f, throwing away %d bytes",*strOut, (int)strlen(buf)); | |||
| #endif | |||
| return (int)strlen(buf); | |||
| } | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_fread(void *opaque, EEL_F *fpp, EEL_F *strOut, EEL_F *flen) | |||
| { | |||
| int use_len = (int) *flen; | |||
| if (use_len < 1) return 0.0; | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| EEL_STRING_STORAGECLASS *wr=NULL; | |||
| EEL_STRING_GET_FOR_WRITE(*strOut, &wr); | |||
| if (!wr) | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("fread: bad destination specifier passed %f, not reading %d bytes",*strOut, use_len); | |||
| #endif | |||
| return -1; | |||
| } | |||
| FILE *fp = EEL_FILE_GETFP((int)*fpp); | |||
| if (!fp) | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("fread(): file handle %f not valid",*fpp); | |||
| #endif | |||
| if (wr) wr->Set(""); | |||
| return 0.0; | |||
| } | |||
| wr->SetLen(use_len); | |||
| if (wr->GetLength() != use_len) | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("fread: error allocating storage for read of %d bytes", use_len); | |||
| #endif | |||
| return -1.0; | |||
| } | |||
| use_len = (int)fread((char *)wr->Get(),1,use_len,fp); | |||
| wr->SetLen(use_len > 0 ? use_len : 0, true); | |||
| return (EEL_F) use_len; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_fwrite(void *opaque, EEL_F *fpp, EEL_F *strOut, EEL_F *flen) | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| int use_len = (int) *flen; | |||
| EEL_STRING_STORAGECLASS *wr=NULL; | |||
| const char *str=EEL_STRING_GET_FOR_INDEX(*strOut, &wr); | |||
| if (!wr && !str) | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("fwrite: bad source specifier passed %f, not writing %d bytes",*strOut, use_len); | |||
| #endif | |||
| return -1.0; | |||
| } | |||
| if (!wr) | |||
| { | |||
| const int ssl = (int)strlen(str); | |||
| if (use_len < 1 || use_len > ssl) use_len = ssl; | |||
| } | |||
| else | |||
| { | |||
| if (use_len < 1 || use_len > wr->GetLength()) use_len = wr->GetLength(); | |||
| } | |||
| if (use_len < 1) return 0.0; | |||
| FILE *fp = EEL_FILE_GETFP((int)*fpp); | |||
| if (!fp) | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("fwrite(): file handle %f not valid",*fpp); | |||
| #endif | |||
| return 0.0; | |||
| } | |||
| return (EEL_F) fwrite(str,1,use_len,fp); | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_fprintf(void *opaque, INT_PTR nparam, EEL_F **parm) | |||
| { | |||
| if (opaque && nparam > 1) | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| FILE *fp = EEL_FILE_GETFP((int)*(parm[0])); | |||
| if (!fp) | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("fprintf(): file handle %f not valid",parm[0][0]); | |||
| #endif | |||
| return 0.0; | |||
| } | |||
| EEL_STRING_STORAGECLASS *wr_src=NULL; | |||
| const char *fmt = EEL_STRING_GET_FOR_INDEX(*(parm[1]),&wr_src); | |||
| if (fmt) | |||
| { | |||
| char buf[16384]; | |||
| const int len = eel_format_strings(opaque,fmt,wr_src?(fmt+wr_src->GetLength()) : NULL, buf,(int)sizeof(buf),(int)nparam-2,parm+2); | |||
| if (len >= 0) | |||
| { | |||
| return (EEL_F) fwrite(buf,1,len,fp); | |||
| } | |||
| else | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("fprintf: bad format string %s",fmt); | |||
| #endif | |||
| } | |||
| } | |||
| else | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("fprintf: bad format specifier passed %f",*(parm[1])); | |||
| #endif | |||
| } | |||
| } | |||
| return 0.0; | |||
| } | |||
| void EEL_file_register() | |||
| { | |||
| NSEEL_addfunc_retval("fopen",2,NSEEL_PProc_THIS,&_eel_fopen); | |||
| NSEEL_addfunc_retval("fread",3,NSEEL_PProc_THIS,&_eel_fread); | |||
| NSEEL_addfunc_retval("fgets",2,NSEEL_PProc_THIS,&_eel_fgets); | |||
| NSEEL_addfunc_retval("fgetc",1,NSEEL_PProc_THIS,&_eel_fgetc); | |||
| NSEEL_addfunc_retval("fwrite",3,NSEEL_PProc_THIS,&_eel_fwrite); | |||
| NSEEL_addfunc_varparm("fprintf",2,NSEEL_PProc_THIS,&_eel_fprintf); | |||
| NSEEL_addfunc_retval("fseek",3,NSEEL_PProc_THIS,&_eel_fseek); | |||
| NSEEL_addfunc_retval("ftell",1,NSEEL_PProc_THIS,&_eel_ftell); | |||
| NSEEL_addfunc_retval("feof",1,NSEEL_PProc_THIS,&_eel_feof); | |||
| NSEEL_addfunc_retval("fflush",1,NSEEL_PProc_THIS,&_eel_fflush); | |||
| NSEEL_addfunc_retval("fclose",1,NSEEL_PProc_THIS,&_eel_fclose); | |||
| } | |||
| #ifdef EEL_WANT_DOCUMENTATION | |||
| static const char *eel_file_function_reference = | |||
| "fopen\t\"fn\",\"mode\"\tOpens a file \"fn\" with mode \"mode\". For read, use \"r\" or \"rb\", write \"w\" or \"wb\". Returns a positive integer on success.\0" | |||
| "fclose\tfp\tCloses a file previously opened with fopen().\0" | |||
| "fread\tfp,#str,length\tReads from file fp into #str, up to length bytes. Returns actual length read, or negative if error.\0" | |||
| "fgets\tfp,#str\tReads a line from file fp into #str. Returns length of #str read.\0" | |||
| "fgetc\tfp\tReads a character from file fp, returns -1 if EOF.\0" | |||
| "fwrite\tfp,#str,len\tWrites up to len characters of #str to file fp. If len is less than 1, the full contents of #str will be written. Returns the number of bytes written to file.\0" | |||
| "fprintf\tfp,\"format\"[,...]\tFormats a string and writes it to file fp. For more information on format specifiers, see sprintf(). Returns bytes written to file.\0" | |||
| "fseek\tfp,offset,whence\tSeeks file fp, offset bytes from whence reference. Whence negative specifies start of file, positive whence specifies end of file, and zero whence specifies current file position.\0" | |||
| "ftell\tfp\tRetunrs the current file position.\0" | |||
| "feof\tfp\tReturns nonzero if the file fp is at the end of file.\0" | |||
| "fflush\tfp\tIf file fp is open for writing, flushes out any buffered data to disk.\0" | |||
| ; | |||
| #endif | |||
| #endif | |||
| @@ -0,0 +1,104 @@ | |||
| /******************************************************************************************* | |||
| * imported EEL | |||
| ******************************************************************************************/ | |||
| void (*NSEEL_addfunc_ret_type)(const char *name, int np, int ret_type, NSEEL_PPPROC pproc, void *fptr, eel_function_table *destination); // ret_type=-1 for bool, 1 for value, 0 for ptr | |||
| void (*NSEEL_addfunc_varparm_ex)(const char *name, int min_np, int want_exact, NSEEL_PPPROC pproc, EEL_F (NSEEL_CGEN_CALL *fptr)(void *, INT_PTR, EEL_F **), eel_function_table *destination); | |||
| NSEEL_VMCTX (*NSEEL_VM_alloc)(); // return a handle | |||
| void (*NSEEL_VM_SetGRAM)(NSEEL_VMCTX, void **); | |||
| void (*NSEEL_VM_free)(NSEEL_VMCTX ctx); // free when done with a VM and ALL of its code have been freed, as well | |||
| void (*NSEEL_VM_SetFunctionTable)(NSEEL_VMCTX, eel_function_table *tab); // use NULL to use default (global) table | |||
| EEL_F *(*NSEEL_VM_regvar)(NSEEL_VMCTX ctx, const char *name); // register a variable (before compilation) | |||
| void (*NSEEL_VM_SetCustomFuncThis)(NSEEL_VMCTX ctx, void *thisptr); | |||
| NSEEL_CODEHANDLE (*NSEEL_code_compile_ex)(NSEEL_VMCTX ctx, const char *code, int lineoffs, int flags); | |||
| void (*NSEEL_VM_set_var_resolver)(NSEEL_VMCTX ctx, EEL_F *(*res)(void *userctx, const char *name), void *userctx); | |||
| char *(*NSEEL_code_getcodeerror)(NSEEL_VMCTX ctx); | |||
| void (*NSEEL_code_execute)(NSEEL_CODEHANDLE code); | |||
| void (*NSEEL_code_free)(NSEEL_CODEHANDLE code); | |||
| EEL_F *(*nseel_int_register_var)(compileContext *ctx, const char *name, int isReg, const char **namePtrOut); | |||
| void (*NSEEL_VM_enumallvars)(NSEEL_VMCTX ctx, int (*func)(const char *name, EEL_F *val, void *ctx), void *userctx); | |||
| EEL_F *(*NSEEL_VM_getramptr)(NSEEL_VMCTX ctx, unsigned int offs, int *validAmt); | |||
| void ** (*eel_gmem_attach)(const char *nm, bool is_alloc); | |||
| void (*eel_fft_register)(eel_function_table*); | |||
| struct eelStringSegmentRec { | |||
| struct eelStringSegmentRec *_next; | |||
| const char *str_start; // escaped characters, including opening/trailing characters | |||
| int str_len; | |||
| }; | |||
| void (*NSEEL_VM_SetStringFunc)(NSEEL_VMCTX ctx, | |||
| EEL_F (*onString)(void *caller_this, struct eelStringSegmentRec *list), | |||
| EEL_F (*onNamedString)(void *caller_this, const char *name)); | |||
| // call with NULL to calculate size, or non-null to generate to buffer (returning size used -- will not null terminate, caller responsibility) | |||
| int (*nseel_stringsegments_tobuf)(char *bufOut, int bufout_sz, struct eelStringSegmentRec *list); | |||
| void *(*NSEEL_PProc_RAM)(void *data, int data_size, struct _compileContext *ctx); | |||
| void *(*NSEEL_PProc_THIS)(void *data, int data_size, struct _compileContext *ctx); | |||
| void (*eel_enterfp)(int s[2]); | |||
| void (*eel_leavefp)(int s[2]); | |||
| eel_function_table g_eel_function_table; | |||
| #define NSEEL_ADDFUNC_DESTINATION (&g_eel_function_table) | |||
| // | |||
| // adds a function that returns a value (EEL_F) | |||
| #define NSEEL_addfunc_retval(name,np,pproc,fptr) \ | |||
| NSEEL_addfunc_ret_type(name,np,1,pproc,(void *)(fptr),NSEEL_ADDFUNC_DESTINATION) | |||
| // adds a function that returns a pointer (EEL_F*) | |||
| #define NSEEL_addfunc_retptr(name,np,pproc,fptr) \ | |||
| NSEEL_addfunc_ret_type(name,np,0,pproc,(void *)(fptr),NSEEL_ADDFUNC_DESTINATION) | |||
| // adds a void or bool function | |||
| #define NSEEL_addfunc_retbool(name,np,pproc,fptr) \ | |||
| NSEEL_addfunc_ret_type(name,np,-1,pproc,(void *)(fptr),NSEEL_ADDFUNC_DESTINATION) | |||
| // adds a function that takes min_np or more parameters (func sig needs to be EEL_F func(void *ctx, INT_PTR np, EEL_F **parms) | |||
| #define NSEEL_addfunc_varparm(name, min_np, pproc, fptr) \ | |||
| NSEEL_addfunc_varparm_ex(name,min_np,0,pproc,fptr,NSEEL_ADDFUNC_DESTINATION) | |||
| // adds a function that takes np parameters via func: sig needs to be EEL_F func(void *ctx, INT_PTR np, EEL_F **parms) | |||
| #define NSEEL_addfunc_exparms(name, np, pproc, fptr) \ | |||
| NSEEL_addfunc_varparm_ex(name,np,1,pproc,fptr,NSEEL_ADDFUNC_DESTINATION) | |||
| class eel_string_context_state; | |||
| #define __NS_EELINT_H__ | |||
| #define EEL_IMPORT_ALL(IMPORT_FUNC) \ | |||
| IMPORT_FUNC(NSEEL_addfunc_ret_type) \ | |||
| IMPORT_FUNC(NSEEL_addfunc_varparm_ex) \ | |||
| IMPORT_FUNC(NSEEL_VM_free) \ | |||
| IMPORT_FUNC(NSEEL_VM_SetFunctionTable) \ | |||
| IMPORT_FUNC(NSEEL_VM_regvar) \ | |||
| IMPORT_FUNC(NSEEL_VM_SetCustomFuncThis) \ | |||
| IMPORT_FUNC(NSEEL_code_compile_ex) \ | |||
| IMPORT_FUNC(NSEEL_code_getcodeerror) \ | |||
| IMPORT_FUNC(NSEEL_code_execute) \ | |||
| IMPORT_FUNC(NSEEL_code_free) \ | |||
| IMPORT_FUNC(NSEEL_PProc_THIS) \ | |||
| IMPORT_FUNC(NSEEL_PProc_RAM) \ | |||
| IMPORT_FUNC(NSEEL_VM_SetStringFunc) \ | |||
| IMPORT_FUNC(NSEEL_VM_enumallvars) \ | |||
| IMPORT_FUNC(NSEEL_VM_getramptr) \ | |||
| IMPORT_FUNC(NSEEL_VM_SetGRAM) \ | |||
| IMPORT_FUNC(eel_gmem_attach) \ | |||
| IMPORT_FUNC(eel_fft_register) \ | |||
| IMPORT_FUNC(nseel_stringsegments_tobuf) \ | |||
| IMPORT_FUNC(nseel_int_register_var) \ | |||
| IMPORT_FUNC(eel_leavefp) \ | |||
| IMPORT_FUNC(eel_enterfp) \ | |||
| IMPORT_FUNC(NSEEL_VM_set_var_resolver) \ | |||
| IMPORT_FUNC(NSEEL_VM_alloc) /* keep NSEEL_VM_alloc last */ | |||
| /******************************************************************************************* | |||
| * END of imported EEL | |||
| ******************************************************************************************/ | |||
| @@ -0,0 +1,782 @@ | |||
| #ifndef _EEL_MDCT_H_ | |||
| #define _EEL_MDCT_H_ | |||
| #include "ns-eel-int.h" | |||
| #ifdef _WIN32 | |||
| #include <malloc.h> | |||
| #endif | |||
| #include <string.h> | |||
| #include <stdlib.h> | |||
| #include <math.h> | |||
| #define EEL_DCT_MINBITLEN 5 | |||
| #define EEL_DCT_MAXBITLEN 12 | |||
| #define PI 3.1415926535897932384626433832795 | |||
| typedef struct { | |||
| int n; | |||
| int log2n; | |||
| EEL_F *trig; | |||
| int *bitrev; | |||
| EEL_F scale; | |||
| EEL_F *window; | |||
| } mdct_lookup; | |||
| static void mdct(EEL_F *in, EEL_F *out, int len) | |||
| { | |||
| int k; | |||
| EEL_F pioverlen = PI * 0.5 / (EEL_F)len; | |||
| for (k = 0; k < len / 2; k ++) | |||
| { | |||
| int i; | |||
| EEL_F d = 0.0; | |||
| for (i = 0; i < len; i ++) | |||
| { | |||
| d += in[i] * cos(pioverlen * (2.0 * i + 1.0 + len * 0.5) * (2.0 * k + 1.0)); | |||
| } | |||
| out[k] = (EEL_F)d; | |||
| } | |||
| } | |||
| static void imdct(EEL_F *in, EEL_F *out, int len) | |||
| { | |||
| int k; | |||
| EEL_F fourovern = 4.0 / (EEL_F)len; | |||
| EEL_F pioverlen = PI * 0.5 / (EEL_F)len; | |||
| for (k = 0; k < len; k ++) | |||
| { | |||
| int i; | |||
| EEL_F d = 0.0; | |||
| for (i = 0; i < len / 2; i ++) | |||
| { | |||
| d += in[i] * cos(pioverlen * (2.0 * k + 1.0 + len * 0.5) * (2 * i + 1.0)); | |||
| } | |||
| out[k] = (EEL_F)(d * fourovern); | |||
| } | |||
| } | |||
| // MDCT/iMDCT borrowed from Vorbis, thanks xiph! | |||
| #define cPI3_8 .38268343236508977175 | |||
| #define cPI2_8 .70710678118654752441 | |||
| #define cPI1_8 .92387953251128675613 | |||
| #define FLOAT_CONV(x) ((EEL_F) ( x )) | |||
| #define MULT_NORM(x) (x) | |||
| #define HALVE(x) ((x)*.5f) | |||
| /* 8 point butterfly (in place, 4 register) */ | |||
| static void mdct_butterfly_8(EEL_F *x) { | |||
| EEL_F r0 = x[6] + x[2]; | |||
| EEL_F r1 = x[6] - x[2]; | |||
| EEL_F r2 = x[4] + x[0]; | |||
| EEL_F r3 = x[4] - x[0]; | |||
| x[6] = r0 + r2; | |||
| x[4] = r0 - r2; | |||
| r0 = x[5] - x[1]; | |||
| r2 = x[7] - x[3]; | |||
| x[0] = r1 + r0; | |||
| x[2] = r1 - r0; | |||
| r0 = x[5] + x[1]; | |||
| r1 = x[7] + x[3]; | |||
| x[3] = r2 + r3; | |||
| x[1] = r2 - r3; | |||
| x[7] = r1 + r0; | |||
| x[5] = r1 - r0; | |||
| } | |||
| /* 16 point butterfly (in place, 4 register) */ | |||
| static void mdct_butterfly_16(EEL_F *x) { | |||
| EEL_F r0 = x[1] - x[9]; | |||
| EEL_F r1 = x[0] - x[8]; | |||
| x[8] += x[0]; | |||
| x[9] += x[1]; | |||
| x[0] = MULT_NORM((r0 + r1) * cPI2_8); | |||
| x[1] = MULT_NORM((r0 - r1) * cPI2_8); | |||
| r0 = x[3] - x[11]; | |||
| r1 = x[10] - x[2]; | |||
| x[10] += x[2]; | |||
| x[11] += x[3]; | |||
| x[2] = r0; | |||
| x[3] = r1; | |||
| r0 = x[12] - x[4]; | |||
| r1 = x[13] - x[5]; | |||
| x[12] += x[4]; | |||
| x[13] += x[5]; | |||
| x[4] = MULT_NORM((r0 - r1) * cPI2_8); | |||
| x[5] = MULT_NORM((r0 + r1) * cPI2_8); | |||
| r0 = x[14] - x[6]; | |||
| r1 = x[15] - x[7]; | |||
| x[14] += x[6]; | |||
| x[15] += x[7]; | |||
| x[6] = r0; | |||
| x[7] = r1; | |||
| mdct_butterfly_8(x); | |||
| mdct_butterfly_8(x + 8); | |||
| } | |||
| /* 32 point butterfly (in place, 4 register) */ | |||
| static void mdct_butterfly_32(EEL_F *x) { | |||
| EEL_F r0 = x[30] - x[14]; | |||
| EEL_F r1 = x[31] - x[15]; | |||
| x[30] += x[14]; | |||
| x[31] += x[15]; | |||
| x[14] = r0; | |||
| x[15] = r1; | |||
| r0 = x[28] - x[12]; | |||
| r1 = x[29] - x[13]; | |||
| x[28] += x[12]; | |||
| x[29] += x[13]; | |||
| x[12] = MULT_NORM( r0 * cPI1_8 - r1 * cPI3_8 ); | |||
| x[13] = MULT_NORM( r0 * cPI3_8 + r1 * cPI1_8 ); | |||
| r0 = x[26] - x[10]; | |||
| r1 = x[27] - x[11]; | |||
| x[26] += x[10]; | |||
| x[27] += x[11]; | |||
| x[10] = MULT_NORM(( r0 - r1 ) * cPI2_8); | |||
| x[11] = MULT_NORM(( r0 + r1 ) * cPI2_8); | |||
| r0 = x[24] - x[8]; | |||
| r1 = x[25] - x[9]; | |||
| x[24] += x[8]; | |||
| x[25] += x[9]; | |||
| x[8] = MULT_NORM( r0 * cPI3_8 - r1 * cPI1_8 ); | |||
| x[9] = MULT_NORM( r1 * cPI3_8 + r0 * cPI1_8 ); | |||
| r0 = x[22] - x[6]; | |||
| r1 = x[7] - x[23]; | |||
| x[22] += x[6]; | |||
| x[23] += x[7]; | |||
| x[6] = r1; | |||
| x[7] = r0; | |||
| r0 = x[4] - x[20]; | |||
| r1 = x[5] - x[21]; | |||
| x[20] += x[4]; | |||
| x[21] += x[5]; | |||
| x[4] = MULT_NORM( r1 * cPI1_8 + r0 * cPI3_8 ); | |||
| x[5] = MULT_NORM( r1 * cPI3_8 - r0 * cPI1_8 ); | |||
| r0 = x[2] - x[18]; | |||
| r1 = x[3] - x[19]; | |||
| x[18] += x[2]; | |||
| x[19] += x[3]; | |||
| x[2] = MULT_NORM(( r1 + r0 ) * cPI2_8); | |||
| x[3] = MULT_NORM(( r1 - r0 ) * cPI2_8); | |||
| r0 = x[0] - x[16]; | |||
| r1 = x[1] - x[17]; | |||
| x[16] += x[0]; | |||
| x[17] += x[1]; | |||
| x[0] = MULT_NORM( r1 * cPI3_8 + r0 * cPI1_8 ); | |||
| x[1] = MULT_NORM( r1 * cPI1_8 - r0 * cPI3_8 ); | |||
| mdct_butterfly_16(x); | |||
| mdct_butterfly_16(x + 16); | |||
| } | |||
| /* N point first stage butterfly (in place, 2 register) */ | |||
| static void mdct_butterfly_first(EEL_F *T, | |||
| EEL_F *x, | |||
| int points) { | |||
| EEL_F *x1 = x + points - 8; | |||
| EEL_F *x2 = x + (points >> 1) - 8; | |||
| EEL_F r0; | |||
| EEL_F r1; | |||
| do { | |||
| r0 = x1[6] - x2[6]; | |||
| r1 = x1[7] - x2[7]; | |||
| x1[6] += x2[6]; | |||
| x1[7] += x2[7]; | |||
| x2[6] = MULT_NORM(r1 * T[1] + r0 * T[0]); | |||
| x2[7] = MULT_NORM(r1 * T[0] - r0 * T[1]); | |||
| r0 = x1[4] - x2[4]; | |||
| r1 = x1[5] - x2[5]; | |||
| x1[4] += x2[4]; | |||
| x1[5] += x2[5]; | |||
| x2[4] = MULT_NORM(r1 * T[5] + r0 * T[4]); | |||
| x2[5] = MULT_NORM(r1 * T[4] - r0 * T[5]); | |||
| r0 = x1[2] - x2[2]; | |||
| r1 = x1[3] - x2[3]; | |||
| x1[2] += x2[2]; | |||
| x1[3] += x2[3]; | |||
| x2[2] = MULT_NORM(r1 * T[9] + r0 * T[8]); | |||
| x2[3] = MULT_NORM(r1 * T[8] - r0 * T[9]); | |||
| r0 = x1[0] - x2[0]; | |||
| r1 = x1[1] - x2[1]; | |||
| x1[0] += x2[0]; | |||
| x1[1] += x2[1]; | |||
| x2[0] = MULT_NORM(r1 * T[13] + r0 * T[12]); | |||
| x2[1] = MULT_NORM(r1 * T[12] - r0 * T[13]); | |||
| x1 -= 8; | |||
| x2 -= 8; | |||
| T += 16; | |||
| } while(x2 >= x); | |||
| } | |||
| /* N/stage point generic N stage butterfly (in place, 2 register) */ | |||
| static void mdct_butterfly_generic(EEL_F *T, | |||
| EEL_F *x, | |||
| int points, | |||
| int trigint) { | |||
| EEL_F *x1 = x + points - 8; | |||
| EEL_F *x2 = x + (points >> 1) - 8; | |||
| EEL_F r0; | |||
| EEL_F r1; | |||
| do { | |||
| r0 = x1[6] - x2[6]; | |||
| r1 = x1[7] - x2[7]; | |||
| x1[6] += x2[6]; | |||
| x1[7] += x2[7]; | |||
| x2[6] = MULT_NORM(r1 * T[1] + r0 * T[0]); | |||
| x2[7] = MULT_NORM(r1 * T[0] - r0 * T[1]); | |||
| T += trigint; | |||
| r0 = x1[4] - x2[4]; | |||
| r1 = x1[5] - x2[5]; | |||
| x1[4] += x2[4]; | |||
| x1[5] += x2[5]; | |||
| x2[4] = MULT_NORM(r1 * T[1] + r0 * T[0]); | |||
| x2[5] = MULT_NORM(r1 * T[0] - r0 * T[1]); | |||
| T += trigint; | |||
| r0 = x1[2] - x2[2]; | |||
| r1 = x1[3] - x2[3]; | |||
| x1[2] += x2[2]; | |||
| x1[3] += x2[3]; | |||
| x2[2] = MULT_NORM(r1 * T[1] + r0 * T[0]); | |||
| x2[3] = MULT_NORM(r1 * T[0] - r0 * T[1]); | |||
| T += trigint; | |||
| r0 = x1[0] - x2[0]; | |||
| r1 = x1[1] - x2[1]; | |||
| x1[0] += x2[0]; | |||
| x1[1] += x2[1]; | |||
| x2[0] = MULT_NORM(r1 * T[1] + r0 * T[0]); | |||
| x2[1] = MULT_NORM(r1 * T[0] - r0 * T[1]); | |||
| T += trigint; | |||
| x1 -= 8; | |||
| x2 -= 8; | |||
| } while(x2 >= x); | |||
| } | |||
| static void mdct_butterflies(mdct_lookup *init, | |||
| EEL_F *x, | |||
| int points) { | |||
| EEL_F *T = init->trig; | |||
| int stages = init->log2n - 5; | |||
| int i, j; | |||
| if(--stages > 0) { | |||
| mdct_butterfly_first(T, x, points); | |||
| } | |||
| for(i = 1; --stages > 0; i++) { | |||
| for(j = 0; j < (1 << i); j++) | |||
| mdct_butterfly_generic(T, x + (points >> i)*j, points >> i, 4 << i); | |||
| } | |||
| for(j = 0; j < points; j += 32) | |||
| mdct_butterfly_32(x + j); | |||
| } | |||
| static void mdct_bitreverse(mdct_lookup *init, | |||
| EEL_F *x) { | |||
| int n = init->n; | |||
| int *bit = init->bitrev; | |||
| EEL_F *w0 = x; | |||
| EEL_F *w1 = x = w0 + (n >> 1); | |||
| EEL_F *T = init->trig + n; | |||
| do { | |||
| EEL_F *x0 = x + bit[0]; | |||
| EEL_F *x1 = x + bit[1]; | |||
| EEL_F r0 = x0[1] - x1[1]; | |||
| EEL_F r1 = x0[0] + x1[0]; | |||
| EEL_F r2 = MULT_NORM(r1 * T[0] + r0 * T[1]); | |||
| EEL_F r3 = MULT_NORM(r1 * T[1] - r0 * T[0]); | |||
| w1 -= 4; | |||
| r0 = HALVE(x0[1] + x1[1]); | |||
| r1 = HALVE(x0[0] - x1[0]); | |||
| w0[0] = r0 + r2; | |||
| w1[2] = r0 - r2; | |||
| w0[1] = r1 + r3; | |||
| w1[3] = r3 - r1; | |||
| x0 = x + bit[2]; | |||
| x1 = x + bit[3]; | |||
| r0 = x0[1] - x1[1]; | |||
| r1 = x0[0] + x1[0]; | |||
| r2 = MULT_NORM(r1 * T[2] + r0 * T[3]); | |||
| r3 = MULT_NORM(r1 * T[3] - r0 * T[2]); | |||
| r0 = HALVE(x0[1] + x1[1]); | |||
| r1 = HALVE(x0[0] - x1[0]); | |||
| w0[2] = r0 + r2; | |||
| w1[0] = r0 - r2; | |||
| w0[3] = r1 + r3; | |||
| w1[1] = r3 - r1; | |||
| T += 4; | |||
| bit += 4; | |||
| w0 += 4; | |||
| } while(w0 < w1); | |||
| } | |||
| static void megabuf_mdct_apply_window(void *init, EEL_F *inbuf, EEL_F *outbuf) | |||
| { | |||
| mdct_lookup *p = (mdct_lookup *)init; | |||
| EEL_F *w; | |||
| int cnt; | |||
| if (!p) return; | |||
| w = p->window; | |||
| if (!w) return; | |||
| cnt = p->n / 2; | |||
| while (cnt--) *outbuf++ = *inbuf++ * *w++; | |||
| cnt = p->n / 2; | |||
| while (cnt--) *outbuf++ = *inbuf++ * *--w; | |||
| } | |||
| static void *megabuf_mdct_init(int n) { | |||
| mdct_lookup *lookup = (mdct_lookup *)calloc(sizeof(mdct_lookup), 1); | |||
| int i; | |||
| EEL_F c = (PI / (EEL_F) n); | |||
| int *bitrev; | |||
| EEL_F *T; | |||
| int n2, log2n; | |||
| if (!lookup) return 0; | |||
| lookup->n = n; | |||
| lookup->window = (EEL_F *)calloc(sizeof(EEL_F), n / 2); | |||
| if (!lookup->window) return lookup; | |||
| for (i = 0; i < n / 2; i ++) | |||
| { | |||
| lookup->window[i] = sin(c * (i + 0.5)); | |||
| } | |||
| if (n <= 32) return lookup; | |||
| bitrev = (int*)calloc(sizeof(int), (n / 4)); | |||
| lookup->bitrev = bitrev; | |||
| if (!bitrev) return lookup; | |||
| T = (EEL_F*)calloc(sizeof(EEL_F), (n + n / 4)); | |||
| lookup->trig = T; | |||
| if (!T) return lookup; | |||
| n2 = n >> 1; | |||
| log2n = lookup->log2n = (int)(log((double)n) / log(2.0) + 0.5); | |||
| /* trig lookups... */ | |||
| for(i = 0; i < n / 4; i++) { | |||
| T[i * 2] = FLOAT_CONV(cos((PI / n) * (4 * i))); | |||
| T[i * 2 + 1] = FLOAT_CONV(-sin((PI / n) * (4 * i))); | |||
| T[n2 + i * 2] = FLOAT_CONV(cos((PI / (2 * n)) * (2 * i + 1))); | |||
| T[n2 + i * 2 + 1] = FLOAT_CONV(sin((PI / (2 * n)) * (2 * i + 1))); | |||
| } | |||
| for(i = 0; i < n / 8; i++) { | |||
| T[n + i * 2] = FLOAT_CONV(cos((PI / n) * (4 * i + 2)) * .5); | |||
| T[n + i * 2 + 1] = FLOAT_CONV(-sin((PI / n) * (4 * i + 2)) * .5); | |||
| } | |||
| /* bitreverse lookup... */ | |||
| { | |||
| int mask = (1 << (log2n - 1)) - 1, j; | |||
| int msb = 1 << (log2n - 2); | |||
| for(i = 0; i < n / 8; i++) { | |||
| int acc = 0; | |||
| for(j = 0; msb >> j; j++) | |||
| if((msb >> j)&i)acc |= 1 << j; | |||
| bitrev[i * 2] = ((~acc)&mask) - 1; | |||
| bitrev[i * 2 + 1] = acc; | |||
| } | |||
| } | |||
| lookup->scale = FLOAT_CONV(4.f / n); | |||
| return lookup; | |||
| } | |||
| static void megabuf_mdct_backward(void *init, EEL_F *in, EEL_F *out) { | |||
| mdct_lookup *lookup = (mdct_lookup *)init; | |||
| int n, n2, n4; | |||
| EEL_F *iX, *oX, *T; | |||
| if (!lookup) return; | |||
| n = lookup->n; | |||
| if (n <= 32 || !lookup->bitrev || !lookup->trig) | |||
| { | |||
| imdct(in, out, n); | |||
| return; | |||
| } | |||
| n2 = n >> 1; | |||
| n4 = n >> 2; | |||
| /* rotate */ | |||
| iX = in + n2 - 7; | |||
| oX = out + n2 + n4; | |||
| T = lookup->trig + n4; | |||
| do { | |||
| oX -= 4; | |||
| oX[0] = MULT_NORM(-iX[2] * T[3] - iX[0] * T[2]); | |||
| oX[1] = MULT_NORM (iX[0] * T[3] - iX[2] * T[2]); | |||
| oX[2] = MULT_NORM(-iX[6] * T[1] - iX[4] * T[0]); | |||
| oX[3] = MULT_NORM (iX[4] * T[1] - iX[6] * T[0]); | |||
| iX -= 8; | |||
| T += 4; | |||
| } while(iX >= in); | |||
| iX = in + n2 - 8; | |||
| oX = out + n2 + n4; | |||
| T = lookup->trig + n4; | |||
| do { | |||
| T -= 4; | |||
| oX[0] = MULT_NORM (iX[4] * T[3] + iX[6] * T[2]); | |||
| oX[1] = MULT_NORM (iX[4] * T[2] - iX[6] * T[3]); | |||
| oX[2] = MULT_NORM (iX[0] * T[1] + iX[2] * T[0]); | |||
| oX[3] = MULT_NORM (iX[0] * T[0] - iX[2] * T[1]); | |||
| iX -= 8; | |||
| oX += 4; | |||
| } while(iX >= in); | |||
| mdct_butterflies(lookup, out + n2, n2); | |||
| mdct_bitreverse(lookup, out); | |||
| /* roatate + window */ | |||
| { | |||
| EEL_F *oX1 = out + n2 + n4; | |||
| EEL_F *oX2 = out + n2 + n4; | |||
| iX = out; | |||
| T = lookup->trig + n2; | |||
| do { | |||
| oX1 -= 4; | |||
| oX1[3] = MULT_NORM (iX[0] * T[1] - iX[1] * T[0]); | |||
| oX2[0] = -MULT_NORM (iX[0] * T[0] + iX[1] * T[1]); | |||
| oX1[2] = MULT_NORM (iX[2] * T[3] - iX[3] * T[2]); | |||
| oX2[1] = -MULT_NORM (iX[2] * T[2] + iX[3] * T[3]); | |||
| oX1[1] = MULT_NORM (iX[4] * T[5] - iX[5] * T[4]); | |||
| oX2[2] = -MULT_NORM (iX[4] * T[4] + iX[5] * T[5]); | |||
| oX1[0] = MULT_NORM (iX[6] * T[7] - iX[7] * T[6]); | |||
| oX2[3] = -MULT_NORM (iX[6] * T[6] + iX[7] * T[7]); | |||
| oX2 += 4; | |||
| iX += 8; | |||
| T += 8; | |||
| } while(iX < oX1); | |||
| iX = out + n2 + n4; | |||
| oX1 = out + n4; | |||
| oX2 = oX1; | |||
| do { | |||
| oX1 -= 4; | |||
| iX -= 4; | |||
| oX2[0] = -(oX1[3] = iX[3]); | |||
| oX2[1] = -(oX1[2] = iX[2]); | |||
| oX2[2] = -(oX1[1] = iX[1]); | |||
| oX2[3] = -(oX1[0] = iX[0]); | |||
| oX2 += 4; | |||
| } while(oX2 < iX); | |||
| iX = out + n2 + n4; | |||
| oX1 = out + n2 + n4; | |||
| oX2 = out + n2; | |||
| do { | |||
| oX1 -= 4; | |||
| oX1[0] = iX[3]; | |||
| oX1[1] = iX[2]; | |||
| oX1[2] = iX[1]; | |||
| oX1[3] = iX[0]; | |||
| iX += 4; | |||
| } while(oX1 > oX2); | |||
| } | |||
| } | |||
| static void megabuf_mdct_forward(void *init, EEL_F *in, EEL_F *out) { | |||
| mdct_lookup *lookup = (mdct_lookup *)init; | |||
| int n, n2, n4, n8; | |||
| EEL_F *w, *w2; | |||
| if (!lookup) return; | |||
| n = lookup->n; | |||
| if (n <= 32 || !lookup->bitrev || !lookup->trig) | |||
| { | |||
| mdct(in, out, n); | |||
| return; | |||
| } | |||
| n2 = n >> 1; | |||
| n4 = n >> 2; | |||
| n8 = n >> 3; | |||
| EEL_F oldw[1<<EEL_DCT_MAXBITLEN]; | |||
| w = oldw; | |||
| w2 = w + n2; | |||
| /* rotate */ | |||
| /* window + rotate + step 1 */ | |||
| { | |||
| EEL_F r0; | |||
| EEL_F r1; | |||
| EEL_F *x0 = in + n2 + n4; | |||
| EEL_F *x1 = x0 + 1; | |||
| EEL_F *T = lookup->trig + n2; | |||
| int i = 0; | |||
| for(i = 0; i < n8; i += 2) { | |||
| x0 -= 4; | |||
| T -= 2; | |||
| r0 = x0[2] + x1[0]; | |||
| r1 = x0[0] + x1[2]; | |||
| w2[i] = MULT_NORM(r1 * T[1] + r0 * T[0]); | |||
| w2[i + 1] = MULT_NORM(r1 * T[0] - r0 * T[1]); | |||
| x1 += 4; | |||
| } | |||
| x1 = in + 1; | |||
| for(; i < n2 - n8; i += 2) { | |||
| T -= 2; | |||
| x0 -= 4; | |||
| r0 = x0[2] - x1[0]; | |||
| r1 = x0[0] - x1[2]; | |||
| w2[i] = MULT_NORM(r1 * T[1] + r0 * T[0]); | |||
| w2[i + 1] = MULT_NORM(r1 * T[0] - r0 * T[1]); | |||
| x1 += 4; | |||
| } | |||
| x0 = in + n; | |||
| for(; i < n2; i += 2) { | |||
| T -= 2; | |||
| x0 -= 4; | |||
| r0 = -x0[2] - x1[0]; | |||
| r1 = -x0[0] - x1[2]; | |||
| w2[i] = MULT_NORM(r1 * T[1] + r0 * T[0]); | |||
| w2[i + 1] = MULT_NORM(r1 * T[0] - r0 * T[1]); | |||
| x1 += 4; | |||
| } | |||
| mdct_butterflies(lookup, w + n2, n2); | |||
| mdct_bitreverse(lookup, w); | |||
| /* roatate + window */ | |||
| T = lookup->trig + n2; | |||
| x0 = out + n2; | |||
| for(i = 0; i < n4; i++) { | |||
| x0--; | |||
| out[i] = MULT_NORM((w[0] * T[0] + w[1] * T[1]) * lookup->scale); | |||
| x0[0] = MULT_NORM((w[0] * T[1] - w[1] * T[0]) * lookup->scale); | |||
| w += 2; | |||
| T += 2; | |||
| } | |||
| } | |||
| } | |||
| #if 0 | |||
| static void dct(EEL_F *in, EEL_F *out, int len) | |||
| { | |||
| int k; | |||
| EEL_F wk = sqrt(2.0 / len); | |||
| EEL_F overtwolen = 0.5 / (EEL_F)len; | |||
| for (k = 0; k < len; k ++) | |||
| { | |||
| int n; | |||
| EEL_F d = 0.0; | |||
| for (n = 0; n < len; n ++) | |||
| { | |||
| int an = n + 1; | |||
| d += in[n] * cos(PI * (2.0 * n + 1.0) * (EEL_F)k * overtwolen); | |||
| } | |||
| if (!k) d /= sqrt(len); | |||
| else d *= wk; | |||
| out[k] = (EEL_F)d; | |||
| } | |||
| } | |||
| static void idct(EEL_F *in, EEL_F *out, int len) | |||
| { | |||
| int n; | |||
| EEL_F dd0 = 1.0 / sqrt(len); | |||
| EEL_F dd1 = sqrt(2.0 / len); | |||
| EEL_F overtwolen = 0.5 / len; | |||
| for (n = 0; n < len; n ++) | |||
| { | |||
| int k; | |||
| EEL_F d = 0.0; | |||
| for (k = 0; k < len; k ++) | |||
| { | |||
| EEL_F dd; | |||
| if (!k) dd = dd0 * in[k]; | |||
| else dd = dd1 * in[k]; | |||
| d += dd * cos(PI * (2.0 * n + 1.0) * k * overtwolen); | |||
| } | |||
| out[n] = (EEL_F)d; | |||
| } | |||
| } | |||
| #endif | |||
| // 0 is megabuf blocks | |||
| // 1 is need_free flag | |||
| static EEL_F * NSEEL_CGEN_CALL mdct_func(int dir, EEL_F **blocks, EEL_F *start, EEL_F *length) | |||
| { | |||
| int l = (int)(*length + 0.0001); | |||
| int offs = (int)(*start + 0.0001); | |||
| int bitl = 0; | |||
| int ilen; | |||
| int bidx; | |||
| EEL_F *ptr; | |||
| while (l > 1 && bitl < EEL_DCT_MAXBITLEN) | |||
| { | |||
| bitl++; | |||
| l >>= 1; | |||
| } | |||
| if (bitl < EEL_DCT_MINBITLEN) | |||
| { | |||
| return start; | |||
| } | |||
| ilen = 1 << bitl; | |||
| bidx = bitl - EEL_DCT_MINBITLEN; | |||
| // check to make sure we don't cross a boundary | |||
| if (offs / NSEEL_RAM_ITEMSPERBLOCK != (offs + ilen * 2 - 1) / NSEEL_RAM_ITEMSPERBLOCK) | |||
| { | |||
| return start; | |||
| } | |||
| ptr = __NSEEL_RAMAlloc(blocks, offs); | |||
| if (!ptr || ptr == &nseel_ramalloc_onfail) | |||
| { | |||
| return start; | |||
| } | |||
| if (ilen > 1) | |||
| { | |||
| static void *mdct_ctxs[1 + EEL_DCT_MAXBITLEN - EEL_DCT_MINBITLEN]; | |||
| if (!mdct_ctxs[bidx]) | |||
| { | |||
| NSEEL_HOSTSTUB_EnterMutex(); | |||
| if (!mdct_ctxs[bidx]) | |||
| mdct_ctxs[bidx] = megabuf_mdct_init(ilen); | |||
| NSEEL_HOSTSTUB_LeaveMutex(); | |||
| } | |||
| if (mdct_ctxs[bidx]) | |||
| { | |||
| EEL_F buf[1 << EEL_DCT_MAXBITLEN]; | |||
| if (dir < 0) | |||
| { | |||
| megabuf_mdct_backward(mdct_ctxs[bidx], ptr, buf); | |||
| megabuf_mdct_apply_window(mdct_ctxs[bidx], buf, ptr); | |||
| } | |||
| else | |||
| { | |||
| megabuf_mdct_apply_window(mdct_ctxs[bidx], ptr, buf); | |||
| megabuf_mdct_forward(mdct_ctxs[bidx], buf, ptr); | |||
| } | |||
| } | |||
| } | |||
| return start; | |||
| } | |||
| static EEL_F * NSEEL_CGEN_CALL megabuf_mdct(EEL_F **blocks, EEL_F *start, EEL_F *length) | |||
| { | |||
| return mdct_func(0, blocks, start, length); | |||
| } | |||
| static EEL_F * NSEEL_CGEN_CALL megabuf_imdct(EEL_F **blocks, EEL_F *start, EEL_F *length) | |||
| { | |||
| return mdct_func(-1, blocks, start, length); | |||
| } | |||
| void EEL_mdct_register() | |||
| { | |||
| NSEEL_addfunc_retptr("mdct", 2, NSEEL_PProc_RAM, &megabuf_mdct); | |||
| NSEEL_addfunc_retptr("imdct", 2, NSEEL_PProc_RAM, &megabuf_imdct); | |||
| } | |||
| #ifdef EEL_WANT_DOCUMENTATION | |||
| static const char *eel_mdct_function_reference = | |||
| "mdct\tbuffer,length\tPerforms a windowed modified DCT, taking length inputs and producing length/2 outputs. buffer must not cross a 65,536 item boundary, and length must be 64, 128, 256, 512, 2048 or 4096.\0" | |||
| "imdct\tbuffer,length\tPerforms a windowed inverse modified DCT, taking length/2 inputs and producing length outputs. buffer must not cross a 65,536 item boundary, and length must be 64, 128, 256, 512, 2048 or 4096.\0" | |||
| ; | |||
| #endif | |||
| #endif | |||
| @@ -0,0 +1,73 @@ | |||
| #ifndef _EEL_MISC_H_ | |||
| #define _EEL_MISC_H_ | |||
| #ifndef _WIN32 | |||
| #include <sys/time.h> | |||
| #endif | |||
| #include <time.h> | |||
| // some generic EEL functions for things like time | |||
| #ifndef EEL_MISC_NO_SLEEP | |||
| static EEL_F NSEEL_CGEN_CALL _eel_sleep(void *opaque, EEL_F *amt) | |||
| { | |||
| if (*amt >= 0.0) | |||
| { | |||
| #ifdef _WIN32 | |||
| if (*amt > 30000000.0) Sleep(30000000); | |||
| else Sleep((DWORD)(*amt+0.5)); | |||
| #else | |||
| if (*amt > 30000000.0) usleep(((useconds_t)30000000)*1000); | |||
| else usleep((useconds_t)(*amt*1000.0+0.5)); | |||
| #endif | |||
| } | |||
| return 0.0; | |||
| } | |||
| #endif | |||
| static EEL_F * NSEEL_CGEN_CALL _eel_time(void *opaque, EEL_F *v) | |||
| { | |||
| *v = (EEL_F) time(NULL); | |||
| return v; | |||
| } | |||
| static EEL_F * NSEEL_CGEN_CALL _eel_time_precise(void *opaque, EEL_F *v) | |||
| { | |||
| #ifdef _WIN32 | |||
| LARGE_INTEGER freq,now; | |||
| QueryPerformanceFrequency(&freq); | |||
| QueryPerformanceCounter(&now); | |||
| *v = (double)now.QuadPart / (double)freq.QuadPart; | |||
| // *v = (EEL_F)timeGetTime() * 0.001; | |||
| #else | |||
| struct timeval tm={0,}; | |||
| gettimeofday(&tm,NULL); | |||
| *v = tm.tv_sec + tm.tv_usec*0.000001; | |||
| #endif | |||
| return v; | |||
| } | |||
| void EEL_misc_register() | |||
| { | |||
| #ifndef EEL_MISC_NO_SLEEP | |||
| NSEEL_addfunc_retval("sleep",1,NSEEL_PProc_THIS,&_eel_sleep); | |||
| #endif | |||
| NSEEL_addfunc_retptr("time",1,NSEEL_PProc_THIS,&_eel_time); | |||
| NSEEL_addfunc_retptr("time_precise",1,NSEEL_PProc_THIS,&_eel_time_precise); | |||
| } | |||
| #ifdef EEL_WANT_DOCUMENTATION | |||
| static const char *eel_misc_function_reference = | |||
| #ifndef EEL_MISC_NO_SLEEP | |||
| "sleep\tms\tYields the CPU for the millisecond count specified, calling Sleep() on Windows or usleep() on other platforms.\0" | |||
| #endif | |||
| "time\t[&val]\tSets the parameter (or a temporary buffer if omitted) to the number of seconds since January 1, 1970, and returns a reference to that value. " | |||
| "The granularity of the value returned is 1 second.\0" | |||
| "time_precise\t[&val]\tSets the parameter (or a temporary buffer if omitted) to a system-local timestamp in seconds, and returns a reference to that value. " | |||
| "The granularity of the value returned is system defined (but generally significantly smaller than one second).\0" | |||
| ; | |||
| #endif | |||
| #endif | |||
| @@ -0,0 +1,564 @@ | |||
| #ifndef _EEL_NET_H_ | |||
| #define _EEL_NET_H_ | |||
| // x = tcp_listen(port[,interface, connected_ip_out]) poll this, returns connection id > 0, or <0 on error, or 0 if no new connect -- interface only valid on first call (or after tcp_listen_end(port)) | |||
| // tcp_listen_end(port); | |||
| // connection = tcp_connect(host, port[, block]) // connection id > 0 on ok | |||
| // tcp_set_block(connection, block?) | |||
| // tcp_close(connection) | |||
| // tcp_send(connection, string[, length]) // can return 0 if block, -1 if error, otherwise returns length sent | |||
| // tcp_recv(connection, string[, maxlength]) // 0 on nothing, -1 on error, otherwise returns length recv'd | |||
| // need: | |||
| // #define EEL_NET_GET_CONTEXT(opaque) (((sInst *)opaque)->m_net_state) | |||
| // you must pass a JNL_AsyncDNS object to eel_net_state to support nonblocking connect with DNS resolution, otherwise DNS will block | |||
| // #define EEL_NET_NO_SYNC_DNS -- never ever call gethostbyname() synchronously, may disable DNS for blocking connect, or if a JNL_IAsyncDNS is not provided. | |||
| #ifndef EEL_NET_MAXSEND | |||
| #define EEL_NET_MAXSEND (EEL_STRING_MAXUSERSTRING_LENGTH_HINT+4096) | |||
| #endif | |||
| #include "../jnetlib/netinc.h" | |||
| #define JNL_NO_IMPLEMENTATION | |||
| #include "../jnetlib/asyncdns.h" | |||
| class eel_net_state | |||
| { | |||
| public: | |||
| enum { STATE_FREE=0, STATE_RESOLVING, STATE_CONNECTED, STATE_ERR }; | |||
| enum { CONNECTION_ID_BASE=0x110000 }; | |||
| eel_net_state(int max_con, JNL_IAsyncDNS *dns); | |||
| ~eel_net_state(); | |||
| struct connection_state { | |||
| char *hostname; // set during resolve only | |||
| SOCKET sock; | |||
| int state; // STATE_RESOLVING... | |||
| int port; | |||
| bool blockmode; | |||
| }; | |||
| WDL_TypedBuf<connection_state> m_cons; | |||
| WDL_IntKeyedArray<SOCKET> m_listens; | |||
| JNL_IAsyncDNS *m_dns; | |||
| EEL_F onConnect(char *hostNameOwned, int port, int block); | |||
| EEL_F onClose(void *opaque, EEL_F handle); | |||
| EEL_F set_block(void *opaque, EEL_F handle, bool block); | |||
| EEL_F onListen(void *opaque, EEL_F handle, int mode, EEL_F *ifStr, EEL_F *ipOut); | |||
| int __run_connect(connection_state *cs, unsigned int ip); | |||
| int __run(connection_state *cs); | |||
| int do_send(void *opaque, EEL_F h, const char *src, int len); | |||
| int do_recv(void *opaque, EEL_F h, char *buf, int maxlen); | |||
| #ifdef _WIN32 | |||
| bool m_had_socketlib_init; | |||
| #endif | |||
| }; | |||
| eel_net_state::eel_net_state(int max_con, JNL_IAsyncDNS *dns) | |||
| { | |||
| #ifdef _WIN32 | |||
| m_had_socketlib_init=false; | |||
| #endif | |||
| m_cons.Resize(max_con); | |||
| int x; | |||
| for (x=0;x<m_cons.GetSize();x++) | |||
| { | |||
| m_cons.Get()[x].state = STATE_FREE; | |||
| m_cons.Get()[x].sock = INVALID_SOCKET; | |||
| m_cons.Get()[x].hostname = NULL; | |||
| } | |||
| m_dns=dns; | |||
| } | |||
| eel_net_state::~eel_net_state() | |||
| { | |||
| int x; | |||
| for (x=0;x<m_cons.GetSize();x++) | |||
| { | |||
| SOCKET s=m_cons.Get()[x].sock; | |||
| if (s != INVALID_SOCKET) | |||
| { | |||
| shutdown(s,SHUT_RDWR); | |||
| closesocket(s); | |||
| } | |||
| free(m_cons.Get()[x].hostname); | |||
| } | |||
| for (x=0;x<m_listens.GetSize();x++) | |||
| { | |||
| SOCKET s=m_listens.Enumerate(x); | |||
| shutdown(s, SHUT_RDWR); | |||
| closesocket(s); | |||
| } | |||
| } | |||
| EEL_F eel_net_state::onConnect(char *hostNameOwned, int port, int block) | |||
| { | |||
| int x; | |||
| #ifdef _WIN32 | |||
| if (!m_had_socketlib_init) | |||
| { | |||
| m_had_socketlib_init=1; | |||
| WSADATA wsaData; | |||
| WSAStartup(MAKEWORD(1, 1), &wsaData); | |||
| } | |||
| #endif | |||
| for(x=0;x<m_cons.GetSize();x++) | |||
| { | |||
| connection_state *s=m_cons.Get()+x; | |||
| if (s->state == STATE_FREE) | |||
| { | |||
| unsigned int ip=inet_addr(hostNameOwned); | |||
| if (m_dns && ip == INADDR_NONE && !block) | |||
| { | |||
| const int r=m_dns->resolve(hostNameOwned,&ip); | |||
| if (r<0) break; // error! | |||
| if (r>0) ip = INADDR_NONE; | |||
| } | |||
| #ifndef EEL_NET_NO_SYNC_DNS | |||
| else if (ip == INADDR_NONE) | |||
| { | |||
| struct hostent *he = gethostbyname(hostNameOwned); | |||
| if (he) ip = *(int *)he->h_addr; | |||
| } | |||
| #endif | |||
| if (hostNameOwned || ip != INADDR_NONE) | |||
| { | |||
| if (ip != INADDR_NONE) | |||
| { | |||
| free(hostNameOwned); | |||
| hostNameOwned=NULL; | |||
| } | |||
| s->state = STATE_RESOLVING; | |||
| s->hostname = hostNameOwned; | |||
| s->blockmode = !!block; | |||
| s->port = port; | |||
| if (hostNameOwned || __run_connect(s,ip)) return x + CONNECTION_ID_BASE; | |||
| s->state=STATE_FREE; | |||
| s->hostname=NULL; | |||
| } | |||
| break; | |||
| } | |||
| } | |||
| free(hostNameOwned); | |||
| return -1; | |||
| } | |||
| EEL_F eel_net_state::onListen(void *opaque, EEL_F handle, int mode, EEL_F *ifStr, EEL_F *ipOut) | |||
| { | |||
| const int port = (int) handle; | |||
| if (port < 1 || port > 65535) | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("tcp_listen(%d): invalid port specified, will never succeed",port); | |||
| #endif | |||
| return 0.0; | |||
| } | |||
| #ifdef _WIN32 | |||
| if (!m_had_socketlib_init) | |||
| { | |||
| m_had_socketlib_init=1; | |||
| WSADATA wsaData; | |||
| WSAStartup(MAKEWORD(1, 1), &wsaData); | |||
| } | |||
| #endif | |||
| SOCKET *sockptr = m_listens.GetPtr(port); | |||
| if (mode<0) | |||
| { | |||
| if (!sockptr) return -1.0; | |||
| SOCKET ss=*sockptr; | |||
| m_listens.Delete(port); | |||
| if (ss != INVALID_SOCKET) | |||
| { | |||
| shutdown(ss, SHUT_RDWR); | |||
| closesocket(ss); | |||
| } | |||
| return 0.0; | |||
| } | |||
| if (!sockptr) | |||
| { | |||
| struct sockaddr_in sin; | |||
| memset((char *) &sin, 0,sizeof(sin)); | |||
| if (ifStr) | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| const char *fn = EEL_STRING_GET_FOR_INDEX(*ifStr,NULL); | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| if (!fn) EEL_STRING_DEBUGOUT("tcp_listen(%d): bad string identifier %f for second parameter (interface)",port,*ifStr); | |||
| #endif | |||
| if (fn && *fn) sin.sin_addr.s_addr=inet_addr(fn); | |||
| } | |||
| if (!sin.sin_addr.s_addr || sin.sin_addr.s_addr==INADDR_NONE) sin.sin_addr.s_addr = INADDR_ANY; | |||
| sin.sin_family = AF_INET; | |||
| sin.sin_port = htons( (short) port ); | |||
| SOCKET sock = socket(AF_INET,SOCK_STREAM,0); | |||
| if (sock != INVALID_SOCKET) | |||
| { | |||
| SET_SOCK_DEFAULTS(sock); | |||
| SET_SOCK_BLOCK(sock,0); | |||
| if (bind(sock,(struct sockaddr *)&sin,sizeof(sin)) || listen(sock,8)==-1) | |||
| { | |||
| shutdown(sock, SHUT_RDWR); | |||
| closesocket(sock); | |||
| sock=INVALID_SOCKET; | |||
| } | |||
| } | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| //if (sock == INVALID_SOCKET) EEL_STRING_DEBUGOUT("tcp_listen(%d): failed listening on port",port); | |||
| // we report -1 to the caller, no need to error message | |||
| #endif | |||
| m_listens.Insert(port,sock); | |||
| sockptr = m_listens.GetPtr(port); | |||
| } | |||
| if (!sockptr || *sockptr == INVALID_SOCKET) return -1; | |||
| struct sockaddr_in saddr; | |||
| socklen_t length = sizeof(struct sockaddr_in); | |||
| SOCKET newsock = accept(*sockptr, (struct sockaddr *) &saddr, &length); | |||
| if (newsock == INVALID_SOCKET) | |||
| { | |||
| return 0; // nothing to report here | |||
| } | |||
| SET_SOCK_DEFAULTS(newsock); | |||
| int x; | |||
| for(x=0;x<m_cons.GetSize();x++) | |||
| { | |||
| connection_state *cs=m_cons.Get()+x; | |||
| if (cs->state == STATE_FREE) | |||
| { | |||
| cs->state=STATE_CONNECTED; | |||
| free(cs->hostname); | |||
| cs->hostname=NULL; | |||
| cs->sock = newsock; | |||
| cs->blockmode=true; | |||
| cs->port=0; | |||
| if (ipOut) | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| WDL_FastString *ws=NULL; | |||
| EEL_STRING_GET_FOR_WRITE(*ipOut,&ws); | |||
| if (ws) | |||
| { | |||
| const unsigned int a = ntohl(saddr.sin_addr.s_addr); | |||
| ws->SetFormatted(128,"%d.%d.%d.%d",(a>>24)&0xff,(a>>16)&0xff,(a>>8)&0xff,a&0xff); | |||
| } | |||
| else | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("tcp_listen(%d): bad string identifier %f for third parameter (IP-out)",port,*ipOut); | |||
| #endif | |||
| } | |||
| } | |||
| return x + CONNECTION_ID_BASE; | |||
| } | |||
| } | |||
| shutdown(newsock, SHUT_RDWR); | |||
| closesocket(newsock); | |||
| return -1; | |||
| } | |||
| int eel_net_state::__run_connect(connection_state *cs, unsigned int ip) | |||
| { | |||
| SOCKET s=socket(AF_INET,SOCK_STREAM,0); | |||
| if (s == INVALID_SOCKET) return 0; | |||
| SET_SOCK_DEFAULTS(s); | |||
| if (!cs->blockmode) SET_SOCK_BLOCK(s,0); | |||
| struct sockaddr_in sa={0,}; | |||
| sa.sin_family=AF_INET; | |||
| sa.sin_addr.s_addr = ip; | |||
| sa.sin_port = htons(cs->port); | |||
| if (!connect(s,(struct sockaddr *)&sa,16) || (!cs->blockmode && JNL_ERRNO == JNL_EINPROGRESS)) | |||
| { | |||
| cs->state = STATE_CONNECTED; | |||
| cs->sock = s; | |||
| return 1; | |||
| } | |||
| shutdown(s, SHUT_RDWR); | |||
| closesocket(s); | |||
| return 0; | |||
| } | |||
| int eel_net_state::__run(connection_state *cs) | |||
| { | |||
| if (cs->sock != INVALID_SOCKET) return 0; | |||
| if (!cs->hostname) return -1; | |||
| unsigned int ip=INADDR_NONE; | |||
| const int r=m_dns ? m_dns->resolve(cs->hostname,&ip) : -1; | |||
| if (r>0) return 0; | |||
| free(cs->hostname); | |||
| cs->hostname=NULL; | |||
| if (r<0 || !__run_connect(cs,ip)) | |||
| { | |||
| cs->state = STATE_ERR; | |||
| return -1; | |||
| } | |||
| return 0; | |||
| } | |||
| int eel_net_state::do_recv(void *opaque, EEL_F h, char *buf, int maxlen) | |||
| { | |||
| const int idx=(int)h-CONNECTION_ID_BASE; | |||
| if (idx>=0 && idx<m_cons.GetSize()) | |||
| { | |||
| connection_state *s=m_cons.Get()+idx; | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| if (s->sock == INVALID_SOCKET && !s->hostname) | |||
| EEL_STRING_DEBUGOUT("tcp_recv: connection identifier %f is not open",h); | |||
| #endif | |||
| if (__run(s) || s->sock == INVALID_SOCKET) return s->state == STATE_ERR ? -1 : 0; | |||
| if (maxlen == 0) return 0; | |||
| const int rv=(int)recv(s->sock,buf,maxlen,0); | |||
| if (rv < 0 && !s->blockmode && (JNL_ERRNO == JNL_EWOULDBLOCK || JNL_ERRNO == JNL_ENOTCONN)) return 0; | |||
| if (!rv) return -1; // TCP, 0=connection terminated | |||
| return rv; | |||
| } | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("tcp_recv: connection identifier %f is out of range",h); | |||
| #endif | |||
| return -1; | |||
| } | |||
| int eel_net_state::do_send(void *opaque, EEL_F h, const char *src, int len) | |||
| { | |||
| const int idx=(int)h-CONNECTION_ID_BASE; | |||
| if (idx>=0 && idx<m_cons.GetSize()) | |||
| { | |||
| connection_state *s=m_cons.Get()+idx; | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| if (s->sock == INVALID_SOCKET && !s->hostname) | |||
| EEL_STRING_DEBUGOUT("tcp_send: connection identifier %f is not open",h); | |||
| #endif | |||
| if (__run(s) || s->sock == INVALID_SOCKET) return s->state == STATE_ERR ? -1 : 0; | |||
| const int rv=(int)send(s->sock,src,len,0); | |||
| if (rv < 0 && !s->blockmode && (JNL_ERRNO == JNL_EWOULDBLOCK || JNL_ERRNO == JNL_ENOTCONN)) return 0; | |||
| return rv; | |||
| } | |||
| else | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("tcp_send: connection identifier %f out of range",h); | |||
| #endif | |||
| } | |||
| return -1; | |||
| } | |||
| EEL_F eel_net_state::set_block(void *opaque, EEL_F handle, bool block) | |||
| { | |||
| int idx=(int)handle-CONNECTION_ID_BASE; | |||
| if (idx>=0 && idx<m_cons.GetSize()) | |||
| { | |||
| connection_state *s=m_cons.Get()+idx; | |||
| if (s->blockmode != block) | |||
| { | |||
| s->blockmode=block; | |||
| if (s->sock != INVALID_SOCKET) | |||
| { | |||
| SET_SOCK_BLOCK(s->sock,(block?1:0)); | |||
| } | |||
| else | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| if (!s->hostname) EEL_STRING_DEBUGOUT("tcp_set_block: connection identifier %f is not open",handle); | |||
| #endif | |||
| } | |||
| return 1; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("tcp_set_block: connection identifier %f out of range",handle); | |||
| #endif | |||
| } | |||
| return 0; | |||
| } | |||
| EEL_F eel_net_state::onClose(void *opaque, EEL_F handle) | |||
| { | |||
| int idx=(int)handle-CONNECTION_ID_BASE; | |||
| if (idx>=0 && idx<m_cons.GetSize()) | |||
| { | |||
| connection_state *s=m_cons.Get()+idx; | |||
| const bool hadhn = !!s->hostname; | |||
| free(s->hostname); | |||
| s->hostname = NULL; | |||
| s->state = STATE_ERR; | |||
| if (s->sock != INVALID_SOCKET) | |||
| { | |||
| shutdown(s->sock,SHUT_RDWR); | |||
| closesocket(s->sock); | |||
| s->sock = INVALID_SOCKET; | |||
| return 1.0; | |||
| } | |||
| else if (!hadhn) | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("tcp_close: connection identifier %f is not open",handle); | |||
| #endif | |||
| } | |||
| } | |||
| else | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("tcp_close: connection identifier %f is out of range",handle); | |||
| #endif | |||
| } | |||
| return 0.0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_tcp_connect(void *opaque, INT_PTR np, EEL_F **parms) | |||
| { | |||
| eel_net_state *ctx; | |||
| if (np > 1 && NULL != (ctx=EEL_NET_GET_CONTEXT(opaque))) | |||
| { | |||
| char *dest=NULL; | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| const char *fn = EEL_STRING_GET_FOR_INDEX(parms[0][0],NULL); | |||
| if (fn) dest=strdup(fn); | |||
| else | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("tcp_connect(): host string identifier %f invalid",parms[0][0]); | |||
| #endif | |||
| } | |||
| } | |||
| if (dest) return ctx->onConnect(dest, (int) (parms[1][0]+0.5), np < 3 || parms[2][0] >= 0.5); | |||
| } | |||
| return -1.0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_tcp_set_block(void *opaque, EEL_F *handle, EEL_F *bl) | |||
| { | |||
| eel_net_state *ctx; | |||
| if (NULL != (ctx=EEL_NET_GET_CONTEXT(opaque))) return ctx->set_block(opaque,*handle, *bl >= 0.5); | |||
| return 0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_tcp_close(void *opaque, EEL_F *handle) | |||
| { | |||
| eel_net_state *ctx; | |||
| if (NULL != (ctx=EEL_NET_GET_CONTEXT(opaque))) return ctx->onClose(opaque,*handle); | |||
| return 0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_tcp_recv(void *opaque, INT_PTR np, EEL_F **parms) | |||
| { | |||
| eel_net_state *ctx; | |||
| if (np > 1 && NULL != (ctx=EEL_NET_GET_CONTEXT(opaque))) | |||
| { | |||
| char buf[EEL_STRING_MAXUSERSTRING_LENGTH_HINT]; | |||
| int ml = np > 2 ? (int)parms[2][0] : 4096; | |||
| if (ml < 0 || ml > EEL_STRING_MAXUSERSTRING_LENGTH_HINT) ml = EEL_STRING_MAXUSERSTRING_LENGTH_HINT; | |||
| ml=ctx->do_recv(opaque,parms[0][0],buf,ml); | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| WDL_FastString *ws=NULL; | |||
| EEL_STRING_GET_FOR_WRITE(parms[1][0],&ws); | |||
| if (ws) | |||
| { | |||
| if (ml<=0) ws->Set(""); | |||
| else ws->SetRaw(buf,ml); | |||
| } | |||
| } | |||
| return ml; | |||
| } | |||
| return -1; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_tcp_send(void *opaque, INT_PTR np, EEL_F **parms) | |||
| { | |||
| eel_net_state *ctx; | |||
| if (np > 1 && NULL != (ctx=EEL_NET_GET_CONTEXT(opaque))) | |||
| { | |||
| char buf[EEL_NET_MAXSEND]; | |||
| int l; | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| WDL_FastString *ws=NULL; | |||
| const char *fn = EEL_STRING_GET_FOR_INDEX(parms[1][0],&ws); | |||
| l = ws ? ws->GetLength() : (int) strlen(fn); | |||
| if (np > 2) | |||
| { | |||
| int al=(int)parms[2][0]; | |||
| if (al<0) al=0; | |||
| if (al<l) l=al; | |||
| } | |||
| if (l > 0) memcpy(buf,fn,l); | |||
| } | |||
| if (l>0) return ctx->do_send(opaque,parms[0][0],buf,l); | |||
| return 0; | |||
| } | |||
| return -1; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_tcp_listen(void *opaque, INT_PTR np, EEL_F **parms) | |||
| { | |||
| eel_net_state *ctx; | |||
| if (NULL != (ctx=EEL_NET_GET_CONTEXT(opaque))) return ctx->onListen(opaque,parms[0][0],1,np>1?parms[1]:NULL,np>2?parms[2]:NULL); | |||
| return 0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_tcp_listen_end(void *opaque, EEL_F *handle) | |||
| { | |||
| eel_net_state *ctx; | |||
| if (NULL != (ctx=EEL_NET_GET_CONTEXT(opaque))) return ctx->onListen(opaque,*handle,-1,NULL,NULL); | |||
| return 0; | |||
| } | |||
| void EEL_tcp_register() | |||
| { | |||
| NSEEL_addfunc_varparm("tcp_listen",1,NSEEL_PProc_THIS,&_eel_tcp_listen); | |||
| NSEEL_addfunc_retval("tcp_listen_end",1,NSEEL_PProc_THIS,&_eel_tcp_listen_end); | |||
| NSEEL_addfunc_varparm("tcp_connect",2,NSEEL_PProc_THIS,&_eel_tcp_connect); | |||
| NSEEL_addfunc_varparm("tcp_send",2,NSEEL_PProc_THIS,&_eel_tcp_send); | |||
| NSEEL_addfunc_varparm("tcp_recv",2,NSEEL_PProc_THIS,&_eel_tcp_recv); | |||
| NSEEL_addfunc_retval("tcp_set_block",2,NSEEL_PProc_THIS,&_eel_tcp_set_block); | |||
| NSEEL_addfunc_retval("tcp_close",1,NSEEL_PProc_THIS,&_eel_tcp_close); | |||
| } | |||
| #ifdef EEL_WANT_DOCUMENTATION | |||
| const char *eel_net_function_reference = | |||
| "tcp_listen\tport[,\"interface\",#ip_out]\tListens on port specified. Returns less than 0 if could not listen, 0 if no new connection available, or greater than 0 (as a TCP connection ID) if a new connection was made. If a connection made and #ip_out specified, it will be set to the remote IP. interface can be empty for all interfaces, otherwise an interface IP as a string.\0" | |||
| "tcp_listen_end\tport\tEnds listening on port specified.\0" | |||
| "tcp_connect\t\"address\",port[,block]\tCreate a new TCP connection to address:port. If block is specified and 0, connection will be made nonblocking. Returns TCP connection ID greater than 0 on success.\0" | |||
| "tcp_send\tconnection,\"str\"[,len]\tSends a string to connection. Returns -1 on error, 0 if connection is non-blocking and would block, otherwise returns length sent. If len is specified and not less than 1, only the first len bytes of the string parameter will be sent.\0" | |||
| "tcp_recv\tconnection,#str[,maxlen]\tReceives data from a connection to #str. If maxlen is specified, no more than maxlen bytes will be received. If non-blocking, 0 will be returned if would block. Returns less than 0 if error.\0" | |||
| "tcp_set_block\tconnection,block\tSets whether a connection blocks.\0" | |||
| "tcp_close\tconnection\tCloses a TCP connection created by tcp_listen() or tcp_connect().\0" | |||
| ; | |||
| #endif | |||
| #endif | |||
| @@ -0,0 +1,776 @@ | |||
| #ifndef _WIN32 | |||
| #include <unistd.h> | |||
| #ifndef EELSCRIPT_NO_LICE | |||
| #include "../swell/swell.h" | |||
| #endif | |||
| #endif | |||
| #include "../wdltypes.h" | |||
| #include "../ptrlist.h" | |||
| #include "../wdlstring.h" | |||
| #include "../assocarray.h" | |||
| #include "../queue.h" | |||
| #include "../win32_utf8.h" | |||
| #include "ns-eel.h" | |||
| #ifndef EELSCRIPT_MAX_FILE_HANDLES | |||
| #define EELSCRIPT_MAX_FILE_HANDLES 512 | |||
| #endif | |||
| #ifndef EELSCRIPT_FILE_HANDLE_INDEX_BASE | |||
| #define EELSCRIPT_FILE_HANDLE_INDEX_BASE 1000000 | |||
| #endif | |||
| #ifndef EEL_STRING_MAXUSERSTRING_LENGTH_HINT | |||
| #define EEL_STRING_MAXUSERSTRING_LENGTH_HINT (1<<16) // 64KB per string max | |||
| #endif | |||
| #ifndef EEL_STRING_MAX_USER_STRINGS | |||
| #define EEL_STRING_MAX_USER_STRINGS 32768 | |||
| #endif | |||
| #ifndef EEL_STRING_LITERAL_BASE | |||
| #define EEL_STRING_LITERAL_BASE 2000000 | |||
| #endif | |||
| #ifndef EELSCRIPT_LICE_MAX_IMAGES | |||
| #define EELSCRIPT_LICE_MAX_IMAGES 1024 | |||
| #endif | |||
| #ifndef EELSCRIPT_LICE_MAX_FONTS | |||
| #define EELSCRIPT_LICE_MAX_FONTS 128 | |||
| #endif | |||
| #ifndef EELSCRIPT_NET_MAXCON | |||
| #define EELSCRIPT_NET_MAXCON 4096 | |||
| #endif | |||
| #ifndef EELSCRIPT_LICE_CLASSNAME | |||
| #define EELSCRIPT_LICE_CLASSNAME "eelscript_gfx" | |||
| #endif | |||
| // #define EELSCRIPT_NO_NET | |||
| // #define EELSCRIPT_NO_LICE | |||
| // #define EELSCRIPT_NO_FILE | |||
| // #define EELSCRIPT_NO_FFT | |||
| // #define EELSCRIPT_NO_MDCT | |||
| // #define EELSCRIPT_NO_EVAL | |||
| class eel_string_context_state; | |||
| #ifndef EELSCRIPT_NO_NET | |||
| class eel_net_state; | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_LICE | |||
| class eel_lice_state; | |||
| #endif | |||
| class eelScriptInst { | |||
| public: | |||
| static int init(); | |||
| eelScriptInst(); | |||
| virtual ~eelScriptInst(); | |||
| NSEEL_CODEHANDLE compile_code(const char *code, const char **err); | |||
| int runcode(const char *code, int showerr, const char *showerrfn, bool canfree, bool ignoreEndOfInputChk, bool doExec); | |||
| int loadfile(const char *fn, const char *callerfn, bool allowstdin); | |||
| NSEEL_VMCTX m_vm; | |||
| WDL_PtrList<void> m_code_freelist; | |||
| #ifndef EELSCRIPT_NO_FILE | |||
| FILE *m_handles[EELSCRIPT_MAX_FILE_HANDLES]; | |||
| virtual EEL_F OpenFile(const char *fn, const char *mode) | |||
| { | |||
| if (!*fn || !*mode) return 0.0; | |||
| #ifndef EELSCRIPT_NO_STDIO | |||
| if (!strcmp(fn,"stdin")) return 1; | |||
| if (!strcmp(fn,"stdout")) return 2; | |||
| if (!strcmp(fn,"stderr")) return 3; | |||
| #endif | |||
| WDL_FastString fnstr(fn); | |||
| if (!translateFilename(&fnstr,mode)) return 0.0; | |||
| int x; | |||
| for (x=0;x<EELSCRIPT_MAX_FILE_HANDLES && m_handles[x];x++); | |||
| if (x>= EELSCRIPT_MAX_FILE_HANDLES) return 0.0; | |||
| FILE *fp = fopenUTF8(fnstr.Get(),mode); | |||
| if (!fp) return 0.0; | |||
| m_handles[x]=fp; | |||
| return x + EELSCRIPT_FILE_HANDLE_INDEX_BASE; | |||
| } | |||
| virtual EEL_F CloseFile(int fp_idx) | |||
| { | |||
| fp_idx-=EELSCRIPT_FILE_HANDLE_INDEX_BASE; | |||
| if (fp_idx>=0 && fp_idx<EELSCRIPT_MAX_FILE_HANDLES && m_handles[fp_idx]) | |||
| { | |||
| fclose(m_handles[fp_idx]); | |||
| m_handles[fp_idx]=0; | |||
| return 0.0; | |||
| } | |||
| return -1.0; | |||
| } | |||
| virtual FILE *GetFileFP(int fp_idx) | |||
| { | |||
| #ifndef EELSCRIPT_NO_STDIO | |||
| if (fp_idx==1) return stdin; | |||
| if (fp_idx==2) return stdout; | |||
| if (fp_idx==3) return stderr; | |||
| #endif | |||
| fp_idx-=EELSCRIPT_FILE_HANDLE_INDEX_BASE; | |||
| if (fp_idx>=0 && fp_idx<EELSCRIPT_MAX_FILE_HANDLES) return m_handles[fp_idx]; | |||
| return NULL; | |||
| } | |||
| #endif | |||
| virtual bool translateFilename(WDL_FastString *fs, const char *mode) { return true; } | |||
| virtual bool GetFilenameForParameter(EEL_F idx, WDL_FastString *fs, int iswrite); | |||
| eel_string_context_state *m_string_context; | |||
| #ifndef EELSCRIPT_NO_NET | |||
| eel_net_state *m_net_state; | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_LICE | |||
| eel_lice_state *m_gfx_state; | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_EVAL | |||
| struct evalCacheEnt { | |||
| char *str; | |||
| NSEEL_CODEHANDLE ch; | |||
| }; | |||
| int m_eval_depth; | |||
| WDL_TypedBuf<evalCacheEnt> m_eval_cache; | |||
| virtual char *evalCacheGet(const char *str, NSEEL_CODEHANDLE *ch); | |||
| virtual void evalCacheDispose(char *key, NSEEL_CODEHANDLE ch); | |||
| WDL_Queue m_defer_eval, m_atexit_eval; | |||
| void runCodeQ(WDL_Queue *q, const char *fname); | |||
| void runAtExitCode() | |||
| { | |||
| runCodeQ(&m_atexit_eval,"atexit"); | |||
| m_atexit_eval.Clear(); // make sure nothing gets added in atexit(), in case the user called runAtExitCode before destroying | |||
| } | |||
| #endif | |||
| virtual bool run_deferred(); // requires eval support to be useful | |||
| virtual bool has_deferred(); | |||
| WDL_StringKeyedArray<bool> m_loaded_fnlist; // imported file list (to avoid repeats) | |||
| }; | |||
| //#define EEL_STRINGS_MUTABLE_LITERALS | |||
| //#define EEL_STRING_WANT_MUTEX | |||
| #define EEL_STRING_GET_CONTEXT_POINTER(opaque) (((eelScriptInst *)opaque)->m_string_context) | |||
| #ifndef EEL_STRING_STDOUT_WRITE | |||
| #ifndef EELSCRIPT_NO_STDIO | |||
| #define EEL_STRING_STDOUT_WRITE(x,len) { fwrite(x,len,1,stdout); fflush(stdout); } | |||
| #endif | |||
| #endif | |||
| #include "eel_strings.h" | |||
| #include "eel_misc.h" | |||
| #ifndef EELSCRIPT_NO_FILE | |||
| #define EEL_FILE_OPEN(fn,mode) ((eelScriptInst*)opaque)->OpenFile(fn,mode) | |||
| #define EEL_FILE_GETFP(fp) ((eelScriptInst*)opaque)->GetFileFP(fp) | |||
| #define EEL_FILE_CLOSE(fpindex) ((eelScriptInst*)opaque)->CloseFile(fpindex) | |||
| #include "eel_files.h" | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_FFT | |||
| #include "eel_fft.h" | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_MDCT | |||
| #include "eel_mdct.h" | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_NET | |||
| #define EEL_NET_GET_CONTEXT(opaque) (((eelScriptInst *)opaque)->m_net_state) | |||
| #include "eel_net.h" | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_LICE | |||
| #ifndef EEL_LICE_WANT_STANDALONE | |||
| #define EEL_LICE_WANT_STANDALONE | |||
| #endif | |||
| #ifndef EELSCRIPT_LICE_NOUPDATE | |||
| #define EEL_LICE_WANT_STANDALONE_UPDATE // gfx_update() which runs message pump and updates screen etc | |||
| #endif | |||
| #define EEL_LICE_GET_FILENAME_FOR_STRING(idx, fs, p) (((eelScriptInst*)opaque)->GetFilenameForParameter(idx,fs,p)) | |||
| #define EEL_LICE_GET_CONTEXT(opaque) ((opaque) ? (((eelScriptInst *)opaque)->m_gfx_state) : NULL) | |||
| #include "eel_lice.h" | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_EVAL | |||
| #define EEL_EVAL_GET_CACHED(str, ch) ((eelScriptInst *)opaque)->evalCacheGet(str,&(ch)) | |||
| #define EEL_EVAL_SET_CACHED(str, ch) ((eelScriptInst *)opaque)->evalCacheDispose(str,ch) | |||
| #define EEL_EVAL_GET_VMCTX(opaque) (((eelScriptInst *)opaque)->m_vm) | |||
| #define EEL_EVAL_SCOPE_ENTER (((eelScriptInst *)opaque)->m_eval_depth < 3 ? \ | |||
| ++((eelScriptInst *)opaque)->m_eval_depth : 0) | |||
| #define EEL_EVAL_SCOPE_LEAVE ((eelScriptInst *)opaque)->m_eval_depth--; | |||
| #include "eel_eval.h" | |||
| static EEL_F NSEEL_CGEN_CALL _eel_defer(void *opaque, EEL_F *s) | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| const char *str=EEL_STRING_GET_FOR_INDEX(*s,NULL); | |||
| if (str && *str && *s >= EEL_STRING_MAX_USER_STRINGS) // don't allow defer(0) etc | |||
| { | |||
| eelScriptInst *inst = (eelScriptInst *)opaque; | |||
| if (inst->m_defer_eval.Available() < EEL_STRING_MAXUSERSTRING_LENGTH_HINT) | |||
| { | |||
| inst->m_defer_eval.Add(str,strlen(str)+1); | |||
| return 1.0; | |||
| } | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("defer(): too much defer() code already added, ignoring"); | |||
| #endif | |||
| } | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| else if (!str) | |||
| { | |||
| EEL_STRING_DEBUGOUT("defer(): invalid string identifier specified %f",*s); | |||
| } | |||
| else if (*s < EEL_STRING_MAX_USER_STRINGS) | |||
| { | |||
| EEL_STRING_DEBUGOUT("defer(): user string identifier %f specified but not allowed",*s); | |||
| } | |||
| #endif | |||
| return 0.0; | |||
| } | |||
| static EEL_F NSEEL_CGEN_CALL _eel_atexit(void *opaque, EEL_F *s) | |||
| { | |||
| EEL_STRING_MUTEXLOCK_SCOPE | |||
| const char *str=EEL_STRING_GET_FOR_INDEX(*s,NULL); | |||
| if (str && *str && *s >= EEL_STRING_MAX_USER_STRINGS) // don't allow atexit(0) etc | |||
| { | |||
| eelScriptInst *inst = (eelScriptInst *)opaque; | |||
| if (inst->m_atexit_eval.Available() < EEL_STRING_MAXUSERSTRING_LENGTH_HINT) | |||
| { | |||
| inst->m_atexit_eval.Add(str,strlen(str)+1); | |||
| return 1.0; | |||
| } | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("atexit(): too much atexit() code already added, ignoring"); | |||
| #endif | |||
| } | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| else if (!str) | |||
| { | |||
| EEL_STRING_DEBUGOUT("atexit(): invalid string identifier specified %f",*s); | |||
| } | |||
| else if (*s < EEL_STRING_MAX_USER_STRINGS) | |||
| { | |||
| EEL_STRING_DEBUGOUT("atexit(): user string identifier %f specified but not allowed",*s); | |||
| } | |||
| #endif | |||
| return 0.0; | |||
| } | |||
| #endif | |||
| #define opaque ((void *)this) | |||
| eelScriptInst::eelScriptInst() : m_loaded_fnlist(false) | |||
| { | |||
| #ifndef EELSCRIPT_NO_FILE | |||
| memset(m_handles,0,sizeof(m_handles)); | |||
| #endif | |||
| m_vm = NSEEL_VM_alloc(); | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| if (!m_vm) EEL_STRING_DEBUGOUT("NSEEL_VM_alloc(): failed"); | |||
| #endif | |||
| NSEEL_VM_SetCustomFuncThis(m_vm,this); | |||
| #ifdef NSEEL_ADDFUNC_DESTINATION | |||
| NSEEL_VM_SetFunctionTable(m_vm,NSEEL_ADDFUNC_DESTINATION); | |||
| #endif | |||
| m_string_context = new eel_string_context_state; | |||
| eel_string_initvm(m_vm); | |||
| #ifndef EELSCRIPT_NO_NET | |||
| m_net_state = new eel_net_state(EELSCRIPT_NET_MAXCON,NULL); | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_LICE | |||
| m_gfx_state = new eel_lice_state(m_vm,this,EELSCRIPT_LICE_MAX_IMAGES,EELSCRIPT_LICE_MAX_FONTS); | |||
| m_gfx_state->resetVarsToStock(); | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_EVAL | |||
| m_eval_depth=0; | |||
| #endif | |||
| } | |||
| eelScriptInst::~eelScriptInst() | |||
| { | |||
| #ifndef EELSCRIPT_NO_EVAL | |||
| if (m_atexit_eval.GetSize()>0) runAtExitCode(); | |||
| #endif | |||
| int x; | |||
| m_code_freelist.Empty((void (*)(void *))NSEEL_code_free); | |||
| #ifndef EELSCRIPT_NO_EVAL | |||
| for (x=0;x<m_eval_cache.GetSize();x++) | |||
| { | |||
| free(m_eval_cache.Get()[x].str); | |||
| NSEEL_code_free(m_eval_cache.Get()[x].ch); | |||
| } | |||
| #endif | |||
| if (m_vm) NSEEL_VM_free(m_vm); | |||
| #ifndef EELSCRIPT_NO_FILE | |||
| for (x=0;x<EELSCRIPT_MAX_FILE_HANDLES;x++) | |||
| { | |||
| if (m_handles[x]) fclose(m_handles[x]); | |||
| m_handles[x]=0; | |||
| } | |||
| #endif | |||
| delete m_string_context; | |||
| #ifndef EELSCRIPT_NO_NET | |||
| delete m_net_state; | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_LICE | |||
| delete m_gfx_state; | |||
| #endif | |||
| } | |||
| bool eelScriptInst::GetFilenameForParameter(EEL_F idx, WDL_FastString *fs, int iswrite) | |||
| { | |||
| const char *fmt = EEL_STRING_GET_FOR_INDEX(idx,NULL); | |||
| if (!fmt) return false; | |||
| fs->Set(fmt); | |||
| return translateFilename(fs,iswrite?"w":"r"); | |||
| } | |||
| NSEEL_CODEHANDLE eelScriptInst::compile_code(const char *code, const char **err) | |||
| { | |||
| if (!m_vm) | |||
| { | |||
| *err = "EEL VM not initialized"; | |||
| return NULL; | |||
| } | |||
| NSEEL_CODEHANDLE ch = NSEEL_code_compile_ex(m_vm, code, 0, NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS); | |||
| if (ch) | |||
| { | |||
| m_string_context->update_named_vars(m_vm); | |||
| m_code_freelist.Add((void*)ch); | |||
| return ch; | |||
| } | |||
| *err = NSEEL_code_getcodeerror(m_vm); | |||
| return NULL; | |||
| } | |||
| int eelScriptInst::runcode(const char *codeptr, int showerr, const char *showerrfn, bool canfree, bool ignoreEndOfInputChk, bool doExec) | |||
| { | |||
| if (m_vm) | |||
| { | |||
| NSEEL_CODEHANDLE code = NSEEL_code_compile_ex(m_vm,codeptr,0,canfree ? 0 : NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS); | |||
| if (code) m_string_context->update_named_vars(m_vm); | |||
| char *err; | |||
| if (!code && (err=NSEEL_code_getcodeerror(m_vm))) | |||
| { | |||
| if (!ignoreEndOfInputChk && (NSEEL_code_geterror_flag(m_vm)&1)) return 1; | |||
| if (showerr) | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| if (showerr==2) | |||
| { | |||
| EEL_STRING_DEBUGOUT("Warning: %s:%s",WDL_get_filepart(showerrfn),err); | |||
| } | |||
| else | |||
| { | |||
| EEL_STRING_DEBUGOUT("%s:%s",WDL_get_filepart(showerrfn),err); | |||
| } | |||
| #endif | |||
| } | |||
| return -1; | |||
| } | |||
| else | |||
| { | |||
| if (code) | |||
| { | |||
| #ifdef EELSCRIPT_DO_DISASSEMBLE | |||
| codeHandleType *p = (codeHandleType*)code; | |||
| char buf[512]; | |||
| buf[0]=0; | |||
| #ifdef _WIN32 | |||
| GetTempPath(sizeof(buf)-64,buf); | |||
| lstrcatn(buf,"jsfx-out",sizeof(buf)); | |||
| #else | |||
| lstrcpyn_safe(buf,"/tmp/jsfx-out",sizeof(buf)); | |||
| #endif | |||
| FILE *fp = fopenUTF8(buf,"wb"); | |||
| if (fp) | |||
| { | |||
| fwrite(p->code,1,p->code_size,fp); | |||
| fclose(fp); | |||
| char buf2[2048]; | |||
| #ifdef _WIN32 | |||
| snprintf(buf2,sizeof(buf2),"disasm \"%s\"",buf); | |||
| #else | |||
| #ifdef __aarch64__ | |||
| snprintf(buf2,sizeof(buf2), "objdump -D -b binary -maarch64 \"%s\"",buf); | |||
| #elif defined(__arm__) | |||
| snprintf(buf2,sizeof(buf2), "objdump -D -b binary -m arm \"%s\"",buf); | |||
| #elif defined(__LP64__) | |||
| #ifdef __APPLE__ | |||
| snprintf(buf2,sizeof(buf2),"distorm3 --b64 \"%s\"",buf); | |||
| #else | |||
| snprintf(buf2,sizeof(buf2),"objdump -D -b binary -m i386:x86-64 \"%s\"",buf); | |||
| #endif | |||
| #else | |||
| snprintf(buf2,sizeof(buf2),"distorm3 --b32 \"%s\"",buf); | |||
| #endif | |||
| #endif | |||
| system(buf2); | |||
| } | |||
| #endif | |||
| if (doExec) NSEEL_code_execute(code); | |||
| if (canfree) NSEEL_code_free(code); | |||
| else m_code_freelist.Add((void*)code); | |||
| } | |||
| return 0; | |||
| } | |||
| } | |||
| return -1; | |||
| } | |||
| FILE *eelscript_resolvePath(WDL_FastString &usefn, const char *fn, const char *callerfn) | |||
| { | |||
| // resolve path relative to current | |||
| int x; | |||
| bool had_abs=false; | |||
| for (x=0;x<2; x ++) | |||
| { | |||
| #ifdef _WIN32 | |||
| if (!x && ((fn[0] == '\\' && fn[1] == '\\') || (fn[0] && fn[1] == ':'))) | |||
| #else | |||
| if (!x && fn[0] == '/') | |||
| #endif | |||
| { | |||
| usefn.Set(fn); | |||
| had_abs=true; | |||
| } | |||
| else | |||
| { | |||
| const char *fnu = fn; | |||
| if (x) | |||
| { | |||
| while (*fnu) fnu++; | |||
| while (fnu >= fn && *fnu != '\\' && *fnu != '/') fnu--; | |||
| if (fnu < fn) break; | |||
| fnu++; | |||
| } | |||
| usefn.Set(callerfn); | |||
| int l=usefn.GetLength(); | |||
| while (l > 0 && usefn.Get()[l-1] != '\\' && usefn.Get()[l-1] != '/') l--; | |||
| if (l > 0) | |||
| { | |||
| usefn.SetLen(l); | |||
| usefn.Append(fnu); | |||
| } | |||
| else | |||
| { | |||
| usefn.Set(fnu); | |||
| } | |||
| int last_slash_pos=-1; | |||
| for (l = 0; l < usefn.GetLength(); l ++) | |||
| { | |||
| if (usefn.Get()[l] == '/' || usefn.Get()[l] == '\\') | |||
| { | |||
| if (usefn.Get()[l+1] == '.' && usefn.Get()[l+2] == '.' && | |||
| (usefn.Get()[l+3] == '/' || usefn.Get()[l+3] == '\\')) | |||
| { | |||
| if (last_slash_pos >= 0) | |||
| usefn.DeleteSub(last_slash_pos, l+3-last_slash_pos); | |||
| else | |||
| usefn.DeleteSub(0,l+3+1); | |||
| } | |||
| else | |||
| { | |||
| last_slash_pos=l; | |||
| } | |||
| } | |||
| // take currentfn, remove filename part, add fnu | |||
| } | |||
| } | |||
| FILE *fp = fopenUTF8(usefn.Get(),"r"); | |||
| if (fp) return fp; | |||
| } | |||
| if (had_abs) usefn.Set(fn); | |||
| return NULL; | |||
| } | |||
| int eelScriptInst::loadfile(const char *fn, const char *callerfn, bool allowstdin) | |||
| { | |||
| WDL_FastString usefn; | |||
| FILE *fp = NULL; | |||
| if (!strcmp(fn,"-")) | |||
| { | |||
| if (callerfn) | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| EEL_STRING_DEBUGOUT("@import: can't import \"-\" (stdin)"); | |||
| #endif | |||
| return -1; | |||
| } | |||
| if (allowstdin) | |||
| { | |||
| fp = stdin; | |||
| fn = "(stdin)"; | |||
| } | |||
| } | |||
| else if (!callerfn) | |||
| { | |||
| fp = fopenUTF8(fn,"r"); | |||
| if (fp) m_loaded_fnlist.Insert(fn,true); | |||
| } | |||
| else | |||
| { | |||
| fp = eelscript_resolvePath(usefn,fn,callerfn); | |||
| if (fp) | |||
| { | |||
| if (m_loaded_fnlist.Get(usefn.Get())) | |||
| { | |||
| fclose(fp); | |||
| return 0; | |||
| } | |||
| m_loaded_fnlist.Insert(usefn.Get(),true); | |||
| fn = usefn.Get(); | |||
| } | |||
| } | |||
| if (!fp) | |||
| { | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| if (callerfn) | |||
| EEL_STRING_DEBUGOUT("Warning: @import could not open '%s'",fn); | |||
| else | |||
| EEL_STRING_DEBUGOUT("Error opening %s",fn); | |||
| #endif | |||
| return -1; | |||
| } | |||
| WDL_FastString code; | |||
| char line[4096]; | |||
| for (;;) | |||
| { | |||
| line[0]=0; | |||
| fgets(line,sizeof(line),fp); | |||
| if (!line[0]) break; | |||
| if (!strnicmp(line,"@import",7) && isspace((unsigned char)line[7])) | |||
| { | |||
| char *p=line+7; | |||
| while (isspace((unsigned char)*p)) p++; | |||
| char *ep=p; | |||
| while (*ep) ep++; | |||
| while (ep>p && isspace((unsigned char)ep[-1])) ep--; | |||
| *ep=0; | |||
| if (*p) loadfile(p,fn,false); | |||
| } | |||
| else | |||
| { | |||
| code.Append(line); | |||
| } | |||
| } | |||
| if (fp != stdin) fclose(fp); | |||
| return runcode(code.Get(),callerfn ? 2 : 1, fn,false,true,!callerfn); | |||
| } | |||
| char *eelScriptInst::evalCacheGet(const char *str, NSEEL_CODEHANDLE *ch) | |||
| { | |||
| // should mutex protect if multiple threads access this eelScriptInst context | |||
| int x=m_eval_cache.GetSize(); | |||
| while (--x >= 0) | |||
| { | |||
| char *ret; | |||
| if (!strcmp(ret=m_eval_cache.Get()[x].str, str)) | |||
| { | |||
| *ch = m_eval_cache.Get()[x].ch; | |||
| m_eval_cache.Delete(x); | |||
| return ret; | |||
| } | |||
| } | |||
| return NULL; | |||
| } | |||
| void eelScriptInst::evalCacheDispose(char *key, NSEEL_CODEHANDLE ch) | |||
| { | |||
| // should mutex protect if multiple threads access this eelScriptInst context | |||
| evalCacheEnt ecc; | |||
| ecc.str= key; | |||
| ecc.ch = ch; | |||
| if (m_eval_cache.GetSize() > 1024) | |||
| { | |||
| NSEEL_code_free(m_eval_cache.Get()->ch); | |||
| free(m_eval_cache.Get()->str); | |||
| m_eval_cache.Delete(0); | |||
| } | |||
| m_eval_cache.Add(ecc); | |||
| } | |||
| int eelScriptInst::init() | |||
| { | |||
| EEL_string_register(); | |||
| #ifndef EELSCRIPT_NO_FILE | |||
| EEL_file_register(); | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_FFT | |||
| EEL_fft_register(); | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_MDCT | |||
| EEL_mdct_register(); | |||
| #endif | |||
| EEL_misc_register(); | |||
| #ifndef EELSCRIPT_NO_EVAL | |||
| EEL_eval_register(); | |||
| NSEEL_addfunc_retval("defer",1,NSEEL_PProc_THIS,&_eel_defer); | |||
| NSEEL_addfunc_retval("runloop", 1, NSEEL_PProc_THIS, &_eel_defer); | |||
| NSEEL_addfunc_retval("atexit",1,NSEEL_PProc_THIS,&_eel_atexit); | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_NET | |||
| EEL_tcp_register(); | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_LICE | |||
| eel_lice_register(); | |||
| #ifdef _WIN32 | |||
| eel_lice_register_standalone(GetModuleHandle(NULL),EELSCRIPT_LICE_CLASSNAME,NULL,NULL); | |||
| #else | |||
| eel_lice_register_standalone(NULL,EELSCRIPT_LICE_CLASSNAME,NULL,NULL); | |||
| #endif | |||
| #endif | |||
| return 0; | |||
| } | |||
| bool eelScriptInst::has_deferred() | |||
| { | |||
| #ifndef EELSCRIPT_NO_EVAL | |||
| return m_defer_eval.Available() && m_vm; | |||
| #else | |||
| return false; | |||
| #endif | |||
| } | |||
| #ifndef EELSCRIPT_NO_EVAL | |||
| void eelScriptInst::runCodeQ(WDL_Queue *q, const char *callername) | |||
| { | |||
| const int endptr = q->Available(); | |||
| int offs = 0; | |||
| while (offs < endptr) | |||
| { | |||
| if (q->Available() < endptr) break; // should never happen, but safety first! | |||
| const char *ptr = (const char *)q->Get() + offs; | |||
| offs += strlen(ptr)+1; | |||
| NSEEL_CODEHANDLE ch=NULL; | |||
| char *sv=evalCacheGet(ptr,&ch); | |||
| if (!sv) sv=strdup(ptr); | |||
| if (!ch) ch=NSEEL_code_compile(m_vm,sv,0); | |||
| if (!ch) | |||
| { | |||
| free(sv); | |||
| #ifdef EEL_STRING_DEBUGOUT | |||
| const char *err = NSEEL_code_getcodeerror(m_vm); | |||
| if (err) EEL_STRING_DEBUGOUT("%s: error in code: %s",callername,err); | |||
| #endif | |||
| } | |||
| else | |||
| { | |||
| NSEEL_code_execute(ch); | |||
| evalCacheDispose(sv,ch); | |||
| } | |||
| } | |||
| q->Advance(endptr); | |||
| } | |||
| #endif | |||
| bool eelScriptInst::run_deferred() | |||
| { | |||
| #ifndef EELSCRIPT_NO_EVAL | |||
| if (!m_defer_eval.Available()||!m_vm) return false; | |||
| runCodeQ(&m_defer_eval,"defer"); | |||
| m_defer_eval.Compact(); | |||
| return m_defer_eval.Available()>0; | |||
| #else | |||
| return false; | |||
| #endif | |||
| } | |||
| #ifdef EEL_WANT_DOCUMENTATION | |||
| #include "ns-eel-func-ref.h" | |||
| void EELScript_GenerateFunctionList(WDL_PtrList<const char> *fs) | |||
| { | |||
| const char *p = nseel_builtin_function_reference; | |||
| while (*p) { fs->Add(p); p += strlen(p) + 1; } | |||
| p = eel_strings_function_reference; | |||
| while (*p) { fs->Add(p); p += strlen(p) + 1; } | |||
| p = eel_misc_function_reference; | |||
| while (*p) { fs->Add(p); p += strlen(p) + 1; } | |||
| #ifndef EELSCRIPT_NO_EVAL | |||
| fs->Add("atexit\t\"code\"\t" | |||
| #ifndef EELSCRIPT_HELP_NO_DEFER_DESC | |||
| "Adds code to be executed when the script finishes." | |||
| #endif | |||
| ); | |||
| fs->Add("defer\t\"code\"\t" | |||
| #ifndef EELSCRIPT_HELP_NO_DEFER_DESC | |||
| "Adds code which will be executed some small amount of time after the current code finishes. Identical to runloop()" | |||
| #endif | |||
| ); | |||
| fs->Add("runloop\t\"code\"\t" | |||
| #ifndef EELSCRIPT_HELP_NO_DEFER_DESC | |||
| "Adds code which will be executed some small amount of time after the current code finishes. Identical to defer()" | |||
| #endif | |||
| ); | |||
| p = eel_eval_function_reference; | |||
| while (*p) { fs->Add(p); p += strlen(p) + 1; } | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_NET | |||
| p = eel_net_function_reference; | |||
| while (*p) { fs->Add(p); p += strlen(p) + 1; } | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_FFT | |||
| p = eel_fft_function_reference; | |||
| while (*p) { fs->Add(p); p += strlen(p) + 1; } | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_FILE | |||
| p = eel_file_function_reference; | |||
| while (*p) { fs->Add(p); p += strlen(p) + 1; } | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_MDCT | |||
| p = eel_mdct_function_reference; | |||
| while (*p) { fs->Add(p); p += strlen(p) + 1; } | |||
| #endif | |||
| #ifndef EELSCRIPT_NO_LICE | |||
| p = eel_lice_function_reference; | |||
| while (*p) { fs->Add(p); p += strlen(p) + 1; } | |||
| #endif | |||
| } | |||
| #endif | |||
| #undef opaque | |||
| @@ -0,0 +1,399 @@ | |||
| #ifndef _NSEEL_GLUE_AARCH64_H_ | |||
| #define _NSEEL_GLUE_AARCH64_H_ | |||
| // x0=return value, first parm, x1-x2 parms (x3-x7 more params) | |||
| // x8 return struct? | |||
| // x9-x15 temporary | |||
| // x16-x17 = PLT, linker | |||
| // x18 reserved (TLS) | |||
| // x19-x28 callee-saved | |||
| // x19 = worktable | |||
| // x20 = ramtable | |||
| // x21 = consttab | |||
| // x22 = worktable ptr | |||
| // x23-x28 spare | |||
| // x29 frame pointer | |||
| // x30 link register | |||
| // x31 SP/zero | |||
| // x0=p1 | |||
| // x1=p2 | |||
| // x2=p3 | |||
| // d0 is return value for fp? | |||
| // d/v/f0-7 = arguments/results | |||
| // 8-15 callee saved | |||
| // 16-31 temporary | |||
| // v8-v15 spill registers | |||
| #define GLUE_MAX_SPILL_REGS 8 | |||
| #define GLUE_SAVE_TO_SPILL_SIZE(x) (4) | |||
| #define GLUE_RESTORE_SPILL_TO_FPREG2_SIZE(x) (4) | |||
| static void GLUE_RESTORE_SPILL_TO_FPREG2(void *b, int ws) | |||
| { | |||
| *(unsigned int *)b = 0x1e604101 + (ws<<5); // fmov d1, d8+ws | |||
| } | |||
| static void GLUE_SAVE_TO_SPILL(void *b, int ws) | |||
| { | |||
| *(unsigned int *)b = 0x1e604008 + ws; // fmov d8+ws, d0 | |||
| } | |||
| #define GLUE_HAS_FPREG2 1 | |||
| static const unsigned int GLUE_COPY_FPSTACK_TO_FPREG2[] = { 0x1e604001 }; // fmov d1, d0 | |||
| static unsigned int GLUE_POP_STACK_TO_FPREG2[] = { | |||
| 0xfc4107e1 // ldr d1, [sp], #16 | |||
| }; | |||
| #define GLUE_MAX_FPSTACK_SIZE 0 // no stack support | |||
| #define GLUE_MAX_JMPSIZE ((1<<20) - 1024) // maximum relative jump size | |||
| // endOfInstruction is end of jump with relative offset, offset passed in is offset from end of dest instruction. | |||
| // 0 = current instruction | |||
| static void GLUE_JMP_SET_OFFSET(void *endOfInstruction, int offset) | |||
| { | |||
| unsigned int *a = (unsigned int*) endOfInstruction - 1; | |||
| offset += 4; | |||
| offset >>= 2; // as dwords | |||
| if ((a[0] & 0xFC000000) == 0x14000000) | |||
| { | |||
| // NC b = 0x14 + 26 bit offset | |||
| a[0] = 0x14000000 | (offset & 0x3FFFFFF); | |||
| } | |||
| else if ((a[0] & 0xFF000000) == 0x54000000) | |||
| { | |||
| // condb = 0x54 + 20 bit offset + 5 bit condition: 0=eq, 1=ne, b=lt, c=gt, d=le, a=ge | |||
| a[0] = 0x54000000 | (a[0] & 0xF) | ((offset & 0x7FFFF) << 5); | |||
| } | |||
| } | |||
| static const unsigned int GLUE_JMP_NC[] = { 0x14000000 }; | |||
| static const unsigned int GLUE_JMP_IF_P1_Z[]= | |||
| { | |||
| 0x7100001f, // cmp w0, #0 | |||
| 0x54000000, // b.eq | |||
| }; | |||
| static const unsigned int GLUE_JMP_IF_P1_NZ[]= | |||
| { | |||
| 0x7100001f, // cmp w0, #0 | |||
| 0x54000001, // b.ne | |||
| }; | |||
| #define GLUE_MOV_PX_DIRECTVALUE_TOFPREG2_SIZE 16 // wr=-2, sets d1 | |||
| #define GLUE_MOV_PX_DIRECTVALUE_SIZE 12 | |||
| static void GLUE_MOV_PX_DIRECTVALUE_GEN(void *b, INT_PTR v, int wv) | |||
| { | |||
| static const unsigned int tab[3] = { | |||
| 0xd2800000, // mov x0, #0000 (val<<5) | reg | |||
| 0xf2a00000, // movk x0, #0000, lsl 16 (val<<5) | reg | |||
| 0xf2c00000, // movk x0, #0000, lsl 32 (val<<5) | reg | |||
| }; | |||
| // 0xABAAA, B is register, A are bits of word | |||
| unsigned int *p=(unsigned int *)b; | |||
| int wvo = wv; | |||
| if (wv<0) wv=0; | |||
| p[0] = tab[0] | wv | ((v&0xFFFF)<<5); | |||
| p[1] = tab[1] | wv | (((v>>16)&0xFFFF)<<5); | |||
| p[2] = tab[2] | wv | (((v>>32)&0xFFFF)<<5); | |||
| if (wvo == -2) p[3] = 0xfd400001; // ldr d1, [x0] | |||
| } | |||
| const static unsigned int GLUE_FUNC_ENTER[2] = { 0xa9bf7bfd, 0x910003fd }; // stp x29, x30, [sp, #-16]! ; mov x29, sp | |||
| #define GLUE_FUNC_ENTER_SIZE 4 | |||
| const static unsigned int GLUE_FUNC_LEAVE[1] = { 0 }; // let GLUE_RET pop | |||
| #define GLUE_FUNC_LEAVE_SIZE 0 | |||
| const static unsigned int GLUE_RET[]={ 0xa8c17bfd, 0xd65f03c0 }; // ldp x29,x30, [sp], #16 ; ret | |||
| static int GLUE_RESET_WTP(unsigned char *out, void *ptr) | |||
| { | |||
| const static unsigned int GLUE_SET_WTP_FROM_R19 = 0xaa1303f6; // mov r22, r19 | |||
| if (out) memcpy(out,&GLUE_SET_WTP_FROM_R19,sizeof(GLUE_SET_WTP_FROM_R19)); | |||
| return 4; | |||
| } | |||
| const static unsigned int GLUE_PUSH_P1[1]={ 0xf81f0fe0 }; // str x0, [sp, #-16]! | |||
| #define GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(offs) ((offs)>=32768 ? 8 : 4) | |||
| static void GLUE_STORE_P1_TO_STACK_AT_OFFS(void *b, int offs) | |||
| { | |||
| if (offs >= 32768) | |||
| { | |||
| // add x1, sp, (offs/4096) lsl 12 | |||
| *(unsigned int *)b = 0x914003e1 + ((offs>>12)<<10); | |||
| // str x0, [x1, #offs & 4095] | |||
| offs &= 4095; | |||
| offs <<= 10-3; | |||
| offs &= 0x7FFC00; | |||
| ((unsigned int *)b)[1] = 0xf9000020 + offs; | |||
| } | |||
| else | |||
| { | |||
| // str x0, [sp, #offs] | |||
| offs <<= 10-3; | |||
| offs &= 0x7FFC00; | |||
| *(unsigned int *)b = 0xf90003e0 + offs; | |||
| } | |||
| } | |||
| #define GLUE_MOVE_PX_STACKPTR_SIZE 4 | |||
| static void GLUE_MOVE_PX_STACKPTR_GEN(void *b, int wv) | |||
| { | |||
| // mov xX, sp | |||
| *(unsigned int *)b = 0x910003e0 + wv; | |||
| } | |||
| #define GLUE_MOVE_STACK_SIZE 4 | |||
| static void GLUE_MOVE_STACK(void *b, int amt) | |||
| { | |||
| if (amt>=0) | |||
| { | |||
| if (amt >= 4096) | |||
| *(unsigned int*)b = 0x914003ff | (((amt+4095)>>12)<<10); | |||
| else | |||
| *(unsigned int*)b = 0x910003ff | (amt << 10); | |||
| } | |||
| else | |||
| { | |||
| amt = -amt; | |||
| if (amt >= 4096) | |||
| *(unsigned int*)b = 0xd14003ff | (((amt+4095)>>12)<<10); | |||
| else | |||
| *(unsigned int*)b = 0xd10003ff | (amt << 10); | |||
| } | |||
| } | |||
| #define GLUE_POP_PX_SIZE 4 | |||
| static void GLUE_POP_PX(void *b, int wv) | |||
| { | |||
| ((unsigned int *)b)[0] = 0xf84107e0 | wv; // ldr x, [sp], 16 | |||
| } | |||
| #define GLUE_SET_PX_FROM_P1_SIZE 4 | |||
| static void GLUE_SET_PX_FROM_P1(void *b, int wv) | |||
| { | |||
| *(unsigned int *)b = 0xaa0003e0 | wv; | |||
| } | |||
| static const unsigned int GLUE_PUSH_P1PTR_AS_VALUE[] = | |||
| { | |||
| 0xfd400007, // ldr d7, [x0] | |||
| 0xfc1f0fe7, // str d7, [sp, #-16]! | |||
| }; | |||
| static int GLUE_POP_VALUE_TO_ADDR(unsigned char *buf, void *destptr) | |||
| { | |||
| if (buf) | |||
| { | |||
| unsigned int *bufptr = (unsigned int *)buf; | |||
| *bufptr++ = 0xfc4107e7; // ldr d7, [sp], #16 | |||
| GLUE_MOV_PX_DIRECTVALUE_GEN(bufptr, (INT_PTR)destptr,0); | |||
| bufptr += GLUE_MOV_PX_DIRECTVALUE_SIZE/4; | |||
| *bufptr++ = 0xfd000007; // str d7, [x0] | |||
| } | |||
| return 2*4 + GLUE_MOV_PX_DIRECTVALUE_SIZE; | |||
| } | |||
| static int GLUE_COPY_VALUE_AT_P1_TO_PTR(unsigned char *buf, void *destptr) | |||
| { | |||
| if (buf) | |||
| { | |||
| unsigned int *bufptr = (unsigned int *)buf; | |||
| *bufptr++ = 0xfd400007; // ldr d7, [x0] | |||
| GLUE_MOV_PX_DIRECTVALUE_GEN(bufptr, (INT_PTR)destptr,0); | |||
| bufptr += GLUE_MOV_PX_DIRECTVALUE_SIZE/4; | |||
| *bufptr++ = 0xfd000007; // str d7, [x0] | |||
| } | |||
| return 2*4 + GLUE_MOV_PX_DIRECTVALUE_SIZE; | |||
| } | |||
| #ifndef _MSC_VER | |||
| #define GLUE_CALL_CODE(bp, cp, rt) do { \ | |||
| unsigned long f; \ | |||
| if (!(h->compile_flags&NSEEL_CODE_COMPILE_FLAG_NOFPSTATE) && \ | |||
| !((f=glue_getscr())&(1<<24))) { \ | |||
| glue_setscr(f|(1<<24)); \ | |||
| eel_callcode64(bp, cp, rt); \ | |||
| glue_setscr(f); \ | |||
| } else eel_callcode64(bp, cp, rt);\ | |||
| } while(0) | |||
| static void eel_callcode64(INT_PTR bp, INT_PTR cp, INT_PTR rt) | |||
| { | |||
| //fwrite((void *)cp,4,20,stdout); | |||
| //return; | |||
| static const double consttab[] = { | |||
| NSEEL_CLOSEFACTOR, | |||
| 0.0, | |||
| 1.0, | |||
| -1.0, | |||
| -0.5, // for invsqrt | |||
| 1.5, | |||
| }; | |||
| __asm__( | |||
| "mov x1, %2\n" | |||
| "mov x2, %3\n" | |||
| "mov x3, %1\n" | |||
| "mov x0, %0\n" | |||
| "stp x29, x30, [sp, #-64]!\n" | |||
| "stp x18, x20, [sp, 16]\n" | |||
| "stp x21, x19, [sp, 32]\n" | |||
| "stp x22, x23, [sp, 48]\n" | |||
| "mov x29, sp\n" | |||
| "mov x19, x3\n" | |||
| "mov x20, x1\n" | |||
| "mov x21, x2\n" | |||
| "blr x0\n" | |||
| "ldp x29, x30, [sp], 16\n" | |||
| "ldp x18, x20, [sp], 16\n" | |||
| "ldp x21, x19, [sp], 16\n" | |||
| "ldp x22, x23, [sp], 16\n" | |||
| ::"r" (cp), "r" (bp), "r" (rt), "r" (consttab) :"x0","x1","x2","x3","x4","x5","x6","x7", | |||
| "x8","x9","x10","x11","x12","x13","x14","x15", | |||
| "v8","v9","v10","v11","v12","v13","v14","v15"); | |||
| }; | |||
| #endif | |||
| static unsigned char *EEL_GLUE_set_immediate(void *_p, INT_PTR newv) | |||
| { | |||
| unsigned int *p=(unsigned int *)_p; | |||
| // 0xd2800000, // mov x0, #0000 (val<<5) | reg | |||
| // 0xf2a00000, // movk x0, #0000, lsl 16 (val<<5) | reg | |||
| // 0xf2c00000, // movk x0, #0000, lsl 32 (val<<5) | reg | |||
| while (((p[0]>>5)&0xffff)!=0xdead || | |||
| ((p[1]>>5)&0xffff)!=0xbeef || | |||
| ((p[2]>>5)&0xffff)!=0xbeef) p++; | |||
| p[0] = (p[0] & 0xFFE0001F) | ((newv&0xffff)<<5); | |||
| p[1] = (p[1] & 0xFFE0001F) | (((newv>>16)&0xffff)<<5); | |||
| p[2] = (p[2] & 0xFFE0001F) | (((newv>>32)&0xffff)<<5); | |||
| return (unsigned char *)(p+2); | |||
| } | |||
| #define GLUE_SET_PX_FROM_WTP_SIZE sizeof(int) | |||
| static void GLUE_SET_PX_FROM_WTP(void *b, int wv) | |||
| { | |||
| *(unsigned int *)b = 0xaa1603e0 + wv; // mov x, x22 | |||
| } | |||
| static int GLUE_POP_FPSTACK_TO_PTR(unsigned char *buf, void *destptr) | |||
| { | |||
| if (buf) | |||
| { | |||
| unsigned int *bufptr = (unsigned int *)buf; | |||
| GLUE_MOV_PX_DIRECTVALUE_GEN(bufptr, (INT_PTR)destptr,0); | |||
| bufptr += GLUE_MOV_PX_DIRECTVALUE_SIZE/4; | |||
| *bufptr++ = 0xfd000000; // str d0, [x0] | |||
| } | |||
| return GLUE_MOV_PX_DIRECTVALUE_SIZE + sizeof(int); | |||
| } | |||
| #define GLUE_POP_FPSTACK_SIZE 0 | |||
| static const unsigned int GLUE_POP_FPSTACK[1] = { 0 }; // no need to pop, not a stack | |||
| static const unsigned int GLUE_POP_FPSTACK_TOSTACK[] = { | |||
| 0xfc1f0fe0, // str d0, [sp, #-16]! | |||
| }; | |||
| static const unsigned int GLUE_POP_FPSTACK_TO_WTP[] = { | |||
| 0xfc0086c0, // str d0, [x22], #8 | |||
| }; | |||
| #define GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE 4 | |||
| static void GLUE_PUSH_VAL_AT_PX_TO_FPSTACK(void *b, int wv) | |||
| { | |||
| *(unsigned int *)b = 0xfd400000 + (wv<<5); // ldr d0, [xX] | |||
| } | |||
| #define GLUE_POP_FPSTACK_TO_WTP_TO_PX_SIZE (sizeof(GLUE_POP_FPSTACK_TO_WTP) + GLUE_SET_PX_FROM_WTP_SIZE) | |||
| static void GLUE_POP_FPSTACK_TO_WTP_TO_PX(unsigned char *buf, int wv) | |||
| { | |||
| GLUE_SET_PX_FROM_WTP(buf,wv); | |||
| memcpy(buf + GLUE_SET_PX_FROM_WTP_SIZE,GLUE_POP_FPSTACK_TO_WTP,sizeof(GLUE_POP_FPSTACK_TO_WTP)); | |||
| }; | |||
| static const unsigned int GLUE_SET_P1_Z[] = { 0x52800000 }; // mov w0, #0 | |||
| static const unsigned int GLUE_SET_P1_NZ[] = { 0x52800020 }; // mov w0, #1 | |||
| static void *GLUE_realAddress(void *fn, int *size) | |||
| { | |||
| while ((*(int*)fn & 0xFC000000) == 0x14000000) | |||
| { | |||
| int offset = (*(int*)fn & 0x3FFFFFF); | |||
| if (offset & 0x2000000) | |||
| offset |= 0xFC000000; | |||
| fn = (int*)fn + offset; | |||
| } | |||
| static const unsigned int sig[3] = { 0xaa0003e0, 0xaa0103e1, 0xaa0203e2 }; | |||
| unsigned char *p = (unsigned char *)fn; | |||
| while (memcmp(p,sig,sizeof(sig))) p+=4; | |||
| p+=sizeof(sig); | |||
| fn = p; | |||
| while (memcmp(p,sig,sizeof(sig))) p+=4; | |||
| *size = p - (unsigned char *)fn; | |||
| return fn; | |||
| } | |||
| static unsigned long __attribute__((unused)) glue_getscr() | |||
| { | |||
| unsigned long rv; | |||
| asm volatile ( "mrs %0, fpcr" : "=r" (rv)); | |||
| return rv; | |||
| } | |||
| static void __attribute__((unused)) glue_setscr(unsigned long v) | |||
| { | |||
| asm volatile ( "msr fpcr, %0" :: "r"(v)); | |||
| } | |||
| void eel_enterfp(int _s[2]) | |||
| { | |||
| unsigned long *s = (unsigned long*)_s; | |||
| s[0] = glue_getscr(); | |||
| glue_setscr(s[0] | (1<<24)); | |||
| } | |||
| void eel_leavefp(int _s[2]) | |||
| { | |||
| unsigned long *s = (unsigned long*)_s; | |||
| glue_setscr(s[0]); | |||
| } | |||
| #define GLUE_HAS_FUSE 1 | |||
| static int GLUE_FUSE(compileContext *ctx, unsigned char *code, int left_size, int right_size, int fuse_flags, int spill_reg) | |||
| { | |||
| if (left_size>=4 && right_size == 4) | |||
| { | |||
| unsigned int instr = ((unsigned int *)code)[-1]; | |||
| if (spill_reg >= 0 && (instr & 0xfffffc1f) == 0x1e604001) // fmov d1, dX | |||
| { | |||
| const int src_reg = (instr>>5)&0x1f; | |||
| if (src_reg == spill_reg + 8) | |||
| { | |||
| instr = ((unsigned int *)code)[0]; | |||
| if ((instr & 0xffffcfff) == 0x1e600820) | |||
| { | |||
| ((unsigned int *)code)[-1] = instr + ((src_reg-1) << 5); | |||
| return -4; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| return 0; | |||
| } | |||
| #endif | |||
| @@ -0,0 +1,335 @@ | |||
| #ifndef _NSEEL_GLUE_ARM_H_ | |||
| #define _NSEEL_GLUE_ARM_H_ | |||
| // r0=return value, first parm, r1-r2 parms | |||
| // r3+ should be reserved | |||
| // blx addr | |||
| // stmfd sp!, {register list, lr} | |||
| // ldmfd sp!, {register list, pc} | |||
| // let's make r8 = worktable | |||
| // let's make r7 = ramtable | |||
| // r6 = consttab | |||
| // r5 = worktable ptr | |||
| // r0=p1 | |||
| // r1=p2 | |||
| // r2=p3 | |||
| // d0 is return value? | |||
| #define GLUE_HAS_FPREG2 1 | |||
| static const unsigned int GLUE_COPY_FPSTACK_TO_FPREG2[] = { | |||
| 0xeeb01b40 // fcpyd d1, d0 | |||
| }; | |||
| static unsigned int GLUE_POP_STACK_TO_FPREG2[] = { | |||
| 0xed9d1b00,// vldr d1, [sp] | |||
| 0xe28dd008,// add sp, sp, #8 | |||
| }; | |||
| #define GLUE_MAX_SPILL_REGS 8 | |||
| #define GLUE_SAVE_TO_SPILL_SIZE(x) (4) | |||
| #define GLUE_RESTORE_SPILL_TO_FPREG2_SIZE(x) (4) | |||
| static void GLUE_RESTORE_SPILL_TO_FPREG2(void *b, int ws) | |||
| { | |||
| *(unsigned int *)b = 0xeeb01b48 + ws; // fcpyd d1, d8+ws | |||
| } | |||
| static void GLUE_SAVE_TO_SPILL(void *b, int ws) | |||
| { | |||
| *(unsigned int *)b = 0xeeb08b40 + (ws<<12); // fcpyd d8+ws, d0 | |||
| } | |||
| #define GLUE_MAX_FPSTACK_SIZE 0 // no stack support | |||
| #define GLUE_MAX_JMPSIZE ((1<<25) - 1024) // maximum relative jump size | |||
| // endOfInstruction is end of jump with relative offset, offset passed in is offset from end of dest instruction. | |||
| // TODO: verify, but offset probably from next instruction (PC is ahead) | |||
| #define GLUE_JMP_SET_OFFSET(endOfInstruction,offset) (((int *)(endOfInstruction))[-1] = (((int *)(endOfInstruction))[-1]&0xFF000000)|((((offset)>>2)-1))) | |||
| // /=conditional=always = 0xE | |||
| // |/= 101(L), so 8+2+0 = 10 = A | |||
| static const unsigned int GLUE_JMP_NC[] = { 0xEA000000 }; | |||
| static const unsigned int GLUE_JMP_IF_P1_Z[]= | |||
| { | |||
| 0xe1100000, // tst r0, r0 | |||
| 0x0A000000, // branch if Z set | |||
| }; | |||
| static const unsigned int GLUE_JMP_IF_P1_NZ[]= | |||
| { | |||
| 0xe1100000, // tst r0, r0 | |||
| 0x1A000000, // branch if Z clear | |||
| }; | |||
| #define GLUE_MOV_PX_DIRECTVALUE_TOFPREG2_SIZE 12 // wr=-2, sets d1 | |||
| #define GLUE_MOV_PX_DIRECTVALUE_SIZE 8 | |||
| static void GLUE_MOV_PX_DIRECTVALUE_GEN(void *b, INT_PTR v, int wv) | |||
| { | |||
| // requires ARMv6thumb2 or later | |||
| const unsigned int reg_add = wdl_max(wv,0) << 12; | |||
| static const unsigned int tab[2] = { | |||
| 0xe3000000, // movw r0, #0000 | |||
| 0xe3400000, // movt r0, #0000 | |||
| }; | |||
| // 0xABAAA, B is register, A are bits of word | |||
| unsigned int *p=(unsigned int *)b; | |||
| p[0] = tab[0] | reg_add | (v&0xfff) | ((v&0xf000)<<4); | |||
| p[1] = tab[1] | reg_add | ((v>>16)&0xfff) | ((v&0xf0000000)>>12); | |||
| if (wv == -2) p[2] = 0xed901b00; // fldd d1, [r0] | |||
| } | |||
| const static unsigned int GLUE_FUNC_ENTER[1] = { 0xe92d4010 }; // push {r4, lr} | |||
| #define GLUE_FUNC_ENTER_SIZE 4 | |||
| const static unsigned int GLUE_FUNC_LEAVE[1] = { 0 }; // let GLUE_RET pop | |||
| #define GLUE_FUNC_LEAVE_SIZE 0 | |||
| const static unsigned int GLUE_RET[]={ 0xe8bd8010 }; // pop {r4, pc} | |||
| static int GLUE_RESET_WTP(unsigned char *out, void *ptr) | |||
| { | |||
| const static unsigned int GLUE_SET_WTP_FROM_R8 = 0xe1a05008; // mov r5, r8 | |||
| if (out) memcpy(out,&GLUE_SET_WTP_FROM_R8,sizeof(GLUE_SET_WTP_FROM_R8)); | |||
| return sizeof(GLUE_SET_WTP_FROM_R8); | |||
| } | |||
| const static unsigned int GLUE_PUSH_P1[1]={ 0xe52d0008 }; // push {r0}, aligned to 8 | |||
| static int arm_encode_constforalu(int amt) | |||
| { | |||
| int nrot = 16; | |||
| while (amt >= 0x100 && nrot > 1) | |||
| { | |||
| // ARM encodes integers for ALU operations as rotated right by third nibble*2 | |||
| amt = (amt + 3)>>2; | |||
| nrot--; | |||
| } | |||
| return ((nrot&15) << 8) | amt; | |||
| } | |||
| #define GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(x) ((x)>=4096 ? 8 : 4) | |||
| static void GLUE_STORE_P1_TO_STACK_AT_OFFS(void *b, int offs) | |||
| { | |||
| if (offs >= 4096) | |||
| { | |||
| // add r2, sp, (offs&~4095) | |||
| *(unsigned int *)b = 0xe28d2000 | arm_encode_constforalu(offs&~4095); | |||
| // str r0, [r2, offs&4095] | |||
| ((unsigned int *)b)[1] = 0xe5820000 + (offs&4095); | |||
| } | |||
| else | |||
| { | |||
| // str r0, [sp, #offs] | |||
| *(unsigned int *)b = 0xe58d0000 + offs; | |||
| } | |||
| } | |||
| #define GLUE_MOVE_PX_STACKPTR_SIZE 4 | |||
| static void GLUE_MOVE_PX_STACKPTR_GEN(void *b, int wv) | |||
| { | |||
| // mov rX, sp | |||
| *(unsigned int *)b = 0xe1a0000d + (wv<<12); | |||
| } | |||
| #define GLUE_MOVE_STACK_SIZE 4 | |||
| static void GLUE_MOVE_STACK(void *b, int amt) | |||
| { | |||
| unsigned int instr = 0xe28dd000; | |||
| if (amt < 0) | |||
| { | |||
| instr = 0xe24dd000; | |||
| amt=-amt; | |||
| } | |||
| *(unsigned int*)b = instr | arm_encode_constforalu(amt); | |||
| } | |||
| #define GLUE_POP_PX_SIZE 4 | |||
| static void GLUE_POP_PX(void *b, int wv) | |||
| { | |||
| ((unsigned int *)b)[0] = 0xe49d0008 | (wv<<12); // pop {rX}, aligned to 8 | |||
| } | |||
| #define GLUE_SET_PX_FROM_P1_SIZE 4 | |||
| static void GLUE_SET_PX_FROM_P1(void *b, int wv) | |||
| { | |||
| *(unsigned int *)b = 0xe1a00000 | (wv<<12); // mov rX, r0 | |||
| } | |||
| static const unsigned int GLUE_PUSH_P1PTR_AS_VALUE[] = | |||
| { | |||
| 0xed907b00, // fldd d7, [r0] | |||
| 0xe24dd008, // sub sp, sp, #8 | |||
| 0xed8d7b00, // fstd d7, [sp] | |||
| }; | |||
| static int GLUE_POP_VALUE_TO_ADDR(unsigned char *buf, void *destptr) | |||
| { | |||
| if (buf) | |||
| { | |||
| unsigned int *bufptr = (unsigned int *)buf; | |||
| *bufptr++ = 0xed9d7b00; // fldd d7, [sp] | |||
| *bufptr++ = 0xe28dd008; // add sp, sp, #8 | |||
| GLUE_MOV_PX_DIRECTVALUE_GEN(bufptr, (INT_PTR)destptr,0); | |||
| bufptr += GLUE_MOV_PX_DIRECTVALUE_SIZE/4; | |||
| *bufptr++ = 0xed807b00; // fstd d7, [r0] | |||
| } | |||
| return 3*4 + GLUE_MOV_PX_DIRECTVALUE_SIZE; | |||
| } | |||
| static int GLUE_COPY_VALUE_AT_P1_TO_PTR(unsigned char *buf, void *destptr) | |||
| { | |||
| if (buf) | |||
| { | |||
| unsigned int *bufptr = (unsigned int *)buf; | |||
| *bufptr++ = 0xed907b00; // fldd d7, [r0] | |||
| GLUE_MOV_PX_DIRECTVALUE_GEN(bufptr, (INT_PTR)destptr,0); | |||
| bufptr += GLUE_MOV_PX_DIRECTVALUE_SIZE/4; | |||
| *bufptr++ = 0xed807b00; // fstd d7, [r0] | |||
| } | |||
| return 2*4 + GLUE_MOV_PX_DIRECTVALUE_SIZE; | |||
| } | |||
| #ifndef _MSC_VER | |||
| #define GLUE_CALL_CODE(bp, cp, rt) do { \ | |||
| unsigned int f; \ | |||
| if (!(h->compile_flags&NSEEL_CODE_COMPILE_FLAG_NOFPSTATE) && \ | |||
| !((f=glue_getscr())&(1<<24))) { \ | |||
| glue_setscr(f|(1<<24)); \ | |||
| eel_callcode32(bp, cp, rt); \ | |||
| glue_setscr(f); \ | |||
| } else eel_callcode32(bp, cp, rt);\ | |||
| } while(0) | |||
| static const double __consttab[] = { | |||
| NSEEL_CLOSEFACTOR, | |||
| 0.0, | |||
| 1.0, | |||
| -1.0, | |||
| -0.5, // for invsqrt | |||
| 1.5, | |||
| }; | |||
| static void eel_callcode32(INT_PTR bp, INT_PTR cp, INT_PTR rt) | |||
| { | |||
| __asm__ volatile( | |||
| "mov r7, %2\n" | |||
| "mov r6, %3\n" | |||
| "mov r8, %1\n" | |||
| "mov r0, %0\n" | |||
| "mov r1, sp\n" | |||
| "bic sp, sp, #7\n" | |||
| "push {r1, lr}\n" | |||
| "blx r0\n" | |||
| "pop {r1, lr}\n" | |||
| "mov sp, r1\n" | |||
| ::"r" (cp), "r" (bp), "r" (rt), "r" (__consttab) : | |||
| "r5", "r6", "r7", "r8", "r10", | |||
| "d8","d9","d10","d11","d12","d13","d14","d15"); | |||
| }; | |||
| #endif | |||
| static unsigned char *EEL_GLUE_set_immediate(void *_p, INT_PTR newv) | |||
| { | |||
| unsigned int *p=(unsigned int *)_p; | |||
| while ((p[0]&0x000F0FFF) != 0x000d0ead && | |||
| (p[1]&0x000F0FFF) != 0x000b0eef) p++; | |||
| p[0] = (p[0]&0xFFF0F000) | (newv&0xFFF) | ((newv << 4) & 0xF0000); | |||
| p[1] = (p[1]&0xFFF0F000) | ((newv>>16)&0xFFF) | ((newv >> 12)&0xF0000); | |||
| return (unsigned char *)(p+1); | |||
| } | |||
| #define GLUE_SET_PX_FROM_WTP_SIZE sizeof(int) | |||
| static void GLUE_SET_PX_FROM_WTP(void *b, int wv) | |||
| { | |||
| *(unsigned int *)b = 0xe1a00005 + (wv<<12); // mov rX, r5 | |||
| } | |||
| static int GLUE_POP_FPSTACK_TO_PTR(unsigned char *buf, void *destptr) | |||
| { | |||
| if (buf) | |||
| { | |||
| unsigned int *bufptr = (unsigned int *)buf; | |||
| GLUE_MOV_PX_DIRECTVALUE_GEN(bufptr, (INT_PTR)destptr,0); | |||
| bufptr += GLUE_MOV_PX_DIRECTVALUE_SIZE/4; | |||
| *bufptr++ = 0xed800b00; // fstd d0, [r0] | |||
| } | |||
| return GLUE_MOV_PX_DIRECTVALUE_SIZE + sizeof(int); | |||
| } | |||
| #define GLUE_POP_FPSTACK_SIZE 0 | |||
| static const unsigned int GLUE_POP_FPSTACK[1] = { 0 }; // no need to pop, not a stack | |||
| static const unsigned int GLUE_POP_FPSTACK_TOSTACK[] = { | |||
| 0xe24dd008, // sub sp, sp, #8 | |||
| 0xed8d0b00, // fstd d0, [sp] | |||
| }; | |||
| static const unsigned int GLUE_POP_FPSTACK_TO_WTP[] = { | |||
| 0xed850b00, // fstd d0, [r5] | |||
| 0xe2855008, // add r5, r5, #8 | |||
| }; | |||
| #define GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE 4 | |||
| static void GLUE_PUSH_VAL_AT_PX_TO_FPSTACK(void *b, int wv) | |||
| { | |||
| *(unsigned int *)b = 0xed900b00 + (wv<<16); // fldd d0, [rX] | |||
| } | |||
| #define GLUE_POP_FPSTACK_TO_WTP_TO_PX_SIZE (sizeof(GLUE_POP_FPSTACK_TO_WTP) + GLUE_SET_PX_FROM_WTP_SIZE) | |||
| static void GLUE_POP_FPSTACK_TO_WTP_TO_PX(unsigned char *buf, int wv) | |||
| { | |||
| GLUE_SET_PX_FROM_WTP(buf,wv); | |||
| memcpy(buf + GLUE_SET_PX_FROM_WTP_SIZE,GLUE_POP_FPSTACK_TO_WTP,sizeof(GLUE_POP_FPSTACK_TO_WTP)); | |||
| }; | |||
| static const unsigned int GLUE_SET_P1_Z[] = { 0xe3a00000 }; // mov r0, #0 | |||
| static const unsigned int GLUE_SET_P1_NZ[] = { 0xe3a00001 }; // mov r0, #1 | |||
| static void *GLUE_realAddress(void *fn, int *size) | |||
| { | |||
| static const unsigned int sig[3] = { 0xe1a00000, 0xe1a01001, 0xe1a02002 }; | |||
| unsigned char *p = (unsigned char *)fn; | |||
| while (memcmp(p,sig,sizeof(sig))) p+=4; | |||
| p+=sizeof(sig); | |||
| fn = p; | |||
| while (memcmp(p,sig,sizeof(sig))) p+=4; | |||
| *size = p - (unsigned char *)fn; | |||
| return fn; | |||
| } | |||
| static unsigned int __attribute__((unused)) glue_getscr() | |||
| { | |||
| unsigned int rv; | |||
| asm volatile ( "fmrx %0, fpscr" : "=r" (rv)); | |||
| return rv; | |||
| } | |||
| static void __attribute__((unused)) glue_setscr(unsigned int v) | |||
| { | |||
| asm volatile ( "fmxr fpscr, %0" :: "r"(v)); | |||
| } | |||
| void eel_enterfp(int s[2]) | |||
| { | |||
| s[0] = glue_getscr(); | |||
| glue_setscr(s[0] | (1<<24)); // could also do 3<<22 for RTZ | |||
| } | |||
| void eel_leavefp(int s[2]) | |||
| { | |||
| glue_setscr(s[0]); | |||
| } | |||
| #endif | |||