// Options: /* ------------------------------------------------------------ name: "FaustSawtooth" Code generated with Faust 2.70.3 (https://faust.grame.fr) Compilation options: -a /opt/homebrew/Cellar/faust/2.70.3/share/faust/esp32/esp32.cpp -lang cpp -i -ct 1 -es 1 -mcd 16 -mdd 1024 -mdy 33 -single -ftz 0 ------------------------------------------------------------ */ #ifndef __mydsp_H__ #define __mydsp_H__ /************************************************************************ IMPORTANT NOTE : this file contains two clearly delimited sections : the ARCHITECTURE section (in two parts) and the USER section. Each section is governed by its own copyright and license. Please check individually each section for license and copyright information. *************************************************************************/ /*******************BEGIN ARCHITECTURE SECTION (part 1/2)****************/ /************************************************************************ FAUST Architecture File Copyright (C) 2019-2020 GRAME, Centre National de Creation Musicale & Aalborg University (Copenhagen, Denmark) --------------------------------------------------------------------- This Architecture section is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; If not, see . EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ************************************************************************ ************************************************************************/ #include "FaustSawtooth.h" /************************** BEGIN meta.h ******************************* FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ************************************************************************/ #ifndef __meta__ #define __meta__ /************************************************************************ FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ***************************************************************************/ #ifndef __export__ #define __export__ // Version as a global string #define FAUSTVERSION "2.70.3" // Version as separated [major,minor,patch] values #define FAUSTMAJORVERSION 2 #define FAUSTMINORVERSION 70 #define FAUSTPATCHVERSION 3 // Use FAUST_API for code that is part of the external API but is also compiled in faust and libfaust // Use LIBFAUST_API for code that is compiled in faust and libfaust #ifdef _WIN32 #pragma warning (disable: 4251) #ifdef FAUST_EXE #define FAUST_API #define LIBFAUST_API #elif FAUST_LIB #define FAUST_API __declspec(dllexport) #define LIBFAUST_API __declspec(dllexport) #else #define FAUST_API #define LIBFAUST_API #endif #else #ifdef FAUST_EXE #define FAUST_API #define LIBFAUST_API #else #define FAUST_API __attribute__((visibility("default"))) #define LIBFAUST_API __attribute__((visibility("default"))) #endif #endif #endif /** The base class of Meta handler to be used in dsp::metadata(Meta* m) method to retrieve (key, value) metadata. */ struct FAUST_API Meta { virtual ~Meta() {} virtual void declare(const char* key, const char* value) = 0; }; #endif /************************** END meta.h **************************/ /************************** BEGIN dsp.h ******************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ************************************************************************/ #ifndef __dsp__ #define __dsp__ #include #include #include #ifndef FAUSTFLOAT #define FAUSTFLOAT float #endif struct FAUST_API UI; struct FAUST_API Meta; /** * DSP memory manager. */ struct FAUST_API dsp_memory_manager { virtual ~dsp_memory_manager() {} /** * Inform the Memory Manager with the number of expected memory zones. * @param count - the number of expected memory zones */ virtual void begin(size_t /*count*/) {} /** * Give the Memory Manager information on a given memory zone. * @param size - the size in bytes of the memory zone * @param reads - the number of Read access to the zone used to compute one frame * @param writes - the number of Write access to the zone used to compute one frame */ virtual void info(size_t /*size*/, size_t /*reads*/, size_t /*writes*/) {} /** * Inform the Memory Manager that all memory zones have been described, * to possibly start a 'compute the best allocation strategy' step. */ virtual void end() {} /** * Allocate a memory zone. * @param size - the memory zone size in bytes */ virtual void* allocate(size_t size) = 0; /** * Destroy a memory zone. * @param ptr - the memory zone pointer to be deallocated */ virtual void destroy(void* ptr) = 0; }; /** * Signal processor definition. */ class FAUST_API dsp { public: dsp() {} virtual ~dsp() {} /* Return instance number of audio inputs */ virtual int getNumInputs() = 0; /* Return instance number of audio outputs */ virtual int getNumOutputs() = 0; /** * Trigger the ui_interface parameter with instance specific calls * to 'openTabBox', 'addButton', 'addVerticalSlider'... in order to build the UI. * * @param ui_interface - the user interface builder */ virtual void buildUserInterface(UI* ui_interface) = 0; /* Return the sample rate currently used by the instance */ virtual int getSampleRate() = 0; /** * Global init, calls the following methods: * - static class 'classInit': static tables initialization * - 'instanceInit': constants and instance state initialization * * @param sample_rate - the sampling rate in Hz */ virtual void init(int sample_rate) = 0; /** * Init instance state * * @param sample_rate - the sampling rate in Hz */ virtual void instanceInit(int sample_rate) = 0; /** * Init instance constant state * * @param sample_rate - the sampling rate in Hz */ virtual void instanceConstants(int sample_rate) = 0; /* Init default control parameters values */ virtual void instanceResetUserInterface() = 0; /* Init instance state (like delay lines...) but keep the control parameter values */ virtual void instanceClear() = 0; /** * Return a clone of the instance. * * @return a copy of the instance on success, otherwise a null pointer. */ virtual dsp* clone() = 0; /** * Trigger the Meta* parameter with instance specific calls to 'declare' (key, value) metadata. * * @param m - the Meta* meta user */ virtual void metadata(Meta* m) = 0; /** * DSP instance computation, to be called with successive in/out audio buffers. * * @param count - the number of frames to compute * @param inputs - the input audio buffers as an array of non-interleaved FAUSTFLOAT samples (eiher float, double or quad) * @param outputs - the output audio buffers as an array of non-interleaved FAUSTFLOAT samples (eiher float, double or quad) * */ virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) = 0; /** * DSP instance computation: alternative method to be used by subclasses. * * @param date_usec - the timestamp in microsec given by audio driver. * @param count - the number of frames to compute * @param inputs - the input audio buffers as an array of non-interleaved FAUSTFLOAT samples (either float, double or quad) * @param outputs - the output audio buffers as an array of non-interleaved FAUSTFLOAT samples (either float, double or quad) * */ virtual void compute(double /*date_usec*/, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { compute(count, inputs, outputs); } }; /** * Generic DSP decorator. */ class FAUST_API decorator_dsp : public dsp { protected: dsp* fDSP; public: decorator_dsp(dsp* dsp = nullptr):fDSP(dsp) {} virtual ~decorator_dsp() { delete fDSP; } virtual int getNumInputs() { return fDSP->getNumInputs(); } virtual int getNumOutputs() { return fDSP->getNumOutputs(); } virtual void buildUserInterface(UI* ui_interface) { fDSP->buildUserInterface(ui_interface); } virtual int getSampleRate() { return fDSP->getSampleRate(); } virtual void init(int sample_rate) { fDSP->init(sample_rate); } virtual void instanceInit(int sample_rate) { fDSP->instanceInit(sample_rate); } virtual void instanceConstants(int sample_rate) { fDSP->instanceConstants(sample_rate); } virtual void instanceResetUserInterface() { fDSP->instanceResetUserInterface(); } virtual void instanceClear() { fDSP->instanceClear(); } virtual decorator_dsp* clone() { return new decorator_dsp(fDSP->clone()); } virtual void metadata(Meta* m) { fDSP->metadata(m); } // Beware: subclasses usually have to overload the two 'compute' methods virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { fDSP->compute(count, inputs, outputs); } virtual void compute(double date_usec, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { fDSP->compute(date_usec, count, inputs, outputs); } }; /** * DSP factory class, used with LLVM and Interpreter backends * to create DSP instances from a compiled DSP program. */ class FAUST_API dsp_factory { protected: // So that to force sub-classes to use deleteDSPFactory(dsp_factory* factory); virtual ~dsp_factory() {} public: /* Return factory name */ virtual std::string getName() = 0; /* Return factory SHA key */ virtual std::string getSHAKey() = 0; /* Return factory expanded DSP code */ virtual std::string getDSPCode() = 0; /* Return factory compile options */ virtual std::string getCompileOptions() = 0; /* Get the Faust DSP factory list of library dependancies */ virtual std::vector getLibraryList() = 0; /* Get the list of all used includes */ virtual std::vector getIncludePathnames() = 0; /* Get warning messages list for a given compilation */ virtual std::vector getWarningMessages() = 0; /* Create a new DSP instance, to be deleted with C++ 'delete' */ virtual dsp* createDSPInstance() = 0; /* Static tables initialization, possibly implemened in sub-classes*/ virtual void classInit(int sample_rate) {}; /* Set a custom memory manager to be used when creating instances */ virtual void setMemoryManager(dsp_memory_manager* manager) = 0; /* Return the currently set custom memory manager */ virtual dsp_memory_manager* getMemoryManager() = 0; }; // Denormal handling #if defined (__SSE__) #include #endif class FAUST_API ScopedNoDenormals { private: intptr_t fpsr = 0; void setFpStatusRegister(intptr_t fpsr_aux) noexcept { #if defined (__arm64__) || defined (__aarch64__) asm volatile("msr fpcr, %0" : : "ri" (fpsr_aux)); #elif defined (__SSE__) // The volatile keyword here is needed to workaround a bug in AppleClang 13.0 // which aggressively optimises away the variable otherwise volatile uint32_t fpsr_w = static_cast(fpsr_aux); _mm_setcsr(fpsr_w); #endif } void getFpStatusRegister() noexcept { #if defined (__arm64__) || defined (__aarch64__) asm volatile("mrs %0, fpcr" : "=r" (fpsr)); #elif defined (__SSE__) fpsr = static_cast(_mm_getcsr()); #endif } public: ScopedNoDenormals() noexcept { #if defined (__arm64__) || defined (__aarch64__) intptr_t mask = (1 << 24 /* FZ */); #elif defined (__SSE__) #if defined (__SSE2__) intptr_t mask = 0x8040; #else intptr_t mask = 0x8000; #endif #else intptr_t mask = 0x0000; #endif getFpStatusRegister(); setFpStatusRegister(fpsr | mask); } ~ScopedNoDenormals() noexcept { setFpStatusRegister(fpsr); } }; #define AVOIDDENORMALS ScopedNoDenormals ftz_scope; #endif /************************** END dsp.h **************************/ /************************** BEGIN MapUI.h ****************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ***********************************************************************/ #ifndef FAUST_MAPUI_H #define FAUST_MAPUI_H #include #include #include #include /************************** BEGIN UI.h ***************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ********************************************************************/ #ifndef __UI_H__ #define __UI_H__ #ifndef FAUSTFLOAT #define FAUSTFLOAT float #endif /******************************************************************************* * UI : Faust DSP User Interface * User Interface as expected by the buildUserInterface() method of a DSP. * This abstract class contains only the method that the Faust compiler can * generate to describe a DSP user interface. ******************************************************************************/ struct Soundfile; template struct FAUST_API UIReal { UIReal() {} virtual ~UIReal() {} // -- widget's layouts virtual void openTabBox(const char* label) = 0; virtual void openHorizontalBox(const char* label) = 0; virtual void openVerticalBox(const char* label) = 0; virtual void closeBox() = 0; // -- active widgets virtual void addButton(const char* label, REAL* zone) = 0; virtual void addCheckButton(const char* label, REAL* zone) = 0; virtual void addVerticalSlider(const char* label, REAL* zone, REAL init, REAL min, REAL max, REAL step) = 0; virtual void addHorizontalSlider(const char* label, REAL* zone, REAL init, REAL min, REAL max, REAL step) = 0; virtual void addNumEntry(const char* label, REAL* zone, REAL init, REAL min, REAL max, REAL step) = 0; // -- passive widgets virtual void addHorizontalBargraph(const char* label, REAL* zone, REAL min, REAL max) = 0; virtual void addVerticalBargraph(const char* label, REAL* zone, REAL min, REAL max) = 0; // -- soundfiles virtual void addSoundfile(const char* label, const char* filename, Soundfile** sf_zone) = 0; // -- metadata declarations virtual void declare(REAL* /*zone*/, const char* /*key*/, const char* /*val*/) {} // To be used by LLVM client virtual int sizeOfFAUSTFLOAT() { return sizeof(FAUSTFLOAT); } }; struct FAUST_API UI : public UIReal { UI() {} virtual ~UI() {} }; #endif /************************** END UI.h **************************/ /************************** BEGIN PathBuilder.h ************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ************************************************************************/ #ifndef __PathBuilder__ #define __PathBuilder__ #include #include #include #include #include /******************************************************************************* * PathBuilder : Faust User Interface * Helper class to build complete hierarchical path for UI items. ******************************************************************************/ class FAUST_API PathBuilder { protected: std::vector fControlsLevel; std::vector fFullPaths; std::map fFull2Short; // filled by computeShortNames() /** * @brief check if a character is acceptable for an ID * * @param c * @return true is the character is acceptable for an ID */ bool isIDChar(char c) const { return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) || ((c >= '0') && (c <= '9')); } /** * @brief remove all "/0x00" parts * * @param src * @return modified string */ std::string remove0x00(const std::string& src_aux) const { std::string src = src_aux; std::string from = "/0x00"; std::string to = ""; for (size_t pos = src.find(from); pos != std::string::npos; pos = src.find(from, pos + 1)) { src.replace(pos, from.length(), to); } return src; } /** * @brief replace all non ID char with '_' (one '_' may replace several non ID char) * * @param src * @return modified string */ std::string str2ID(const std::string& src) const { std::string dst; bool need_underscore = false; for (char c : src) { if (isIDChar(c) || (c == '/')) { if (need_underscore) { dst.push_back('_'); need_underscore = false; } dst.push_back(c); } else { need_underscore = true; } } return dst; } /** * @brief Keep only the last n slash-parts * * @param src * @param n : 1 indicates the last slash-part * @return modified string */ std::string cut(const std::string& src, int n) const { std::string rdst; for (int i = int(src.length())-1; i >= 0; i--) { char c = src[i]; if (c != '/') { rdst.push_back(c); } else if (n == 1) { std::string dst; for (int j = int(rdst.length())-1; j >= 0; j--) { dst.push_back(rdst[j]); } return dst; } else { n--; rdst.push_back(c); } } return src; } void addFullPath(const std::string& label) { fFullPaths.push_back(buildPath(label)); } /** * @brief Compute the mapping between full path and short names */ void computeShortNames() { std::vector uniquePaths; // all full paths transformed but made unique with a prefix std::map unique2full; // all full paths transformed but made unique with a prefix char num_buffer[16]; int pnum = 0; for (const auto& s : fFullPaths) { // Using snprintf since Teensy does not have the std::to_string function snprintf(num_buffer, 16, "%d", pnum++); std::string u = "/P" + std::string(num_buffer) + str2ID(remove0x00(s)); uniquePaths.push_back(u); unique2full[u] = s; // remember the full path associated to a unique path } std::map uniquePath2level; // map path to level for (const auto& s : uniquePaths) uniquePath2level[s] = 1; // we init all levels to 1 bool have_collisions = true; while (have_collisions) { // compute collision list std::set collisionSet; std::map short2full; have_collisions = false; for (const auto& it : uniquePath2level) { std::string u = it.first; int n = it.second; std::string shortName = cut(u, n); auto p = short2full.find(shortName); if (p == short2full.end()) { // no collision short2full[shortName] = u; } else { // we have a collision, add the two paths to the collision set have_collisions = true; collisionSet.insert(u); collisionSet.insert(p->second); } } for (const auto& s : collisionSet) uniquePath2level[s]++; // increase level of colliding path } for (const auto& it : uniquePath2level) { std::string u = it.first; int n = it.second; std::string shortName = replaceCharList(cut(u, n), {'/'}, '_'); fFull2Short[unique2full[u]] = shortName; } } std::string replaceCharList(const std::string& str, const std::vector& ch1, char ch2) { auto beg = ch1.begin(); auto end = ch1.end(); std::string res = str; for (size_t i = 0; i < str.length(); ++i) { if (std::find(beg, end, str[i]) != end) res[i] = ch2; } return res; } public: PathBuilder() {} virtual ~PathBuilder() {} // Return true for the first level of groups bool pushLabel(const std::string& label) { fControlsLevel.push_back(label); return fControlsLevel.size() == 1;} // Return true for the last level of groups bool popLabel() { fControlsLevel.pop_back(); return fControlsLevel.size() == 0; } // Return a complete path built from a label std::string buildPath(const std::string& label) { std::string res = "/"; for (size_t i = 0; i < fControlsLevel.size(); i++) { res = res + fControlsLevel[i] + "/"; } res += label; return replaceCharList(res, {' ', '#', '*', ',', '?', '[', ']', '{', '}', '(', ')'}, '_'); } // Assuming shortnames have been built, return the shortname from a label std::string buildShortname(const std::string& label) { return (hasShortname()) ? fFull2Short[buildPath(label)] : ""; } bool hasShortname() { return fFull2Short.size() > 0; } }; #endif // __PathBuilder__ /************************** END PathBuilder.h **************************/ /******************************************************************************* * MapUI : Faust User Interface. * * This class creates: * - a map of 'labels' and zones for each UI item. * - a map of unique 'shortname' (built so that they never collide) and zones for each UI item * - a map of complete hierarchical 'paths' and zones for each UI item * * Simple 'labels', 'shortname' and complete 'paths' (to fully discriminate between possible same * 'labels' at different location in the UI hierachy) can be used to access a given parameter. ******************************************************************************/ class FAUST_API MapUI : public UI, public PathBuilder { protected: // Label zone map std::map fLabelZoneMap; // Shortname zone map std::map fShortnameZoneMap; // Full path map std::map fPathZoneMap; void addZoneLabel(const std::string& label, FAUSTFLOAT* zone) { std::string path = buildPath(label); fFullPaths.push_back(path); fPathZoneMap[path] = zone; fLabelZoneMap[label] = zone; } public: MapUI() {} virtual ~MapUI() {} // -- widget's layouts void openTabBox(const char* label) { pushLabel(label); } void openHorizontalBox(const char* label) { pushLabel(label); } void openVerticalBox(const char* label) { pushLabel(label); } void closeBox() { if (popLabel()) { // Shortnames can be computed when all fullnames are known computeShortNames(); // Fill 'shortname' map for (const auto& it : fFullPaths) { fShortnameZoneMap[fFull2Short[it]] = fPathZoneMap[it]; } } } // -- active widgets void addButton(const char* label, FAUSTFLOAT* zone) { addZoneLabel(label, zone); } void addCheckButton(const char* label, FAUSTFLOAT* zone) { addZoneLabel(label, zone); } void addVerticalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT fmin, FAUSTFLOAT fmax, FAUSTFLOAT step) { addZoneLabel(label, zone); } void addHorizontalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT fmin, FAUSTFLOAT fmax, FAUSTFLOAT step) { addZoneLabel(label, zone); } void addNumEntry(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT fmin, FAUSTFLOAT fmax, FAUSTFLOAT step) { addZoneLabel(label, zone); } // -- passive widgets void addHorizontalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT fmin, FAUSTFLOAT fmax) { addZoneLabel(label, zone); } void addVerticalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT fmin, FAUSTFLOAT fmax) { addZoneLabel(label, zone); } // -- soundfiles virtual void addSoundfile(const char* label, const char* filename, Soundfile** sf_zone) {} // -- metadata declarations virtual void declare(FAUSTFLOAT* zone, const char* key, const char* val) {} //------------------------------------------------------------------------------- // Public API //------------------------------------------------------------------------------- /** * Set the param value. * * @param str - the UI parameter label/shortname/path * @param value - the UI parameter value * */ void setParamValue(const std::string& str, FAUSTFLOAT value) { const auto fPathZoneMapIter = fPathZoneMap.find(str); if (fPathZoneMapIter != fPathZoneMap.end()) { *fPathZoneMapIter->second = value; return; } const auto fShortnameZoneMapIter = fShortnameZoneMap.find(str); if (fShortnameZoneMapIter != fShortnameZoneMap.end()) { *fShortnameZoneMapIter->second = value; return; } const auto fLabelZoneMapIter = fLabelZoneMap.find(str); if (fLabelZoneMapIter != fLabelZoneMap.end()) { *fLabelZoneMapIter->second = value; return; } fprintf(stderr, "ERROR : setParamValue '%s' not found\n", str.c_str()); } /** * Return the param value. * * @param str - the UI parameter label/shortname/path * * @return the param value. */ FAUSTFLOAT getParamValue(const std::string& str) { const auto fPathZoneMapIter = fPathZoneMap.find(str); if (fPathZoneMapIter != fPathZoneMap.end()) { return *fPathZoneMapIter->second; } const auto fShortnameZoneMapIter = fShortnameZoneMap.find(str); if (fShortnameZoneMapIter != fShortnameZoneMap.end()) { return *fShortnameZoneMapIter->second; } const auto fLabelZoneMapIter = fLabelZoneMap.find(str); if (fLabelZoneMapIter != fLabelZoneMap.end()) { return *fLabelZoneMapIter->second; } fprintf(stderr, "ERROR : getParamValue '%s' not found\n", str.c_str()); return 0; } // map access std::map& getFullpathMap() { return fPathZoneMap; } std::map& getShortnameMap() { return fShortnameZoneMap; } std::map& getLabelMap() { return fLabelZoneMap; } /** * Return the number of parameters in the UI. * * @return the number of parameters */ int getParamsCount() { return int(fPathZoneMap.size()); } /** * Return the param path. * * @param index - the UI parameter index * * @return the param path */ std::string getParamAddress(int index) { if (index < 0 || index > int(fPathZoneMap.size())) { return ""; } else { auto it = fPathZoneMap.begin(); while (index-- > 0 && it++ != fPathZoneMap.end()) {} return it->first; } } const char* getParamAddress1(int index) { if (index < 0 || index > int(fPathZoneMap.size())) { return nullptr; } else { auto it = fPathZoneMap.begin(); while (index-- > 0 && it++ != fPathZoneMap.end()) {} return it->first.c_str(); } } /** * Return the param shortname. * * @param index - the UI parameter index * * @return the param shortname */ std::string getParamShortname(int index) { if (index < 0 || index > int(fShortnameZoneMap.size())) { return ""; } else { auto it = fShortnameZoneMap.begin(); while (index-- > 0 && it++ != fShortnameZoneMap.end()) {} return it->first; } } const char* getParamShortname1(int index) { if (index < 0 || index > int(fShortnameZoneMap.size())) { return nullptr; } else { auto it = fShortnameZoneMap.begin(); while (index-- > 0 && it++ != fShortnameZoneMap.end()) {} return it->first.c_str(); } } /** * Return the param label. * * @param index - the UI parameter index * * @return the param label */ std::string getParamLabel(int index) { if (index < 0 || index > int(fLabelZoneMap.size())) { return ""; } else { auto it = fLabelZoneMap.begin(); while (index-- > 0 && it++ != fLabelZoneMap.end()) {} return it->first; } } const char* getParamLabel1(int index) { if (index < 0 || index > int(fLabelZoneMap.size())) { return nullptr; } else { auto it = fLabelZoneMap.begin(); while (index-- > 0 && it++ != fLabelZoneMap.end()) {} return it->first.c_str(); } } /** * Return the param path. * * @param zone - the UI parameter memory zone * * @return the param path */ std::string getParamAddress(FAUSTFLOAT* zone) { for (const auto& it : fPathZoneMap) { if (it.second == zone) return it.first; } return ""; } /** * Return the param memory zone. * * @param zone - the UI parameter label/shortname/path * * @return the param path */ FAUSTFLOAT* getParamZone(const std::string& str) { const auto fPathZoneMapIter = fPathZoneMap.find(str); if (fPathZoneMapIter != fPathZoneMap.end()) { return fPathZoneMapIter->second; } const auto fShortnameZoneMapIter = fShortnameZoneMap.find(str); if (fShortnameZoneMapIter != fShortnameZoneMap.end()) { return fShortnameZoneMapIter->second; } const auto fLabelZoneMapIter = fLabelZoneMap.find(str); if (fLabelZoneMapIter != fLabelZoneMap.end()) { return fLabelZoneMapIter->second; } return nullptr; } /** * Return the param memory zone. * * @param zone - the UI parameter index * * @return the param path */ FAUSTFLOAT* getParamZone(int index) { if (index < 0 || index > int(fPathZoneMap.size())) { return nullptr; } else { auto it = fPathZoneMap.begin(); while (index-- > 0 && it++ != fPathZoneMap.end()) {} return it->second; } } static bool endsWith(const std::string& str, const std::string& end) { size_t l1 = str.length(); size_t l2 = end.length(); return (l1 >= l2) && (0 == str.compare(l1 - l2, l2, end)); } }; #endif // FAUST_MAPUI_H /************************** END MapUI.h **************************/ /************************** BEGIN esp32audio.h ************************* FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ************************************************************************/ #ifndef __esp32audio__ #define __esp32audio__ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/i2s.h" /************************** BEGIN audio.h ***************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ***********************************************************************/ #ifndef __audio__ #define __audio__ #include #include class dsp; typedef void (* shutdown_callback)(const char* message, void* arg); typedef void (* compute_callback)(void* arg); class audio { protected: shutdown_callback fShutdown; // Shutdown callback void* fShutdownArg; // Shutdown callback data std::set > fComputeCallbackList; public: audio():fShutdown(nullptr), fShutdownArg(nullptr) {} virtual ~audio() {} /** * Init the DSP. * @param name - the DSP name to be given to the audio driven * (could appear as a JACK client for instance) * @param dsp - the dsp that will be initialized with the driver sample rate * * @return true is sucessful, false in case of driver failure. **/ virtual bool init(const char* name, dsp* dsp) = 0; /** * Start audio processing. * @return true is sucessful, false if case of driver failure. **/ virtual bool start() = 0; /** * Stop audio processing. **/ virtual void stop() = 0; void setShutdownCallback(shutdown_callback cb, void* arg) { fShutdown = cb; fShutdownArg = arg; } void addControlCallback(compute_callback cb, void* arg) { fComputeCallbackList.insert(std::make_pair(cb, arg)); } bool removeControlCallback(compute_callback cb, void* arg) { return (fComputeCallbackList.erase(std::make_pair(cb, arg)) == 1); } void runControlCallbacks() { for (const auto& it : fComputeCallbackList) { it.first(it.second); } } // Return buffer size in frames. virtual int getBufferSize() = 0; // Return the driver sample rate in Hz. virtual int getSampleRate() = 0; // Return the driver hardware inputs number. virtual int getNumInputs() = 0; // Return the driver hardware outputs number. virtual int getNumOutputs() = 0; /** * @return Returns the average proportion of available CPU * being spent inside the audio callbacks (between 0.0 and 1.0). **/ virtual float getCPULoad() { return 0.f; } }; #endif /************************** END audio.h **************************/ #define MULT_S32 2147483647 #define DIV_S32 4.6566129e-10 #define clip(sample) std::max(-MULT_S32, std::min(MULT_S32, ((int32_t)(sample * MULT_S32)))); #define AUDIO_MAX_CHAN 2 class esp32audio : public audio { private: int fSampleRate; int fBufferSize; int fNumInputs; int fNumOutputs; float** fInChannel; float** fOutChannel; TaskHandle_t fHandle; dsp* fDSP; bool fRunning; template void audioTask() { while (fRunning) { if (INPUTS > 0) { // Read from the card int32_t samples_data_in[AUDIO_MAX_CHAN*fBufferSize]; size_t bytes_read = 0; i2s_read((i2s_port_t)0, &samples_data_in, AUDIO_MAX_CHAN*sizeof(float)*fBufferSize, &bytes_read, portMAX_DELAY); // Convert and copy inputs if (INPUTS == AUDIO_MAX_CHAN) { // if stereo for (int i = 0; i < fBufferSize; i++) { fInChannel[0][i] = (float)samples_data_in[i*AUDIO_MAX_CHAN]*DIV_S32; fInChannel[1][i] = (float)samples_data_in[i*AUDIO_MAX_CHAN+1]*DIV_S32; } } else { // otherwise only first channel for (int i = 0; i < fBufferSize; i++) { fInChannel[0][i] = (float)samples_data_in[i*AUDIO_MAX_CHAN]*DIV_S32; } } } // Call DSP fDSP->compute(fBufferSize, fInChannel, fOutChannel); // Convert and copy outputs int32_t samples_data_out[AUDIO_MAX_CHAN*fBufferSize]; if (OUTPUTS == AUDIO_MAX_CHAN) { // if stereo for (int i = 0; i < fBufferSize; i++) { samples_data_out[i*AUDIO_MAX_CHAN] = clip(fOutChannel[0][i]); samples_data_out[i*AUDIO_MAX_CHAN+1] = clip(fOutChannel[1][i]); } } else { // otherwise only first channel for (int i = 0; i < fBufferSize; i++) { samples_data_out[i*AUDIO_MAX_CHAN] = clip(fOutChannel[0][i]); samples_data_out[i*AUDIO_MAX_CHAN+1] = samples_data_out[i*AUDIO_MAX_CHAN]; } } // Write to the card size_t bytes_written = 0; i2s_write((i2s_port_t)0, &samples_data_out, AUDIO_MAX_CHAN*sizeof(float)*fBufferSize, &bytes_written, portMAX_DELAY); } // Task has to deleted itself beforee returning vTaskDelete(nullptr); } void destroy() { for (int i = 0; i < fNumInputs; i++) { delete[] fInChannel[i]; } delete [] fInChannel; for (int i = 0; i < fNumOutputs; i++) { delete[] fOutChannel[i]; } delete [] fOutChannel; } static void audioTaskHandler(void* arg) { esp32audio* audio = static_cast(arg); if (audio->fNumInputs == 0 && audio->fNumOutputs == 1) { audio->audioTask<0,1>(); } else if (audio->fNumInputs == 0 && audio->fNumOutputs == 2) { audio->audioTask<0,2>(); } else if (audio->fNumInputs == 1 && audio->fNumOutputs == 1) { audio->audioTask<1,1>(); } else if (audio->fNumInputs == 1 && audio->fNumOutputs == 2) { audio->audioTask<1,2>(); } else if (audio->fNumInputs == 2 && audio->fNumOutputs == 1) { audio->audioTask<2,1>(); } else if (audio->fNumInputs == 2 && audio->fNumOutputs == 2) { audio->audioTask<2,2>(); } } public: esp32audio(int srate, int bsize): fSampleRate(srate), fBufferSize(bsize), fNumInputs(0), fNumOutputs(0), fInChannel(nullptr), fOutChannel(nullptr), fHandle(nullptr), fDSP(nullptr), fRunning(false) { i2s_pin_config_t pin_config; #if TTGO_TAUDIO pin_config = { .bck_io_num = 33, .ws_io_num = 25, .data_out_num = 26, .data_in_num = 27 }; #elif A1S_BOARD pin_config = { .bck_io_num = 27, .ws_io_num = 26, .data_out_num = 25, .data_in_num = 35 }; #elif LYRA_T pin_config = { .bck_io_num = 5, .ws_io_num = 25, .data_out_num = 26, .data_in_num = 35 }; #else // Default pin_config = { .bck_io_num = 33, .ws_io_num = 25, .data_out_num = 26, .data_in_num = 27 }; #endif #if A1S_BOARD i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX), .sample_rate = uint32_t(fSampleRate), .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), .intr_alloc_flags = ESP_INTR_FLAG_LEVEL3, // high interrupt priority .dma_buf_count = 3, .dma_buf_len = fBufferSize, .use_apll = true }; #else // default i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX), .sample_rate = uint32_t(fSampleRate), .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // high interrupt priority .dma_buf_count = 3, .dma_buf_len = fBufferSize, .use_apll = false }; #endif i2s_driver_install((i2s_port_t)0, &i2s_config, 0, nullptr); i2s_set_pin((i2s_port_t)0, &pin_config); PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); REG_WRITE(PIN_CTRL, 0xFFFFFFF0); } virtual ~esp32audio() { destroy(); } virtual bool init(const char* name, dsp* dsp) { destroy(); fDSP = dsp; fNumInputs = fDSP->getNumInputs(); fNumOutputs = fDSP->getNumOutputs(); fDSP->init(fSampleRate); if (fNumInputs > 0) { fInChannel = new FAUSTFLOAT*[fNumInputs]; for (int i = 0; i < fNumInputs; i++) { fInChannel[i] = new FAUSTFLOAT[fBufferSize]; } } else { fInChannel = nullptr; } if (fNumOutputs > 0) { fOutChannel = new FAUSTFLOAT*[fNumOutputs]; for (int i = 0; i < fNumOutputs; i++) { fOutChannel[i] = new FAUSTFLOAT[fBufferSize]; } } else { fOutChannel = nullptr; } return true; } virtual bool start() { if (!fRunning) { fRunning = true; return (xTaskCreatePinnedToCore(audioTaskHandler, "Faust DSP Task", 4096, (void*)this, 24, &fHandle, 0) == pdPASS); } else { return true; } } virtual void stop() { if (fRunning) { fRunning = false; vTaskDelay(1/portTICK_PERIOD_MS); fHandle = nullptr; } } virtual int getBufferSize() { return fBufferSize; } virtual int getSampleRate() { return fSampleRate; } virtual int getNumInputs() { return AUDIO_MAX_CHAN; } virtual int getNumOutputs() { return AUDIO_MAX_CHAN; } // Returns the average proportion of available CPU being spent inside the audio callbacks (between 0 and 1.0). virtual float getCPULoad() { return 0.f; } }; #endif /************************** END esp32audio.h **************************/ #ifdef SOUNDFILE #define ESP32 /************************** BEGIN SoundUI.h ************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ********************************************************************/ #ifndef __SoundUI_H__ #define __SoundUI_H__ #include #include #include #include #include /************************** BEGIN SimpleParser.h ********************* FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ********************************************************************/ #ifndef SIMPLEPARSER_H #define SIMPLEPARSER_H // --------------------------------------------------------------------- // Simple Parser // A parser returns true if it was able to parse what it is // supposed to parse and advance the pointer. Otherwise it returns false // and the pointer is not advanced so that another parser can be tried. // --------------------------------------------------------------------- #include #include #include #include #include #include #include // We use the lighter fprintf code #include #include #ifndef _WIN32 # pragma GCC diagnostic ignored "-Wunused-function" #endif struct itemInfo { std::string type; std::string label; std::string shortname; std::string address; std::string url; int index; double init; double fmin; double fmax; double step; std::vector > meta; itemInfo():index(0), init(0.), fmin(0.), fmax(0.), step(0.) {} }; // --------------------------------------------------------------------- // Elementary parsers // --------------------------------------------------------------------- // Report a parsing error static bool parseError(const char*& p, const char* errmsg) { fprintf(stderr, "Parse error : %s here : %s\n", errmsg, p); return true; } /** * @brief skipBlank : advance pointer p to the first non blank character * @param p the string to parse, then the remaining string */ static void skipBlank(const char*& p) { while (isspace(*p)) { p++; } } // Parse character x, but don't report error if fails static bool tryChar(const char*& p, char x) { skipBlank(p); if (x == *p) { p++; return true; } else { return false; } } /** * @brief parseChar : parse a specific character x * @param p the string to parse, then the remaining string * @param x the character to recognize * @return true if x was found at the begin of p */ static bool parseChar(const char*& p, char x) { skipBlank(p); if (x == *p) { p++; return true; } else { return false; } } /** * @brief parseWord : parse a specific string w * @param p the string to parse, then the remaining string * @param w the string to recognize * @return true if string w was found at the begin of p */ static bool parseWord(const char*& p, const char* w) { skipBlank(p); const char* saved = p; // to restore position if we fail while ((*w == *p) && (*w)) {++w; ++p;} if (*w) { p = saved; return false; } else { return true; } } /** * @brief parseDouble : parse number [s]dddd[.dddd] or [s]d[.dddd][E|e][s][dddd] and store the result in x * @param p the string to parse, then the remaining string * @param x the float number found if any * @return true if a float number was found at the begin of p */ static bool parseDouble(const char*& p, double& x) { double sign = 1.0; // sign of the number double ipart = 0; // integral part of the number double dpart = 0; // decimal part of the number before division double dcoef = 1.0; // division factor for the decimal part double expsign = 1.0; // sign of the E|e part double expcoef = 0.0; // multiplication factor of E|e part bool valid = false; // true if the number contains at least one digit skipBlank(p); const char* saved = p; // to restore position if we fail // Sign if (parseChar(p, '+')) { sign = 1.0; } else if (parseChar(p, '-')) { sign = -1.0; } // Integral part while (isdigit(*p)) { valid = true; ipart = ipart*10 + (*p - '0'); p++; } // Possible decimal part if (parseChar(p, '.')) { while (isdigit(*p)) { valid = true; dpart = dpart*10 + (*p - '0'); dcoef *= 10.0; p++; } } // Possible E|e part if (parseChar(p, 'E') || parseChar(p, 'e')) { if (parseChar(p, '+')) { expsign = 1.0; } else if (parseChar(p, '-')) { expsign = -1.0; } while (isdigit(*p)) { expcoef = expcoef*10 + (*p - '0'); p++; } } if (valid) { x = (sign*(ipart + dpart/dcoef)) * std::pow(10.0, expcoef*expsign); } else { p = saved; } return valid; } /** * @brief parseString, parse an arbitrary quoted string q...q and store the result in s * @param p the string to parse, then the remaining string * @param quote the character used to quote the string * @param s the (unquoted) string found if any * @return true if a string was found at the begin of p */ static bool parseString(const char*& p, char quote, std::string& s) { std::string str; skipBlank(p); const char* saved = p; // to restore position if we fail if (*p++ == quote) { while ((*p != 0) && (*p != quote)) { str += *p++; } if (*p++ == quote) { s = str; return true; } } p = saved; return false; } /** * @brief parseSQString, parse a single quoted string '...' and store the result in s * @param p the string to parse, then the remaining string * @param s the (unquoted) string found if any * @return true if a string was found at the begin of p */ static bool parseSQString(const char*& p, std::string& s) { return parseString(p, '\'', s); } /** * @brief parseDQString, parse a double quoted string "..." and store the result in s * @param p the string to parse, then the remaining string * @param s the (unquoted) string found if any * @return true if a string was found at the begin of p */ static bool parseDQString(const char*& p, std::string& s) { return parseString(p, '"', s); } // --------------------------------------------------------------------- // // IMPLEMENTATION // // --------------------------------------------------------------------- /** * @brief parseMenuItem, parse a menu item ...'low':440.0... * @param p the string to parse, then the remaining string * @param name the name found * @param value the value found * @return true if a nemu item was found */ static bool parseMenuItem(const char*& p, std::string& name, double& value) { const char* saved = p; // to restore position if we fail if (parseSQString(p, name) && parseChar(p, ':') && parseDouble(p, value)) { return true; } else { p = saved; return false; } } static bool parseMenuItem2(const char*& p, std::string& name) { const char* saved = p; // to restore position if we fail // single quoted if (parseSQString(p, name)) { return true; } else { p = saved; return false; } } /** * @brief parseMenuList, parse a menu list {'low' : 440.0; 'mid' : 880.0; 'hi' : 1760.0}... * @param p the string to parse, then the remaining string * @param names the vector of names found * @param values the vector of values found * @return true if a menu list was found */ static bool parseMenuList(const char*& p, std::vector& names, std::vector& values) { std::vector tmpnames; std::vector tmpvalues; const char* saved = p; // to restore position if we fail if (parseChar(p, '{')) { do { std::string n; double v; if (parseMenuItem(p, n, v)) { tmpnames.push_back(n); tmpvalues.push_back(v); } else { p = saved; return false; } } while (parseChar(p, ';')); if (parseChar(p, '}')) { // we suceeded names = tmpnames; values = tmpvalues; return true; } } p = saved; return false; } static bool parseMenuList2(const char*& p, std::vector& names, bool debug) { std::vector tmpnames; const char* saved = p; // to restore position if we fail if (parseChar(p, '{')) { do { std::string n; if (parseMenuItem2(p, n)) { tmpnames.push_back(n); } else { goto error; } } while (parseChar(p, ';')); if (parseChar(p, '}')) { // we suceeded names = tmpnames; return true; } } error: if (debug) { fprintf(stderr, "parseMenuList2 : (%s) is not a valid list !\n", p); } p = saved; return false; } /// --------------------------------------------------------------------- // Parse list of strings /// --------------------------------------------------------------------- static bool parseList(const char*& p, std::vector& items) { const char* saved = p; // to restore position if we fail if (parseChar(p, '[')) { do { std::string item; if (!parseDQString(p, item)) { p = saved; return false; } items.push_back(item); } while (tryChar(p, ',')); return parseChar(p, ']'); } else { p = saved; return false; } } static bool parseMetaData(const char*& p, std::map& metadatas) { const char* saved = p; // to restore position if we fail std::string metaKey, metaValue; if (parseChar(p, ':') && parseChar(p, '[')) { do { if (parseChar(p, '{') && parseDQString(p, metaKey) && parseChar(p, ':') && parseDQString(p, metaValue) && parseChar(p, '}')) { metadatas[metaKey] = metaValue; } } while (tryChar(p, ',')); return parseChar(p, ']'); } else { p = saved; return false; } } static bool parseItemMetaData(const char*& p, std::vector >& metadatas) { const char* saved = p; // to restore position if we fail std::string metaKey, metaValue; if (parseChar(p, ':') && parseChar(p, '[')) { do { if (parseChar(p, '{') && parseDQString(p, metaKey) && parseChar(p, ':') && parseDQString(p, metaValue) && parseChar(p, '}')) { metadatas.push_back(std::make_pair(metaKey, metaValue)); } } while (tryChar(p, ',')); return parseChar(p, ']'); } else { p = saved; return false; } } // --------------------------------------------------------------------- // Parse metadatas of the interface: // "name" : "...", "inputs" : "...", "outputs" : "...", ... // and store the result as key/value /// --------------------------------------------------------------------- static bool parseGlobalMetaData(const char*& p, std::string& key, std::string& value, double& dbl, std::map& metadatas, std::vector& items) { const char* saved = p; // to restore position if we fail if (parseDQString(p, key)) { if (key == "meta") { return parseMetaData(p, metadatas); } else { return parseChar(p, ':') && (parseDQString(p, value) || parseList(p, items) || parseDouble(p, dbl)); } } else { p = saved; return false; } } // --------------------------------------------------------------------- // Parse gui: // "type" : "...", "label" : "...", "address" : "...", ... // and store the result in uiItems Vector /// --------------------------------------------------------------------- static bool parseUI(const char*& p, std::vector& uiItems, int& numItems) { const char* saved = p; // to restore position if we fail if (parseChar(p, '{')) { std::string label; std::string value; double dbl = 0; do { if (parseDQString(p, label)) { if (label == "type") { if (uiItems.size() != 0) { numItems++; } if (parseChar(p, ':') && parseDQString(p, value)) { itemInfo item; item.type = value; uiItems.push_back(item); } } else if (label == "label") { if (parseChar(p, ':') && parseDQString(p, value)) { uiItems[numItems].label = value; } } else if (label == "shortname") { if (parseChar(p, ':') && parseDQString(p, value)) { uiItems[numItems].shortname = value; } } else if (label == "address") { if (parseChar(p, ':') && parseDQString(p, value)) { uiItems[numItems].address = value; } } else if (label == "url") { if (parseChar(p, ':') && parseDQString(p, value)) { uiItems[numItems].url = value; } } else if (label == "index") { if (parseChar(p, ':') && parseDouble(p, dbl)) { uiItems[numItems].index = int(dbl); } } else if (label == "meta") { if (!parseItemMetaData(p, uiItems[numItems].meta)) { return false; } } else if (label == "init") { if (parseChar(p, ':') && parseDouble(p, dbl)) { uiItems[numItems].init = dbl; } } else if (label == "min") { if (parseChar(p, ':') && parseDouble(p, dbl)) { uiItems[numItems].fmin = dbl; } } else if (label == "max") { if (parseChar(p, ':') && parseDouble(p, dbl)) { uiItems[numItems].fmax = dbl; } } else if (label == "step") { if (parseChar(p, ':') && parseDouble(p, dbl)) { uiItems[numItems].step = dbl; } } else if (label == "items") { if (parseChar(p, ':') && parseChar(p, '[')) { do { if (!parseUI(p, uiItems, numItems)) { p = saved; return false; } } while (tryChar(p, ',')); if (parseChar(p, ']')) { itemInfo item; item.type = "close"; uiItems.push_back(item); numItems++; } } } else { fprintf(stderr, "Parse error unknown : %s \n", label.c_str()); assert(false); } } else { p = saved; return false; } } while (tryChar(p, ',')); return parseChar(p, '}'); } else { return true; // "items": [] is valid } } // --------------------------------------------------------------------- // Parse full JSON record describing a JSON/Faust interface : // {"metadatas": "...", "ui": [{ "type": "...", "label": "...", "items": [...], "address": "...","init": "...", "min": "...", "max": "...","step": "..."}]} // // and store the result in map Metadatas and vector containing the items of the interface. Returns true if parsing was successfull. /// --------------------------------------------------------------------- static bool parseJson(const char*& p, std::map >& metaDatas0, std::map& metaDatas1, std::map >& metaDatas2, std::vector& uiItems) { parseChar(p, '{'); do { std::string key; std::string value; double dbl = 0; std::vector items; if (parseGlobalMetaData(p, key, value, dbl, metaDatas1, items)) { if (key != "meta") { // keep "name", "inputs", "outputs" key/value pairs if (items.size() > 0) { metaDatas2[key] = items; items.clear(); } else if (value != "") { metaDatas0[key].first = value; } else { metaDatas0[key].second = dbl; } } } else if (key == "ui") { int numItems = 0; parseChar(p, '[') && parseUI(p, uiItems, numItems); } } while (tryChar(p, ',')); return parseChar(p, '}'); } #endif // SIMPLEPARSER_H /************************** END SimpleParser.h **************************/ /************************** BEGIN DecoratorUI.h ************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. *************************************************************************/ #ifndef Decorator_UI_H #define Decorator_UI_H //---------------------------------------------------------------- // Generic UI empty implementation //---------------------------------------------------------------- class FAUST_API GenericUI : public UI { public: GenericUI() {} virtual ~GenericUI() {} // -- widget's layouts virtual void openTabBox(const char* label) {} virtual void openHorizontalBox(const char* label) {} virtual void openVerticalBox(const char* label) {} virtual void closeBox() {} // -- active widgets virtual void addButton(const char* label, FAUSTFLOAT* zone) {} virtual void addCheckButton(const char* label, FAUSTFLOAT* zone) {} virtual void addVerticalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) {} virtual void addHorizontalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) {} virtual void addNumEntry(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) {} // -- passive widgets virtual void addHorizontalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) {} virtual void addVerticalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) {} // -- soundfiles virtual void addSoundfile(const char* label, const char* soundpath, Soundfile** sf_zone) {} virtual void declare(FAUSTFLOAT* zone, const char* key, const char* val) {} }; //---------------------------------------------------------------- // Generic UI decorator //---------------------------------------------------------------- class FAUST_API DecoratorUI : public UI { protected: UI* fUI; public: DecoratorUI(UI* ui = 0):fUI(ui) {} virtual ~DecoratorUI() { delete fUI; } // -- widget's layouts virtual void openTabBox(const char* label) { fUI->openTabBox(label); } virtual void openHorizontalBox(const char* label) { fUI->openHorizontalBox(label); } virtual void openVerticalBox(const char* label) { fUI->openVerticalBox(label); } virtual void closeBox() { fUI->closeBox(); } // -- active widgets virtual void addButton(const char* label, FAUSTFLOAT* zone) { fUI->addButton(label, zone); } virtual void addCheckButton(const char* label, FAUSTFLOAT* zone) { fUI->addCheckButton(label, zone); } virtual void addVerticalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) { fUI->addVerticalSlider(label, zone, init, min, max, step); } virtual void addHorizontalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) { fUI->addHorizontalSlider(label, zone, init, min, max, step); } virtual void addNumEntry(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) { fUI->addNumEntry(label, zone, init, min, max, step); } // -- passive widgets virtual void addHorizontalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) { fUI->addHorizontalBargraph(label, zone, min, max); } virtual void addVerticalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) { fUI->addVerticalBargraph(label, zone, min, max); } // -- soundfiles virtual void addSoundfile(const char* label, const char* filename, Soundfile** sf_zone) { fUI->addSoundfile(label, filename, sf_zone); } virtual void declare(FAUSTFLOAT* zone, const char* key, const char* val) { fUI->declare(zone, key, val); } }; // Defined here to simplify header #include inclusion class FAUST_API SoundUIInterface : public GenericUI {}; #endif /************************** END DecoratorUI.h **************************/ #if defined(__APPLE__) && !defined(__VCVRACK__) && !defined(JUCE_32BIT) && !defined(JUCE_64BIT) #include #endif // Always included otherwise -i mode later on will not always include it (with the conditional includes) /************************** BEGIN Soundfile.h ************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ********************************************************************/ #ifndef __Soundfile__ #define __Soundfile__ #include #include #include #ifndef FAUSTFLOAT #define FAUSTFLOAT float #endif #define BUFFER_SIZE 1024 #define SAMPLE_RATE 44100 #define MAX_CHAN 64 #define MAX_SOUNDFILE_PARTS 256 #ifdef _MSC_VER #define PRE_PACKED_STRUCTURE __pragma(pack(push, 1)) #define POST_PACKED_STRUCTURE \ ; \ __pragma(pack(pop)) #else #define PRE_PACKED_STRUCTURE #define POST_PACKED_STRUCTURE __attribute__((__packed__)) #endif /* The soundfile structure to be used by the DSP code. Soundfile has a MAX_SOUNDFILE_PARTS parts (even a single soundfile or an empty soundfile). The fLength, fOffset and fSR fields are filled accordingly by repeating the actual parts if needed. The fBuffers contains MAX_CHAN non-interleaved arrays of samples. It has to be 'packed' to that the LLVM backend can correctly access it. Index computation: - p is the current part number [0..MAX_SOUNDFILE_PARTS-1] (must be proved by the type system) - i is the current position in the part. It will be constrained between [0..length] - idx(p,i) = fOffset[p] + max(0, min(i, fLength[p])); */ PRE_PACKED_STRUCTURE struct Soundfile { void* fBuffers; // will correspond to a double** or float** pointer chosen at runtime int* fLength; // length of each part (so fLength[P] contains the length in frames of part P) int* fSR; // sample rate of each part (so fSR[P] contains the SR of part P) int* fOffset; // offset of each part in the global buffer (so fOffset[P] contains the offset in frames of part P) int fChannels; // max number of channels of all concatenated files int fParts; // the total number of loaded parts bool fIsDouble; // keep the sample format (float or double) Soundfile(int cur_chan, int length, int max_chan, int total_parts, bool is_double) { fLength = new int[MAX_SOUNDFILE_PARTS]; fSR = new int[MAX_SOUNDFILE_PARTS]; fOffset = new int[MAX_SOUNDFILE_PARTS]; fIsDouble = is_double; fChannels = cur_chan; fParts = total_parts; if (fIsDouble) { fBuffers = allocBufferReal(cur_chan, length, max_chan); } else { fBuffers = allocBufferReal(cur_chan, length, max_chan); } } template void* allocBufferReal(int cur_chan, int length, int max_chan) { REAL** buffers = new REAL*[max_chan]; for (int chan = 0; chan < cur_chan; chan++) { buffers[chan] = new REAL[length]; memset(buffers[chan], 0, sizeof(REAL) * length); } return buffers; } void copyToOut(int size, int channels, int max_channels, int offset, void* buffer) { if (fIsDouble) { copyToOutReal(size, channels, max_channels, offset, buffer); } else { copyToOutReal(size, channels, max_channels, offset, buffer); } } void shareBuffers(int cur_chan, int max_chan) { // Share the same buffers for all other channels so that we have max_chan channels available if (fIsDouble) { for (int chan = cur_chan; chan < max_chan; chan++) { static_cast(fBuffers)[chan] = static_cast(fBuffers)[chan % cur_chan]; } } else { for (int chan = cur_chan; chan < max_chan; chan++) { static_cast(fBuffers)[chan] = static_cast(fBuffers)[chan % cur_chan]; } } } template void copyToOutReal(int size, int channels, int max_channels, int offset, void* buffer) { for (int sample = 0; sample < size; sample++) { for (int chan = 0; chan < channels; chan++) { static_cast(fBuffers)[chan][offset + sample] = static_cast(buffer)[sample * max_channels + chan]; } } } template void getBuffersOffsetReal(void* buffers, int offset) { for (int chan = 0; chan < fChannels; chan++) { static_cast(buffers)[chan] = &(static_cast(fBuffers))[chan][offset]; } } void emptyFile(int part, int& offset) { fLength[part] = BUFFER_SIZE; fSR[part] = SAMPLE_RATE; fOffset[part] = offset; // Update offset offset += fLength[part]; } ~Soundfile() { // Free the real channels only if (fIsDouble) { for (int chan = 0; chan < fChannels; chan++) { delete[] static_cast(fBuffers)[chan]; } delete[] static_cast(fBuffers); } else { for (int chan = 0; chan < fChannels; chan++) { delete[] static_cast(fBuffers)[chan]; } delete[] static_cast(fBuffers); } delete[] fLength; delete[] fSR; delete[] fOffset; } typedef std::vector Directories; } POST_PACKED_STRUCTURE; /* The generic soundfile reader. */ class SoundfileReader { protected: int fDriverSR; // Check if a soundfile exists and return its real path_name std::string checkFile(const Soundfile::Directories& sound_directories, const std::string& file_name) { if (checkFile(file_name)) { return file_name; } else { for (size_t i = 0; i < sound_directories.size(); i++) { std::string path_name = sound_directories[i] + "/" + file_name; if (checkFile(path_name)) { return path_name; } } return ""; } } bool isResampling(int sample_rate) { return (fDriverSR > 0 && fDriverSR != sample_rate); } // To be implemented by subclasses /** * Check the availability of a sound resource. * * @param path_name - the name of the file, or sound resource identified this way * * @return true if the sound resource is available, false otherwise. */ virtual bool checkFile(const std::string& path_name) = 0; /** * Check the availability of a sound resource. * * @param buffer - the sound buffer * @param size - the sound buffer length * * @return true if the sound resource is available, false otherwise. */ virtual bool checkFile(unsigned char* buffer, size_t size) { return true; } /** * Get the channels and length values of the given sound resource. * * @param path_name - the name of the file, or sound resource identified this way * @param channels - the channels value to be filled with the sound resource number of channels * @param length - the length value to be filled with the sound resource length in frames * */ virtual void getParamsFile(const std::string& path_name, int& channels, int& length) = 0; /** * Get the channels and length values of the given sound resource. * * @param buffer - the sound buffer * @param size - the sound buffer length * @param channels - the channels value to be filled with the sound resource number of channels * @param length - the length value to be filled with the sound resource length in frames * */ virtual void getParamsFile(unsigned char* buffer, size_t size, int& channels, int& length) {} /** * Read one sound resource and fill the 'soundfile' structure accordingly * * @param soundfile - the soundfile to be filled * @param path_name - the name of the file, or sound resource identified this way * @param part - the part number to be filled in the soundfile * @param offset - the offset value to be incremented with the actual sound resource length in frames * @param max_chan - the maximum number of mono channels to fill * */ virtual void readFile(Soundfile* soundfile, const std::string& path_name, int part, int& offset, int max_chan) = 0; /** * Read one sound resource and fill the 'soundfile' structure accordingly * * @param soundfile - the soundfile to be filled * @param buffer - the sound buffer * @param size - the sound buffer length * @param part - the part number to be filled in the soundfile * @param offset - the offset value to be incremented with the actual sound resource length in frames * @param max_chan - the maximum number of mono channels to fill * */ virtual void readFile(Soundfile* soundfile, unsigned char* buffer, size_t size, int part, int& offset, int max_chan) {} public: SoundfileReader() {} virtual ~SoundfileReader() {} void setSampleRate(int sample_rate) { fDriverSR = sample_rate; } Soundfile* createSoundfile(const std::vector& path_name_list, int max_chan, bool is_double) { try { int cur_chan = 1; // At least one channel int total_length = 0; // Compute total length and channels max of all files for (size_t i = 0; i < path_name_list.size(); i++) { int chan, length; if (path_name_list[i] == "__empty_sound__") { length = BUFFER_SIZE; chan = 1; } else { getParamsFile(path_name_list[i], chan, length); } cur_chan = std::max(cur_chan, chan); total_length += length; } // Complete with empty parts total_length += (MAX_SOUNDFILE_PARTS - path_name_list.size()) * BUFFER_SIZE; // Create the soundfile Soundfile* soundfile = new Soundfile(cur_chan, total_length, max_chan, path_name_list.size(), is_double); // Init offset int offset = 0; // Read all files for (size_t i = 0; i < path_name_list.size(); i++) { if (path_name_list[i] == "__empty_sound__") { soundfile->emptyFile(i, offset); } else { readFile(soundfile, path_name_list[i], i, offset, max_chan); } } // Complete with empty parts for (size_t i = path_name_list.size(); i < MAX_SOUNDFILE_PARTS; i++) { soundfile->emptyFile(i, offset); } // Share the same buffers for all other channels so that we have max_chan channels available soundfile->shareBuffers(cur_chan, max_chan); return soundfile; } catch (...) { return nullptr; } } // Check if all soundfiles exist and return their real path_name std::vector checkFiles(const Soundfile::Directories& sound_directories, const std::vector& file_name_list) { std::vector path_name_list; for (size_t i = 0; i < file_name_list.size(); i++) { std::string path_name = checkFile(sound_directories, file_name_list[i]); // If 'path_name' is not found, it is replaced by an empty sound (= silence) path_name_list.push_back((path_name == "") ? "__empty_sound__" : path_name); } return path_name_list; } }; #endif /************************** END Soundfile.h **************************/ #if defined(JUCE_32BIT) || defined(JUCE_64BIT) /************************** BEGIN JuceReader.h ************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ************************************************************************/ #ifndef __JuceReader__ #define __JuceReader__ #include #include "../JuceLibraryCode/JuceHeader.h" struct JuceReader : public SoundfileReader { juce::AudioFormatManager fFormatManager; JuceReader() { fFormatManager.registerBasicFormats(); } virtual ~JuceReader() {} bool checkFile(const std::string& path_name) override { juce::File file = juce::File::getCurrentWorkingDirectory().getChildFile(path_name); if (file.existsAsFile()) { return true; } else { //std::cerr << "ERROR : cannot open '" << path_name << "'" << std::endl; return false; } } void getParamsFile(const std::string& path_name, int& channels, int& length) override { std::unique_ptr formatReader (fFormatManager.createReaderFor (juce::File::getCurrentWorkingDirectory().getChildFile(path_name))); channels = int(formatReader->numChannels); length = int(formatReader->lengthInSamples); } void readFile(Soundfile* soundfile, const std::string& path_name, int part, int& offset, int max_chan) override { std::unique_ptr formatReader (fFormatManager.createReaderFor (juce::File::getCurrentWorkingDirectory().getChildFile(path_name))); soundfile->fLength[part] = int(formatReader->lengthInSamples); soundfile->fSR[part] = int(formatReader->sampleRate); soundfile->fOffset[part] = offset; void* buffers; if (soundfile->fIsDouble) { buffers = alloca(soundfile->fChannels * sizeof(double*)); soundfile->getBuffersOffsetReal(buffers, offset); } else { buffers = alloca(soundfile->fChannels * sizeof(float*)); soundfile->getBuffersOffsetReal(buffers, offset); } if (formatReader->read(reinterpret_cast(buffers), int(formatReader->numChannels), 0, int(formatReader->lengthInSamples), false)) { // Possibly convert samples if (!formatReader->usesFloatingPointData) { for (int chan = 0; chan < int(formatReader->numChannels); ++chan) { if (soundfile->fIsDouble) { // TODO } else { float* buffer = &(static_cast(soundfile->fBuffers))[chan][soundfile->fOffset[part]]; juce::FloatVectorOperations::convertFixedToFloat(buffer, reinterpret_cast(buffer), 1.0f/0x7fffffff, int(formatReader->lengthInSamples)); } } } } else { std::cerr << "Error reading the file : " << path_name << std::endl; } // Update offset offset += soundfile->fLength[part]; } }; #endif /************************** END JuceReader.h **************************/ static JuceReader gReader; #elif defined(DAISY) || defined(SUPERCOLLIDER) /************************** BEGIN WaveReader.h ************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ********************************************************************/ #ifndef __WaveReader__ #define __WaveReader__ #include #include #include // WAVE file description typedef struct { // The canonical WAVE format starts with the RIFF header /** Variable: chunk_id Contains the letters "RIFF" in ASCII form (0x52494646 big-endian form). **/ int chunk_id; /** Variable: chunk_size 36 + SubChunk2Size, or more precisely: 4 + (8 + SubChunk1Size) + (8 + SubChunk2Size) This is the size of the rest of the chunk following this number. This is the size of the entire file in bytes minus 8 bytes for the two fields not included in this count: ChunkID and ChunkSize. **/ int chunk_size; /** Variable: format Contains the letters "WAVE" (0x57415645 big-endian form). **/ int format; // The "WAVE" format consists of two subchunks: "fmt " and "data": // The "fmt " subchunk describes the sound data's format: /** Variable: subchunk_1_id Contains the letters "fmt " (0x666d7420 big-endian form). **/ int subchunk_1_id; /** Variable: subchunk_1_size 16 for PCM. This is the size of the rest of the Subchunk which follows this number. **/ int subchunk_1_size; /** Variable: audio_format PCM = 1 (i.e. Linear quantization) Values other than 1 indicate some form of compression. **/ short audio_format; /** Variable: num_channels Mono = 1, Stereo = 2, etc. **/ short num_channels; /** Variable: sample_rate 8000, 44100, etc. **/ int sample_rate; /** Variable: byte_rate == SampleRate * NumChannels * BitsPerSample/8 **/ int byte_rate; /** Variable: block_align == NumChannels * BitsPerSample/8 The number of bytes for one sample including all channels. I wonder what happens when this number isn't an integer? **/ short block_align; /** Variable: bits_per_sample 8 bits = 8, 16 bits = 16, etc. **/ short bits_per_sample; /** Here should come some extra parameters which i will avoid. **/ // The "data" subchunk contains the size of the data and the actual sound: /** Variable: subchunk_2_id Contains the letters "data" (0x64617461 big-endian form). **/ int subchunk_2_id; /** Variable: subchunk_2_size == NumSamples * NumChannels * BitsPerSample/8 This is the number of bytes in the data. You can also think of this as the size of the read of the subchunk following this number. **/ int subchunk_2_size; /** Variable: data The actual sound data. **/ char* data; } wave_t; // Base reader struct Reader { wave_t* fWave; inline int is_big_endian() { int a = 1; return !((char*)&a)[0]; } inline int convert_to_int(char* buffer, int len) { int a = 0; if (!is_big_endian()) { for(int i = 0; i < len; i++) { ((char*)&a)[i] = buffer[i]; } } else { for(int i = 0; i < len; i++) { ((char*)&a)[3-i] = buffer[i]; } } return a; } Reader() { fWave = (wave_t*)calloc(1, sizeof(wave_t)); } virtual ~Reader() { free(fWave->data); free(fWave); } bool load_wave_header() { char buffer[4]; read(buffer, 4); if (strncmp(buffer, "RIFF", 4) != 0) { fprintf(stderr, "This is not valid WAV file!\n"); return false; } fWave->chunk_id = convert_to_int(buffer, 4); read(buffer, 4); fWave->chunk_size = convert_to_int(buffer, 4); read(buffer, 4); fWave->format = convert_to_int(buffer, 4); read(buffer, 4); fWave->subchunk_1_id = convert_to_int(buffer, 4); read(buffer, 4); fWave->subchunk_1_size = convert_to_int(buffer, 4); read(buffer, 2); fWave->audio_format = convert_to_int(buffer, 2); read(buffer, 2); fWave->num_channels = convert_to_int(buffer, 2); read(buffer, 4); fWave->sample_rate = convert_to_int(buffer, 4); read(buffer, 4); fWave->byte_rate = convert_to_int(buffer, 4); read(buffer, 2); fWave->block_align = convert_to_int(buffer, 2); read(buffer, 2); fWave->bits_per_sample = convert_to_int(buffer, 2); read(buffer, 4); if (strncmp(buffer, "data", 4) != 0) { read(buffer, 4); int _extra_size = convert_to_int(buffer, 4); char _extra_data[_extra_size]; read(_extra_data, _extra_size); read(buffer, 4); fWave->subchunk_2_id = convert_to_int(buffer, 4); } else { fWave->subchunk_2_id = convert_to_int(buffer, 4); } read(buffer, 4); fWave->subchunk_2_size = convert_to_int(buffer, 4); return true; } void load_wave() { // Read sound data fWave->data = (char*)malloc(fWave->subchunk_2_size); read(fWave->data, fWave->subchunk_2_size); } virtual void read(char* buffer, unsigned int size) = 0; }; struct FileReader : public Reader { FILE* fFile; FileReader(const std::string& file_path) { fFile = fopen(file_path.c_str(), "rb"); if (!fFile) { fprintf(stderr, "FileReader : cannot open file!\n"); throw -1; } if (!load_wave_header()) { fprintf(stderr, "FileReader : not a WAV file!\n"); throw -1; } } virtual ~FileReader() { fclose(fFile); } void read(char* buffer, unsigned int size) { fread(buffer, 1, size, fFile); } }; extern const uint8_t file_start[] asm("_binary_FILE_start"); extern const uint8_t file_end[] asm("_binary_FILE_end"); struct MemoryReader : public Reader { int fPos; const uint8_t* fStart; const uint8_t* fEnd; MemoryReader(const uint8_t* start, const uint8_t* end):fPos(0) { fStart = start; fEnd = end; if (!load_wave_header()) { fprintf(stderr, "MemoryReader : not a WAV file!\n"); throw -1; } } virtual ~MemoryReader() {} void read(char* buffer, unsigned int size) { memcpy(buffer, fStart + fPos, size); fPos += size; } }; // Using a FileReader to implement SoundfileReader struct WaveReader : public SoundfileReader { WaveReader() {} virtual ~WaveReader() {} bool checkFile(const std::string& path_name) override { try { FileReader reader(path_name); return true; } catch (...) { return false; } } void getParamsFile(const std::string& path_name, int& channels, int& length) override { FileReader reader(path_name); channels = reader.fWave->num_channels; length = (reader.fWave->subchunk_2_size * 8) / (reader.fWave->num_channels * reader.fWave->bits_per_sample); } void readFile(Soundfile* soundfile, const std::string& path_name, int part, int& offset, int max_chan) override { FileReader reader(path_name); reader.load_wave(); soundfile->fLength[part] = (reader.fWave->subchunk_2_size * 8) / (reader.fWave->num_channels * reader.fWave->bits_per_sample); soundfile->fSR[part] = reader.fWave->sample_rate; soundfile->fOffset[part] = offset; // Audio frames have to be written for each chan if (reader.fWave->bits_per_sample == 16) { float factor = 1.f/32767.f; for (int sample = 0; sample < soundfile->fLength[part]; sample++) { short* frame = (short*)&reader.fWave->data[reader.fWave->block_align * sample]; if (soundfile->fIsDouble) { for (int chan = 0; chan < reader.fWave->num_channels; chan++) { static_cast(soundfile->fBuffers)[chan][offset + sample] = frame[chan] * factor; } } else { for (int chan = 0; chan < reader.fWave->num_channels; chan++) { static_cast(soundfile->fBuffers)[chan][offset + sample] = frame[chan] * factor; } } } } else if (reader.fWave->bits_per_sample == 32) { fprintf(stderr, "readFile : not implemented\n"); } // Update offset offset += soundfile->fLength[part]; } }; #endif /************************** END WaveReader.h **************************/ static WaveReader gReader; #elif defined(ESP32) /************************** BEGIN Esp32Reader.h ************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. *************************************************************************/ #ifndef FAUST_ESP32READER_H #define FAUST_ESP32READER_H #include #include "esp_err.h" #include "esp_log.h" #include "esp_spi_flash.h" #include "esp_vfs_fat.h" #include "driver/sdspi_host.h" #include "sdmmc_cmd.h" #define TAG "Esp32Reader" #define SD_PIN_NUM_MISO GPIO_NUM_2 #define SD_PIN_NUM_MOSI GPIO_NUM_15 #define SD_PIN_NUM_CLK GPIO_NUM_14 #define SD_PIN_NUM_CS GPIO_NUM_13 struct Esp32Reader : public WaveReader { void sdcard_init() { ESP_LOGI(TAG, "Initializing SD card"); ESP_LOGI(TAG, "Using SPI peripheral"); sdmmc_host_t host = SDSPI_HOST_DEFAULT(); sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT(); slot_config.gpio_miso = SD_PIN_NUM_MISO; slot_config.gpio_mosi = SD_PIN_NUM_MOSI; slot_config.gpio_sck = SD_PIN_NUM_CLK; slot_config.gpio_cs = SD_PIN_NUM_CS; // This initializes the slot without card detect (CD) and write protect (WP) signals. // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals. // Options for mounting the filesystem. // If format_if_mount_failed is set to true, SD card will be partitioned and // formatted in case when mounting fails. esp_vfs_fat_sdmmc_mount_config_t mount_config = { .format_if_mount_failed = false, .max_files = 5, .allocation_unit_size = 16 * 1024 }; // Use settings defined above to initialize SD card and mount FAT filesystem. // Note: esp_vfs_fat_sdmmc_mount is an all-in-one convenience function. // Please check its source code and implement error recovery when developing // production applications. sdmmc_card_t* card; esp_err_t ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card); if (ret != ESP_OK) { if (ret == ESP_FAIL) { ESP_LOGE(TAG, "Failed to mount filesystem. " "If you want the card to be formatted, set format_if_mount_failed = true."); } else { ESP_LOGE(TAG, "Failed to initialize the card (%s). " "Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret)); } return; } // Card has been initialized, print its properties sdmmc_card_print_info(stdout, card); ESP_LOGI(TAG, "SD card initialized"); } Esp32Reader() { sdcard_init(); } // Access methods inherited from WaveReader }; #endif // FAUST_ESP32READER_H /************************** END Esp32Reader.h **************************/ static Esp32Reader gReader; #elif defined(MEMORY_READER) /************************** BEGIN MemoryReader.h ************************ FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ************************************************************************/ #ifndef __MemoryReader__ #define __MemoryReader__ /* A 'MemoryReader' object can be used to prepare a set of sound resources in memory, to be used by SoundUI::addSoundfile. A Soundfile* object will have to be filled with a list of sound resources: the fLength, fOffset, fSampleRate and fBuffers fields have to be completed with the appropriate values, and will be accessed in the DSP object while running. * */ // To adapt for a real case use #define SOUND_CHAN 2 #define SOUND_LENGTH 4096 #define SOUND_SR 44100 struct MemoryReader : public SoundfileReader { MemoryReader() {} virtual ~MemoryReader() {} /** * Check the availability of a sound resource. * * @param path_name - the name of the file, or sound resource identified this way * * @return true if the sound resource is available, false otherwise. */ virtual bool checkFile(const std::string& path_name) override { return true; } /** * Get the channels and length values of the given sound resource. * * @param path_name - the name of the file, or sound resource identified this way * @param channels - the channels value to be filled with the sound resource number of channels * @param length - the length value to be filled with the sound resource length in frames * */ virtual void getParamsFile(const std::string& path_name, int& channels, int& length) override { channels = SOUND_CHAN; length = SOUND_LENGTH; } /** * Read one sound resource and fill the 'soundfile' structure accordingly * * @param path_name - the name of the file, or sound resource identified this way * @param part - the part number to be filled in the soundfile * @param offset - the offset value to be incremented with the actual sound resource length in frames * @param max_chan - the maximum number of mono channels to fill * */ virtual void readFile(Soundfile* soundfile, const std::string& path_name, int part, int& offset, int max_chan) override { soundfile->fLength[part] = SOUND_LENGTH; soundfile->fSR[part] = SOUND_SR; soundfile->fOffset[part] = offset; // Audio frames have to be written for each chan if (soundfile->fIsDouble) { for (int sample = 0; sample < SOUND_LENGTH; sample++) { for (int chan = 0; chan < SOUND_CHAN; chan++) { static_cast(soundfile->fBuffers)[chan][offset + sample] = 0.f; } } } else { for (int sample = 0; sample < SOUND_LENGTH; sample++) { for (int chan = 0; chan < SOUND_CHAN; chan++) { static_cast(soundfile->fBuffers)[chan][offset + sample] = 0.f; } } } // Update offset offset += SOUND_LENGTH; } }; #endif /************************** END MemoryReader.h **************************/ static MemoryReader gReader; #else /************************** BEGIN LibsndfileReader.h ********************* FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ************************************************************************/ #ifndef __LibsndfileReader__ #define __LibsndfileReader__ #ifdef _SAMPLERATE #include #endif #include #include #include #include #include /* // Deactivated for now, since the macOS remote cross-compiler fails with this code. #if __has_include() && __cplusplus >= 201703L #define HAS_FILESYSTEM #include namespace fs = std::filesystem; #elif __has_include() && __cplusplus >= 201103L #define HAS_FILESYSTEM #include namespace fs = std::experimental::filesystem; #endif */ struct VFLibsndfile { #define SIGNED_SIZEOF(x) ((int)sizeof(x)) unsigned char* fBuffer; size_t fLength; size_t fOffset; SF_VIRTUAL_IO fVIO; VFLibsndfile(unsigned char* buffer, size_t length):fBuffer(buffer), fLength(length), fOffset(0) { fVIO.get_filelen = vfget_filelen; fVIO.seek = vfseek; fVIO.read = vfread; fVIO.write = vfwrite; fVIO.tell = vftell; } static sf_count_t vfget_filelen(void* user_data) { VFLibsndfile* vf = static_cast(user_data); return vf->fLength; } static sf_count_t vfseek(sf_count_t offset, int whence, void* user_data) { VFLibsndfile* vf = static_cast(user_data); switch (whence) { case SEEK_SET: vf->fOffset = offset; break; case SEEK_CUR: vf->fOffset = vf->fOffset + offset; break; case SEEK_END: vf->fOffset = vf->fLength + offset; break; default: break; }; return vf->fOffset; } static sf_count_t vfread(void* ptr, sf_count_t count, void* user_data) { VFLibsndfile* vf = static_cast(user_data); /* ** This will break badly for files over 2Gig in length, but ** is sufficient for testing. */ if (vf->fOffset + count > vf->fLength) { count = vf->fLength - vf->fOffset; } memcpy(ptr, vf->fBuffer + vf->fOffset, count); vf->fOffset += count; return count; } static sf_count_t vfwrite(const void* ptr, sf_count_t count, void* user_data) { VFLibsndfile* vf = static_cast(user_data); /* ** This will break badly for files over 2Gig in length, but ** is sufficient for testing. */ if (vf->fOffset >= SIGNED_SIZEOF(vf->fBuffer)) { return 0; } if (vf->fOffset + count > SIGNED_SIZEOF(vf->fBuffer)) { count = sizeof (vf->fBuffer) - vf->fOffset; } memcpy(vf->fBuffer + vf->fOffset, ptr, (size_t)count); vf->fOffset += count; if (vf->fOffset > vf->fLength) { vf->fLength = vf->fOffset; } return count; } static sf_count_t vftell(void* user_data) { VFLibsndfile* vf = static_cast(user_data); return vf->fOffset; } }; struct LibsndfileReader : public SoundfileReader { LibsndfileReader() {} typedef sf_count_t (* sample_read)(SNDFILE* sndfile, void* buffer, sf_count_t frames); // Check file bool checkFile(const std::string& path_name) override { /* // Better since it supports Unicode characters. #ifdef HAS_FILESYSTEM if (!fs::exists(path_name)) { std::cerr << "FILE NOT FOUND\n"; return false; } #endif */ std::ofstream ofs; ofs.open(path_name, std::ios_base::in); if (!ofs.is_open()) { return false; } SF_INFO snd_info; snd_info.format = 0; SNDFILE* snd_file = sf_open(path_name.c_str(), SFM_READ, &snd_info); return checkFileAux(snd_file, path_name); } bool checkFile(unsigned char* buffer, size_t length) override { SF_INFO snd_info; snd_info.format = 0; VFLibsndfile vio(buffer, length); SNDFILE* snd_file = sf_open_virtual(&vio.fVIO, SFM_READ, &snd_info, &vio); return checkFileAux(snd_file, "virtual file"); } bool checkFileAux(SNDFILE* snd_file, const std::string& path_name) { if (snd_file) { sf_close(snd_file); return true; } else { std::cerr << "ERROR : cannot open '" << path_name << "' (" << sf_strerror(NULL) << ")" << std::endl; return false; } } // Open the file and returns its length and channels void getParamsFile(const std::string& path_name, int& channels, int& length) override { SF_INFO snd_info; snd_info.format = 0; SNDFILE* snd_file = sf_open(path_name.c_str(), SFM_READ, &snd_info); getParamsFileAux(snd_file, snd_info, channels, length); } void getParamsFile(unsigned char* buffer, size_t size, int& channels, int& length) override { SF_INFO snd_info; snd_info.format = 0; VFLibsndfile vio(buffer, size); SNDFILE* snd_file = sf_open_virtual(&vio.fVIO, SFM_READ, &snd_info, &vio); getParamsFileAux(snd_file, snd_info, channels, length); } void getParamsFileAux(SNDFILE* snd_file, const SF_INFO& snd_info, int& channels, int& length) { assert(snd_file); channels = int(snd_info.channels); #ifdef _SAMPLERATE length = (isResampling(snd_info.samplerate)) ? ((double(snd_info.frames) * double(fDriverSR) / double(snd_info.samplerate)) + BUFFER_SIZE) : int(snd_info.frames); #else length = int(snd_info.frames); #endif sf_close(snd_file); } // Read the file void readFile(Soundfile* soundfile, const std::string& path_name, int part, int& offset, int max_chan) override { SF_INFO snd_info; snd_info.format = 0; SNDFILE* snd_file = sf_open(path_name.c_str(), SFM_READ, &snd_info); readFileAux(soundfile, snd_file, snd_info, part, offset, max_chan); } void readFile(Soundfile* soundfile, unsigned char* buffer, size_t length, int part, int& offset, int max_chan) override { SF_INFO snd_info; snd_info.format = 0; VFLibsndfile vio(buffer, length); SNDFILE* snd_file = sf_open_virtual(&vio.fVIO, SFM_READ, &snd_info, &vio); readFileAux(soundfile, snd_file, snd_info, part, offset, max_chan); } // Will be called to fill all parts from 0 to MAX_SOUNDFILE_PARTS-1 void readFileAux(Soundfile* soundfile, SNDFILE* snd_file, const SF_INFO& snd_info, int part, int& offset, int max_chan) { assert(snd_file); int channels = std::min(max_chan, snd_info.channels); #ifdef _SAMPLERATE if (isResampling(snd_info.samplerate)) { soundfile->fLength[part] = int(double(snd_info.frames) * double(fDriverSR) / double(snd_info.samplerate)); soundfile->fSR[part] = fDriverSR; } else { soundfile->fLength[part] = int(snd_info.frames); soundfile->fSR[part] = snd_info.samplerate; } #else soundfile->fLength[part] = int(snd_info.frames); soundfile->fSR[part] = snd_info.samplerate; #endif soundfile->fOffset[part] = offset; // Read and fill snd_info.channels number of channels sf_count_t nbf; sample_read reader; void* buffer_in = nullptr; if (soundfile->fIsDouble) { buffer_in = static_cast(alloca(BUFFER_SIZE * sizeof(double) * snd_info.channels)); reader = reinterpret_cast(sf_readf_double); } else { buffer_in = static_cast(alloca(BUFFER_SIZE * sizeof(float) * snd_info.channels)); reader = reinterpret_cast(sf_readf_float); } #ifdef _SAMPLERATE // Resampling SRC_STATE* resampler = nullptr; float* src_buffer_out = nullptr; float* src_buffer_in = nullptr; void* buffer_out = nullptr; if (isResampling(snd_info.samplerate)) { int error; resampler = src_new(SRC_SINC_FASTEST, channels, &error); if (error != 0) { std::cerr << "ERROR : src_new " << src_strerror(error) << std::endl; throw -1; } if (soundfile->fIsDouble) { // Additional buffers for SRC resampling src_buffer_in = static_cast(alloca(BUFFER_SIZE * sizeof(float) * snd_info.channels)); src_buffer_out = static_cast(alloca(BUFFER_SIZE * sizeof(float) * snd_info.channels)); buffer_out = static_cast(alloca(BUFFER_SIZE * sizeof(double) * snd_info.channels)); } else { buffer_out = static_cast(alloca(BUFFER_SIZE * sizeof(float) * snd_info.channels)); } } #endif do { nbf = reader(snd_file, buffer_in, BUFFER_SIZE); #ifdef _SAMPLERATE // Resampling if (isResampling(snd_info.samplerate)) { int in_offset = 0; SRC_DATA src_data; src_data.src_ratio = double(fDriverSR)/double(snd_info.samplerate); if (soundfile->fIsDouble) { for (int frame = 0; frame < (BUFFER_SIZE * snd_info.channels); frame++) { src_buffer_in[frame] = float(static_cast(buffer_in)[frame]); } } do { if (soundfile->fIsDouble) { src_data.data_in = src_buffer_in; src_data.data_out = src_buffer_out; } else { src_data.data_in = static_cast(buffer_in); src_data.data_out = static_cast(buffer_out); } src_data.input_frames = nbf - in_offset; src_data.output_frames = BUFFER_SIZE; src_data.end_of_input = (nbf < BUFFER_SIZE); int res = src_process(resampler, &src_data); if (res != 0) { std::cerr << "ERROR : src_process " << src_strerror(res) << std::endl; throw -1; } if (soundfile->fIsDouble) { for (int frame = 0; frame < (BUFFER_SIZE * snd_info.channels); frame++) { static_cast(buffer_out)[frame] = double(src_buffer_out[frame]); } } soundfile->copyToOut(src_data.output_frames_gen, channels, snd_info.channels, offset, buffer_out); in_offset += src_data.input_frames_used; // Update offset offset += src_data.output_frames_gen; } while (in_offset < nbf); } else { soundfile->copyToOut(nbf, channels, snd_info.channels, offset, buffer_in); // Update offset offset += nbf; } #else soundfile->copyToOut(nbf, channels, snd_info.channels, offset, buffer_in); // Update offset offset += nbf; #endif } while (nbf == BUFFER_SIZE); sf_close(snd_file); #ifdef _SAMPLERATE if (resampler) src_delete(resampler); #endif } }; #endif /************************** END LibsndfileReader.h **************************/ static LibsndfileReader gReader; #endif // To be used by DSP code if no SoundUI is used static std::vector gPathNameList; static Soundfile* defaultsound = nullptr; class SoundUI : public SoundUIInterface { protected: // The soundfile directories Soundfile::Directories fSoundfileDir; // Map to share loaded soundfiles std::map> fSoundfileMap; // The soundfile reader std::shared_ptr fSoundReader; bool fIsDouble; public: /** * Create a soundfile loader which will typically use a concrete SoundfileReader like LibsndfileReader or JuceReader to load soundfiles. * * @param sound_directory - the base directory to look for files, which paths will be relative to this one * @param sample_rate - the audio driver SR which may be different from the file SR, to possibly resample files * @param reader - an alternative soundfile reader * @param is_double - whether Faust code has been compiled in -double mode and soundfile buffers have to be in double * * @return the soundfile loader. */ SoundUI(const std::string& sound_directory = "", int sample_rate = -1, SoundfileReader* reader = nullptr, bool is_double = false) { fSoundfileDir.push_back(sound_directory); fSoundReader = (reader) ? std::shared_ptr(reader) // the static gReader should not be deleted, so use an empty destructor : std::shared_ptr(std::shared_ptr{}, &gReader); fSoundReader->setSampleRate(sample_rate); fIsDouble = is_double; if (!defaultsound) defaultsound = gReader.createSoundfile(gPathNameList, MAX_CHAN, is_double); } /** * Create a soundfile loader which will typically use a concrete SoundfileReader like LibsndfileReader or JuceReader to load soundfiles. * * @param sound_directories - a vector of base directories to look for files, which paths will be relative to these ones * @param sample_rate - the audio driver SR which may be different from the file SR, to possibly resample files * @param reader - an alternative soundfile reader * @param is_double - whether Faust code has been compiled in -double mode and soundfile buffers have to be in double * * @return the soundfile loader. */ SoundUI(const Soundfile::Directories& sound_directories, int sample_rate = -1, SoundfileReader* reader = nullptr, bool is_double = false) :fSoundfileDir(sound_directories) { fSoundReader = (reader) ? std::shared_ptr(reader) // the static gReader should not be deleted, so use an empty destructor : std::shared_ptr(std::shared_ptr{}, &gReader); fSoundReader->setSampleRate(sample_rate); fIsDouble = is_double; if (!defaultsound) defaultsound = gReader.createSoundfile(gPathNameList, MAX_CHAN, is_double); } virtual ~SoundUI() {} // -- soundfiles virtual void addSoundfile(const char* label, const char* url, Soundfile** sf_zone) { const char* saved_url = url; // 'url' is consumed by parseMenuList2 std::vector file_name_list; bool menu = parseMenuList2(url, file_name_list, true); // If not a list, we have as single file if (!menu) { file_name_list.push_back(saved_url); } // Parse the possible list std::string saved_url_real = std::string(saved_url) + "_" + std::to_string(fIsDouble); // fIsDouble is used in the key if (fSoundfileMap.find(saved_url_real) == fSoundfileMap.end()) { // Check all files and get their complete path std::vector path_name_list = fSoundReader->checkFiles(fSoundfileDir, file_name_list); // Read them and create the Soundfile Soundfile* sound_file = fSoundReader->createSoundfile(path_name_list, MAX_CHAN, fIsDouble); if (sound_file) { fSoundfileMap[saved_url_real] = std::shared_ptr(sound_file); } else { // If failure, use 'defaultsound' std::cerr << "addSoundfile : soundfile for " << saved_url << " cannot be created !" << std::endl; *sf_zone = defaultsound; return; } } // Get the soundfile pointer *sf_zone = fSoundfileMap[saved_url_real].get(); } /** * An OS dependant function to get the path of the running executable or plugin. * This will typically be used when creating a SoundUI soundfile loader, like new SoundUI(SoundUI::getBinaryPath()); * * @return the running executable or plugin path. */ static std::string getBinaryPath() { std::string bundle_path_str; #if defined(__APPLE__) && !defined(__VCVRACK__) && !defined(JUCE_32BIT) && !defined(JUCE_64BIT) CFURLRef bundle_ref = CFBundleCopyBundleURL(CFBundleGetMainBundle()); if (!bundle_ref) { std::cerr << "getBinaryPath CFBundleCopyBundleURL error\n"; return ""; } UInt8 bundle_path[1024]; if (CFURLGetFileSystemRepresentation(bundle_ref, true, bundle_path, 1024)) { bundle_path_str = std::string((char*)bundle_path); } else { std::cerr << "getBinaryPath CFURLGetFileSystemRepresentation error\n"; } #endif #ifdef ANDROID_DRIVER bundle_path_str = "/data/data/__CURRENT_ANDROID_PACKAGE__/files"; #endif return bundle_path_str; } /** * An OS dependant function to get the path of the running executable or plugin. * This will typically be used when creating a SoundUI soundfile loader, like new SoundUI(SoundUI::getBinaryPathFrom()); * * @param path - entry point to start getting the path of the running executable or plugin. * * @return the running executable or plugin path. */ static std::string getBinaryPathFrom(const std::string& path) { std::string bundle_path_str; #if defined(__APPLE__) && !defined(__VCVRACK__) && !defined(JUCE_32BIT) && !defined(JUCE_64BIT) CFBundleRef bundle = CFBundleGetBundleWithIdentifier(CFStringCreateWithCString(kCFAllocatorDefault, path.c_str(), CFStringGetSystemEncoding())); if (!bundle) { std::cerr << "getBinaryPathFrom CFBundleGetBundleWithIdentifier error '" << path << "'" << std::endl; return ""; } CFURLRef bundle_ref = CFBundleCopyBundleURL(bundle); if (!bundle_ref) { std::cerr << "getBinaryPathFrom CFBundleCopyBundleURL error\n"; return ""; } UInt8 bundle_path[1024]; if (CFURLGetFileSystemRepresentation(bundle_ref, true, bundle_path, 1024)) { bundle_path_str = std::string((char*)bundle_path); } else { std::cerr << "getBinaryPathFrom CFURLGetFileSystemRepresentation error\n"; } #endif #ifdef ANDROID_DRIVER bundle_path_str = "/data/data/__CURRENT_ANDROID_PACKAGE__/files"; #endif return bundle_path_str; } }; #endif /************************** END SoundUI.h **************************/ #endif // MIDI support #ifdef MIDICTRL /************************** BEGIN MidiUI.h **************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ************************************************************************/ #ifndef FAUST_MIDIUI_H #define FAUST_MIDIUI_H #include #include #include #include #include /************************** BEGIN GUI.h ********************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. *************************************************************************/ #ifndef __GUI_H__ #define __GUI_H__ #include #include #include #include #ifdef _WIN32 # pragma warning (disable: 4100) #else # pragma GCC diagnostic ignored "-Wunused-parameter" #endif /************************** BEGIN ValueConverter.h ******************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ********************************************************************/ #ifndef __ValueConverter__ #define __ValueConverter__ /*************************************************************************************** ValueConverter.h (GRAME, Copyright 2015-2019) Set of conversion objects used to map user interface values (for example a gui slider delivering values between 0 and 1) to faust values (for example a vslider between 20 and 20000) using a log scale. -- Utilities Range(lo,hi) : clip a value x between lo and hi Interpolator(lo,hi,v1,v2) : Maps a value x between lo and hi to a value y between v1 and v2 Interpolator3pt(lo,mi,hi,v1,vm,v2) : Map values between lo mid hi to values between v1 vm v2 -- Value Converters ValueConverter::ui2faust(x) ValueConverter::faust2ui(x) -- ValueConverters used for sliders depending of the scale LinearValueConverter(umin, umax, fmin, fmax) LinearValueConverter2(lo, mi, hi, v1, vm, v2) using 2 segments LogValueConverter(umin, umax, fmin, fmax) ExpValueConverter(umin, umax, fmin, fmax) -- ValueConverters used for accelerometers based on 3 points AccUpConverter(amin, amid, amax, fmin, fmid, fmax) -- curve 0 AccDownConverter(amin, amid, amax, fmin, fmid, fmax) -- curve 1 AccUpDownConverter(amin, amid, amax, fmin, fmid, fmax) -- curve 2 AccDownUpConverter(amin, amid, amax, fmin, fmid, fmax) -- curve 3 -- lists of ZoneControl are used to implement accelerometers metadata for each axes ZoneControl(zone, valueConverter) : a zone with an accelerometer data converter -- ZoneReader are used to implement screencolor metadata ZoneReader(zone, valueConverter) : a zone with a data converter ****************************************************************************************/ #include #include // std::max #include #include #include //-------------------------------------------------------------------------------------- // Interpolator(lo,hi,v1,v2) // Maps a value x between lo and hi to a value y between v1 and v2 // y = v1 + (x-lo)/(hi-lo)*(v2-v1) // y = v1 + (x-lo) * coef with coef = (v2-v1)/(hi-lo) // y = v1 + x*coef - lo*coef // y = v1 - lo*coef + x*coef // y = offset + x*coef with offset = v1 - lo*coef //-------------------------------------------------------------------------------------- class FAUST_API Interpolator { private: //-------------------------------------------------------------------------------------- // Range(lo,hi) clip a value between lo and hi //-------------------------------------------------------------------------------------- struct Range { double fLo; double fHi; Range(double x, double y) : fLo(std::min(x,y)), fHi(std::max(x,y)) {} double operator()(double x) { return (xfHi) ? fHi : x; } }; Range fRange; double fCoef; double fOffset; public: Interpolator(double lo, double hi, double v1, double v2) : fRange(lo,hi) { if (hi != lo) { // regular case fCoef = (v2-v1)/(hi-lo); fOffset = v1 - lo*fCoef; } else { // degenerate case, avoids division by zero fCoef = 0; fOffset = (v1+v2)/2; } } double operator()(double v) { double x = fRange(v); return fOffset + x*fCoef; } void getLowHigh(double& amin, double& amax) { amin = fRange.fLo; amax = fRange.fHi; } }; //-------------------------------------------------------------------------------------- // Interpolator3pt(lo,mi,hi,v1,vm,v2) // Map values between lo mid hi to values between v1 vm v2 //-------------------------------------------------------------------------------------- class FAUST_API Interpolator3pt { private: Interpolator fSegment1; Interpolator fSegment2; double fMid; public: Interpolator3pt(double lo, double mi, double hi, double v1, double vm, double v2) : fSegment1(lo, mi, v1, vm), fSegment2(mi, hi, vm, v2), fMid(mi) {} double operator()(double x) { return (x < fMid) ? fSegment1(x) : fSegment2(x); } void getMappingValues(double& amin, double& amid, double& amax) { fSegment1.getLowHigh(amin, amid); fSegment2.getLowHigh(amid, amax); } }; //-------------------------------------------------------------------------------------- // Abstract ValueConverter class. Converts values between UI and Faust representations //-------------------------------------------------------------------------------------- class FAUST_API ValueConverter { public: virtual ~ValueConverter() {} virtual double ui2faust(double x) { return x; }; virtual double faust2ui(double x) { return x; }; }; //-------------------------------------------------------------------------------------- // A converter than can be updated //-------------------------------------------------------------------------------------- class FAUST_API UpdatableValueConverter : public ValueConverter { protected: bool fActive; public: UpdatableValueConverter():fActive(true) {} virtual ~UpdatableValueConverter() {} virtual void setMappingValues(double amin, double amid, double amax, double min, double init, double max) = 0; virtual void getMappingValues(double& amin, double& amid, double& amax) = 0; void setActive(bool on_off) { fActive = on_off; } bool getActive() { return fActive; } }; //-------------------------------------------------------------------------------------- // Linear conversion between ui and Faust values //-------------------------------------------------------------------------------------- class FAUST_API LinearValueConverter : public ValueConverter { private: Interpolator fUI2F; Interpolator fF2UI; public: LinearValueConverter(double umin, double umax, double fmin, double fmax) : fUI2F(umin,umax,fmin,fmax), fF2UI(fmin,fmax,umin,umax) {} LinearValueConverter() : fUI2F(0.,0.,0.,0.), fF2UI(0.,0.,0.,0.) {} virtual double ui2faust(double x) { return fUI2F(x); } virtual double faust2ui(double x) { return fF2UI(x); } }; //-------------------------------------------------------------------------------------- // Two segments linear conversion between ui and Faust values //-------------------------------------------------------------------------------------- class FAUST_API LinearValueConverter2 : public UpdatableValueConverter { private: Interpolator3pt fUI2F; Interpolator3pt fF2UI; public: LinearValueConverter2(double amin, double amid, double amax, double min, double init, double max) : fUI2F(amin, amid, amax, min, init, max), fF2UI(min, init, max, amin, amid, amax) {} LinearValueConverter2() : fUI2F(0.,0.,0.,0.,0.,0.), fF2UI(0.,0.,0.,0.,0.,0.) {} virtual double ui2faust(double x) { return fUI2F(x); } virtual double faust2ui(double x) { return fF2UI(x); } virtual void setMappingValues(double amin, double amid, double amax, double min, double init, double max) { fUI2F = Interpolator3pt(amin, amid, amax, min, init, max); fF2UI = Interpolator3pt(min, init, max, amin, amid, amax); } virtual void getMappingValues(double& amin, double& amid, double& amax) { fUI2F.getMappingValues(amin, amid, amax); } }; //-------------------------------------------------------------------------------------- // Logarithmic conversion between ui and Faust values //-------------------------------------------------------------------------------------- class FAUST_API LogValueConverter : public LinearValueConverter { public: // We use DBL_EPSILON which is bigger than DBL_MIN (safer) LogValueConverter(double umin, double umax, double fmin, double fmax) : LinearValueConverter(umin, umax, std::log(std::max(DBL_EPSILON, fmin)), std::log(std::max(DBL_EPSILON, fmax))) {} virtual double ui2faust(double x) { return std::exp(LinearValueConverter::ui2faust(x)); } virtual double faust2ui(double x) { return LinearValueConverter::faust2ui(std::log(std::max(DBL_EPSILON, x))); } }; //-------------------------------------------------------------------------------------- // Exponential conversion between ui and Faust values //-------------------------------------------------------------------------------------- class FAUST_API ExpValueConverter : public LinearValueConverter { public: ExpValueConverter(double umin, double umax, double fmin, double fmax) : LinearValueConverter(umin, umax, std::min(DBL_MAX, std::exp(fmin)), std::min(DBL_MAX, std::exp(fmax))) {} virtual double ui2faust(double x) { return std::log(LinearValueConverter::ui2faust(x)); } virtual double faust2ui(double x) { return LinearValueConverter::faust2ui(std::min(DBL_MAX, std::exp(x))); } }; //-------------------------------------------------------------------------------------- // Convert accelerometer or gyroscope values to Faust values // Using an Up curve (curve 0) //-------------------------------------------------------------------------------------- class FAUST_API AccUpConverter : public UpdatableValueConverter { private: Interpolator3pt fA2F; Interpolator3pt fF2A; public: AccUpConverter(double amin, double amid, double amax, double fmin, double fmid, double fmax) : fA2F(amin,amid,amax,fmin,fmid,fmax), fF2A(fmin,fmid,fmax,amin,amid,amax) {} virtual double ui2faust(double x) { return fA2F(x); } virtual double faust2ui(double x) { return fF2A(x); } virtual void setMappingValues(double amin, double amid, double amax, double fmin, double fmid, double fmax) { //__android_log_print(ANDROID_LOG_ERROR, "Faust", "AccUpConverter update %f %f %f %f %f %f", amin,amid,amax,fmin,fmid,fmax); fA2F = Interpolator3pt(amin, amid, amax, fmin, fmid, fmax); fF2A = Interpolator3pt(fmin, fmid, fmax, amin, amid, amax); } virtual void getMappingValues(double& amin, double& amid, double& amax) { fA2F.getMappingValues(amin, amid, amax); } }; //-------------------------------------------------------------------------------------- // Convert accelerometer or gyroscope values to Faust values // Using a Down curve (curve 1) //-------------------------------------------------------------------------------------- class FAUST_API AccDownConverter : public UpdatableValueConverter { private: Interpolator3pt fA2F; Interpolator3pt fF2A; public: AccDownConverter(double amin, double amid, double amax, double fmin, double fmid, double fmax) : fA2F(amin,amid,amax,fmax,fmid,fmin), fF2A(fmin,fmid,fmax,amax,amid,amin) {} virtual double ui2faust(double x) { return fA2F(x); } virtual double faust2ui(double x) { return fF2A(x); } virtual void setMappingValues(double amin, double amid, double amax, double fmin, double fmid, double fmax) { //__android_log_print(ANDROID_LOG_ERROR, "Faust", "AccDownConverter update %f %f %f %f %f %f", amin,amid,amax,fmin,fmid,fmax); fA2F = Interpolator3pt(amin, amid, amax, fmax, fmid, fmin); fF2A = Interpolator3pt(fmin, fmid, fmax, amax, amid, amin); } virtual void getMappingValues(double& amin, double& amid, double& amax) { fA2F.getMappingValues(amin, amid, amax); } }; //-------------------------------------------------------------------------------------- // Convert accelerometer or gyroscope values to Faust values // Using an Up-Down curve (curve 2) //-------------------------------------------------------------------------------------- class FAUST_API AccUpDownConverter : public UpdatableValueConverter { private: Interpolator3pt fA2F; Interpolator fF2A; public: AccUpDownConverter(double amin, double amid, double amax, double fmin, double fmid, double fmax) : fA2F(amin,amid,amax,fmin,fmax,fmin), fF2A(fmin,fmax,amin,amax) // Special, pseudo inverse of a non monotonic function {} virtual double ui2faust(double x) { return fA2F(x); } virtual double faust2ui(double x) { return fF2A(x); } virtual void setMappingValues(double amin, double amid, double amax, double fmin, double fmid, double fmax) { //__android_log_print(ANDROID_LOG_ERROR, "Faust", "AccUpDownConverter update %f %f %f %f %f %f", amin,amid,amax,fmin,fmid,fmax); fA2F = Interpolator3pt(amin, amid, amax, fmin, fmax, fmin); fF2A = Interpolator(fmin, fmax, amin, amax); } virtual void getMappingValues(double& amin, double& amid, double& amax) { fA2F.getMappingValues(amin, amid, amax); } }; //-------------------------------------------------------------------------------------- // Convert accelerometer or gyroscope values to Faust values // Using a Down-Up curve (curve 3) //-------------------------------------------------------------------------------------- class FAUST_API AccDownUpConverter : public UpdatableValueConverter { private: Interpolator3pt fA2F; Interpolator fF2A; public: AccDownUpConverter(double amin, double amid, double amax, double fmin, double fmid, double fmax) : fA2F(amin,amid,amax,fmax,fmin,fmax), fF2A(fmin,fmax,amin,amax) // Special, pseudo inverse of a non monotonic function {} virtual double ui2faust(double x) { return fA2F(x); } virtual double faust2ui(double x) { return fF2A(x); } virtual void setMappingValues(double amin, double amid, double amax, double fmin, double fmid, double fmax) { //__android_log_print(ANDROID_LOG_ERROR, "Faust", "AccDownUpConverter update %f %f %f %f %f %f", amin,amid,amax,fmin,fmid,fmax); fA2F = Interpolator3pt(amin, amid, amax, fmax, fmin, fmax); fF2A = Interpolator(fmin, fmax, amin, amax); } virtual void getMappingValues(double& amin, double& amid, double& amax) { fA2F.getMappingValues(amin, amid, amax); } }; //-------------------------------------------------------------------------------------- // Base class for ZoneControl //-------------------------------------------------------------------------------------- class FAUST_API ZoneControl { protected: FAUSTFLOAT* fZone; public: ZoneControl(FAUSTFLOAT* zone) : fZone(zone) {} virtual ~ZoneControl() {} virtual void update(double v) const {} virtual void setMappingValues(int curve, double amin, double amid, double amax, double min, double init, double max) {} virtual void getMappingValues(double& amin, double& amid, double& amax) {} FAUSTFLOAT* getZone() { return fZone; } virtual void setActive(bool on_off) {} virtual bool getActive() { return false; } virtual int getCurve() { return -1; } }; //-------------------------------------------------------------------------------------- // Useful to implement accelerometers metadata as a list of ZoneControl for each axes //-------------------------------------------------------------------------------------- class FAUST_API ConverterZoneControl : public ZoneControl { protected: ValueConverter* fValueConverter; public: ConverterZoneControl(FAUSTFLOAT* zone, ValueConverter* converter) : ZoneControl(zone), fValueConverter(converter) {} virtual ~ConverterZoneControl() { delete fValueConverter; } // Assuming fValueConverter is not kept elsewhere... virtual void update(double v) const { *fZone = FAUSTFLOAT(fValueConverter->ui2faust(v)); } ValueConverter* getConverter() { return fValueConverter; } }; //-------------------------------------------------------------------------------------- // Association of a zone and a four value converter, each one for each possible curve. // Useful to implement accelerometers metadata as a list of ZoneControl for each axes //-------------------------------------------------------------------------------------- class FAUST_API CurveZoneControl : public ZoneControl { private: std::vector fValueConverters; int fCurve; public: CurveZoneControl(FAUSTFLOAT* zone, int curve, double amin, double amid, double amax, double min, double init, double max) : ZoneControl(zone), fCurve(0) { assert(curve >= 0 && curve <= 3); fValueConverters.push_back(new AccUpConverter(amin, amid, amax, min, init, max)); fValueConverters.push_back(new AccDownConverter(amin, amid, amax, min, init, max)); fValueConverters.push_back(new AccUpDownConverter(amin, amid, amax, min, init, max)); fValueConverters.push_back(new AccDownUpConverter(amin, amid, amax, min, init, max)); fCurve = curve; } virtual ~CurveZoneControl() { for (const auto& it : fValueConverters) { delete it; } } void update(double v) const { if (fValueConverters[fCurve]->getActive()) *fZone = FAUSTFLOAT(fValueConverters[fCurve]->ui2faust(v)); } void setMappingValues(int curve, double amin, double amid, double amax, double min, double init, double max) { fValueConverters[curve]->setMappingValues(amin, amid, amax, min, init, max); fCurve = curve; } void getMappingValues(double& amin, double& amid, double& amax) { fValueConverters[fCurve]->getMappingValues(amin, amid, amax); } void setActive(bool on_off) { for (const auto& it : fValueConverters) { it->setActive(on_off); } } int getCurve() { return fCurve; } }; class FAUST_API ZoneReader { private: FAUSTFLOAT* fZone; Interpolator fInterpolator; public: ZoneReader(FAUSTFLOAT* zone, double lo, double hi) : fZone(zone), fInterpolator(lo, hi, 0, 255) {} virtual ~ZoneReader() {} int getValue() { return (fZone != nullptr) ? int(fInterpolator(*fZone)) : 127; } }; #endif /************************** END ValueConverter.h **************************/ /************************** BEGIN MetaDataUI.h ************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ************************************************************************/ #ifndef MetaData_UI_H #define MetaData_UI_H #ifndef FAUSTFLOAT #define FAUSTFLOAT float #endif #include #include #include #include #include #include // We use the lighter fprintf code static bool startWith(const std::string& str, const std::string& prefix) { return (str.substr(0, prefix.size()) == prefix); } /** * Convert a dB value into a scale between 0 and 1 (following IEC standard ?) */ static FAUSTFLOAT dB2Scale(FAUSTFLOAT dB) { FAUSTFLOAT scale = FAUSTFLOAT(1.0); /*if (dB < -70.0f) scale = 0.0f; else*/ if (dB < FAUSTFLOAT(-60.0)) scale = (dB + FAUSTFLOAT(70.0)) * FAUSTFLOAT(0.0025); else if (dB < FAUSTFLOAT(-50.0)) scale = (dB + FAUSTFLOAT(60.0)) * FAUSTFLOAT(0.005) + FAUSTFLOAT(0.025); else if (dB < FAUSTFLOAT(-40.0)) scale = (dB + FAUSTFLOAT(50.0)) * FAUSTFLOAT(0.0075) + FAUSTFLOAT(0.075); else if (dB < FAUSTFLOAT(-30.0)) scale = (dB + FAUSTFLOAT(40.0)) * FAUSTFLOAT(0.015) + FAUSTFLOAT(0.15); else if (dB < FAUSTFLOAT(-20.0)) scale = (dB + FAUSTFLOAT(30.0)) * FAUSTFLOAT(0.02) + FAUSTFLOAT(0.3); else if (dB < FAUSTFLOAT(-0.001) || dB > FAUSTFLOAT(0.001)) /* if (dB < 0.0) */ scale = (dB + FAUSTFLOAT(20.0)) * FAUSTFLOAT(0.025) + FAUSTFLOAT(0.5); return scale; } /******************************************************************************* * MetaDataUI : Common class for MetaData handling ******************************************************************************/ //============================= BEGIN GROUP LABEL METADATA=========================== // Unlike widget's label, metadata inside group's label are not extracted directly by // the Faust compiler. Therefore they must be extracted within the architecture file //----------------------------------------------------------------------------------- class MetaDataUI { protected: std::string fGroupTooltip; std::map fGuiSize; // map widget zone with widget size coef std::map fTooltip; // map widget zone with tooltip strings std::map fUnit; // map widget zone to unit string (i.e. "dB") std::map fRadioDescription; // map zone to {'low':440; ...; 'hi':1000.0} std::map fMenuDescription; // map zone to {'low':440; ...; 'hi':1000.0} std::set fKnobSet; // set of widget zone to be knobs std::set fLedSet; // set of widget zone to be LEDs std::set fNumSet; // set of widget zone to be numerical bargraphs std::set fLogSet; // set of widget zone having a log UI scale std::set fExpSet; // set of widget zone having an exp UI scale std::set fHiddenSet; // set of hidden widget zone void clearMetadata() { fGuiSize.clear(); fTooltip.clear(); fUnit.clear(); fRadioDescription.clear(); fMenuDescription.clear(); fKnobSet.clear(); fLedSet.clear(); fNumSet.clear(); fLogSet.clear(); fExpSet.clear(); fHiddenSet.clear(); fGroupTooltip = ""; } /** * rmWhiteSpaces(): Remove the leading and trailing white spaces of a string * (but not those in the middle of the string) */ static std::string rmWhiteSpaces(const std::string& s) { size_t i = s.find_first_not_of(" \t"); size_t j = s.find_last_not_of(" \t"); if ((i != std::string::npos) && (j != std::string::npos)) { return s.substr(i, 1+j-i); } else { return ""; } } /** * Format tooltip string by replacing some white spaces by * return characters so that line width doesn't exceed n. * Limitation : long words exceeding n are not cut. */ std::string formatTooltip(int n, const std::string& tt) { std::string ss = tt; // ss string we are going to format int lws = 0; // last white space encountered int lri = 0; // last return inserted for (int i = 0; i < (int)tt.size(); i++) { if (tt[i] == ' ') lws = i; if (((i-lri) >= n) && (lws > lri)) { // insert return here ss[lws] = '\n'; lri = lws; } } return ss; } public: virtual ~MetaDataUI() {} enum Scale { kLin, kLog, kExp }; Scale getScale(FAUSTFLOAT* zone) { if (fLogSet.count(zone) > 0) return kLog; if (fExpSet.count(zone) > 0) return kExp; return kLin; } bool isKnob(FAUSTFLOAT* zone) { return fKnobSet.count(zone) > 0; } bool isRadio(FAUSTFLOAT* zone) { return fRadioDescription.count(zone) > 0; } bool isMenu(FAUSTFLOAT* zone) { return fMenuDescription.count(zone) > 0; } bool isLed(FAUSTFLOAT* zone) { return fLedSet.count(zone) > 0; } bool isNumerical(FAUSTFLOAT* zone) { return fNumSet.count(zone) > 0; } bool isHidden(FAUSTFLOAT* zone) { return fHiddenSet.count(zone) > 0; } /** * Extracts metadata from a label : 'vol [unit: dB]' -> 'vol' + metadata(unit=dB) */ static void extractMetadata(const std::string& fulllabel, std::string& label, std::map& metadata) { enum {kLabel, kEscape1, kEscape2, kEscape3, kKey, kValue}; int state = kLabel; int deep = 0; std::string key, value; for (unsigned int i = 0; i < fulllabel.size(); i++) { char c = fulllabel[i]; switch (state) { case kLabel : assert(deep == 0); switch (c) { case '\\' : state = kEscape1; break; case '[' : state = kKey; deep++; break; default : label += c; } break; case kEscape1: label += c; state = kLabel; break; case kEscape2: key += c; state = kKey; break; case kEscape3: value += c; state = kValue; break; case kKey: assert(deep > 0); switch (c) { case '\\': state = kEscape2; break; case '[': deep++; key += c; break; case ':': if (deep == 1) { state = kValue; } else { key += c; } break; case ']': deep--; if (deep < 1) { metadata[rmWhiteSpaces(key)] = ""; state = kLabel; key = ""; value = ""; } else { key += c; } break; default : key += c; } break; case kValue: assert(deep > 0); switch (c) { case '\\': state = kEscape3; break; case '[': deep++; value += c; break; case ']': deep--; if (deep < 1) { metadata[rmWhiteSpaces(key)] = rmWhiteSpaces(value); state = kLabel; key = ""; value = ""; } else { value += c; } break; default : value += c; } break; default: fprintf(stderr, "ERROR unrecognized state %d\n", state); } } label = rmWhiteSpaces(label); } /** * Analyses the widget zone metadata declarations and takes appropriate actions. */ void declare(FAUSTFLOAT* zone, const char* key, const char* value) { if (zone == 0) { // special zone 0 means group metadata if (strcmp(key, "tooltip") == 0) { // only group tooltip are currently implemented fGroupTooltip = formatTooltip(30, value); } else if ((strcmp(key, "hidden") == 0) && (strcmp(value, "1") == 0)) { fHiddenSet.insert(zone); } } else { if (strcmp(key, "size") == 0) { fGuiSize[zone] = atof(value); } else if (strcmp(key, "tooltip") == 0) { fTooltip[zone] = formatTooltip(30, value); } else if (strcmp(key, "unit") == 0) { fUnit[zone] = value; } else if ((strcmp(key, "hidden") == 0) && (strcmp(value, "1") == 0)) { fHiddenSet.insert(zone); } else if (strcmp(key, "scale") == 0) { if (strcmp(value, "log") == 0) { fLogSet.insert(zone); } else if (strcmp(value, "exp") == 0) { fExpSet.insert(zone); } } else if (strcmp(key, "style") == 0) { if (strcmp(value, "knob") == 0) { fKnobSet.insert(zone); } else if (strcmp(value, "led") == 0) { fLedSet.insert(zone); } else if (strcmp(value, "numerical") == 0) { fNumSet.insert(zone); } else { const char* p = value; if (parseWord(p, "radio")) { fRadioDescription[zone] = std::string(p); } else if (parseWord(p, "menu")) { fMenuDescription[zone] = std::string(p); } } } } } }; #endif /************************** END MetaDataUI.h **************************/ /************************** BEGIN ring-buffer.h **************************/ /* Copyright (C) 2000 Paul Davis Copyright (C) 2003 Rohan Drape Copyright (C) 2016 GRAME (renaming for internal use) This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ISO/POSIX C version of Paul Davis's lock free ringbuffer C++ code. This is safe for the case of one read thread and one write thread. */ #ifndef __ring_buffer__ #define __ring_buffer__ #include #include #ifdef WIN32 # pragma warning (disable: 4334) #else # pragma GCC diagnostic ignored "-Wunused-function" #endif typedef struct { char *buf; size_t len; } ringbuffer_data_t; typedef struct { char *buf; volatile size_t write_ptr; volatile size_t read_ptr; size_t size; size_t size_mask; int mlocked; } ringbuffer_t; static ringbuffer_t *ringbuffer_create(size_t sz); static void ringbuffer_free(ringbuffer_t *rb); static void ringbuffer_get_read_vector(const ringbuffer_t *rb, ringbuffer_data_t *vec); static void ringbuffer_get_write_vector(const ringbuffer_t *rb, ringbuffer_data_t *vec); static size_t ringbuffer_read(ringbuffer_t *rb, char *dest, size_t cnt); static size_t ringbuffer_peek(ringbuffer_t *rb, char *dest, size_t cnt); static void ringbuffer_read_advance(ringbuffer_t *rb, size_t cnt); static size_t ringbuffer_read_space(const ringbuffer_t *rb); static int ringbuffer_mlock(ringbuffer_t *rb); static void ringbuffer_reset(ringbuffer_t *rb); static void ringbuffer_reset_size (ringbuffer_t * rb, size_t sz); static size_t ringbuffer_write(ringbuffer_t *rb, const char *src, size_t cnt); static void ringbuffer_write_advance(ringbuffer_t *rb, size_t cnt); static size_t ringbuffer_write_space(const ringbuffer_t *rb); /* Create a new ringbuffer to hold at least `sz' bytes of data. The actual buffer size is rounded up to the next power of two. */ static ringbuffer_t * ringbuffer_create (size_t sz) { size_t power_of_two; ringbuffer_t *rb; if ((rb = (ringbuffer_t *) malloc (sizeof (ringbuffer_t))) == NULL) { return NULL; } for (power_of_two = 1u; 1u << power_of_two < sz; power_of_two++); rb->size = 1u << power_of_two; rb->size_mask = rb->size; rb->size_mask -= 1; rb->write_ptr = 0; rb->read_ptr = 0; if ((rb->buf = (char *) malloc (rb->size)) == NULL) { free (rb); return NULL; } rb->mlocked = 0; return rb; } /* Free all data associated with the ringbuffer `rb'. */ static void ringbuffer_free (ringbuffer_t * rb) { #ifdef USE_MLOCK if (rb->mlocked) { munlock (rb->buf, rb->size); } #endif /* USE_MLOCK */ free (rb->buf); free (rb); } /* Lock the data block of `rb' using the system call 'mlock'. */ static int ringbuffer_mlock (ringbuffer_t * rb) { #ifdef USE_MLOCK if (mlock (rb->buf, rb->size)) { return -1; } #endif /* USE_MLOCK */ rb->mlocked = 1; return 0; } /* Reset the read and write pointers to zero. This is not thread safe. */ static void ringbuffer_reset (ringbuffer_t * rb) { rb->read_ptr = 0; rb->write_ptr = 0; memset(rb->buf, 0, rb->size); } /* Reset the read and write pointers to zero. This is not thread safe. */ static void ringbuffer_reset_size (ringbuffer_t * rb, size_t sz) { rb->size = sz; rb->size_mask = rb->size; rb->size_mask -= 1; rb->read_ptr = 0; rb->write_ptr = 0; } /* Return the number of bytes available for reading. This is the number of bytes in front of the read pointer and behind the write pointer. */ static size_t ringbuffer_read_space (const ringbuffer_t * rb) { size_t w, r; w = rb->write_ptr; r = rb->read_ptr; if (w > r) { return w - r; } else { return (w - r + rb->size) & rb->size_mask; } } /* Return the number of bytes available for writing. This is the number of bytes in front of the write pointer and behind the read pointer. */ static size_t ringbuffer_write_space (const ringbuffer_t * rb) { size_t w, r; w = rb->write_ptr; r = rb->read_ptr; if (w > r) { return ((r - w + rb->size) & rb->size_mask) - 1; } else if (w < r) { return (r - w) - 1; } else { return rb->size - 1; } } /* The copying data reader. Copy at most `cnt' bytes from `rb' to `dest'. Returns the actual number of bytes copied. */ static size_t ringbuffer_read (ringbuffer_t * rb, char *dest, size_t cnt) { size_t free_cnt; size_t cnt2; size_t to_read; size_t n1, n2; if ((free_cnt = ringbuffer_read_space (rb)) == 0) { return 0; } to_read = cnt > free_cnt ? free_cnt : cnt; cnt2 = rb->read_ptr + to_read; if (cnt2 > rb->size) { n1 = rb->size - rb->read_ptr; n2 = cnt2 & rb->size_mask; } else { n1 = to_read; n2 = 0; } memcpy (dest, &(rb->buf[rb->read_ptr]), n1); rb->read_ptr = (rb->read_ptr + n1) & rb->size_mask; if (n2) { memcpy (dest + n1, &(rb->buf[rb->read_ptr]), n2); rb->read_ptr = (rb->read_ptr + n2) & rb->size_mask; } return to_read; } /* The copying data reader w/o read pointer advance. Copy at most `cnt' bytes from `rb' to `dest'. Returns the actual number of bytes copied. */ static size_t ringbuffer_peek (ringbuffer_t * rb, char *dest, size_t cnt) { size_t free_cnt; size_t cnt2; size_t to_read; size_t n1, n2; size_t tmp_read_ptr; tmp_read_ptr = rb->read_ptr; if ((free_cnt = ringbuffer_read_space (rb)) == 0) { return 0; } to_read = cnt > free_cnt ? free_cnt : cnt; cnt2 = tmp_read_ptr + to_read; if (cnt2 > rb->size) { n1 = rb->size - tmp_read_ptr; n2 = cnt2 & rb->size_mask; } else { n1 = to_read; n2 = 0; } memcpy (dest, &(rb->buf[tmp_read_ptr]), n1); tmp_read_ptr = (tmp_read_ptr + n1) & rb->size_mask; if (n2) { memcpy (dest + n1, &(rb->buf[tmp_read_ptr]), n2); } return to_read; } /* The copying data writer. Copy at most `cnt' bytes to `rb' from `src'. Returns the actual number of bytes copied. */ static size_t ringbuffer_write (ringbuffer_t * rb, const char *src, size_t cnt) { size_t free_cnt; size_t cnt2; size_t to_write; size_t n1, n2; if ((free_cnt = ringbuffer_write_space (rb)) == 0) { return 0; } to_write = cnt > free_cnt ? free_cnt : cnt; cnt2 = rb->write_ptr + to_write; if (cnt2 > rb->size) { n1 = rb->size - rb->write_ptr; n2 = cnt2 & rb->size_mask; } else { n1 = to_write; n2 = 0; } memcpy (&(rb->buf[rb->write_ptr]), src, n1); rb->write_ptr = (rb->write_ptr + n1) & rb->size_mask; if (n2) { memcpy (&(rb->buf[rb->write_ptr]), src + n1, n2); rb->write_ptr = (rb->write_ptr + n2) & rb->size_mask; } return to_write; } /* Advance the read pointer `cnt' places. */ static void ringbuffer_read_advance (ringbuffer_t * rb, size_t cnt) { size_t tmp = (rb->read_ptr + cnt) & rb->size_mask; rb->read_ptr = tmp; } /* Advance the write pointer `cnt' places. */ static void ringbuffer_write_advance (ringbuffer_t * rb, size_t cnt) { size_t tmp = (rb->write_ptr + cnt) & rb->size_mask; rb->write_ptr = tmp; } /* The non-copying data reader. `vec' is an array of two places. Set the values at `vec' to hold the current readable data at `rb'. If the readable data is in one segment the second segment has zero length. */ static void ringbuffer_get_read_vector (const ringbuffer_t * rb, ringbuffer_data_t * vec) { size_t free_cnt; size_t cnt2; size_t w, r; w = rb->write_ptr; r = rb->read_ptr; if (w > r) { free_cnt = w - r; } else { free_cnt = (w - r + rb->size) & rb->size_mask; } cnt2 = r + free_cnt; if (cnt2 > rb->size) { /* Two part vector: the rest of the buffer after the current write ptr, plus some from the start of the buffer. */ vec[0].buf = &(rb->buf[r]); vec[0].len = rb->size - r; vec[1].buf = rb->buf; vec[1].len = cnt2 & rb->size_mask; } else { /* Single part vector: just the rest of the buffer */ vec[0].buf = &(rb->buf[r]); vec[0].len = free_cnt; vec[1].len = 0; } } /* The non-copying data writer. `vec' is an array of two places. Set the values at `vec' to hold the current writeable data at `rb'. If the writeable data is in one segment the second segment has zero length. */ static void ringbuffer_get_write_vector (const ringbuffer_t * rb, ringbuffer_data_t * vec) { size_t free_cnt; size_t cnt2; size_t w, r; w = rb->write_ptr; r = rb->read_ptr; if (w > r) { free_cnt = ((r - w + rb->size) & rb->size_mask) - 1; } else if (w < r) { free_cnt = (r - w) - 1; } else { free_cnt = rb->size - 1; } cnt2 = w + free_cnt; if (cnt2 > rb->size) { /* Two part vector: the rest of the buffer after the current write ptr, plus some from the start of the buffer. */ vec[0].buf = &(rb->buf[w]); vec[0].len = rb->size - w; vec[1].buf = rb->buf; vec[1].len = cnt2 & rb->size_mask; } else { vec[0].buf = &(rb->buf[w]); vec[0].len = free_cnt; vec[1].len = 0; } } #endif // __ring_buffer__ /************************** END ring-buffer.h **************************/ /******************************************************************************* * GUI : Abstract Graphic User Interface * Provides additional mechanisms to synchronize widgets and zones. Widgets * should both reflect the value of a zone and allow to change this value. ******************************************************************************/ class uiItem; class GUI; struct clist; typedef void (*uiCallback)(FAUSTFLOAT val, void* data); /** * Base class for uiTypedItem: memory zones that can be grouped and synchronized, using an internal cache. */ struct uiItemBase { uiItemBase(GUI* ui, FAUSTFLOAT* zone) { assert(ui); assert(zone); } virtual ~uiItemBase() {} /** * This method will be called when the value changes externally, * and will signal the new value to all linked uItem * when the value is different from the cached one. * * @param v - the new value */ virtual void modifyZone(FAUSTFLOAT v) = 0; /** * This method will be called when the value changes externally, * and will signal the new value to all linked uItem * when the value is different from the cached one. * * @param date - the timestamp of the received value in usec * @param v - the new value */ virtual void modifyZone(double date, FAUSTFLOAT v) {} /** * This method is called by the synchronisation mecanism and is expected * to 'reflect' the new value, by changing the Widget layout for instance, * or sending a message (OSC, MIDI...) */ virtual void reflectZone() = 0; /** * Return the cached value. * * @return - the cached value */ virtual double cache() = 0; }; // Declared as 'static' to avoid code duplication at link time static void deleteClist(clist* cl); /** * A list containing all groupe uiItemBase objects. */ struct clist : public std::list { virtual ~clist() { deleteClist(this); } }; static void createUiCallbackItem(GUI* ui, FAUSTFLOAT* zone, uiCallback foo, void* data); typedef std::map zmap; typedef std::map ztimedmap; class GUI : public UI { private: static std::list fGuiList; zmap fZoneMap; bool fStopped; public: GUI():fStopped(false) { fGuiList.push_back(this); } virtual ~GUI() { // delete all items for (const auto& it : fZoneMap) { delete it.second; } // suppress 'this' in static fGuiList fGuiList.remove(this); } // -- registerZone(z,c) : zone management void registerZone(FAUSTFLOAT* z, uiItemBase* c) { if (fZoneMap.find(z) == fZoneMap.end()) fZoneMap[z] = new clist(); fZoneMap[z]->push_back(c); } void updateZone(FAUSTFLOAT* z) { FAUSTFLOAT v = *z; clist* cl = fZoneMap[z]; for (const auto& c : *cl) { if (c->cache() != v) c->reflectZone(); } } void updateAllZones() { for (const auto& m : fZoneMap) { updateZone(m.first); } } static void updateAllGuis() { for (const auto& g : fGuiList) { g->updateAllZones(); } } void addCallback(FAUSTFLOAT* zone, uiCallback foo, void* data) { createUiCallbackItem(this, zone, foo, data); } // Start event or message processing virtual bool run() { return false; }; // Stop event or message processing virtual void stop() { fStopped = true; } bool stopped() { return fStopped; } // -- widget's layouts virtual void openTabBox(const char* label) {} virtual void openHorizontalBox(const char* label) {} virtual void openVerticalBox(const char* label) {} virtual void closeBox() {} // -- active widgets virtual void addButton(const char* label, FAUSTFLOAT* zone) {} virtual void addCheckButton(const char* label, FAUSTFLOAT* zone) {} virtual void addVerticalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) {} virtual void addHorizontalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) {} virtual void addNumEntry(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) {} // -- passive widgets virtual void addHorizontalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) {} virtual void addVerticalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) {} // -- soundfiles virtual void addSoundfile(const char* label, const char* filename, Soundfile** sf_zone) {} // -- metadata declarations virtual void declare(FAUSTFLOAT*, const char*, const char*) {} // Static global for timed zones, shared between all UI that will set timed values static ztimedmap gTimedZoneMap; }; /** * User Interface Item: abstract definition. */ template class uiTypedItemReal : public uiItemBase { protected: GUI* fGUI; REAL* fZone; REAL fCache; uiTypedItemReal(GUI* ui, REAL* zone):uiItemBase(ui, static_cast(zone)), fGUI(ui), fZone(zone), fCache(REAL(-123456.654321)) { ui->registerZone(zone, this); } public: virtual ~uiTypedItemReal() {} void modifyZone(REAL v) { fCache = v; if (*fZone != v) { *fZone = v; fGUI->updateZone(fZone); } } double cache() { return fCache; } }; class uiItem : public uiTypedItemReal { protected: uiItem(GUI* ui, FAUSTFLOAT* zone):uiTypedItemReal(ui, zone) {} public: virtual ~uiItem() {} void modifyZone(FAUSTFLOAT v) { fCache = v; if (*fZone != v) { *fZone = v; fGUI->updateZone(fZone); } } }; /** * Base class for items with a value converter. */ struct uiConverter { ValueConverter* fConverter; uiConverter(MetaDataUI::Scale scale, FAUSTFLOAT umin, FAUSTFLOAT umax, FAUSTFLOAT fmin, FAUSTFLOAT fmax) { // Select appropriate converter according to scale mode if (scale == MetaDataUI::kLog) { fConverter = new LogValueConverter(umin, umax, fmin, fmax); } else if (scale == MetaDataUI::kExp) { fConverter = new ExpValueConverter(umin, umax, fmin, fmax); } else { fConverter = new LinearValueConverter(umin, umax, fmin, fmax); } } virtual ~uiConverter() { delete fConverter; } }; /** * User Interface item owned (and so deleted) by external code. */ class uiOwnedItem : public uiItem { protected: uiOwnedItem(GUI* ui, FAUSTFLOAT* zone):uiItem(ui, zone) {} public: virtual ~uiOwnedItem() {} virtual void reflectZone() {} }; /** * Callback Item. */ class uiCallbackItem : public uiItem { protected: uiCallback fCallback; void* fData; public: uiCallbackItem(GUI* ui, FAUSTFLOAT* zone, uiCallback foo, void* data) : uiItem(ui, zone), fCallback(foo), fData(data) {} virtual void reflectZone() { FAUSTFLOAT v = *fZone; fCache = v; fCallback(v, fData); } }; /** * For timestamped control. */ struct DatedControl { double fDate; FAUSTFLOAT fValue; DatedControl(double d = 0., FAUSTFLOAT v = FAUSTFLOAT(0)):fDate(d), fValue(v) {} }; /** * Base class for timed items. */ class uiTimedItem : public uiItem { protected: bool fDelete; public: using uiItem::modifyZone; uiTimedItem(GUI* ui, FAUSTFLOAT* zone):uiItem(ui, zone) { if (GUI::gTimedZoneMap.find(fZone) == GUI::gTimedZoneMap.end()) { GUI::gTimedZoneMap[fZone] = ringbuffer_create(8192); fDelete = true; } else { fDelete = false; } } virtual ~uiTimedItem() { ztimedmap::iterator it; if (fDelete && ((it = GUI::gTimedZoneMap.find(fZone)) != GUI::gTimedZoneMap.end())) { ringbuffer_free((*it).second); GUI::gTimedZoneMap.erase(it); } } virtual void modifyZone(double date, FAUSTFLOAT v) { size_t res; DatedControl dated_val(date, v); if ((res = ringbuffer_write(GUI::gTimedZoneMap[fZone], (const char*)&dated_val, sizeof(DatedControl))) != sizeof(DatedControl)) { fprintf(stderr, "ringbuffer_write error DatedControl\n"); } } }; /** * Allows to group a set of zones. */ class uiGroupItem : public uiItem { protected: std::vector fZoneMap; public: uiGroupItem(GUI* ui, FAUSTFLOAT* zone):uiItem(ui, zone) {} virtual ~uiGroupItem() {} virtual void reflectZone() { FAUSTFLOAT v = *fZone; fCache = v; // Update all zones of the same group for (const auto& it : fZoneMap) { *it = v; } } void addZone(FAUSTFLOAT* zone) { fZoneMap.push_back(zone); } }; // Cannot be defined as method in the classes. static void createUiCallbackItem(GUI* ui, FAUSTFLOAT* zone, uiCallback foo, void* data) { new uiCallbackItem(ui, zone, foo, data); } static void deleteClist(clist* cl) { for (const auto& it : *cl) { // This specific code is only used in JUCE context. TODO: use proper 'shared_ptr' based memory management. #if defined(JUCE_32BIT) || defined(JUCE_64BIT) uiOwnedItem* owned = dynamic_cast(it); // owned items are deleted by external code if (!owned) { delete it; } #else delete it; #endif } } #endif /************************** END GUI.h **************************/ /************************** BEGIN JSONUI.h ***************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ************************************************************************/ #ifndef FAUST_JSONUI_H #define FAUST_JSONUI_H #include #include #include #include #include #include #include /******************************************************************************* * JSONUI : Faust User Interface * This class produce a complete JSON decription of the DSP instance. * * Since 'shortname' can only be computed when all paths have been created, * the fAllUI vector is progressively filled with partially built UI items, * which are finally created in the JSON(...) method. ******************************************************************************/ // Instruction complexity statistics struct InstComplexity { int fLoad = 0; int fStore = 0; int fBinop = 0; int fMathop = 0; int fNumbers = 0; int fDeclare = 0; int fCast = 0; int fSelect = 0; int fLoop = 0; std::map fFunctionSymbolTable; std::map fBinopSymbolTable; InstComplexity operator+(const InstComplexity& icomp) { fLoad += icomp.fLoad; fStore += icomp.fStore; fBinop += icomp.fBinop; fMathop += icomp.fMathop; fNumbers += icomp.fNumbers; fDeclare += icomp.fDeclare; fCast += icomp.fCast; fSelect += icomp.fSelect; fLoop += icomp.fLoop; return *this; } }; // DSP or field name, type, size, size-in-bytes, reads, writes typedef std::tuple MemoryLayoutItem; typedef std::vector MemoryLayoutType; typedef std::map PathTableType; /* Build a JSON description of the DSP. */ template class FAUST_API JSONUIReal : public PathBuilder, public Meta, public UIReal { protected: std::stringstream fUI; std::vector fAllUI; std::stringstream fMeta; std::vector > fMetaAux; std::string fVersion; // Compiler version std::string fCompileOptions; // Compilation options std::vector fLibraryList; std::vector fIncludePathnames; std::string fName; std::string fFileName; std::string fExpandedCode; std::string fSHAKey; std::string fJSON; int fDSPSize; // In bytes PathTableType fPathTable; MemoryLayoutType fMemoryLayout; InstComplexity fIComp; bool fExtended; char fCloseUIPar; char fCloseMetaPar; int fTab; int fInputs, fOutputs, fSRIndex; void tab(int n, std::ostream& fout) { fout << '\n'; while (n-- > 0) { fout << '\t'; } } std::string flatten(const std::string& src) { std::string dst; for (size_t i = 0; i < src.size(); i++) { switch (src[i]) { case '\n': case '\t': break; default: dst += src[i]; break; } } return dst; } void addMeta(int tab_val, bool quote = true) { if (fMetaAux.size() > 0) { tab(tab_val, fUI); fUI << "\"meta\": ["; std::string sep = ""; for (size_t i = 0; i < fMetaAux.size(); i++) { fUI << sep; tab(tab_val + 1, fUI); fUI << "{ \"" << fMetaAux[i].first << "\": \"" << fMetaAux[i].second << "\" }"; sep = ","; } tab(tab_val, fUI); fUI << ((quote) ? "],": "]"); fMetaAux.clear(); } } int getAddressIndex(const std::string& path) { return (fPathTable.find(path) != fPathTable.end()) ? fPathTable[path] : -1; } public: JSONUIReal(const std::string& name, const std::string& filename, int inputs, int outputs, int sr_index, const std::string& sha_key, const std::string& dsp_code, const std::string& version, const std::string& compile_options, const std::vector& library_list, const std::vector& include_pathnames, int size, const PathTableType& path_table, MemoryLayoutType memory_layout, InstComplexity inst_comp) { init(name, filename, inputs, outputs, sr_index, sha_key, dsp_code, version, compile_options, library_list, include_pathnames, size, path_table, memory_layout, inst_comp); } JSONUIReal(const std::string& name, const std::string& filename, int inputs, int outputs) { init(name, filename, inputs, outputs, -1, "", "", "", "", std::vector(), std::vector(), -1, PathTableType(), MemoryLayoutType(), InstComplexity()); } JSONUIReal(int inputs, int outputs) { init("", "", inputs, outputs, -1, "", "","", "", std::vector(), std::vector(), -1, PathTableType(), MemoryLayoutType(), InstComplexity()); } JSONUIReal() { init("", "", -1, -1, -1, "", "", "", "", std::vector(), std::vector(), -1, PathTableType(), MemoryLayoutType(), InstComplexity()); } virtual ~JSONUIReal() {} void setInputs(int inputs) { fInputs = inputs; } void setOutputs(int outputs) { fOutputs = outputs; } void setSRIndex(int sr_index) { fSRIndex = sr_index; } // Init may be called multiple times so fMeta and fUI are reinitialized void init(const std::string& name, const std::string& filename, int inputs, int outputs, int sr_index, const std::string& sha_key, const std::string& dsp_code, const std::string& version, const std::string& compile_options, const std::vector& library_list, const std::vector& include_pathnames, int size, const PathTableType& path_table, MemoryLayoutType memory_layout, InstComplexity inst_comp, bool extended = false) { fTab = 1; fExtended = extended; if (fExtended) { fUI << std::setprecision(std::numeric_limits::max_digits10); fMeta << std::setprecision(std::numeric_limits::max_digits10); } fIComp = inst_comp; // Start Meta generation fMeta.str(""); tab(fTab, fMeta); fMeta << "\"meta\": ["; fCloseMetaPar = ' '; // Start UI generation fUI.str(""); tab(fTab, fUI); fUI << "\"ui\": ["; fCloseUIPar = ' '; fTab += 1; fName = name; fFileName = filename; fInputs = inputs; fOutputs = outputs; fSRIndex = sr_index; fExpandedCode = dsp_code; fSHAKey = sha_key; fDSPSize = size; fPathTable = path_table; fVersion = version; fCompileOptions = compile_options; fLibraryList = library_list; fIncludePathnames = include_pathnames; fMemoryLayout = memory_layout; } // -- widget's layouts virtual void openGenericBox(const char* label, const char* name) { pushLabel(label); fUI << fCloseUIPar; tab(fTab, fUI); fUI << "{"; fTab += 1; tab(fTab, fUI); fUI << "\"type\": \"" << name << "\","; tab(fTab, fUI); fUI << "\"label\": \"" << label << "\","; addMeta(fTab); tab(fTab, fUI); fUI << "\"items\": ["; fCloseUIPar = ' '; fTab += 1; } virtual void openTabBox(const char* label) { openGenericBox(label, "tgroup"); } virtual void openHorizontalBox(const char* label) { openGenericBox(label, "hgroup"); } virtual void openVerticalBox(const char* label) { openGenericBox(label, "vgroup"); } virtual void closeBox() { if (popLabel()) { // Shortnames can be computed when all fullnames are known computeShortNames(); } fTab -= 1; tab(fTab, fUI); fUI << "]"; fTab -= 1; tab(fTab, fUI); fUI << "}"; fCloseUIPar = ','; } // -- active widgets virtual void addGenericButton(const char* label, const char* name) { std::string path = buildPath(label); fFullPaths.push_back(path); fUI << fCloseUIPar; tab(fTab, fUI); fUI << "{"; fTab += 1; tab(fTab, fUI); fUI << "\"type\": \"" << name << "\","; tab(fTab, fUI); fUI << "\"label\": \"" << label << "\","; // Generate 'shortname' entry tab(fTab, fUI); fUI << "\"shortname\": \""; // Add fUI section fAllUI.push_back(fUI.str()); fUI.str(""); if (fPathTable.size() > 0) { tab(fTab, fUI); fUI << "\"address\": \"" << path << "\","; tab(fTab, fUI); fUI << "\"index\": " << getAddressIndex(path) << ((fMetaAux.size() > 0) ? "," : ""); } else { tab(fTab, fUI); fUI << "\"address\": \"" << path << "\"" << ((fMetaAux.size() > 0) ? "," : ""); } addMeta(fTab, false); fTab -= 1; tab(fTab, fUI); fUI << "}"; fCloseUIPar = ','; } virtual void addButton(const char* label, REAL* zone) { addGenericButton(label, "button"); } virtual void addCheckButton(const char* label, REAL* zone) { addGenericButton(label, "checkbox"); } virtual void addGenericRange(const char* label, const char* name, REAL init, REAL min, REAL max, REAL step) { std::string path = buildPath(label); fFullPaths.push_back(path); fUI << fCloseUIPar; tab(fTab, fUI); fUI << "{"; fTab += 1; tab(fTab, fUI); fUI << "\"type\": \"" << name << "\","; tab(fTab, fUI); fUI << "\"label\": \"" << label << "\","; // Generate 'shortname' entry tab(fTab, fUI); fUI << "\"shortname\": \""; // Add fUI section fAllUI.push_back(fUI.str()); fUI.str(""); tab(fTab, fUI); fUI << "\"address\": \"" << path << "\","; if (fPathTable.size() > 0) { tab(fTab, fUI); fUI << "\"index\": " << getAddressIndex(path) << ","; } addMeta(fTab); tab(fTab, fUI); fUI << "\"init\": " << init << ","; tab(fTab, fUI); fUI << "\"min\": " << min << ","; tab(fTab, fUI); fUI << "\"max\": " << max << ","; tab(fTab, fUI); fUI << "\"step\": " << step; fTab -= 1; tab(fTab, fUI); fUI << "}"; fCloseUIPar = ','; } virtual void addVerticalSlider(const char* label, REAL* zone, REAL init, REAL min, REAL max, REAL step) { addGenericRange(label, "vslider", init, min, max, step); } virtual void addHorizontalSlider(const char* label, REAL* zone, REAL init, REAL min, REAL max, REAL step) { addGenericRange(label, "hslider", init, min, max, step); } virtual void addNumEntry(const char* label, REAL* zone, REAL init, REAL min, REAL max, REAL step) { addGenericRange(label, "nentry", init, min, max, step); } // -- passive widgets virtual void addGenericBargraph(const char* label, const char* name, REAL min, REAL max) { std::string path = buildPath(label); fFullPaths.push_back(path); fUI << fCloseUIPar; tab(fTab, fUI); fUI << "{"; fTab += 1; tab(fTab, fUI); fUI << "\"type\": \"" << name << "\","; tab(fTab, fUI); fUI << "\"label\": \"" << label << "\","; // Generate 'shortname' entry tab(fTab, fUI); fUI << "\"shortname\": \""; // Add fUI section fAllUI.push_back(fUI.str()); fUI.str(""); tab(fTab, fUI); fUI << "\"address\": \"" << path << "\","; if (fPathTable.size() > 0) { tab(fTab, fUI); fUI << "\"index\": " << getAddressIndex(path) << ","; } addMeta(fTab); tab(fTab, fUI); fUI << "\"min\": " << min << ","; tab(fTab, fUI); fUI << "\"max\": " << max; fTab -= 1; tab(fTab, fUI); fUI << "}"; fCloseUIPar = ','; } virtual void addHorizontalBargraph(const char* label, REAL* zone, REAL min, REAL max) { addGenericBargraph(label, "hbargraph", min, max); } virtual void addVerticalBargraph(const char* label, REAL* zone, REAL min, REAL max) { addGenericBargraph(label, "vbargraph", min, max); } virtual void addSoundfile(const char* label, const char* url, Soundfile** zone) { std::string path = buildPath(label); fUI << fCloseUIPar; tab(fTab, fUI); fUI << "{"; fTab += 1; tab(fTab, fUI); fUI << "\"type\": \"" << "soundfile" << "\","; tab(fTab, fUI); fUI << "\"label\": \"" << label << "\"" << ","; tab(fTab, fUI); fUI << "\"url\": \"" << url << "\"" << ","; tab(fTab, fUI); fUI << "\"address\": \"" << path << "\"" << ((fPathTable.size() > 0) ? "," : ""); if (fPathTable.size() > 0) { tab(fTab, fUI); fUI << "\"index\": " << getAddressIndex(path); } fTab -= 1; tab(fTab, fUI); fUI << "}"; fCloseUIPar = ','; } // -- metadata declarations virtual void declare(REAL* zone, const char* key, const char* val) { fMetaAux.push_back(std::make_pair(key, val)); } // Meta interface virtual void declare(const char* key, const char* value) { fMeta << fCloseMetaPar; // fName found in metadata if ((strcmp(key, "name") == 0) && (fName == "")) fName = value; // fFileName found in metadata if ((strcmp(key, "filename") == 0) && (fFileName == "")) fFileName = value; tab(fTab, fMeta); fMeta << "{ " << "\"" << key << "\"" << ": " << "\"" << value << "\" }"; fCloseMetaPar = ','; } std::string JSON(bool flat = false) { if (fJSON.empty()) { fTab = 0; std::stringstream JSON; if (fExtended) { JSON << std::setprecision(std::numeric_limits::max_digits10); } JSON << "{"; fTab += 1; tab(fTab, JSON); JSON << "\"name\": \"" << fName << "\","; tab(fTab, JSON); JSON << "\"filename\": \"" << fFileName << "\","; if (fVersion != "") { tab(fTab, JSON); JSON << "\"version\": \"" << fVersion << "\","; } if (fCompileOptions != "") { tab(fTab, JSON); JSON << "\"compile_options\": \"" << fCompileOptions << "\","; } if (fLibraryList.size() > 0) { tab(fTab, JSON); JSON << "\"library_list\": ["; for (size_t i = 0; i < fLibraryList.size(); i++) { JSON << "\"" << fLibraryList[i] << "\""; if (i < (fLibraryList.size() - 1)) JSON << ","; } JSON << "],"; } if (fIncludePathnames.size() > 0) { tab(fTab, JSON); JSON << "\"include_pathnames\": ["; for (size_t i = 0; i < fIncludePathnames.size(); i++) { JSON << "\"" << fIncludePathnames[i] << "\""; if (i < (fIncludePathnames.size() - 1)) JSON << ","; } JSON << "],"; } if (fDSPSize != -1) { tab(fTab, JSON); JSON << "\"size\": " << fDSPSize << ","; } if (fMemoryLayout.size() > 0) { tab(fTab, JSON); JSON << "\"memory_layout\": ["; for (size_t i = 0; i < fMemoryLayout.size(); i++) { // DSP or field name, type, size, size-in-bytes, reads, writes MemoryLayoutItem item = fMemoryLayout[i]; tab(fTab + 1, JSON); JSON << "{ \"name\": \"" << std::get<0>(item) << "\", "; JSON << "\"type\": \"" << std::get<1>(item) << "\", "; JSON << "\"size\": " << std::get<2>(item) << ", "; JSON << "\"size_bytes\": " << std::get<3>(item) << ", "; JSON << "\"read\": " << std::get<4>(item) << ", "; JSON << "\"write\": " << std::get<5>(item) << " }"; if (i < (fMemoryLayout.size() - 1)) JSON << ","; } tab(fTab, JSON); JSON << "],"; // Compute statistics tab(fTab, JSON); JSON << "\"compute_cost\": [{"; tab(fTab + 1, JSON); JSON << "\"load\": " << fIComp.fLoad << ", "; tab(fTab + 1, JSON); JSON << "\"store\": " << fIComp.fStore << ", "; tab(fTab + 1, JSON); JSON << "\"declare\": " << fIComp.fDeclare << ", "; tab(fTab + 1, JSON); JSON << "\"number\": " << fIComp.fNumbers << ", "; tab(fTab + 1, JSON); JSON << "\"cast\": " << fIComp.fCast << ", "; tab(fTab + 1, JSON); JSON << "\"select\": " << fIComp.fSelect << ", "; tab(fTab + 1, JSON); JSON << "\"loop\": " << fIComp.fLoop << ", "; tab(fTab + 1, JSON); JSON << "\"binop\": [{ "; JSON << "\"total\": " << fIComp.fBinop; int size1 = (int)fIComp.fBinopSymbolTable.size(); if (size1 > 0) { JSON << ", "; for (const auto& it : fIComp.fBinopSymbolTable) { JSON << "\"" << it.first << "\": " << it.second; JSON << ((--size1 == 0) ? " }" : ", "); } } else { JSON << " }"; } JSON << "], "; tab(fTab + 1, JSON); JSON << "\"mathop\": [{ "; JSON << "\"total\": " << fIComp.fMathop; int size2 = (int)fIComp.fFunctionSymbolTable.size(); if (size2 > 0) { JSON << ", "; for (const auto& it : fIComp.fFunctionSymbolTable) { JSON << "\"" << it.first << "\": " << it.second; JSON << ((--size2 == 0) ? " }" : ", "); } } else { JSON << " }"; } JSON << "]"; tab(fTab, JSON); JSON << "}],"; } if (fSHAKey != "") { tab(fTab, JSON); JSON << "\"sha_key\": \"" << fSHAKey << "\","; } if (fExpandedCode != "") { tab(fTab, JSON); JSON << "\"code\": \"" << fExpandedCode << "\","; } tab(fTab, JSON); JSON << "\"inputs\": " << fInputs << ","; tab(fTab, JSON); JSON << "\"outputs\": " << fOutputs << ","; if (fSRIndex != -1) { tab(fTab, JSON); JSON << "\"sr_index\": " << fSRIndex << ","; } tab(fTab, fMeta); fMeta << "],"; // Add last UI section fAllUI.push_back(fUI.str()); // Finalize UI generation fUI.str(""); // Add N-1 sections for (size_t i = 0; i < fAllUI.size()-1; i++) { fUI << fAllUI[i] << fFull2Short[fFullPaths[i]] << "\","; } // And the last one fUI << fAllUI[fAllUI.size()-1]; // Terminates the UI section tab(fTab, fUI); fUI << "]"; fTab -= 1; if (fCloseMetaPar == ',') { // If "declare" has been called, fCloseMetaPar state is now ',' JSON << fMeta.str() << fUI.str(); } else { JSON << fUI.str(); } tab(fTab, JSON); JSON << "}"; // Keep result in fJSON fJSON = JSON.str(); } return (flat) ? flatten(fJSON) : fJSON; } }; // Externally available class using FAUSTFLOAT struct FAUST_API JSONUI : public JSONUIReal, public UI { JSONUI(const std::string& name, const std::string& filename, int inputs, int outputs, int sr_index, const std::string& sha_key, const std::string& dsp_code, const std::string& version, const std::string& compile_options, const std::vector& library_list, const std::vector& include_pathnames, int size, const PathTableType& path_table, MemoryLayoutType memory_layout, InstComplexity inst_comp): JSONUIReal(name, filename, inputs, outputs, sr_index, sha_key, dsp_code, version, compile_options, library_list, include_pathnames, size, path_table, memory_layout, inst_comp) {} JSONUI(const std::string& name, const std::string& filename, int inputs, int outputs): JSONUIReal(name, filename, inputs, outputs) {} JSONUI(int inputs, int outputs):JSONUIReal(inputs, outputs) {} JSONUI():JSONUIReal() {} virtual void openTabBox(const char* label) { JSONUIReal::openTabBox(label); } virtual void openHorizontalBox(const char* label) { JSONUIReal::openHorizontalBox(label); } virtual void openVerticalBox(const char* label) { JSONUIReal::openVerticalBox(label); } virtual void closeBox() { JSONUIReal::closeBox(); } // -- active widgets virtual void addButton(const char* label, FAUSTFLOAT* zone) { JSONUIReal::addButton(label, zone); } virtual void addCheckButton(const char* label, FAUSTFLOAT* zone) { JSONUIReal::addCheckButton(label, zone); } virtual void addVerticalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) { JSONUIReal::addVerticalSlider(label, zone, init, min, max, step); } virtual void addHorizontalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) { JSONUIReal::addHorizontalSlider(label, zone, init, min, max, step); } virtual void addNumEntry(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) { JSONUIReal::addNumEntry(label, zone, init, min, max, step); } // -- passive widgets virtual void addHorizontalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) { JSONUIReal::addHorizontalBargraph(label, zone, min, max); } virtual void addVerticalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) { JSONUIReal::addVerticalBargraph(label, zone, min, max); } // -- soundfiles virtual void addSoundfile(const char* label, const char* filename, Soundfile** sf_zone) { JSONUIReal::addSoundfile(label, filename, sf_zone); } // -- metadata declarations virtual void declare(FAUSTFLOAT* zone, const char* key, const char* val) { JSONUIReal::declare(zone, key, val); } virtual void declare(const char* key, const char* val) { JSONUIReal::declare(key, val); } virtual ~JSONUI() {} }; #endif // FAUST_JSONUI_H /************************** END JSONUI.h **************************/ /************************** BEGIN midi.h ******************************* FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ************************************************************************/ #ifndef __midi__ #define __midi__ #include #include #include #include #include class FAUST_API MapUI; /** * A timestamped short MIDI message used with SOUL. */ // Force contiguous memory layout #pragma pack (push, 1) struct MIDIMessage { uint32_t frameIndex; uint8_t byte0, byte1, byte2; }; #pragma pack (pop) /** * For timestamped MIDI messages (in usec). */ struct DatedMessage { double fDate; unsigned char fBuffer[3]; size_t fSize; DatedMessage(double date, unsigned char* buffer, size_t size) :fDate(date), fSize(size) { assert(size <= 3); memcpy(fBuffer, buffer, size); } DatedMessage():fDate(0.0), fSize(0) {} }; /** * MIDI processor definition. * * MIDI input or output handling classes will implement this interface, * so the same method names (keyOn, keyOff, ctrlChange...) will be used either * when decoding MIDI input or encoding MIDI output events. * MIDI channel is numbered in [0..15] in this layer. */ class midi { public: midi() {} virtual ~midi() {} // Additional time-stamped API for MIDI input virtual MapUI* keyOn(double, int channel, int pitch, int velocity) { return keyOn(channel, pitch, velocity); } virtual void keyOff(double, int channel, int pitch, int velocity = 0) { keyOff(channel, pitch, velocity); } virtual void keyPress(double, int channel, int pitch, int press) { keyPress(channel, pitch, press); } virtual void chanPress(double date, int channel, int press) { chanPress(channel, press); } virtual void pitchWheel(double, int channel, int wheel) { pitchWheel(channel, wheel); } virtual void ctrlChange(double, int channel, int ctrl, int value) { ctrlChange(channel, ctrl, value); } virtual void ctrlChange14bits(double, int channel, int ctrl, int value) { ctrlChange14bits(channel, ctrl, value); } virtual void rpn(double, int channel, int ctrl, int value) { rpn(channel, ctrl, value); } virtual void progChange(double, int channel, int pgm) { progChange(channel, pgm); } virtual void sysEx(double, std::vector& message) { sysEx(message); } // MIDI sync virtual void startSync(double date) {} virtual void stopSync(double date) {} virtual void clock(double date) {} // Standard MIDI API virtual MapUI* keyOn(int channel, int pitch, int velocity) { return nullptr; } virtual void keyOff(int channel, int pitch, int velocity) {} virtual void keyPress(int channel, int pitch, int press) {} virtual void chanPress(int channel, int press) {} virtual void ctrlChange(int channel, int ctrl, int value) {} virtual void ctrlChange14bits(int channel, int ctrl, int value) {} virtual void rpn(int channel, int ctrl, int value) {} virtual void pitchWheel(int channel, int wheel) {} virtual void progChange(int channel, int pgm) {} virtual void sysEx(std::vector& message) {} enum MidiStatus { // channel voice messages MIDI_NOTE_OFF = 0x80, MIDI_NOTE_ON = 0x90, MIDI_CONTROL_CHANGE = 0xB0, MIDI_PROGRAM_CHANGE = 0xC0, MIDI_PITCH_BEND = 0xE0, MIDI_AFTERTOUCH = 0xD0, // aka channel pressure MIDI_POLY_AFTERTOUCH = 0xA0, // aka key pressure MIDI_CLOCK = 0xF8, MIDI_START = 0xFA, MIDI_CONT = 0xFB, MIDI_STOP = 0xFC, MIDI_SYSEX_START = 0xF0, MIDI_SYSEX_STOP = 0xF7 }; enum MidiCtrl { ALL_NOTES_OFF = 123, ALL_SOUND_OFF = 120 }; enum MidiNPN { PITCH_BEND_RANGE = 0 }; }; /** * A class to decode NRPN and RPN messages, adapted from JUCE forum message: * https://forum.juce.com/t/14bit-midi-controller-support/11517 */ class MidiNRPN { private: bool ctrlnew; int ctrlnum; int ctrlval; int nrpn_lsb, nrpn_msb; int data_lsb, data_msb; enum { midi_nrpn_lsb = 98, midi_nrpn_msb = 99, midi_rpn_lsb = 100, midi_rpn_msb = 101, midi_data_lsb = 38, midi_data_msb = 6 }; public: MidiNRPN(): ctrlnew(false), nrpn_lsb(-1), nrpn_msb(-1), data_lsb(-1), data_msb(-1) {} // return true if the message has been filtered bool process(int data1, int data2) { switch (data1) { case midi_nrpn_lsb: nrpn_lsb = data2; return true; case midi_nrpn_msb: nrpn_msb = data2; return true; case midi_rpn_lsb: { if (data2 == 127) { nrpn_lsb = data_lsb = -1; } else { nrpn_lsb = 0; data_lsb = -1; } return true; } case midi_rpn_msb: { if (data2 == 127) { nrpn_msb = data_msb = -1; } else { nrpn_msb = 0; data_msb = -1; } return true; } case midi_data_lsb: case midi_data_msb: { if (data1 == midi_data_msb) { if (nrpn_msb < 0) { return false; } data_msb = data2; } else { // midi_data_lsb if (nrpn_lsb < 0) { return false; } data_lsb = data2; } if (data_lsb >= 0 && data_msb >= 0) { ctrlnum = (nrpn_msb << 7) | nrpn_lsb; ctrlval = (data_msb << 7) | data_lsb; data_lsb = data_msb = -1; nrpn_msb = nrpn_lsb = -1; ctrlnew = true; } return true; } default: return false; }; } bool hasNewNRPN() { bool res = ctrlnew; ctrlnew = false; return res; } // results in [0, 16383] int getCtrl() const { return ctrlnum; } int getVal() const { return ctrlval; } }; /** * A pure interface for MIDI handlers that can send/receive MIDI messages to/from 'midi' objects. */ struct midi_interface { virtual void addMidiIn(midi* midi_dsp) = 0; virtual void removeMidiIn(midi* midi_dsp) = 0; virtual ~midi_interface() {} }; /**************************************************** * Base class for MIDI input handling. * * Shared common code used for input handling: * - decoding Real-Time messages: handleSync * - decoding one data byte messages: handleData1 * - decoding two data byte messages: handleData2 * - getting ready messages in polling mode ****************************************************/ class midi_handler : public midi, public midi_interface { protected: std::vector fMidiInputs; std::string fName; MidiNRPN fNRPN; int range(int min, int max, int val) { return (val < min) ? min : ((val >= max) ? max : val); } public: midi_handler(const std::string& name = "MIDIHandler"):midi_interface(), fName(name) {} virtual ~midi_handler() {} void addMidiIn(midi* midi_dsp) { if (midi_dsp) fMidiInputs.push_back(midi_dsp); } void removeMidiIn(midi* midi_dsp) { std::vector::iterator it = std::find(fMidiInputs.begin(), fMidiInputs.end(), midi_dsp); if (it != fMidiInputs.end()) { fMidiInputs.erase(it); } } // Those 2 methods have to be implemented by subclasses virtual bool startMidi() { return true; } virtual void stopMidi() {} void setName(const std::string& name) { fName = name; } std::string getName() { return fName; } // To be used in polling mode virtual int recvMessages(std::vector* message) { return 0; } virtual void sendMessages(std::vector* message, int count) {} // MIDI Real-Time void handleClock(double time) { for (unsigned int i = 0; i < fMidiInputs.size(); i++) { fMidiInputs[i]->clock(time); } } void handleStart(double time) { for (unsigned int i = 0; i < fMidiInputs.size(); i++) { fMidiInputs[i]->startSync(time); } } void handleStop(double time) { for (unsigned int i = 0; i < fMidiInputs.size(); i++) { fMidiInputs[i]->stopSync(time); } } void handleSync(double time, int type) { if (type == MIDI_CLOCK) { handleClock(time); // We can consider start and continue as identical messages } else if ((type == MIDI_START) || (type == MIDI_CONT)) { handleStart(time); } else if (type == MIDI_STOP) { handleStop(time); } } // MIDI 1 data void handleProgChange(double time, int channel, int data1) { for (unsigned int i = 0; i < fMidiInputs.size(); i++) { fMidiInputs[i]->progChange(time, channel, data1); } } void handleAfterTouch(double time, int channel, int data1) { for (unsigned int i = 0; i < fMidiInputs.size(); i++) { fMidiInputs[i]->chanPress(time, channel, data1); } } void handleData1(double time, int type, int channel, int data1) { if (type == MIDI_PROGRAM_CHANGE) { handleProgChange(time, channel, data1); } else if (type == MIDI_AFTERTOUCH) { handleAfterTouch(time, channel, data1); } } // MIDI 2 datas void handleKeyOff(double time, int channel, int data1, int data2) { for (unsigned int i = 0; i < fMidiInputs.size(); i++) { fMidiInputs[i]->keyOff(time, channel, data1, data2); } } void handleKeyOn(double time, int channel, int data1, int data2) { if (data2 == 0) { handleKeyOff(time, channel, data1, data2); } else { for (unsigned int i = 0; i < fMidiInputs.size(); i++) { fMidiInputs[i]->keyOn(time, channel, data1, data2); } } } void handleCtrlChange(double time, int channel, int data1, int data2) { // Special processing for NRPN and RPN if (fNRPN.process(data1, data2)) { if (fNRPN.hasNewNRPN()) { for (unsigned int i = 0; i < fMidiInputs.size(); i++) { fMidiInputs[i]->rpn(time, channel, fNRPN.getCtrl(), fNRPN.getVal()); } } } else { for (unsigned int i = 0; i < fMidiInputs.size(); i++) { fMidiInputs[i]->ctrlChange(time, channel, data1, data2); } } } void handlePitchWheel(double time, int channel, int data1, int data2) { for (unsigned int i = 0; i < fMidiInputs.size(); i++) { fMidiInputs[i]->pitchWheel(time, channel, (data2 << 7) + data1); } } void handlePitchWheel(double time, int channel, int bend) { for (unsigned int i = 0; i < fMidiInputs.size(); i++) { fMidiInputs[i]->pitchWheel(time, channel, bend); } } void handlePolyAfterTouch(double time, int channel, int data1, int data2) { for (unsigned int i = 0; i < fMidiInputs.size(); i++) { fMidiInputs[i]->keyPress(time, channel, data1, data2); } } void handleData2(double time, int type, int channel, int data1, int data2) { if (type == MIDI_NOTE_OFF) { handleKeyOff(time, channel, data1, data2); } else if (type == MIDI_NOTE_ON) { handleKeyOn(time, channel, data1, data2); } else if (type == MIDI_CONTROL_CHANGE) { handleCtrlChange(time, channel, data1, data2); } else if (type == MIDI_PITCH_BEND) { handlePitchWheel(time, channel, data1, data2); } else if (type == MIDI_POLY_AFTERTOUCH) { handlePolyAfterTouch(time, channel, data1, data2); } } // SysEx void handleSysex(double time, std::vector& message) { for (unsigned int i = 0; i < fMidiInputs.size(); i++) { fMidiInputs[i]->sysEx(time, message); } } void handleMessage(double time, int type, std::vector& message) { if (type == MIDI_SYSEX_START) { handleSysex(time, message); } } }; #define ucast(v) static_cast(v) #endif // __midi__ /************************** END midi.h **************************/ #ifdef _MSC_VER #define gsscanf sscanf_s #else #define gsscanf sscanf #endif /** * Helper code for MIDI meta and polyphonic 'nvoices' parsing. */ struct MidiMeta : public Meta, public std::map { void declare(const char* key, const char* value) { (*this)[key] = value; } const std::string get(const char* key, const char* def) { return (this->find(key) != this->end()) ? (*this)[key] : def; } static void analyse(dsp* mono_dsp, bool& midi_sync, int& nvoices) { JSONUI jsonui; mono_dsp->buildUserInterface(&jsonui); std::string json = jsonui.JSON(); midi_sync = ((json.find("midi") != std::string::npos) && ((json.find("start") != std::string::npos) || (json.find("stop") != std::string::npos) || (json.find("clock") != std::string::npos) || (json.find("timestamp") != std::string::npos))); #if defined(NVOICES) && NVOICES!=NUM_VOICES nvoices = NVOICES; #else MidiMeta meta; mono_dsp->metadata(&meta); bool found_voices = false; // If "options" metadata is used std::string options = meta.get("options", ""); if (options != "") { std::map metadata; std::string res; MetaDataUI::extractMetadata(options, res, metadata); if (metadata.find("nvoices") != metadata.end()) { nvoices = std::atoi(metadata["nvoices"].c_str()); found_voices = true; } } // Otherwise test for "nvoices" metadata if (!found_voices) { std::string numVoices = meta.get("nvoices", "0"); nvoices = std::atoi(numVoices.c_str()); } nvoices = std::max(0, nvoices); #endif } static bool checkPolyphony(dsp* mono_dsp) { MapUI map_ui; mono_dsp->buildUserInterface(&map_ui); bool has_freq = false; bool has_gate = false; bool has_gain = false; for (int i = 0; i < map_ui.getParamsCount(); i++) { std::string path = map_ui.getParamAddress(i); has_freq |= MapUI::endsWith(path, "/freq"); has_freq |= MapUI::endsWith(path, "/key"); has_gate |= MapUI::endsWith(path, "/gate"); has_gain |= MapUI::endsWith(path, "/gain"); has_gain |= MapUI::endsWith(path, "/vel"); has_gain |= MapUI::endsWith(path, "/velocity"); } return (has_freq && has_gate && has_gain); } }; /** * uiMidi : Faust User Interface * This class decodes MIDI meta data and maps incoming MIDI messages to them. * Currently ctrlChange, keyOn/keyOff, keyPress, progChange, chanPress, pitchWheel/pitchBend * start/stop/clock meta data is handled. * MIDI channel is numbered in [1..16] in this layer. * Channel 0 means "all channels" when receiving or sending. */ class uiMidi { friend class MidiUI; protected: midi* fMidiOut; bool fInputCtrl; int fChan; bool inRange(FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT v) { return (min <= v && v <= max); } public: uiMidi(midi* midi_out, bool input, int chan = 0):fMidiOut(midi_out), fInputCtrl(input), fChan(chan) {} virtual ~uiMidi() {} }; /** * Base class for MIDI aware UI items. */ class uiMidiItem : public uiMidi, public uiItem { public: uiMidiItem(midi* midi_out, GUI* ui, FAUSTFLOAT* zone, bool input = true, int chan = 0) :uiMidi(midi_out, input, chan), uiItem(ui, zone) {} virtual ~uiMidiItem() {} virtual void reflectZone() {} }; /** * Base class for MIDI aware UI items with timestamp support. */ class uiMidiTimedItem : public uiMidi, public uiTimedItem { public: uiMidiTimedItem(midi* midi_out, GUI* ui, FAUSTFLOAT* zone, bool input = true, int chan = 0) :uiMidi(midi_out, input, chan), uiTimedItem(ui, zone) {} virtual ~uiMidiTimedItem() {} virtual void reflectZone() {} }; /** * MIDI sync. */ class uiMidiStart : public uiMidiTimedItem { public: uiMidiStart(midi* midi_out, GUI* ui, FAUSTFLOAT* zone, bool input = true) :uiMidiTimedItem(midi_out, ui, zone, input) {} virtual ~uiMidiStart() {} virtual void reflectZone() { FAUSTFLOAT v = *fZone; fCache = v; if (v != FAUSTFLOAT(0)) { fMidiOut->startSync(0); } } void modifyZone(double date, FAUSTFLOAT v) { if (fInputCtrl) { uiItem::modifyZone(FAUSTFLOAT(v)); } } }; class uiMidiStop : public uiMidiTimedItem { public: uiMidiStop(midi* midi_out, GUI* ui, FAUSTFLOAT* zone, bool input = true) :uiMidiTimedItem(midi_out, ui, zone, input) {} virtual ~uiMidiStop() {} virtual void reflectZone() { FAUSTFLOAT v = *fZone; fCache = v; if (v != FAUSTFLOAT(1)) { fMidiOut->stopSync(0); } } void modifyZone(double date, FAUSTFLOAT v) { if (fInputCtrl) { uiItem::modifyZone(FAUSTFLOAT(v)); } } }; class uiMidiClock : public uiMidiTimedItem { private: bool fState; public: uiMidiClock(midi* midi_out, GUI* ui, FAUSTFLOAT* zone, bool input = true) :uiMidiTimedItem(midi_out, ui, zone, input), fState(false) {} virtual ~uiMidiClock() {} virtual void reflectZone() { FAUSTFLOAT v = *fZone; fCache = v; fMidiOut->clock(0); } void modifyZone(double date, FAUSTFLOAT v) { if (fInputCtrl) { fState = !fState; uiMidiTimedItem::modifyZone(date, FAUSTFLOAT(fState)); } } }; /** * Standard MIDI events. */ /** * uiMidiProgChange uses the [min...max] range. */ class uiMidiProgChange : public uiMidiTimedItem { public: FAUSTFLOAT fMin, fMax; uiMidiProgChange(midi* midi_out, GUI* ui, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max, bool input = true, int chan = 0) :uiMidiTimedItem(midi_out, ui, zone, input, chan), fMin(min), fMax(max) {} virtual ~uiMidiProgChange() {} virtual void reflectZone() { FAUSTFLOAT v = *fZone; fCache = v; if (inRange(fMin, fMax, v)) { if (fChan == 0) { // Send on [0..15] channels on the MIDI layer for (int chan = 0; chan < 16; chan++) { fMidiOut->progChange(chan, v); } } else { fMidiOut->progChange(fChan - 1, v); } } } void modifyZone(FAUSTFLOAT v) { if (fInputCtrl && inRange(fMin, fMax, v)) { uiItem::modifyZone(v); } } void modifyZone(double date, FAUSTFLOAT v) { if (fInputCtrl && inRange(fMin, fMax, v)) { uiMidiTimedItem::modifyZone(date, v); } } }; /** * uiMidiChanPress. */ class uiMidiChanPress : public uiMidiTimedItem, public uiConverter { public: uiMidiChanPress(midi* midi_out, GUI* ui, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max, bool input = true, MetaDataUI::Scale scale = MetaDataUI::kLin, int chan = 0) :uiMidiTimedItem(midi_out, ui, zone, input, chan), uiConverter(scale, 0., 127., min, max) {} virtual ~uiMidiChanPress() {} virtual void reflectZone() { FAUSTFLOAT v = *fZone; fCache = v; int conv = std::round(fConverter->faust2ui(v)); if (fChan == 0) { // Send on [0..15] channels on the MIDI layer for (int chan = 0; chan < 16; chan++) { fMidiOut->chanPress(chan, conv); } } else { fMidiOut->chanPress(fChan - 1, conv); } } void modifyZone(FAUSTFLOAT v) { if (fInputCtrl) { uiItem::modifyZone(FAUSTFLOAT(fConverter->ui2faust(v))); } } void modifyZone(double date, FAUSTFLOAT v) { if (fInputCtrl) { uiMidiTimedItem::modifyZone(date, FAUSTFLOAT(fConverter->ui2faust(v))); } } }; /** * uiMidiCtrlChange does scale (kLin/kLog/kExp) mapping. */ class uiMidiCtrlChange : public uiMidiTimedItem, public uiConverter { private: int fCtrl; public: uiMidiCtrlChange(midi* midi_out, int ctrl, GUI* ui, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max, bool input = true, MetaDataUI::Scale scale = MetaDataUI::kLin, int chan = 0) :uiMidiTimedItem(midi_out, ui, zone, input, chan), uiConverter(scale, 0., 127., min, max), fCtrl(ctrl) {} virtual ~uiMidiCtrlChange() {} virtual void reflectZone() { FAUSTFLOAT v = *fZone; fCache = v; int conv = std::round(fConverter->faust2ui(v)); if (fChan == 0) { // Send on [0..15] channels on the MIDI layer for (int chan = 0; chan < 16; chan++) { fMidiOut->ctrlChange(chan, fCtrl, conv); } } else { fMidiOut->ctrlChange(fChan - 1, fCtrl, conv); } } void modifyZone(FAUSTFLOAT v) { if (fInputCtrl) { uiItem::modifyZone(FAUSTFLOAT(fConverter->ui2faust(v))); } } void modifyZone(double date, FAUSTFLOAT v) { if (fInputCtrl) { uiMidiTimedItem::modifyZone(date, FAUSTFLOAT(fConverter->ui2faust(v))); } } }; // Use a two segments linear converter class uiMidiPitchWheel : public uiMidiTimedItem { private: LinearValueConverter2 fConverter; public: uiMidiPitchWheel(midi* midi_out, GUI* ui, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max, bool input = true, int chan = 0) :uiMidiTimedItem(midi_out, ui, zone, input, chan) { if (min <= 0 && max >= 0) { fConverter = LinearValueConverter2(0., 8191., 16383., double(min), 0., double(max)); } else { // Degenerated case... fConverter = LinearValueConverter2(0., 8191., 16383., double(min),double(min + (max - min)/FAUSTFLOAT(2)), double(max)); } } virtual ~uiMidiPitchWheel() {} virtual void reflectZone() { FAUSTFLOAT v = *fZone; fCache = v; int conv = std::round(fConverter.faust2ui(v)); if (fChan == 0) { // Send on [0..15] channels on the MIDI layer for (int chan = 0; chan < 16; chan++) { fMidiOut->pitchWheel(chan, conv); } } else { fMidiOut->pitchWheel(fChan - 1, conv); } } void modifyZone(FAUSTFLOAT v) { if (fInputCtrl) { uiItem::modifyZone(FAUSTFLOAT(fConverter.ui2faust(v))); } } void modifyZone(double date, FAUSTFLOAT v) { if (fInputCtrl) { uiMidiTimedItem::modifyZone(FAUSTFLOAT(fConverter.ui2faust(v))); } } void setRange(int val) { double semi = (val / 128) + ((val % 128) / 100.); fConverter.setMappingValues(0., 8191., 16383., -semi, 0., semi); } }; /** * uiMidiKeyOn does scale (kLin/kLog/kExp) mapping for velocity. */ class uiMidiKeyOn : public uiMidiTimedItem, public uiConverter { private: int fKeyOn; public: uiMidiKeyOn(midi* midi_out, int key, GUI* ui, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max, bool input = true, MetaDataUI::Scale scale = MetaDataUI::kLin, int chan = 0) :uiMidiTimedItem(midi_out, ui, zone, input, chan), uiConverter(scale, 0., 127., min, max), fKeyOn(key) {} virtual ~uiMidiKeyOn() {} virtual void reflectZone() { FAUSTFLOAT v = *fZone; fCache = v; int conv = std::round(fConverter->faust2ui(v)); if (fChan == 0) { // Send on [0..15] channels on the MIDI layer for (int chan = 0; chan < 16; chan++) { fMidiOut->keyOn(chan, fKeyOn, conv); } } else { fMidiOut->keyOn(fChan - 1, fKeyOn, conv); } } void modifyZone(FAUSTFLOAT v) { if (fInputCtrl) { uiItem::modifyZone(FAUSTFLOAT(fConverter->ui2faust(v))); } } void modifyZone(double date, FAUSTFLOAT v) { if (fInputCtrl) { uiMidiTimedItem::modifyZone(date, FAUSTFLOAT(fConverter->ui2faust(v))); } } }; /** * uiMidiKeyOff does scale (kLin/kLog/kExp) mapping for velocity. */ class uiMidiKeyOff : public uiMidiTimedItem, public uiConverter { private: int fKeyOff; public: uiMidiKeyOff(midi* midi_out, int key, GUI* ui, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max, bool input = true, MetaDataUI::Scale scale = MetaDataUI::kLin, int chan = 0) :uiMidiTimedItem(midi_out, ui, zone, input, chan), uiConverter(scale, 0., 127., min, max), fKeyOff(key) {} virtual ~uiMidiKeyOff() {} virtual void reflectZone() { FAUSTFLOAT v = *fZone; fCache = v; int conv = std::round(fConverter->faust2ui(v)); if (fChan == 0) { // Send on [0..15] channels on the MIDI layer for (int chan = 0; chan < 16; chan++) { fMidiOut->keyOff(chan, fKeyOff, conv); } } else { fMidiOut->keyOff(fChan - 1, fKeyOff, conv); } } void modifyZone(FAUSTFLOAT v) { if (fInputCtrl) { uiItem::modifyZone(FAUSTFLOAT(fConverter->ui2faust(v))); } } void modifyZone(double date, FAUSTFLOAT v) { if (fInputCtrl) { uiMidiTimedItem::modifyZone(date, FAUSTFLOAT(fConverter->ui2faust(v))); } } }; /** * uiMidiKeyPress does scale (kLin/kLog/kExp) mapping for velocity. */ class uiMidiKeyPress : public uiMidiTimedItem, public uiConverter { private: int fKey; public: uiMidiKeyPress(midi* midi_out, int key, GUI* ui, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max, bool input = true, MetaDataUI::Scale scale = MetaDataUI::kLin, int chan = 0) :uiMidiTimedItem(midi_out, ui, zone, input, chan), uiConverter(scale, 0., 127., min, max), fKey(key) {} virtual ~uiMidiKeyPress() {} virtual void reflectZone() { FAUSTFLOAT v = *fZone; fCache = v; int conv = std::round(fConverter->faust2ui(v)); if (fChan == 0) { // Send on [0..15] channels on the MIDI layer for (int chan = 0; chan < 16; chan++) { fMidiOut->keyPress(chan, fKey, conv); } } else { fMidiOut->keyPress(fChan - 1, fKey, conv); } } void modifyZone(FAUSTFLOAT v) { if (fInputCtrl) { uiItem::modifyZone(FAUSTFLOAT(fConverter->ui2faust(v))); } } void modifyZone(double date, FAUSTFLOAT v) { if (fInputCtrl) { uiMidiTimedItem::modifyZone(date, FAUSTFLOAT(fConverter->ui2faust(v))); } } }; /****************************************************************************************** * MidiUI : Faust User Interface * This class decodes MIDI metadata and maps incoming MIDI messages to them. * Currently ctrlChange, keyOn/keyOff, keyPress, progChange, chanPress, pitchWheel/pitchBend * start/stop/clock meta data are handled. * * Maps associating MIDI event ID (like each ctrl number) with all MIDI aware UI items * are defined and progressively filled when decoding MIDI related metadata. * MIDI aware UI items are used in both directions: * - modifying their internal state when receving MIDI input events * - sending their internal state as MIDI output events *******************************************************************************************/ class MidiUI : public GUI, public midi, public midi_interface, public MetaDataUI { // Add uiItem subclasses objects are deallocated by the inherited GUI class typedef std::map > TCtrlChangeTable; typedef std::vector TProgChangeTable; typedef std::vector TChanPressTable; typedef std::map > TKeyOnTable; typedef std::map > TKeyOffTable; typedef std::map > TKeyPressTable; typedef std::vector TPitchWheelTable; protected: TCtrlChangeTable fCtrlChangeTable; TProgChangeTable fProgChangeTable; TChanPressTable fChanPressTable; TKeyOnTable fKeyOnTable; TKeyOffTable fKeyOffTable; TKeyOnTable fKeyTable; TKeyPressTable fKeyPressTable; TPitchWheelTable fPitchWheelTable; std::vector fStartTable; std::vector fStopTable; std::vector fClockTable; std::vector > fMetaAux; midi_handler* fMidiHandler; bool fDelete; bool fTimeStamp; void addGenericZone(FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max, bool input = true) { if (fMetaAux.size() > 0) { for (size_t i = 0; i < fMetaAux.size(); i++) { unsigned num; unsigned chan; if (fMetaAux[i].first == "midi") { if (gsscanf(fMetaAux[i].second.c_str(), "ctrl %u %u", &num, &chan) == 2) { fCtrlChangeTable[num].push_back(new uiMidiCtrlChange(fMidiHandler, num, this, zone, min, max, input, getScale(zone), chan)); } else if (gsscanf(fMetaAux[i].second.c_str(), "ctrl %u", &num) == 1) { fCtrlChangeTable[num].push_back(new uiMidiCtrlChange(fMidiHandler, num, this, zone, min, max, input, getScale(zone))); } else if (gsscanf(fMetaAux[i].second.c_str(), "keyon %u %u", &num, &chan) == 2) { fKeyOnTable[num].push_back(new uiMidiKeyOn(fMidiHandler, num, this, zone, min, max, input, getScale(zone), chan)); } else if (gsscanf(fMetaAux[i].second.c_str(), "keyon %u", &num) == 1) { fKeyOnTable[num].push_back(new uiMidiKeyOn(fMidiHandler, num, this, zone, min, max, input, getScale(zone))); } else if (gsscanf(fMetaAux[i].second.c_str(), "keyoff %u %u", &num, &chan) == 2) { fKeyOffTable[num].push_back(new uiMidiKeyOff(fMidiHandler, num, this, zone, min, max, input, getScale(zone), chan)); } else if (gsscanf(fMetaAux[i].second.c_str(), "keyoff %u", &num) == 1) { fKeyOffTable[num].push_back(new uiMidiKeyOff(fMidiHandler, num, this, zone, min, max, input, getScale(zone))); } else if (gsscanf(fMetaAux[i].second.c_str(), "key %u %u", &num, &chan) == 2) { fKeyTable[num].push_back(new uiMidiKeyOn(fMidiHandler, num, this, zone, min, max, input, getScale(zone), chan)); } else if (gsscanf(fMetaAux[i].second.c_str(), "key %u", &num) == 1) { fKeyTable[num].push_back(new uiMidiKeyOn(fMidiHandler, num, this, zone, min, max, input, getScale(zone))); } else if (gsscanf(fMetaAux[i].second.c_str(), "keypress %u %u", &num, &chan) == 2) { fKeyPressTable[num].push_back(new uiMidiKeyPress(fMidiHandler, num, this, zone, min, max, input, getScale(zone), chan)); } else if (gsscanf(fMetaAux[i].second.c_str(), "keypress %u", &num) == 1) { fKeyPressTable[num].push_back(new uiMidiKeyPress(fMidiHandler, num, this, zone, min, max, input, getScale(zone))); } else if (gsscanf(fMetaAux[i].second.c_str(), "pgm %u", &chan) == 1) { fProgChangeTable.push_back(new uiMidiProgChange(fMidiHandler, this, zone, min, max, input, chan)); } else if (strcmp(fMetaAux[i].second.c_str(), "pgm") == 0) { fProgChangeTable.push_back(new uiMidiProgChange(fMidiHandler, this, zone, min, max, input)); } else if (gsscanf(fMetaAux[i].second.c_str(), "chanpress %u", &chan) == 1) { fChanPressTable.push_back(new uiMidiChanPress(fMidiHandler, this, zone, min, max, input, getScale(zone), chan)); } else if ((fMetaAux[i].second == "chanpress")) { fChanPressTable.push_back(new uiMidiChanPress(fMidiHandler, this, zone, min, max, input, getScale(zone))); } else if ((gsscanf(fMetaAux[i].second.c_str(), "pitchwheel %u", &chan) == 1) || (gsscanf(fMetaAux[i].second.c_str(), "pitchbend %u", &chan) == 1)) { fPitchWheelTable.push_back(new uiMidiPitchWheel(fMidiHandler, this, zone, min, max, input, chan)); } else if ((fMetaAux[i].second == "pitchwheel") || (fMetaAux[i].second == "pitchbend")) { fPitchWheelTable.push_back(new uiMidiPitchWheel(fMidiHandler, this, zone, min, max, input)); // MIDI sync } else if (fMetaAux[i].second == "start") { fStartTable.push_back(new uiMidiStart(fMidiHandler, this, zone, input)); } else if (fMetaAux[i].second == "stop") { fStopTable.push_back(new uiMidiStop(fMidiHandler, this, zone, input)); } else if (fMetaAux[i].second == "clock") { fClockTable.push_back(new uiMidiClock(fMidiHandler, this, zone, input)); // Explicit metadata to activate 'timestamp' mode } else if (fMetaAux[i].second == "timestamp") { fTimeStamp = true; } } } } fMetaAux.clear(); } template void updateTable1(TABLE& table, double date, int channel, int val1) { for (size_t i = 0; i < table.size(); i++) { int channel_aux = table[i]->fChan; // channel_aux == 0 means "all channels" if (channel_aux == 0 || channel == channel_aux - 1) { if (fTimeStamp) { table[i]->modifyZone(date, FAUSTFLOAT(val1)); } else { table[i]->modifyZone(FAUSTFLOAT(val1)); } } } } template void updateTable2(TABLE& table, double date, int channel, int val1, int val2) { if (table.find(val1) != table.end()) { for (size_t i = 0; i < table[val1].size(); i++) { int channel_aux = table[val1][i]->fChan; // channel_aux == 0 means "all channels" if (channel_aux == 0 || channel == channel_aux - 1) { if (fTimeStamp) { table[val1][i]->modifyZone(date, FAUSTFLOAT(val2)); } else { table[val1][i]->modifyZone(FAUSTFLOAT(val2)); } } } } } public: MidiUI(midi_handler* midi_handler, bool delete_handler = false) { fMidiHandler = midi_handler; fMidiHandler->addMidiIn(this); // TODO: use shared_ptr based implementation fDelete = delete_handler; fTimeStamp = false; } virtual ~MidiUI() { // Remove from fMidiHandler fMidiHandler->removeMidiIn(this); // TODO: use shared_ptr based implementation if (fDelete) delete fMidiHandler; } bool run() { return fMidiHandler->startMidi(); } void stop() { fMidiHandler->stopMidi(); } void addMidiIn(midi* midi_dsp) { fMidiHandler->addMidiIn(midi_dsp); } void removeMidiIn(midi* midi_dsp) { fMidiHandler->removeMidiIn(midi_dsp); } // -- active widgets virtual void addButton(const char* label, FAUSTFLOAT* zone) { addGenericZone(zone, FAUSTFLOAT(0), FAUSTFLOAT(1)); } virtual void addCheckButton(const char* label, FAUSTFLOAT* zone) { addGenericZone(zone, FAUSTFLOAT(0), FAUSTFLOAT(1)); } virtual void addVerticalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) { addGenericZone(zone, min, max); } virtual void addHorizontalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) { addGenericZone(zone, min, max); } virtual void addNumEntry(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) { addGenericZone(zone, min, max); } // -- passive widgets virtual void addHorizontalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) { addGenericZone(zone, min, max, false); } virtual void addVerticalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) { addGenericZone(zone, min, max, false); } // -- metadata declarations virtual void declare(FAUSTFLOAT* zone, const char* key, const char* val) { MetaDataUI::declare(zone, key, val); fMetaAux.push_back(std::make_pair(key, val)); } // -- MIDI API void key(double date, int channel, int note, int velocity) { updateTable2(fKeyTable, date, channel, note, velocity); } MapUI* keyOn(double date, int channel, int note, int velocity) { updateTable2(fKeyOnTable, date, channel, note, velocity); // If note is in fKeyTable, handle it as a keyOn key(date, channel, note, velocity); return nullptr; } void keyOff(double date, int channel, int note, int velocity) { updateTable2(fKeyOffTable, date, channel, note, velocity); // If note is in fKeyTable, handle it as a keyOff with a 0 velocity key(date, channel, note, 0); } void ctrlChange(double date, int channel, int ctrl, int value) { updateTable2(fCtrlChangeTable, date, channel, ctrl, value); } void rpn(double date, int channel, int ctrl, int value) { if (ctrl == midi::PITCH_BEND_RANGE) { for (size_t i = 0; i < fPitchWheelTable.size(); i++) { // channel_aux == 0 means "all channels" int channel_aux = fPitchWheelTable[i]->fChan; if (channel_aux == 0 || channel == channel_aux - 1) { fPitchWheelTable[i]->setRange(value); } } } } void progChange(double date, int channel, int pgm) { updateTable1(fProgChangeTable, date, channel, pgm); } void pitchWheel(double date, int channel, int wheel) { updateTable1(fPitchWheelTable, date, channel, wheel); } void keyPress(double date, int channel, int pitch, int press) { updateTable2(fKeyPressTable, date, channel, pitch, press); } void chanPress(double date, int channel, int press) { updateTable1(fChanPressTable, date, channel, press); } void ctrlChange14bits(double date, int channel, int ctrl, int value) {} // MIDI sync void startSync(double date) { for (size_t i = 0; i < fStartTable.size(); i++) { fStartTable[i]->modifyZone(date, FAUSTFLOAT(1)); } } void stopSync(double date) { for (size_t i = 0; i < fStopTable.size(); i++) { fStopTable[i]->modifyZone(date, FAUSTFLOAT(0)); } } void clock(double date) { for (size_t i = 0; i < fClockTable.size(); i++) { fClockTable[i]->modifyZone(date, FAUSTFLOAT(1)); } } }; #endif // FAUST_MIDIUI_H /************************** END MidiUI.h **************************/ /************************** BEGIN esp32-midi.h **************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ***************************************************************************/ #ifndef __esp32_midi__ #define __esp32_midi__ #include #include "driver/uart.h" #include "jdksmidi/world.h" #include "jdksmidi/midi.h" #include "jdksmidi/msg.h" #include "jdksmidi/sysex.h" #include "jdksmidi/parser.h" using namespace jdksmidi; #ifndef RX1 #define RX1 GPIO_NUM_5 #endif #ifndef TX1 #define TX1 GPIO_NUM_19 #endif #define PORT_NUM UART_NUM_1 #define RX_BUF_SIZE 1024 /** * MIDI handler for the ESP32 boards. */ class esp32_midi : public midi_handler { private: TaskHandle_t fProcessMidiHandle; void processMidi() { double time = 0; uint8_t data[RX_BUF_SIZE]; MIDIParser parser; jdksMIDIMessage message; while (true) { int rxBytes = uart_read_bytes(UART_NUM_1, data, RX_BUF_SIZE, 1); if (rxBytes > 0) { for (int i = 0; i < rxBytes; i++) { if (parser.Parse((uchar)data[i], &message)) { unsigned char status = message.GetStatus(); if (status < 0xF0) { // channel/system message discriminator. unsigned char type = message.GetType(); switch (type) { case 0x80: // Note Off handleKeyOff(time, message.GetChannel(), message.GetNote(), message.GetVelocity()); break; case 0x90: // Note On handleKeyOn(time, message.GetChannel(), message.GetNote(), message.GetVelocity()); break; case 0xA0: // Poly Key Pressure handlePolyAfterTouch(time, message.GetChannel(), message.GetNote(), message.GetByte2()); break; case 0xB0: // Control Change handleCtrlChange(time, message.GetChannel(), message.GetController(), message.GetControllerValue()); break; case 0xC0: // Program Change // No Bank Select in faust? handleProgChange(time, message.GetChannel(), message.GetPGValue()); break; case 0xD0: // Channel Pressure handleAfterTouch(time, message.GetChannel(), message.GetChannelPressure()); break; case 0xE0: // Pitch Bend handlePitchWheel(time, message.GetChannel(), message.GetByte1(), message.GetByte2()); break; default: break; } } else { switch (status) { case 0xF8: // Timing Clock handleClock(time); break; // We can consider start and continue as identical messages. case 0xFA: // Start case 0xFB: // Continue handleStart(time); break; case 0xFC: // Stop handleStop(time); break; case 0xF0: // SysEx Start // TODO break; case 0xF7: // SysEx Stop // TODO break; default: break; } } } // Synchronize all GUI controllers GUI::updateAllGuis(); } } } } static void processMidiHandler(void* arg) { static_cast(arg)->processMidi(); } public: esp32_midi():midi_handler("esp32"),fProcessMidiHandle(NULL) { // Setup UART for MIDI const uart_config_t uart_config = { .baud_rate = 31250, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .rx_flow_ctrl_thresh = 122, .use_ref_tick = false }; uart_param_config(PORT_NUM, &uart_config); uart_set_pin(PORT_NUM, TX1, RX1, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); // We won't use a buffer for sending data. uart_driver_install(PORT_NUM, RX_BUF_SIZE * 2, 0, 0, NULL, 0); } virtual ~esp32_midi() { stopMidi(); } bool startMidi() { // Start MIDI receive task return (xTaskCreatePinnedToCore(processMidiHandler, "Faust MIDI Task", 4096, (void*)this, 5, &fProcessMidiHandle, 1) == pdPASS); } void stopMidi() { if (fProcessMidiHandle != nullptr) { vTaskDelete(fProcessMidiHandle); fProcessMidiHandle = nullptr; } } }; #endif /************************** END esp32-midi.h **************************/ #endif // for polyphonic synths #ifdef NVOICES /************************** BEGIN poly-dsp.h ************************* FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. *********************************************************************/ #ifndef __poly_dsp__ #define __poly_dsp__ #include #include #include #include #include #include #include #include #include #include #include /************************** BEGIN dsp-combiner.h ************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ************************************************************************/ #ifndef __dsp_combiner__ #define __dsp_combiner__ #include #include #include #include /** * @file dsp-combiner.h * @brief DSP Combiner Library * * This library provides classes for combining DSP modules. * It includes classes for sequencing, parallelizing, splitting, merging, recursing, and crossfading DSP modules. * */ enum Layout { kVerticalGroup, kHorizontalGroup, kTabGroup }; /** * @class dsp_binary_combiner * @brief Base class and common code for binary combiners * * This class serves as the base class for various DSP combiners that work with two DSP modules. * It provides common methods for building user interfaces, allocating and deleting channels, and more. */ class dsp_binary_combiner : public dsp { protected: dsp* fDSP1; dsp* fDSP2; int fBufferSize; Layout fLayout; std::string fLabel; void buildUserInterfaceAux(UI* ui_interface) { switch (fLayout) { case kHorizontalGroup: ui_interface->openHorizontalBox(fLabel.c_str()); fDSP1->buildUserInterface(ui_interface); fDSP2->buildUserInterface(ui_interface); ui_interface->closeBox(); break; case kVerticalGroup: ui_interface->openVerticalBox(fLabel.c_str()); fDSP1->buildUserInterface(ui_interface); fDSP2->buildUserInterface(ui_interface); ui_interface->closeBox(); break; case kTabGroup: ui_interface->openTabBox(fLabel.c_str()); ui_interface->openVerticalBox("DSP1"); fDSP1->buildUserInterface(ui_interface); ui_interface->closeBox(); ui_interface->openVerticalBox("DSP2"); fDSP2->buildUserInterface(ui_interface); ui_interface->closeBox(); ui_interface->closeBox(); break; } } FAUSTFLOAT** allocateChannels(int num) { FAUSTFLOAT** channels = new FAUSTFLOAT*[num]; for (int chan = 0; chan < num; chan++) { channels[chan] = new FAUSTFLOAT[fBufferSize]; memset(channels[chan], 0, sizeof(FAUSTFLOAT) * fBufferSize); } return channels; } void deleteChannels(FAUSTFLOAT** channels, int num) { for (int chan = 0; chan < num; chan++) { delete [] channels[chan]; } delete [] channels; } public: dsp_binary_combiner(dsp* dsp1, dsp* dsp2, int buffer_size, Layout layout, const std::string& label) :fDSP1(dsp1), fDSP2(dsp2), fBufferSize(buffer_size), fLayout(layout), fLabel(label) {} virtual ~dsp_binary_combiner() { delete fDSP1; delete fDSP2; } virtual int getSampleRate() { return fDSP1->getSampleRate(); } virtual void init(int sample_rate) { fDSP1->init(sample_rate); fDSP2->init(sample_rate); } virtual void instanceInit(int sample_rate) { fDSP1->instanceInit(sample_rate); fDSP2->instanceInit(sample_rate); } virtual void instanceConstants(int sample_rate) { fDSP1->instanceConstants(sample_rate); fDSP2->instanceConstants(sample_rate); } virtual void instanceResetUserInterface() { fDSP1->instanceResetUserInterface(); fDSP2->instanceResetUserInterface(); } virtual void instanceClear() { fDSP1->instanceClear(); fDSP2->instanceClear(); } virtual void metadata(Meta* m) { fDSP1->metadata(m); fDSP2->metadata(m); } }; /** * @class dsp_sequencer * @brief Combine two 'compatible' DSP modules in sequence * * This class allows you to combine two DSP modules in sequence. * It computes the first DSP module's outputs and uses them as inputs for the second DSP module. */ class dsp_sequencer : public dsp_binary_combiner { private: FAUSTFLOAT** fDSP1Outputs; public: dsp_sequencer(dsp* dsp1, dsp* dsp2, int buffer_size = 4096, Layout layout = Layout::kTabGroup, const std::string& label = "Sequencer") :dsp_binary_combiner(dsp1, dsp2, buffer_size, layout, label) { fDSP1Outputs = allocateChannels(fDSP1->getNumOutputs()); } virtual ~dsp_sequencer() { deleteChannels(fDSP1Outputs, fDSP1->getNumOutputs()); } virtual int getNumInputs() { return fDSP1->getNumInputs(); } virtual int getNumOutputs() { return fDSP2->getNumOutputs(); } virtual void buildUserInterface(UI* ui_interface) { buildUserInterfaceAux(ui_interface); } virtual dsp* clone() { return new dsp_sequencer(fDSP1->clone(), fDSP2->clone(), fBufferSize, fLayout, fLabel); } virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { fDSP1->compute(count, inputs, fDSP1Outputs); fDSP2->compute(count, fDSP1Outputs, outputs); } virtual void compute(double date_usec, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { compute(count, inputs, outputs); } }; /** * @class dsp_parallelizer * @brief Combine two DSP modules in parallel * * This class combines two DSP modules in parallel. * It computes both DSP modules separately and combines their outputs. */ class dsp_parallelizer : public dsp_binary_combiner { private: FAUSTFLOAT** fDSP2Inputs; FAUSTFLOAT** fDSP2Outputs; public: dsp_parallelizer(dsp* dsp1, dsp* dsp2, int buffer_size = 4096, Layout layout = Layout::kTabGroup, const std::string& label = "Parallelizer") :dsp_binary_combiner(dsp1, dsp2, buffer_size, layout, label) { fDSP2Inputs = new FAUSTFLOAT*[fDSP2->getNumInputs()]; fDSP2Outputs = new FAUSTFLOAT*[fDSP2->getNumOutputs()]; } virtual ~dsp_parallelizer() { delete [] fDSP2Inputs; delete [] fDSP2Outputs; } virtual int getNumInputs() { return fDSP1->getNumInputs() + fDSP2->getNumInputs(); } virtual int getNumOutputs() { return fDSP1->getNumOutputs() + fDSP2->getNumOutputs(); } virtual void buildUserInterface(UI* ui_interface) { buildUserInterfaceAux(ui_interface); } virtual dsp* clone() { return new dsp_parallelizer(fDSP1->clone(), fDSP2->clone(), fBufferSize, fLayout, fLabel); } virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { fDSP1->compute(count, inputs, outputs); // Shift inputs/outputs channels for fDSP2 for (int chan = 0; chan < fDSP2->getNumInputs(); chan++) { fDSP2Inputs[chan] = inputs[fDSP1->getNumInputs() + chan]; } for (int chan = 0; chan < fDSP2->getNumOutputs(); chan++) { fDSP2Outputs[chan] = outputs[fDSP1->getNumOutputs() + chan]; } fDSP2->compute(count, fDSP2Inputs, fDSP2Outputs); } virtual void compute(double date_usec, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { compute(count, inputs, outputs); } }; /** * @class dsp_splitter * @brief Combine two 'compatible' DSP modules in a splitter * * This class combines two DSP modules in a splitter configuration. * The outputs of the first DSP module are connected to the inputs of the second DSP module. */ class dsp_splitter : public dsp_binary_combiner { private: FAUSTFLOAT** fDSP1Outputs; FAUSTFLOAT** fDSP2Inputs; public: dsp_splitter(dsp* dsp1, dsp* dsp2, int buffer_size = 4096, Layout layout = Layout::kTabGroup, const std::string& label = "Splitter") :dsp_binary_combiner(dsp1, dsp2, buffer_size, layout, label) { fDSP1Outputs = allocateChannels(fDSP1->getNumOutputs()); fDSP2Inputs = new FAUSTFLOAT*[fDSP2->getNumInputs()]; } virtual ~dsp_splitter() { deleteChannels(fDSP1Outputs, fDSP1->getNumOutputs()); delete [] fDSP2Inputs; } virtual int getNumInputs() { return fDSP1->getNumInputs(); } virtual int getNumOutputs() { return fDSP2->getNumOutputs(); } virtual void buildUserInterface(UI* ui_interface) { buildUserInterfaceAux(ui_interface); } virtual dsp* clone() { return new dsp_splitter(fDSP1->clone(), fDSP2->clone(), fBufferSize, fLayout, fLabel); } virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { fDSP1->compute(count, inputs, fDSP1Outputs); for (int chan = 0; chan < fDSP2->getNumInputs(); chan++) { fDSP2Inputs[chan] = fDSP1Outputs[chan % fDSP1->getNumOutputs()]; } fDSP2->compute(count, fDSP2Inputs, outputs); } }; /** * @class dsp_merger * @brief Combine two 'compatible' DSP modules in a merger * * This class combines two DSP modules in a merger configuration. * The outputs of the first DSP module are combined with the inputs of the second DSP module. */ class dsp_merger : public dsp_binary_combiner { private: FAUSTFLOAT** fDSP1Inputs; FAUSTFLOAT** fDSP1Outputs; FAUSTFLOAT** fDSP2Inputs; void mix(int count, FAUSTFLOAT* dst, FAUSTFLOAT* src) { for (int frame = 0; frame < count; frame++) { dst[frame] += src[frame]; } } public: dsp_merger(dsp* dsp1, dsp* dsp2, int buffer_size = 4096, Layout layout = Layout::kTabGroup, const std::string& label = "Merger") :dsp_binary_combiner(dsp1, dsp2, buffer_size, layout, label) { fDSP1Inputs = allocateChannels(fDSP1->getNumInputs()); fDSP1Outputs = allocateChannels(fDSP1->getNumOutputs()); fDSP2Inputs = new FAUSTFLOAT*[fDSP2->getNumInputs()]; } virtual ~dsp_merger() { deleteChannels(fDSP1Inputs, fDSP1->getNumInputs()); deleteChannels(fDSP1Outputs, fDSP1->getNumOutputs()); delete [] fDSP2Inputs; } virtual int getNumInputs() { return fDSP1->getNumInputs(); } virtual int getNumOutputs() { return fDSP2->getNumOutputs(); } virtual void buildUserInterface(UI* ui_interface) { buildUserInterfaceAux(ui_interface); } virtual dsp* clone() { return new dsp_merger(fDSP1->clone(), fDSP2->clone(), fBufferSize, fLayout, fLabel); } virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { fDSP1->compute(count, fDSP1Inputs, fDSP1Outputs); memset(fDSP2Inputs, 0, sizeof(FAUSTFLOAT*) * fDSP2->getNumInputs()); for (int chan = 0; chan < fDSP1->getNumOutputs(); chan++) { int mchan = chan % fDSP2->getNumInputs(); if (fDSP2Inputs[mchan]) { mix(count, fDSP2Inputs[mchan], fDSP1Outputs[chan]); } else { fDSP2Inputs[mchan] = fDSP1Outputs[chan]; } } fDSP2->compute(count, fDSP2Inputs, outputs); } }; /** * @class dsp_recursiver * @brief Combine two 'compatible' DSP modules in a recursive way * * This class recursively combines two DSP modules. * The outputs of each module are fed as inputs to the other module in a recursive manner. */ class dsp_recursiver : public dsp_binary_combiner { private: FAUSTFLOAT** fDSP1Inputs; FAUSTFLOAT** fDSP1Outputs; FAUSTFLOAT** fDSP2Inputs; FAUSTFLOAT** fDSP2Outputs; public: dsp_recursiver(dsp* dsp1, dsp* dsp2, Layout layout = Layout::kTabGroup, const std::string& label = "Recursiver") :dsp_binary_combiner(dsp1, dsp2, 1, layout, label) { fDSP1Inputs = allocateChannels(fDSP1->getNumInputs()); fDSP1Outputs = allocateChannels(fDSP1->getNumOutputs()); fDSP2Inputs = allocateChannels(fDSP2->getNumInputs()); fDSP2Outputs = allocateChannels(fDSP2->getNumOutputs()); } virtual ~dsp_recursiver() { deleteChannels(fDSP1Inputs, fDSP1->getNumInputs()); deleteChannels(fDSP1Outputs, fDSP1->getNumOutputs()); deleteChannels(fDSP2Inputs, fDSP2->getNumInputs()); deleteChannels(fDSP2Outputs, fDSP2->getNumOutputs()); } virtual int getNumInputs() { return fDSP1->getNumInputs() - fDSP2->getNumOutputs(); } virtual int getNumOutputs() { return fDSP1->getNumOutputs(); } virtual void buildUserInterface(UI* ui_interface) { buildUserInterfaceAux(ui_interface); } virtual dsp* clone() { return new dsp_recursiver(fDSP1->clone(), fDSP2->clone(), fLayout, fLabel); } virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { for (int frame = 0; (frame < count); frame++) { for (int chan = 0; chan < fDSP2->getNumOutputs(); chan++) { fDSP1Inputs[chan][0] = fDSP2Outputs[chan][0]; } for (int chan = 0; chan < fDSP1->getNumInputs() - fDSP2->getNumOutputs(); chan++) { fDSP1Inputs[chan + fDSP2->getNumOutputs()][0] = inputs[chan][frame]; } fDSP1->compute(1, fDSP1Inputs, fDSP1Outputs); for (int chan = 0; chan < fDSP1->getNumOutputs(); chan++) { outputs[chan][frame] = fDSP1Outputs[chan][0]; } for (int chan = 0; chan < fDSP2->getNumInputs(); chan++) { fDSP2Inputs[chan][0] = fDSP1Outputs[chan][0]; } fDSP2->compute(1, fDSP2Inputs, fDSP2Outputs); } } virtual void compute(double date_usec, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { compute(count, inputs, outputs); } }; /** * @class dsp_crossfader * @brief Crossfade between two DSP modules * * This class allows you to crossfade between two DSP modules. * The crossfade parameter (as a slider) controls the mix between the two modules' outputs. * When Crossfade = 1, the first DSP only is computed, when Crossfade = 0, * the second DSP only is computed, otherwise both DSPs are computed and mixed. */ class dsp_crossfader: public dsp_binary_combiner { private: FAUSTFLOAT fCrossfade; FAUSTFLOAT** fDSPOutputs1; FAUSTFLOAT** fDSPOutputs2; public: dsp_crossfader(dsp* dsp1, dsp* dsp2, Layout layout = Layout::kTabGroup, const std::string& label = "Crossfade") :dsp_binary_combiner(dsp1, dsp2, 4096, layout, label),fCrossfade(FAUSTFLOAT(0.5)) { fDSPOutputs1 = allocateChannels(fDSP1->getNumOutputs()); fDSPOutputs2 = allocateChannels(fDSP1->getNumOutputs()); } virtual ~dsp_crossfader() { deleteChannels(fDSPOutputs1, fDSP1->getNumInputs()); deleteChannels(fDSPOutputs2, fDSP1->getNumOutputs()); } virtual int getNumInputs() { return fDSP1->getNumInputs(); } virtual int getNumOutputs() { return fDSP1->getNumOutputs(); } void buildUserInterface(UI* ui_interface) { switch (fLayout) { case kHorizontalGroup: ui_interface->openHorizontalBox(fLabel.c_str()); ui_interface->addHorizontalSlider("Crossfade", &fCrossfade, FAUSTFLOAT(0.5), FAUSTFLOAT(0), FAUSTFLOAT(1), FAUSTFLOAT(0.01)); fDSP1->buildUserInterface(ui_interface); fDSP2->buildUserInterface(ui_interface); ui_interface->closeBox(); break; case kVerticalGroup: ui_interface->openVerticalBox(fLabel.c_str()); ui_interface->addHorizontalSlider("Crossfade", &fCrossfade, FAUSTFLOAT(0.5), FAUSTFLOAT(0), FAUSTFLOAT(1), FAUSTFLOAT(0.01)); fDSP1->buildUserInterface(ui_interface); fDSP2->buildUserInterface(ui_interface); ui_interface->closeBox(); break; case kTabGroup: ui_interface->openTabBox(fLabel.c_str()); ui_interface->openVerticalBox("Crossfade"); ui_interface->addHorizontalSlider("Crossfade", &fCrossfade, FAUSTFLOAT(0.5), FAUSTFLOAT(0), FAUSTFLOAT(1), FAUSTFLOAT(0.01)); ui_interface->closeBox(); ui_interface->openVerticalBox("DSP1"); fDSP1->buildUserInterface(ui_interface); ui_interface->closeBox(); ui_interface->openVerticalBox("DSP2"); fDSP2->buildUserInterface(ui_interface); ui_interface->closeBox(); ui_interface->closeBox(); break; } } virtual dsp* clone() { return new dsp_crossfader(fDSP1->clone(), fDSP2->clone(), fLayout, fLabel); } virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { if (fCrossfade == FAUSTFLOAT(1)) { fDSP1->compute(count, inputs, outputs); } else if (fCrossfade == FAUSTFLOAT(0)) { fDSP2->compute(count, inputs, outputs); } else { // Compute each effect fDSP1->compute(count, inputs, fDSPOutputs1); fDSP2->compute(count, inputs, fDSPOutputs2); // Mix between the two effects FAUSTFLOAT gain1 = fCrossfade; FAUSTFLOAT gain2 = FAUSTFLOAT(1) - gain1; for (int frame = 0; (frame < count); frame++) { for (int chan = 0; chan < fDSP1->getNumOutputs(); chan++) { outputs[chan][frame] = fDSPOutputs1[chan][frame] * gain1 + fDSPOutputs2[chan][frame] * gain2; } } } } virtual void compute(double date_usec, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { compute(count, inputs, outputs); } }; #ifndef __dsp_algebra_api__ #define __dsp_algebra_api__ /** * DSP algebra API allowing to combine DSPs using the 5 operators Faust block algebra and an additional crossfader combiner. * The two arguments GUI are composed in a group, either kVerticalGroup, kHorizontalGroup or kTabGroup with a label. * * Each operation takes two DSP and a optional layout and label parameters, returns the combined DSPs, * or null if failure with an error message. * * It includes methods to create sequencers, parallelizers, splitters, mergers, recursivers, and crossfaders. */ /** * Create a DSP Sequencer * * This method creates a DSP Sequencer, which combines two DSP modules in a sequencer configuration. * The outputs of the first DSP module are connected to the inputs of the second DSP module. * * @param dsp1 The first DSP module to combine * @param dsp2 The second DSP module to combine * @param error A reference to a string to store error messages (if any) * @param layout The layout for the combined user interface (default: kTabGroup) * @param label The label for the combiner (default: "Sequencer") * @return A pointer to the created DSP Sequencer, or nullptr if an error occurs */ static dsp* createDSPSequencer(dsp* dsp1, dsp* dsp2, std::string& error, Layout layout = Layout::kTabGroup, const std::string& label = "Sequencer") { if (dsp1->getNumOutputs() != dsp2->getNumInputs()) { std::stringstream error_aux; error_aux << "Connection error in dsp_sequencer : the number of outputs (" << dsp1->getNumOutputs() << ") of A " << "must be equal to the number of inputs (" << dsp2->getNumInputs() << ") of B" << std::endl; error = error_aux.str(); return nullptr; } else { return new dsp_sequencer(dsp1, dsp2, 4096, layout, label); } } /** * Create a DSP Parallelizer * * This method creates a DSP Parallelizer, which combines two DSP modules in parallel. * The resulting DSP module computes both input modules separately and combines their outputs. * * @param dsp1 The first DSP module to combine * @param dsp2 The second DSP module to combine * @param error A reference to a string to store error messages (if any) * @param layout The layout for the combined user interface (default: kTabGroup) * @param label The label for the combiner (default: "Parallelizer") * @return A pointer to the created DSP Parallelizer, or nullptr if an error occurs */ static dsp* createDSPParallelizer(dsp* dsp1, dsp* dsp2, std::string& error, Layout layout = Layout::kTabGroup, const std::string& label = "Parallelizer") { return new dsp_parallelizer(dsp1, dsp2, 4096, layout, label); } /** * Create a DSP Splitter * * This method creates a DSP Splitter, which combines two 'compatible' DSP modules in a splitter configuration. * The outputs of the first DSP module are connected to the inputs of the second DSP module. * * @param dsp1 The first DSP module to combine * @param dsp2 The second DSP module to combine * @param error A reference to a string to store error messages (if any) * @param layout The layout for the combined user interface (default: kTabGroup) * @param label The label for the combiner (default: "Splitter") * @return A pointer to the created DSP Splitter, or nullptr if an error occurs */ static dsp* createDSPSplitter(dsp* dsp1, dsp* dsp2, std::string& error, Layout layout = Layout::kTabGroup, const std::string& label = "Splitter") { if (dsp1->getNumOutputs() == 0) { error = "Connection error in dsp_splitter : the first expression has no outputs\n"; return nullptr; } else if (dsp2->getNumInputs() == 0) { error = "Connection error in dsp_splitter : the second expression has no inputs\n"; return nullptr; } else if (dsp2->getNumInputs() % dsp1->getNumOutputs() != 0) { std::stringstream error_aux; error_aux << "Connection error in dsp_splitter : the number of outputs (" << dsp1->getNumOutputs() << ") of the first expression should be a divisor of the number of inputs (" << dsp2->getNumInputs() << ") of the second expression" << std::endl; error = error_aux.str(); return nullptr; } else if (dsp2->getNumInputs() == dsp1->getNumOutputs()) { return new dsp_sequencer(dsp1, dsp2, 4096, layout, label); } else { return new dsp_splitter(dsp1, dsp2, 4096, layout, label); } } /** * Create a DSP Merger * * This method creates a DSP Merger, which combines two 'compatible' DSP modules in a merger configuration. * The outputs of the first DSP module are combined with the inputs of the second DSP module. * * @param dsp1 The first DSP module to combine * @param dsp2 The second DSP module to combine * @param error A reference to a string to store error messages (if any) * @param layout The layout for the combined user interface (default: kTabGroup) * @param label The label for the combiner (default: "Merger") * @return A pointer to the created DSP Merger, or nullptr if an error occurs */ static dsp* createDSPMerger(dsp* dsp1, dsp* dsp2, std::string& error, Layout layout = Layout::kTabGroup, const std::string& label = "Merger") { if (dsp1->getNumOutputs() == 0) { error = "Connection error in dsp_merger : the first expression has no outputs\n"; return nullptr; } else if (dsp2->getNumInputs() == 0) { error = "Connection error in dsp_merger : the second expression has no inputs\n"; return nullptr; } else if (dsp1->getNumOutputs() % dsp2->getNumInputs() != 0) { std::stringstream error_aux; error_aux << "Connection error in dsp_merger : the number of outputs (" << dsp1->getNumOutputs() << ") of the first expression should be a multiple of the number of inputs (" << dsp2->getNumInputs() << ") of the second expression" << std::endl; error = error_aux.str(); return nullptr; } else if (dsp2->getNumInputs() == dsp1->getNumOutputs()) { return new dsp_sequencer(dsp1, dsp2, 4096, layout, label); } else { return new dsp_merger(dsp1, dsp2, 4096, layout, label); } } /** * Create a DSP Recursiver * * This method creates a DSP Recursiver, which combines two 'compatible' DSP modules in a recursive way. * The outputs of each module are fed as inputs to the other module in a recursive manner. * * @param dsp1 The first DSP module to combine * @param dsp2 The second DSP module to combine * @param error A reference to a string to store error messages (if any) * @param layout The layout for the combined user interface (default: kTabGroup) * @param label The label for the combiner (default: "Recursiver") * @return A pointer to the created DSP Recursiver, or nullptr if an error occurs */ static dsp* createDSPRecursiver(dsp* dsp1, dsp* dsp2, std::string& error, Layout layout = Layout::kTabGroup, const std::string& label = "Recursiver") { if ((dsp2->getNumInputs() > dsp1->getNumOutputs()) || (dsp2->getNumOutputs() > dsp1->getNumInputs())) { std::stringstream error_aux; error_aux << "Connection error in : dsp_recursiver" << std::endl; if (dsp2->getNumInputs() > dsp1->getNumOutputs()) { error_aux << "The number of outputs " << dsp1->getNumOutputs() << " of the first expression should be greater or equal to the number of inputs (" << dsp2->getNumInputs() << ") of the second expression" << std::endl; } if (dsp2->getNumOutputs() > dsp1->getNumInputs()) { error_aux << "The number of inputs " << dsp1->getNumInputs() << " of the first expression should be greater or equal to the number of outputs (" << dsp2->getNumOutputs() << ") of the second expression" << std::endl; } error = error_aux.str(); return nullptr; } else { return new dsp_recursiver(dsp1, dsp2, layout, label); } } /** * Create a DSP Crossfader * * This method creates a DSP Crossfader, which allows you to crossfade between two DSP modules. * The crossfade parameter (as a slider) controls the mix between the two modules' outputs. * When Crossfade = 1, the first DSP only is computed, when Crossfade = 0, * the second DSP only is computed, otherwise both DSPs are computed and mixed. * * @param dsp1 The first DSP module to combine * @param dsp2 The second DSP module to combine * @param error A reference to a string to store error messages (if any) * @param layout The layout for the combined user interface (default: kTabGroup) * @param label The label for the crossfade slider (default: "Crossfade") * @return A pointer to the created DSP Crossfader, or nullptr if an error occurs */ static dsp* createDSPCrossfader(dsp* dsp1, dsp* dsp2, std::string& error, Layout layout = Layout::kTabGroup, const std::string& label = "Crossfade") { if (dsp1->getNumInputs() != dsp2->getNumInputs()) { std::stringstream error_aux; error_aux << "Error in dsp_crossfader : the number of inputs (" << dsp1->getNumInputs() << ") of A " << "must be equal to the number of inputs (" << dsp2->getNumInputs() << ") of B" << std::endl; error = error_aux.str(); return nullptr; } else if (dsp1->getNumOutputs() != dsp2->getNumOutputs()) { std::stringstream error_aux; error_aux << "Error in dsp_crossfader : the number of outputs (" << dsp1->getNumOutputs() << ") of A " << "must be equal to the number of outputs (" << dsp2->getNumOutputs() << ") of B" << std::endl; error = error_aux.str(); return nullptr; } else { return new dsp_crossfader(dsp1, dsp2, layout, label); } } #endif #endif /************************** END dsp-combiner.h **************************/ /************************** BEGIN dsp-adapter.h ************************* FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ************************************************************************/ #ifndef __dsp_adapter__ #define __dsp_adapter__ #ifndef _WIN32 #include #endif #include #include #include #include // Adapts a DSP for a different number of inputs/outputs class dsp_adapter : public decorator_dsp { private: FAUSTFLOAT** fAdaptedInputs; FAUSTFLOAT** fAdaptedOutputs; int fHWInputs; int fHWOutputs; int fBufferSize; void adaptBuffers(FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { for (int i = 0; i < fHWInputs; i++) { fAdaptedInputs[i] = inputs[i]; } for (int i = 0; i < fHWOutputs; i++) { fAdaptedOutputs[i] = outputs[i]; } } public: dsp_adapter(dsp* dsp, int hw_inputs, int hw_outputs, int buffer_size):decorator_dsp(dsp) { fHWInputs = hw_inputs; fHWOutputs = hw_outputs; fBufferSize = buffer_size; fAdaptedInputs = new FAUSTFLOAT*[dsp->getNumInputs()]; for (int i = 0; i < dsp->getNumInputs() - fHWInputs; i++) { fAdaptedInputs[i + fHWInputs] = new FAUSTFLOAT[buffer_size]; memset(fAdaptedInputs[i + fHWInputs], 0, sizeof(FAUSTFLOAT) * buffer_size); } fAdaptedOutputs = new FAUSTFLOAT*[dsp->getNumOutputs()]; for (int i = 0; i < dsp->getNumOutputs() - fHWOutputs; i++) { fAdaptedOutputs[i + fHWOutputs] = new FAUSTFLOAT[buffer_size]; memset(fAdaptedOutputs[i + fHWOutputs], 0, sizeof(FAUSTFLOAT) * buffer_size); } } virtual ~dsp_adapter() { for (int i = 0; i < fDSP->getNumInputs() - fHWInputs; i++) { delete [] fAdaptedInputs[i + fHWInputs]; } delete [] fAdaptedInputs; for (int i = 0; i < fDSP->getNumOutputs() - fHWOutputs; i++) { delete [] fAdaptedOutputs[i + fHWOutputs]; } delete [] fAdaptedOutputs; } virtual int getNumInputs() { return fHWInputs; } virtual int getNumOutputs() { return fHWOutputs; } virtual dsp_adapter* clone() { return new dsp_adapter(fDSP->clone(), fHWInputs, fHWOutputs, fBufferSize); } virtual void compute(double date_usec, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { adaptBuffers(inputs, outputs); fDSP->compute(date_usec, count, fAdaptedInputs, fAdaptedOutputs); } virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { adaptBuffers(inputs, outputs); fDSP->compute(count, fAdaptedInputs, fAdaptedOutputs); } }; // Adapts a DSP for a different sample size template class dsp_sample_adapter : public decorator_dsp { private: REAL_INT** fAdaptedInputs; REAL_INT** fAdaptedOutputs; void adaptInputBuffers(int count, FAUSTFLOAT** inputs) { for (int chan = 0; chan < fDSP->getNumInputs(); chan++) { for (int frame = 0; frame < count; frame++) { fAdaptedInputs[chan][frame] = REAL_INT(reinterpret_cast(inputs)[chan][frame]); } } } void adaptOutputsBuffers(int count, FAUSTFLOAT** outputs) { for (int chan = 0; chan < fDSP->getNumOutputs(); chan++) { for (int frame = 0; frame < count; frame++) { reinterpret_cast(outputs)[chan][frame] = REAL_EXT(fAdaptedOutputs[chan][frame]); } } } public: dsp_sample_adapter(dsp* dsp):decorator_dsp(dsp) { fAdaptedInputs = new REAL_INT*[dsp->getNumInputs()]; for (int i = 0; i < dsp->getNumInputs(); i++) { fAdaptedInputs[i] = new REAL_INT[4096]; } fAdaptedOutputs = new REAL_INT*[dsp->getNumOutputs()]; for (int i = 0; i < dsp->getNumOutputs(); i++) { fAdaptedOutputs[i] = new REAL_INT[4096]; } } virtual ~dsp_sample_adapter() { for (int i = 0; i < fDSP->getNumInputs(); i++) { delete [] fAdaptedInputs[i]; } delete [] fAdaptedInputs; for (int i = 0; i < fDSP->getNumOutputs(); i++) { delete [] fAdaptedOutputs[i]; } delete [] fAdaptedOutputs; } virtual dsp_sample_adapter* clone() { return new dsp_sample_adapter(fDSP->clone()); } virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { assert(count <= 4096); adaptInputBuffers(count, inputs); // DSP base class uses FAUSTFLOAT** type, so reinterpret_cast has to be used even if the real DSP uses REAL_INT fDSP->compute(count, reinterpret_cast(fAdaptedInputs), reinterpret_cast(fAdaptedOutputs)); adaptOutputsBuffers(count, outputs); } virtual void compute(double date_usec, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { assert(count <= 4096); adaptInputBuffers(count, inputs); // DSP base class uses FAUSTFLOAT** type, so reinterpret_cast has to be used even if the real DSP uses REAL_INT fDSP->compute(date_usec, count, reinterpret_cast(fAdaptedInputs), reinterpret_cast(fAdaptedOutputs)); adaptOutputsBuffers(count, outputs); } }; // Template used to specialize double parameters expressed as NUM/DENOM template struct Double { static constexpr double value() { return double(NUM)/double(DENOM); } }; // Base class for filters template struct Filter { inline int getFactor() { return fVslider1; } }; // Identity filter: copy input to output template struct Identity : public Filter { inline int getFactor() { return fVslider1; } inline void compute(int count, FAUSTFLOAT* input0, FAUSTFLOAT* output0) { memcpy(output0, input0, count * sizeof(FAUSTFLOAT)); } }; // Generated with process = fi.lowpass(3, ma.SR*hslider("FCFactor", 0.4, 0.4, 0.5, 0.01)/hslider("Factor", 2, 2, 8, 1)); template struct LowPass3 : public Filter { REAL fVec0[2]; REAL fRec1[2]; REAL fRec0[3]; inline REAL LowPass3_faustpower2_f(REAL value) { return (value * value); } LowPass3() { for (int l0 = 0; (l0 < 2); l0 = (l0 + 1)) { fVec0[l0] = 0.0; } for (int l1 = 0; (l1 < 2); l1 = (l1 + 1)) { fRec1[l1] = 0.0; } for (int l2 = 0; (l2 < 3); l2 = (l2 + 1)) { fRec0[l2] = 0.0; } } inline void compute(int count, FAUSTFLOAT* input0, FAUSTFLOAT* output0) { // Computed at template specialization time REAL fSlow0 = std::tan((3.1415926535897931 * (REAL(fVslider0::value()) / REAL(fVslider1)))); REAL fSlow1 = (1.0 / fSlow0); REAL fSlow2 = (1.0 / (((fSlow1 + 1.0000000000000002) / fSlow0) + 1.0)); REAL fSlow3 = (1.0 / (fSlow1 + 1.0)); REAL fSlow4 = (1.0 - fSlow1); REAL fSlow5 = (((fSlow1 + -1.0000000000000002) / fSlow0) + 1.0); REAL fSlow6 = (2.0 * (1.0 - (1.0 / LowPass3_faustpower2_f(fSlow0)))); // Computed at runtime for (int i = 0; (i < count); i = (i + 1)) { REAL fTemp0 = REAL(input0[i]); fVec0[0] = fTemp0; fRec1[0] = (0.0 - (fSlow3 * ((fSlow4 * fRec1[1]) - (fTemp0 + fVec0[1])))); fRec0[0] = (fRec1[0] - (fSlow2 * ((fSlow5 * fRec0[2]) + (fSlow6 * fRec0[1])))); output0[i] = FAUSTFLOAT((fSlow2 * (fRec0[2] + (fRec0[0] + (2.0 * fRec0[1]))))); fVec0[1] = fVec0[0]; fRec1[1] = fRec1[0]; fRec0[2] = fRec0[1]; fRec0[1] = fRec0[0]; } } }; // Generated with process = fi.lowpass(4, ma.SR*hslider("FCFactor", 0.4, 0.4, 0.5, 0.01)/hslider("Factor", 2, 2, 8, 1)); template struct LowPass4 : public Filter { REAL fRec1[3]; REAL fRec0[3]; inline REAL LowPass4_faustpower2_f(REAL value) { return (value * value); } LowPass4() { for (int l0 = 0; (l0 < 3); l0 = (l0 + 1)) { fRec1[l0] = 0.0f; } for (int l1 = 0; (l1 < 3); l1 = (l1 + 1)) { fRec0[l1] = 0.0f; } } inline void compute(int count, FAUSTFLOAT* input0, FAUSTFLOAT* output0) { // Computed at template specialization time REAL fSlow0 = std::tan((3.1415926535897931 * (REAL(fVslider0::value()) / REAL(fVslider1)))); REAL fSlow1 = (1.0 / fSlow0); REAL fSlow2 = (1.0 / (((fSlow1 + 0.76536686473017945) / fSlow0) + 1.0)); REAL fSlow3 = (1.0 / (((fSlow1 + 1.8477590650225735) / fSlow0) + 1.0)); REAL fSlow4 = (((fSlow1 + -1.8477590650225735) / fSlow0) + 1.0); REAL fSlow5 = (2.0 * (1.0 - (1.0 / LowPass4_faustpower2_f(fSlow0)))); REAL fSlow6 = (((fSlow1 + -0.76536686473017945) / fSlow0) + 1.0); // Computed at runtime for (int i = 0; (i < count); i = (i + 1)) { fRec1[0] = (REAL(input0[i]) - (fSlow3 * ((fSlow4 * fRec1[2]) + (fSlow5 * fRec1[1])))); fRec0[0] = ((fSlow3 * (fRec1[2] + (fRec1[0] + (2.0 * fRec1[1])))) - (fSlow2 * ((fSlow6 * fRec0[2]) + (fSlow5 * fRec0[1])))); output0[i] = FAUSTFLOAT((fSlow2 * (fRec0[2] + (fRec0[0] + (2.0 * fRec0[1]))))); fRec1[2] = fRec1[1]; fRec1[1] = fRec1[0]; fRec0[2] = fRec0[1]; fRec0[1] = fRec0[0]; } } }; // Generated with process = fi.lowpass3e(ma.SR*hslider("FCFactor", 0.4, 0.4, 0.5, 0.01)/hslider("Factor", 2, 2, 8, 1)); template struct LowPass3e : public Filter { REAL fRec1[3]; REAL fVec0[2]; REAL fRec0[2]; inline REAL LowPass3e_faustpower2_f(REAL value) { return (value * value); } LowPass3e() { for (int l0 = 0; (l0 < 3); l0 = (l0 + 1)) { fRec1[l0] = 0.0; } for (int l1 = 0; (l1 < 2); l1 = (l1 + 1)) { fVec0[l1] = 0.0; } for (int l2 = 0; (l2 < 2); l2 = (l2 + 1)) { fRec0[l2] = 0.0; } } inline void compute(int count, FAUSTFLOAT* input0, FAUSTFLOAT* output0) { // Computed at template specialization time REAL fSlow0 = std::tan((3.1415926535897931 * (REAL(fVslider0::value()) / REAL(fVslider1)))); REAL fSlow1 = (1.0 / fSlow0); REAL fSlow2 = (1.0 / (fSlow1 + 0.82244590899881598)); REAL fSlow3 = (0.82244590899881598 - fSlow1); REAL fSlow4 = (1.0 / (((fSlow1 + 0.80263676416103003) / fSlow0) + 1.4122708937742039)); REAL fSlow5 = LowPass3e_faustpower2_f(fSlow0); REAL fSlow6 = (0.019809144837788999 / fSlow5); REAL fSlow7 = (fSlow6 + 1.1615164189826961); REAL fSlow8 = (((fSlow1 + -0.80263676416103003) / fSlow0) + 1.4122708937742039); REAL fSlow9 = (2.0 * (1.4122708937742039 - (1.0 / fSlow5))); REAL fSlow10 = (2.0 * (1.1615164189826961 - fSlow6)); // Computed at runtime for (int i = 0; (i < count); i = (i + 1)) { fRec1[0] = (REAL(input0[i]) - (fSlow4 * ((fSlow8 * fRec1[2]) + (fSlow9 * fRec1[1])))); REAL fTemp0 = (fSlow4 * (((fSlow7 * fRec1[0]) + (fSlow10 * fRec1[1])) + (fSlow7 * fRec1[2]))); fVec0[0] = fTemp0; fRec0[0] = (0.0 - (fSlow2 * ((fSlow3 * fRec0[1]) - (fTemp0 + fVec0[1])))); output0[i] = FAUSTFLOAT(fRec0[0]); fRec1[2] = fRec1[1]; fRec1[1] = fRec1[0]; fVec0[1] = fVec0[0]; fRec0[1] = fRec0[0]; } } }; // Generated with process = fi.lowpass6e(ma.SR*hslider("FCFactor", 0.4, 0.4, 0.5, 0.01)/hslider("Factor", 2, 2, 8, 1)); template struct LowPass6e : public Filter { REAL fRec2[3]; REAL fRec1[3]; REAL fRec0[3]; inline REAL LowPass6e_faustpower2_f(REAL value) { return (value * value); } LowPass6e() { for (int l0 = 0; (l0 < 3); l0 = (l0 + 1)) { fRec2[l0] = 0.0; } for (int l1 = 0; (l1 < 3); l1 = (l1 + 1)) { fRec1[l1] = 0.0; } for (int l2 = 0; (l2 < 3); l2 = (l2 + 1)) { fRec0[l2] = 0.0; } } inline void compute(int count, FAUSTFLOAT* input0, FAUSTFLOAT* output0) { // Computed at template specialization time REAL fSlow0 = std::tan((3.1415926535897931 * (REAL(fVslider0::value()) / REAL(fVslider1)))); REAL fSlow1 = (1.0 / fSlow0); REAL fSlow2 = (1.0 / (((fSlow1 + 0.16840487111358901) / fSlow0) + 1.0693584077073119)); REAL fSlow3 = LowPass6e_faustpower2_f(fSlow0); REAL fSlow4 = (1.0 / fSlow3); REAL fSlow5 = (fSlow4 + 53.536152954556727); REAL fSlow6 = (1.0 / (((fSlow1 + 0.51247864188914105) / fSlow0) + 0.68962136448467504)); REAL fSlow7 = (fSlow4 + 7.6217312988706034); REAL fSlow8 = (1.0 / (((fSlow1 + 0.78241304682164503) / fSlow0) + 0.24529150870616001)); REAL fSlow9 = (9.9999997054999994e-05 / fSlow3); REAL fSlow10 = (fSlow9 + 0.00043322720055500002); REAL fSlow11 = (((fSlow1 + -0.78241304682164503) / fSlow0) + 0.24529150870616001); REAL fSlow12 = (2.0 * (0.24529150870616001 - fSlow4)); REAL fSlow13 = (2.0 * (0.00043322720055500002 - fSlow9)); REAL fSlow14 = (((fSlow1 + -0.51247864188914105) / fSlow0) + 0.68962136448467504); REAL fSlow15 = (2.0 * (0.68962136448467504 - fSlow4)); REAL fSlow16 = (2.0 * (7.6217312988706034 - fSlow4)); REAL fSlow17 = (((fSlow1 + -0.16840487111358901) / fSlow0) + 1.0693584077073119); REAL fSlow18 = (2.0 * (1.0693584077073119 - fSlow4)); REAL fSlow19 = (2.0 * (53.536152954556727 - fSlow4)); // Computed at runtime for (int i = 0; (i < count); i = (i + 1)) { fRec2[0] = (REAL(input0[i]) - (fSlow8 * ((fSlow11 * fRec2[2]) + (fSlow12 * fRec2[1])))); fRec1[0] = ((fSlow8 * (((fSlow10 * fRec2[0]) + (fSlow13 * fRec2[1])) + (fSlow10 * fRec2[2]))) - (fSlow6 * ((fSlow14 * fRec1[2]) + (fSlow15 * fRec1[1])))); fRec0[0] = ((fSlow6 * (((fSlow7 * fRec1[0]) + (fSlow16 * fRec1[1])) + (fSlow7 * fRec1[2]))) - (fSlow2 * ((fSlow17 * fRec0[2]) + (fSlow18 * fRec0[1])))); output0[i] = FAUSTFLOAT((fSlow2 * (((fSlow5 * fRec0[0]) + (fSlow19 * fRec0[1])) + (fSlow5 * fRec0[2])))); fRec2[2] = fRec2[1]; fRec2[1] = fRec2[0]; fRec1[2] = fRec1[1]; fRec1[1] = fRec1[0]; fRec0[2] = fRec0[1]; fRec0[1] = fRec0[0]; } } }; // A "si.bus(N)" like hard-coded class struct dsp_bus : public dsp { int fChannels; int fSampleRate; dsp_bus(int channels):fChannels(channels), fSampleRate(-1) {} virtual int getNumInputs() { return fChannels; } virtual int getNumOutputs() { return fChannels; } virtual int getSampleRate() { return fSampleRate; } virtual void buildUserInterface(UI* ui_interface) {} virtual void init(int sample_rate) { //classInit(sample_rate); instanceInit(sample_rate); } virtual void instanceInit(int sample_rate) { fSampleRate = sample_rate; instanceConstants(sample_rate); instanceResetUserInterface(); instanceClear(); } virtual void instanceConstants(int sample_rate) {} virtual void instanceResetUserInterface() {} virtual void instanceClear() {} virtual dsp* clone() { return new dsp_bus(fChannels); } virtual void metadata(Meta* m) {} virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { for (int chan = 0; chan < fChannels; chan++) { memcpy(outputs[chan], inputs[chan], sizeof(FAUSTFLOAT) * count); } } virtual void compute(double /*date_usec*/, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { compute(count, inputs, outputs); } }; // Base class for sample-rate adapter template class sr_sampler : public decorator_dsp { protected: std::vector fInputLowPass; std::vector fOutputLowPass; inline int getFactor() { return this->fOutputLowPass[0].getFactor(); } public: sr_sampler(dsp* dsp):decorator_dsp(dsp) { for (int chan = 0; chan < fDSP->getNumInputs(); chan++) { fInputLowPass.push_back(FILTER()); } for (int chan = 0; chan < fDSP->getNumOutputs(); chan++) { fOutputLowPass.push_back(FILTER()); } } }; // Down sample-rate adapter template class dsp_down_sampler : public sr_sampler { public: dsp_down_sampler(dsp* dsp):sr_sampler(dsp) {} virtual void init(int sample_rate) { this->fDSP->init(sample_rate / this->getFactor()); } virtual void instanceInit(int sample_rate) { this->fDSP->instanceInit(sample_rate / this->getFactor()); } virtual void instanceConstants(int sample_rate) { this->fDSP->instanceConstants(sample_rate / this->getFactor()); } virtual dsp_down_sampler* clone() { return new dsp_down_sampler(decorator_dsp::clone()); } virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { int real_count = count / this->getFactor(); // Adapt inputs FAUSTFLOAT** fInputs = (FAUSTFLOAT**)alloca(this->fDSP->getNumInputs() * sizeof(FAUSTFLOAT*)); for (int chan = 0; chan < this->fDSP->getNumInputs(); chan++) { // Lowpass filtering in place on 'inputs' this->fInputLowPass[chan].compute(count, inputs[chan], inputs[chan]); // Allocate fInputs with 'real_count' frames fInputs[chan] = (FAUSTFLOAT*)alloca(sizeof(FAUSTFLOAT) * real_count); // Decimate for (int frame = 0; frame < real_count; frame++) { fInputs[chan][frame] = inputs[chan][frame * this->getFactor()]; } } // Allocate fOutputs with 'real_count' frames FAUSTFLOAT** fOutputs = (FAUSTFLOAT**)alloca(this->fDSP->getNumOutputs() * sizeof(FAUSTFLOAT*)); for (int chan = 0; chan < this->fDSP->getNumOutputs(); chan++) { fOutputs[chan] = (FAUSTFLOAT*)alloca(sizeof(FAUSTFLOAT) * real_count); } // Compute at lower rate this->fDSP->compute(real_count, fInputs, fOutputs); // Adapt outputs for (int chan = 0; chan < this->fDSP->getNumOutputs(); chan++) { // Puts zeros memset(outputs[chan], 0, sizeof(FAUSTFLOAT) * count); for (int frame = 0; frame < real_count; frame++) { // Copy one sample every 'DownFactor' // Apply volume //outputs[chan][frame * this->getFactor()] = fOutputs[chan][frame] * this->getFactor(); outputs[chan][frame * this->getFactor()] = fOutputs[chan][frame]; } // Lowpass filtering in place on 'outputs' this->fOutputLowPass[chan].compute(count, outputs[chan], outputs[chan]); } } virtual void compute(double /*date_usec*/, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { compute(count, inputs, outputs); } }; // Up sample-rate adapter template class dsp_up_sampler : public sr_sampler { public: dsp_up_sampler(dsp* dsp):sr_sampler(dsp) {} virtual void init(int sample_rate) { this->fDSP->init(sample_rate * this->getFactor()); } virtual void instanceInit(int sample_rate) { this->fDSP->instanceInit(sample_rate * this->getFactor()); } virtual void instanceConstants(int sample_rate) { this->fDSP->instanceConstants(sample_rate * this->getFactor()); } virtual dsp_up_sampler* clone() { return new dsp_up_sampler(decorator_dsp::clone()); } virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { int real_count = count * this->getFactor(); // Adapt inputs FAUSTFLOAT** fInputs = (FAUSTFLOAT**)alloca(this->fDSP->getNumInputs() * sizeof(FAUSTFLOAT*)); for (int chan = 0; chan < this->fDSP->getNumInputs(); chan++) { // Allocate fInputs with 'real_count' frames fInputs[chan] = (FAUSTFLOAT*)alloca(sizeof(FAUSTFLOAT) * real_count); // Puts zeros memset(fInputs[chan], 0, sizeof(FAUSTFLOAT) * real_count); for (int frame = 0; frame < count; frame++) { // Copy one sample every 'UpFactor' fInputs[chan][frame * this->getFactor()] = inputs[chan][frame]; } // Lowpass filtering in place on 'fInputs' this->fInputLowPass[chan].compute(real_count, fInputs[chan], fInputs[chan]); } // Allocate fOutputs with 'real_count' frames FAUSTFLOAT** fOutputs = (FAUSTFLOAT**)alloca(this->fDSP->getNumOutputs() * sizeof(FAUSTFLOAT*)); for (int chan = 0; chan < this->fDSP->getNumOutputs(); chan++) { fOutputs[chan] = (FAUSTFLOAT*)alloca(sizeof(FAUSTFLOAT) * real_count); } // Compute at upper rate this->fDSP->compute(real_count, fInputs, fOutputs); // Adapt outputs for (int chan = 0; chan < this->fDSP->getNumOutputs(); chan++) { // Lowpass filtering in place on 'fOutputs' this->fOutputLowPass[chan].compute(real_count, fOutputs[chan], fOutputs[chan]); // Decimate for (int frame = 0; frame < count; frame++) { // Apply volume //outputs[chan][frame] = fOutputs[chan][frame * this->getFactor()] * this->getFactor(); outputs[chan][frame] = fOutputs[chan][frame * this->getFactor()]; } } } virtual void compute(double /*date_usec*/, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { compute(count, inputs, outputs); } }; // Create a UP/DS + Filter adapted DSP template dsp* createSRAdapter(dsp* DSP, std::string& error, int ds = 0, int us = 0, int filter = 0) { if (ds >= 2) { switch (filter) { case 0: if (ds == 2) { return new dsp_down_sampler, 2>>(DSP); } else if (ds == 3) { return new dsp_down_sampler, 3>>(DSP); } else if (ds == 4) { return new dsp_down_sampler, 4>>(DSP); } else if (ds == 8) { return new dsp_down_sampler, 8>>(DSP); } else if (ds == 16) { return new dsp_down_sampler, 16>>(DSP); } else if (ds == 32) { return new dsp_down_sampler, 32>>(DSP); } else { error = "ERROR : ds factor type must be in [2..32] range\n"; return nullptr; } case 1: if (ds == 2) { return new dsp_down_sampler, 2, REAL>>(DSP); } else if (ds == 3) { return new dsp_down_sampler, 3, REAL>>(DSP); } else if (ds == 4) { return new dsp_down_sampler, 4, REAL>>(DSP); } else if (ds == 8) { return new dsp_down_sampler, 8, REAL>>(DSP); } else if (ds == 16) { return new dsp_down_sampler, 16, REAL>>(DSP); } else if (ds == 32) { return new dsp_down_sampler, 32, REAL>>(DSP); } else { error = "ERROR : ds factor type must be in [2..32] range\n"; return nullptr; } case 2: if (ds == 2) { return new dsp_down_sampler, 2, REAL>>(DSP); } else if (ds == 3) { return new dsp_down_sampler, 3, REAL>>(DSP); } else if (ds == 4) { return new dsp_down_sampler, 4, REAL>>(DSP); } else if (ds == 8) { return new dsp_down_sampler, 8, REAL>>(DSP); } else if (ds == 16) { return new dsp_down_sampler, 16, REAL>>(DSP); } else if (ds == 32) { return new dsp_down_sampler, 32, REAL>>(DSP); } else { error = "ERROR : ds factor type must be in [2..32] range\n"; return nullptr; } case 3: if (ds == 2) { return new dsp_down_sampler, 2, REAL>>(DSP); } else if (ds == 3) { return new dsp_down_sampler, 3, REAL>>(DSP); } else if (ds == 4) { return new dsp_down_sampler, 4, REAL>>(DSP); } else if (ds == 8) { return new dsp_down_sampler, 8, REAL>>(DSP); } else if (ds == 16) { return new dsp_down_sampler, 16, REAL>>(DSP); } else if (ds == 32) { return new dsp_down_sampler, 32, REAL>>(DSP); } else { error = "ERROR : ds factor type must be in [2..32] range\n"; return nullptr; } case 4: if (ds == 2) { return new dsp_down_sampler, 2, REAL>>(DSP); } else if (ds == 3) { return new dsp_down_sampler, 3, REAL>>(DSP); } else if (ds == 4) { return new dsp_down_sampler, 4, REAL>>(DSP); } else if (ds == 8) { return new dsp_down_sampler, 8, REAL>>(DSP); } else if (ds == 16) { return new dsp_down_sampler, 16, REAL>>(DSP); } else if (ds == 32) { return new dsp_down_sampler, 32, REAL>>(DSP); } else { error = "ERROR : ds factor type must be in [2..32] range\n"; return nullptr; } default: error = "ERROR : filter type must be in [0..4] range\n"; return nullptr; } } else if (us >= 2) { switch (filter) { case 0: if (us == 2) { return new dsp_up_sampler, 2>>(DSP); } else if (us == 3) { return new dsp_up_sampler, 3>>(DSP); } else if (us == 4) { return new dsp_up_sampler, 4>>(DSP); } else if (us == 8) { return new dsp_up_sampler, 8>>(DSP); } else if (us == 16) { return new dsp_up_sampler, 16>>(DSP); } else if (us == 32) { return new dsp_up_sampler, 32>>(DSP); } else { error = "ERROR : us factor type must be in [2..32] range\n"; return nullptr; } case 1: if (us == 2) { return new dsp_up_sampler, 2, REAL>>(DSP); } else if (us == 3) { return new dsp_up_sampler, 3, REAL>>(DSP); } else if (us == 4) { return new dsp_up_sampler, 4, REAL>>(DSP); } else if (us == 8) { return new dsp_up_sampler, 8, REAL>>(DSP); } else if (us == 16) { return new dsp_up_sampler, 16, REAL>>(DSP); } else if (us == 32) { return new dsp_up_sampler, 32, REAL>>(DSP); } else { error = "ERROR : us factor type must be in [2..32] range\n"; return nullptr; } case 2: if (us == 2) { return new dsp_up_sampler, 2, REAL>>(DSP); } else if (us == 3) { return new dsp_up_sampler, 3, REAL>>(DSP); } else if (us == 4) { return new dsp_up_sampler, 4, REAL>>(DSP); } else if (us == 8) { return new dsp_up_sampler, 8, REAL>>(DSP); } else if (us == 16) { return new dsp_up_sampler, 16, REAL>>(DSP); } else if (us == 32) { return new dsp_up_sampler, 32, REAL>>(DSP); } else { error = "ERROR : us factor type must be in [2..32] range\n"; return nullptr; } case 3: if (us == 2) { return new dsp_up_sampler, 2, REAL>>(DSP); } else if (us == 3) { return new dsp_up_sampler, 3, REAL>>(DSP); } else if (us == 4) { return new dsp_up_sampler, 4, REAL>>(DSP); } else if (us == 8) { return new dsp_up_sampler, 8, REAL>>(DSP); } else if (us == 16) { return new dsp_up_sampler, 16, REAL>>(DSP); } else if (us == 32) { return new dsp_up_sampler, 32, REAL>>(DSP); } else { error = "ERROR : us factor type must be in [2..32] range\n"; return nullptr; } case 4: if (us == 2) { return new dsp_up_sampler, 2, REAL>>(DSP); } else if (us == 3) { return new dsp_up_sampler, 3, REAL>>(DSP); } else if (us == 4) { return new dsp_up_sampler, 4, REAL>>(DSP); } else if (us == 8) { return new dsp_up_sampler, 8, REAL>>(DSP); } else if (us == 16) { return new dsp_up_sampler, 16, REAL>>(DSP); } else if (us == 32) { return new dsp_up_sampler, 32, REAL>>(DSP); } else { error = "ERROR : us factor type must be in [2..32] range\n"; return nullptr; } default: error = "ERROR : filter type must be in [0..4] range\n"; return nullptr; } } else { return DSP; } } #endif /************************** END dsp-adapter.h **************************/ /************************** BEGIN proxy-dsp.h *************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. ***************************************************************************/ #ifndef __proxy_dsp__ #define __proxy_dsp__ #include #include /************************** BEGIN JSONUIDecoder.h ************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. *************************************************************************/ #ifndef __JSONUIDecoder__ #define __JSONUIDecoder__ #include #include #include #include #include #include /************************** BEGIN CGlue.h ***************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. *************************************************************************/ #ifndef CGLUE_H #define CGLUE_H /************************** BEGIN CInterface.h ************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. *************************************************************************/ #ifndef CINTERFACE_H #define CINTERFACE_H #ifndef FAUSTFLOAT #define FAUSTFLOAT float #endif #include #ifdef __cplusplus extern "C" { #endif struct Soundfile; /******************************************************************************* * UI, Meta and MemoryManager structures for C code. ******************************************************************************/ // -- widget's layouts typedef void (* openTabBoxFun) (void* ui_interface, const char* label); typedef void (* openHorizontalBoxFun) (void* ui_interface, const char* label); typedef void (* openVerticalBoxFun) (void* ui_interface, const char* label); typedef void (* closeBoxFun) (void* ui_interface); // -- active widgets typedef void (* addButtonFun) (void* ui_interface, const char* label, FAUSTFLOAT* zone); typedef void (* addCheckButtonFun) (void* ui_interface, const char* label, FAUSTFLOAT* zone); typedef void (* addVerticalSliderFun) (void* ui_interface, const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step); typedef void (* addHorizontalSliderFun) (void* ui_interface, const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step); typedef void (* addNumEntryFun) (void* ui_interface, const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step); // -- passive widgets typedef void (* addHorizontalBargraphFun) (void* ui_interface, const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max); typedef void (* addVerticalBargraphFun) (void* ui_interface, const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max); // -- soundfiles typedef void (* addSoundfileFun) (void* ui_interface, const char* label, const char* url, struct Soundfile** sf_zone); typedef void (* declareFun) (void* ui_interface, FAUSTFLOAT* zone, const char* key, const char* value); typedef struct { void* uiInterface; openTabBoxFun openTabBox; openHorizontalBoxFun openHorizontalBox; openVerticalBoxFun openVerticalBox; closeBoxFun closeBox; addButtonFun addButton; addCheckButtonFun addCheckButton; addVerticalSliderFun addVerticalSlider; addHorizontalSliderFun addHorizontalSlider; addNumEntryFun addNumEntry; addHorizontalBargraphFun addHorizontalBargraph; addVerticalBargraphFun addVerticalBargraph; addSoundfileFun addSoundfile; declareFun declare; } UIGlue; typedef void (* metaDeclareFun) (void* ui_interface, const char* key, const char* value); typedef struct { void* metaInterface; metaDeclareFun declare; } MetaGlue; /*************************************** * Interface for the DSP object ***************************************/ typedef char dsp_imp; typedef dsp_imp* (* newDspFun) (); typedef void (* destroyDspFun) (dsp_imp* dsp); typedef int (* getNumInputsFun) (dsp_imp* dsp); typedef int (* getNumOutputsFun) (dsp_imp* dsp); typedef void (* buildUserInterfaceFun) (dsp_imp* dsp, UIGlue* ui); typedef int (* getSampleRateFun) (dsp_imp* dsp); typedef void (* initFun) (dsp_imp* dsp, int sample_rate); typedef void (* classInitFun) (int sample_rate); typedef void (* instanceInitFun) (dsp_imp* dsp, int sample_rate); typedef void (* instanceConstantsFun) (dsp_imp* dsp, int sample_rate); typedef void (* instanceResetUserInterfaceFun) (dsp_imp* dsp); typedef void (* instanceClearFun) (dsp_imp* dsp); typedef void (* computeFun) (dsp_imp* dsp, int len, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs); typedef void (* metadataFun) (MetaGlue* meta); /*************************************** * DSP memory manager functions ***************************************/ typedef void* (* allocateFun) (void* manager_interface, size_t size); typedef void (* destroyFun) (void* manager_interface, void* ptr); typedef struct { void* managerInterface; allocateFun allocate; destroyFun destroy; } MemoryManagerGlue; #ifdef __cplusplus } #endif #endif /************************** END CInterface.h **************************/ #ifdef __cplusplus extern "C" { #endif /******************************************************************************* * UI glue code ******************************************************************************/ class UIFloat { public: UIFloat() {} virtual ~UIFloat() {} // -- widget's layouts virtual void openTabBox(const char* label) = 0; virtual void openHorizontalBox(const char* label) = 0; virtual void openVerticalBox(const char* label) = 0; virtual void closeBox() = 0; // -- active widgets virtual void addButton(const char* label, float* zone) = 0; virtual void addCheckButton(const char* label, float* zone) = 0; virtual void addVerticalSlider(const char* label, float* zone, float init, float min, float max, float step) = 0; virtual void addHorizontalSlider(const char* label, float* zone, float init, float min, float max, float step) = 0; virtual void addNumEntry(const char* label, float* zone, float init, float min, float max, float step) = 0; // -- passive widgets virtual void addHorizontalBargraph(const char* label, float* zone, float min, float max) = 0; virtual void addVerticalBargraph(const char* label, float* zone, float min, float max) = 0; // -- soundfiles virtual void addSoundfile(const char* label, const char* filename, Soundfile** sf_zone) = 0; // -- metadata declarations virtual void declare(float* zone, const char* key, const char* val) {} }; static void openTabBoxGlueFloat(void* cpp_interface, const char* label) { UIFloat* ui_interface = static_cast(cpp_interface); ui_interface->openTabBox(label); } static void openHorizontalBoxGlueFloat(void* cpp_interface, const char* label) { UIFloat* ui_interface = static_cast(cpp_interface); ui_interface->openHorizontalBox(label); } static void openVerticalBoxGlueFloat(void* cpp_interface, const char* label) { UIFloat* ui_interface = static_cast(cpp_interface); ui_interface->openVerticalBox(label); } static void closeBoxGlueFloat(void* cpp_interface) { UIFloat* ui_interface = static_cast(cpp_interface); ui_interface->closeBox(); } static void addButtonGlueFloat(void* cpp_interface, const char* label, float* zone) { UIFloat* ui_interface = static_cast(cpp_interface); ui_interface->addButton(label, zone); } static void addCheckButtonGlueFloat(void* cpp_interface, const char* label, float* zone) { UIFloat* ui_interface = static_cast(cpp_interface); ui_interface->addCheckButton(label, zone); } static void addVerticalSliderGlueFloat(void* cpp_interface, const char* label, float* zone, float init, float min, float max, float step) { UIFloat* ui_interface = static_cast(cpp_interface); ui_interface->addVerticalSlider(label, zone, init, min, max, step); } static void addHorizontalSliderGlueFloat(void* cpp_interface, const char* label, float* zone, float init, float min, float max, float step) { UIFloat* ui_interface = static_cast(cpp_interface); ui_interface->addHorizontalSlider(label, zone, init, min, max, step); } static void addNumEntryGlueFloat(void* cpp_interface, const char* label, float* zone, float init, float min, float max, float step) { UIFloat* ui_interface = static_cast(cpp_interface); ui_interface->addNumEntry(label, zone, init, min, max, step); } static void addHorizontalBargraphGlueFloat(void* cpp_interface, const char* label, float* zone, float min, float max) { UIFloat* ui_interface = static_cast(cpp_interface); ui_interface->addHorizontalBargraph(label, zone, min, max); } static void addVerticalBargraphGlueFloat(void* cpp_interface, const char* label, float* zone, float min, float max) { UIFloat* ui_interface = static_cast(cpp_interface); ui_interface->addVerticalBargraph(label, zone, min, max); } static void addSoundfileGlueFloat(void* cpp_interface, const char* label, const char* url, Soundfile** sf_zone) { UIFloat* ui_interface = static_cast(cpp_interface); ui_interface->addSoundfile(label, url, sf_zone); } static void declareGlueFloat(void* cpp_interface, float* zone, const char* key, const char* value) { UIFloat* ui_interface = static_cast(cpp_interface); ui_interface->declare(zone, key, value); } class UIDouble { public: UIDouble() {} virtual ~UIDouble() {} // -- widget's layouts virtual void openTabBox(const char* label) = 0; virtual void openHorizontalBox(const char* label) = 0; virtual void openVerticalBox(const char* label) = 0; virtual void closeBox() = 0; // -- active widgets virtual void addButton(const char* label, double* zone) = 0; virtual void addCheckButton(const char* label, double* zone) = 0; virtual void addVerticalSlider(const char* label, double* zone, double init, double min, double max, double step) = 0; virtual void addHorizontalSlider(const char* label, double* zone, double init, double min, double max, double step) = 0; virtual void addNumEntry(const char* label, double* zone, double init, double min, double max, double step) = 0; // -- passive widgets virtual void addHorizontalBargraph(const char* label, double* zone, double min, double max) = 0; virtual void addVerticalBargraph(const char* label, double* zone, double min, double max) = 0; // -- soundfiles virtual void addSoundfile(const char* label, const char* filename, Soundfile** sf_zone) = 0; // -- metadata declarations virtual void declare(double* zone, const char* key, const char* val) {} }; static void openTabBoxGlueDouble(void* cpp_interface, const char* label) { UIDouble* ui_interface = static_cast(cpp_interface); ui_interface->openTabBox(label); } static void openHorizontalBoxGlueDouble(void* cpp_interface, const char* label) { UIDouble* ui_interface = static_cast(cpp_interface); ui_interface->openHorizontalBox(label); } static void openVerticalBoxGlueDouble(void* cpp_interface, const char* label) { UIDouble* ui_interface = static_cast(cpp_interface); ui_interface->openVerticalBox(label); } static void closeBoxGlueDouble(void* cpp_interface) { UIDouble* ui_interface = static_cast(cpp_interface); ui_interface->closeBox(); } static void addButtonGlueDouble(void* cpp_interface, const char* label, double* zone) { UIDouble* ui_interface = static_cast(cpp_interface); ui_interface->addButton(label, zone); } static void addCheckButtonGlueDouble(void* cpp_interface, const char* label, double* zone) { UIDouble* ui_interface = static_cast(cpp_interface); ui_interface->addCheckButton(label, zone); } static void addVerticalSliderGlueDouble(void* cpp_interface, const char* label, double* zone, double init, double min, double max, double step) { UIDouble* ui_interface = static_cast(cpp_interface); ui_interface->addVerticalSlider(label, zone, init, min, max, step); } static void addHorizontalSliderGlueDouble(void* cpp_interface, const char* label, double* zone, double init, double min, double max, double step) { UIDouble* ui_interface = static_cast(cpp_interface); ui_interface->addHorizontalSlider(label, zone, init, min, max, step); } static void addNumEntryGlueDouble(void* cpp_interface, const char* label, double* zone, double init, double min, double max, double step) { UIDouble* ui_interface = static_cast(cpp_interface); ui_interface->addNumEntry(label, zone, init, min, max, step); } static void addHorizontalBargraphGlueDouble(void* cpp_interface, const char* label, double* zone, double min, double max) { UIDouble* ui_interface = static_cast(cpp_interface); ui_interface->addHorizontalBargraph(label, zone, min, max); } static void addVerticalBargraphGlueDouble(void* cpp_interface, const char* label, double* zone, double min, double max) { UIDouble* ui_interface = static_cast(cpp_interface); ui_interface->addVerticalBargraph(label, zone, min, max); } static void addSoundfileGlueDouble(void* cpp_interface, const char* label, const char* url, Soundfile** sf_zone) { UIDouble* ui_interface = static_cast(cpp_interface); ui_interface->addSoundfile(label, url, sf_zone); } static void declareGlueDouble(void* cpp_interface, double* zone, const char* key, const char* value) { UIDouble* ui_interface = static_cast(cpp_interface); ui_interface->declare(zone, key, value); } static void buildUIGlue(UIGlue* glue, UI* ui_interface, bool is_double) { glue->uiInterface = ui_interface; if (is_double) { glue->openTabBox = reinterpret_cast(openTabBoxGlueDouble); glue->openHorizontalBox = reinterpret_cast(openHorizontalBoxGlueDouble); glue->openVerticalBox = reinterpret_cast(openVerticalBoxGlueDouble); glue->closeBox = reinterpret_cast(closeBoxGlueDouble); glue->addButton = reinterpret_cast(addButtonGlueDouble); glue->addCheckButton = reinterpret_cast(addCheckButtonGlueDouble); glue->addVerticalSlider = reinterpret_cast(addVerticalSliderGlueDouble); glue->addHorizontalSlider = reinterpret_cast(addHorizontalSliderGlueDouble); glue->addNumEntry = reinterpret_cast(addNumEntryGlueDouble); glue->addHorizontalBargraph = reinterpret_cast(addHorizontalBargraphGlueDouble); glue->addVerticalBargraph = reinterpret_cast(addVerticalBargraphGlueDouble); glue->addSoundfile = reinterpret_cast(addSoundfileGlueDouble); glue->declare = reinterpret_cast(declareGlueDouble); } else { glue->openTabBox = reinterpret_cast(openTabBoxGlueFloat); glue->openHorizontalBox = reinterpret_cast(openHorizontalBoxGlueFloat); glue->openVerticalBox = reinterpret_cast(openVerticalBoxGlueFloat); glue->closeBox = reinterpret_cast(closeBoxGlueFloat); glue->addButton = reinterpret_cast(addButtonGlueFloat); glue->addCheckButton = reinterpret_cast(addCheckButtonGlueFloat); glue->addVerticalSlider = reinterpret_cast(addVerticalSliderGlueFloat); glue->addHorizontalSlider = reinterpret_cast(addHorizontalSliderGlueFloat); glue->addNumEntry = reinterpret_cast(addNumEntryGlueFloat); glue->addHorizontalBargraph = reinterpret_cast(addHorizontalBargraphGlueFloat); glue->addVerticalBargraph = reinterpret_cast(addVerticalBargraphGlueFloat); glue->addSoundfile = reinterpret_cast(addSoundfileGlueFloat); glue->declare = reinterpret_cast(declareGlueFloat); } } // Base class struct UIInterface { virtual ~UIInterface() {} virtual int sizeOfFAUSTFLOAT() = 0; // -- widget's layouts virtual void openTabBox(const char* label) = 0; virtual void openHorizontalBox(const char* label) = 0; virtual void openVerticalBox(const char* label) = 0; virtual void closeBox() = 0; // float version // -- active widgets virtual void addButton(const char* label, float* zone) = 0; virtual void addCheckButton(const char* label, float* zone) = 0; virtual void addVerticalSlider(const char* label, float* zone, float init, float min, float max, float step) = 0; virtual void addHorizontalSlider(const char* label, float* zone, float init, float min, float max, float step) = 0; virtual void addNumEntry(const char* label, float* zone, float init, float min, float max, float step) = 0; // -- passive widgets virtual void addHorizontalBargraph(const char* label, float* zone, float min, float max) = 0; virtual void addVerticalBargraph(const char* label, float* zone, float min, float max) = 0; // -- metadata declarations virtual void declare(float* zone, const char* key, const char* val) = 0; // double version virtual void addButton(const char* label, double* zone) = 0; virtual void addCheckButton(const char* label, double* zone) = 0; virtual void addVerticalSlider(const char* label, double* zone, double init, double min, double max, double step) = 0; virtual void addHorizontalSlider(const char* label, double* zone, double init, double min, double max, double step) = 0; virtual void addNumEntry(const char* label, double* zone, double init, double min, double max, double step) = 0; // -- soundfiles virtual void addSoundfile(const char* label, const char* url, Soundfile** sf_zone) = 0; // -- passive widgets virtual void addHorizontalBargraph(const char* label, double* zone, double min, double max) = 0; virtual void addVerticalBargraph(const char* label, double* zone, double min, double max) = 0; // -- metadata declarations virtual void declare(double* zone, const char* key, const char* val) = 0; }; struct UITemplate : public UIInterface { void* fCPPInterface; UITemplate(void* cpp_interface):fCPPInterface(cpp_interface) {} virtual ~UITemplate() {} int sizeOfFAUSTFLOAT() { return reinterpret_cast(fCPPInterface)->sizeOfFAUSTFLOAT(); } // -- widget's layouts void openTabBox(const char* label) { openTabBoxGlueFloat(fCPPInterface, label); } void openHorizontalBox(const char* label) { openHorizontalBoxGlueFloat(fCPPInterface, label); } void openVerticalBox(const char* label) { openVerticalBoxGlueFloat(fCPPInterface, label); } void closeBox() { closeBoxGlueFloat(fCPPInterface); } // float version // -- active widgets void addButton(const char* label, float* zone) { addButtonGlueFloat(fCPPInterface, label, zone); } void addCheckButton(const char* label, float* zone) { addCheckButtonGlueFloat(fCPPInterface, label, zone); } void addVerticalSlider(const char* label, float* zone, float init, float min, float max, float step) { addVerticalSliderGlueFloat(fCPPInterface, label, zone, init, min, max, step); } void addHorizontalSlider(const char* label, float* zone, float init, float min, float max, float step) { addHorizontalSliderGlueFloat(fCPPInterface, label, zone, init, min, max, step); } void addNumEntry(const char* label, float* zone, float init, float min, float max, float step) { addNumEntryGlueFloat(fCPPInterface, label, zone, init, min, max, step); } // -- passive widgets void addHorizontalBargraph(const char* label, float* zone, float min, float max) { addHorizontalBargraphGlueFloat(fCPPInterface, label, zone, min, max); } void addVerticalBargraph(const char* label, float* zone, float min, float max) { addVerticalBargraphGlueFloat(fCPPInterface, label, zone, min, max); } // -- metadata declarations void declare(float* zone, const char* key, const char* val) { declareGlueFloat(fCPPInterface, zone, key, val); } // double version void addButton(const char* label, double* zone) { addButtonGlueDouble(fCPPInterface, label, zone); } void addCheckButton(const char* label, double* zone) { addCheckButtonGlueDouble(fCPPInterface, label, zone); } void addVerticalSlider(const char* label, double* zone, double init, double min, double max, double step) { addVerticalSliderGlueDouble(fCPPInterface, label, zone, init, min, max, step); } void addHorizontalSlider(const char* label, double* zone, double init, double min, double max, double step) { addHorizontalSliderGlueDouble(fCPPInterface, label, zone, init, min, max, step); } void addNumEntry(const char* label, double* zone, double init, double min, double max, double step) { addNumEntryGlueDouble(fCPPInterface, label, zone, init, min, max, step); } // -- soundfiles void addSoundfile(const char* label, const char* url, Soundfile** sf_zone) { addSoundfileGlueFloat(fCPPInterface, label, url, sf_zone); } // -- passive widgets void addHorizontalBargraph(const char* label, double* zone, double min, double max) { addHorizontalBargraphGlueDouble(fCPPInterface, label, zone, min, max); } void addVerticalBargraph(const char* label, double* zone, double min, double max) { addVerticalBargraphGlueDouble(fCPPInterface, label, zone, min, max); } // -- metadata declarations void declare(double* zone, const char* key, const char* val) { declareGlueDouble(fCPPInterface, zone, key, val); } }; struct UIGlueTemplate : public UIInterface { UIGlue* fGlue; UIGlueTemplate(UIGlue* glue):fGlue(glue) {} virtual ~UIGlueTemplate() {} virtual int sizeOfFAUSTFLOAT() { return sizeof(FAUSTFLOAT); } // -- widget's layouts void openTabBox(const char* label) { fGlue->openTabBox(fGlue->uiInterface, label); } void openHorizontalBox(const char* label) { fGlue->openHorizontalBox(fGlue->uiInterface, label); } void openVerticalBox(const char* label) { fGlue->openVerticalBox(fGlue->uiInterface, label); } void closeBox() { fGlue->closeBox(fGlue->uiInterface); } // float version // -- active widgets void addButton(const char* label, float* zone) { fGlue->addButton(fGlue->uiInterface, label, reinterpret_cast(zone)); } void addCheckButton(const char* label, float* zone) { fGlue->addCheckButton(fGlue->uiInterface, label, reinterpret_cast(zone)); } void addVerticalSlider(const char* label, float* zone, float init, float min, float max, float step) { fGlue->addVerticalSlider(fGlue->uiInterface, label, reinterpret_cast(zone), init, min, max, step); } void addHorizontalSlider(const char* label, float* zone, float init, float min, float max, float step) { fGlue->addHorizontalSlider(fGlue->uiInterface, label, reinterpret_cast(zone), init, min, max, step); } void addNumEntry(const char* label, float* zone, float init, float min, float max, float step) { fGlue->addNumEntry(fGlue->uiInterface, label, reinterpret_cast(zone), init, min, max, step); } // -- passive widgets void addHorizontalBargraph(const char* label, float* zone, float min, float max) { fGlue->addHorizontalBargraph(fGlue->uiInterface, label, reinterpret_cast(zone), min, max); } void addVerticalBargraph(const char* label, float* zone, float min, float max) { fGlue->addVerticalBargraph(fGlue->uiInterface, label, reinterpret_cast(zone), min, max); } // -- metadata declarations void declare(float* zone, const char* key, const char* val) { fGlue->declare(fGlue->uiInterface, reinterpret_cast(zone), key, val); } // double version void addButton(const char* label, double* zone) { fGlue->addButton(fGlue->uiInterface, label, reinterpret_cast(zone)); } void addCheckButton(const char* label, double* zone) { fGlue->addCheckButton(fGlue->uiInterface, label, reinterpret_cast(zone)); } void addVerticalSlider(const char* label, double* zone, double init, double min, double max, double step) { fGlue->addVerticalSlider(fGlue->uiInterface, label, reinterpret_cast(zone), init, min, max, step); } void addHorizontalSlider(const char* label, double* zone, double init, double min, double max, double step) { fGlue->addHorizontalSlider(fGlue->uiInterface, label, reinterpret_cast(zone), init, min, max, step); } void addNumEntry(const char* label, double* zone, double init, double min, double max, double step) { fGlue->addNumEntry(fGlue->uiInterface, label, reinterpret_cast(zone), init, min, max, step); } // -- soundfiles void addSoundfile(const char* label, const char* url, Soundfile** sf_zone) {} // -- passive widgets void addHorizontalBargraph(const char* label, double* zone, double min, double max) { fGlue->addHorizontalBargraph(fGlue->uiInterface, label, reinterpret_cast(zone), min, max); } void addVerticalBargraph(const char* label, double* zone, double min, double max) { fGlue->addVerticalBargraph(fGlue->uiInterface, label, reinterpret_cast(zone), min, max); } // -- metadata declarations void declare(double* zone, const char* key, const char* val) { fGlue->declare(fGlue->uiInterface, reinterpret_cast(zone), key, val); } }; /******************************************************************************* * Meta glue code ******************************************************************************/ static void declareMetaGlue(void* cpp_interface, const char* key, const char* value) { Meta* meta_interface = static_cast(cpp_interface); meta_interface->declare(key, value); } static void buildMetaGlue(MetaGlue* glue, Meta* meta) { glue->metaInterface = meta; glue->declare = declareMetaGlue; } /******************************************************************************* * Memory manager glue code ******************************************************************************/ static void* allocateMemoryManagerGlue(void* cpp_interface, size_t size) { dsp_memory_manager* manager_interface = static_cast(cpp_interface); return manager_interface->allocate(size); } static void destroyMemoryManagerGlue(void* cpp_interface, void* ptr) { dsp_memory_manager* manager_interface = static_cast(cpp_interface); manager_interface->destroy(ptr); } static void buildManagerGlue(MemoryManagerGlue* glue, dsp_memory_manager* manager) { glue->managerInterface = manager; glue->allocate = allocateMemoryManagerGlue; glue->destroy = destroyMemoryManagerGlue; } #ifdef __cplusplus } #endif #endif /************************** END CGlue.h **************************/ #ifdef _WIN32 #include #define snprintf _snprintf #define STRDUP _strdup #else #define STRDUP strdup #endif //------------------------------------------------------------------------------------------ // Decode a dsp JSON description and implement 'buildUserInterface' and 'metadata' methods //------------------------------------------------------------------------------------------ #define REAL_UI(ui_interface) reinterpret_cast*>(ui_interface) #define REAL_ADR(index) reinterpret_cast(&memory_block[index]) #define REAL_EXT_ADR(index) reinterpret_cast(&memory_block[index]) #define SOUNDFILE_ADR(index) reinterpret_cast(&memory_block[index]) typedef std::function ReflectFunction; typedef std::function ModifyFunction; struct FAUST_API ExtZoneParam { virtual void reflectZone() = 0; virtual void modifyZone() = 0; virtual void setReflectZoneFun(ReflectFunction reflect) = 0; virtual void setModifyZoneFun(ModifyFunction modify) = 0; virtual ~ExtZoneParam() {} }; // Templated decoder struct FAUST_API JSONUIDecoderBase { virtual ~JSONUIDecoderBase() {} virtual void metadata(Meta* m) = 0; virtual void metadata(MetaGlue* glue) = 0; virtual int getDSPSize() = 0; virtual std::string getName() = 0; virtual std::string getLibVersion() = 0; virtual std::string getCompileOptions() = 0; virtual std::vector getLibraryList() = 0; virtual std::vector getIncludePathnames() = 0; virtual int getNumInputs() = 0; virtual int getNumOutputs() = 0; virtual int getSampleRate(char* memory_block) = 0; virtual void setReflectZoneFun(int index, ReflectFunction fun) = 0; virtual void setModifyZoneFun(int index, ModifyFunction fun) = 0; virtual void setupDSPProxy(UI* ui_interface, char* memory_block) = 0; virtual bool hasDSPProxy() = 0; virtual std::vector& getInputControls() = 0; virtual std::vector& getOutputControls() = 0; virtual void resetUserInterface() = 0; virtual void resetUserInterface(char* memory_block, Soundfile* defaultsound = nullptr) = 0; virtual void buildUserInterface(UI* ui_interface) = 0; virtual void buildUserInterface(UI* ui_interface, char* memory_block) = 0; virtual void buildUserInterface(UIGlue* ui_interface, char* memory_block) = 0; virtual bool hasCompileOption(const std::string& option) = 0; }; template struct FAUST_API JSONUIDecoderReal : public JSONUIDecoderBase { struct ZoneParam : public ExtZoneParam { FAUSTFLOAT fZone; ReflectFunction fReflect; ModifyFunction fModify; #if defined(TARGET_OS_IPHONE) || defined(WIN32) ZoneParam(ReflectFunction reflect = nullptr, ModifyFunction modify = nullptr) :fReflect(reflect), fModify(modify) {} void reflectZone() { if (fReflect) fReflect(fZone); } void modifyZone() { if (fModify) fZone = fModify(); } #else ZoneParam(ReflectFunction reflect = [](FAUSTFLOAT value) {}, ModifyFunction modify = []() { return FAUSTFLOAT(-1); }) :fReflect(reflect), fModify(modify) {} void reflectZone() { fReflect(fZone); } void modifyZone() { fZone = fModify(); } #endif void setReflectZoneFun(ReflectFunction reflect) { fReflect = reflect; } void setModifyZoneFun(ModifyFunction modify) { fModify = modify; } }; typedef std::vector controlMap; std::string fName; std::string fFileName; std::string fJSON; std::string fVersion; std::string fCompileOptions; std::map fMetadata; std::vector fUiItems; std::vector fLibraryList; std::vector fIncludePathnames; int fNumInputs, fNumOutputs, fSRIndex; int fDSPSize; bool fDSPProxy; controlMap fPathInputTable; // [path, ZoneParam] controlMap fPathOutputTable; // [path, ZoneParam] bool startWith(const std::string& str, const std::string& prefix) { return (str.substr(0, prefix.size()) == prefix); } bool isInput(const std::string& type) { return (type == "vslider" || type == "hslider" || type == "nentry" || type == "button" || type == "checkbox"); } bool isOutput(const std::string& type) { return (type == "hbargraph" || type == "vbargraph"); } bool isSoundfile(const std::string& type) { return (type == "soundfile"); } std::string getString(std::map >& map, const std::string& key) { return (map.find(key) != map.end()) ? map[key].first : ""; } int getInt(std::map >& map, const std::string& key) { return (map.find(key) != map.end()) ? int(map[key].second) : -1; } void setReflectZoneFun(int index, ReflectFunction fun) { fPathInputTable[index]->setReflectZoneFun(fun); } void setModifyZoneFun(int index, ModifyFunction fun) { fPathOutputTable[index]->setModifyZoneFun(fun); } JSONUIDecoderReal(const std::string& json) { fJSON = json; const char* p = fJSON.c_str(); std::map > meta_data1; std::map > meta_data2; parseJson(p, meta_data1, fMetadata, meta_data2, fUiItems); // meta_data1 contains , , pairs etc... fName = getString(meta_data1, "name"); fFileName = getString(meta_data1, "filename"); fVersion = getString(meta_data1, "version"); fCompileOptions = getString(meta_data1, "compile_options"); if (meta_data2.find("library_list") != meta_data2.end()) { fLibraryList = meta_data2["library_list"]; } else { // 'library_list' is coded as successive 'library_pathN' metadata for (const auto& it : fMetadata) { if (startWith(it.first, "library_path")) { fLibraryList.push_back(it.second); } } } if (meta_data2.find("include_pathnames") != meta_data2.end()) { fIncludePathnames = meta_data2["include_pathnames"]; } fDSPSize = getInt(meta_data1, "size"); fNumInputs = getInt(meta_data1, "inputs"); fNumOutputs = getInt(meta_data1, "outputs"); fSRIndex = getInt(meta_data1, "sr_index"); fDSPProxy = false; // Prepare the fPathTable and init zone for (const auto& it : fUiItems) { std::string type = it.type; // Meta data declaration for input items if (isInput(type)) { ZoneParam* param = new ZoneParam(); fPathInputTable.push_back(param); param->fZone = it.init; } // Meta data declaration for output items else if (isOutput(type)) { ZoneParam* param = new ZoneParam(); fPathOutputTable.push_back(param); param->fZone = REAL(0); } } } virtual ~JSONUIDecoderReal() { for (const auto& it : fPathInputTable) { delete it; } for (const auto& it : fPathOutputTable) { delete it; } } void metadata(Meta* m) { for (const auto& it : fMetadata) { m->declare(it.first.c_str(), it.second.c_str()); } } void metadata(MetaGlue* m) { for (const auto& it : fMetadata) { m->declare(m->metaInterface, it.first.c_str(), it.second.c_str()); } } void resetUserInterface() { int item = 0; for (const auto& it : fUiItems) { if (isInput(it.type)) { static_cast(fPathInputTable[item++])->fZone = it.init; } } } void resetUserInterface(char* memory_block, Soundfile* defaultsound = nullptr) { for (const auto& it : fUiItems) { int index = it.index; if (isInput(it.type)) { *REAL_ADR(index) = it.init; } else if (isSoundfile(it.type)) { if (*SOUNDFILE_ADR(index) == nullptr) { *SOUNDFILE_ADR(index) = defaultsound; } } } } int getSampleRate(char* memory_block) { return *reinterpret_cast(&memory_block[fSRIndex]); } void setupDSPProxy(UI* ui_interface, char* memory_block) { if (!fDSPProxy) { fDSPProxy = true; int countIn = 0; int countOut = 0; for (const auto& it : fUiItems) { std::string type = it.type; int index = it.index; if (isInput(type)) { fPathInputTable[countIn++]->setReflectZoneFun([=](FAUSTFLOAT value) { *REAL_ADR(index) = REAL(value); }); } else if (isOutput(type)) { fPathOutputTable[countOut++]->setModifyZoneFun([=]() { return FAUSTFLOAT(*REAL_ADR(index)); }); } } } // Setup soundfile in any case for (const auto& it : fUiItems) { if (isSoundfile(it.type)) { ui_interface->addSoundfile(it.label.c_str(), it.url.c_str(), SOUNDFILE_ADR(it.index)); } } } bool hasDSPProxy() { return fDSPProxy; } void buildUserInterface(UI* ui_interface) { // MANDATORY: to be sure floats or double are correctly parsed char* tmp_local = setlocale(LC_ALL, nullptr); if (tmp_local != NULL) { tmp_local = STRDUP(tmp_local); } setlocale(LC_ALL, "C"); int countIn = 0; int countOut = 0; int countSound = 0; for (const auto& it : fUiItems) { std::string type = it.type; REAL init = REAL(it.init); REAL min = REAL(it.fmin); REAL max = REAL(it.fmax); REAL step = REAL(it.step); // Meta data declaration for input items if (isInput(type)) { for (size_t i = 0; i < it.meta.size(); i++) { ui_interface->declare(&static_cast(fPathInputTable[countIn])->fZone, it.meta[i].first.c_str(), it.meta[i].second.c_str()); } } // Meta data declaration for output items else if (isOutput(type)) { for (size_t i = 0; i < it.meta.size(); i++) { ui_interface->declare(&static_cast(fPathOutputTable[countOut])->fZone, it.meta[i].first.c_str(), it.meta[i].second.c_str()); } } // Meta data declaration for group opening or closing else { for (size_t i = 0; i < it.meta.size(); i++) { ui_interface->declare(0, it.meta[i].first.c_str(), it.meta[i].second.c_str()); } } if (type == "hgroup") { ui_interface->openHorizontalBox(it.label.c_str()); } else if (type == "vgroup") { ui_interface->openVerticalBox(it.label.c_str()); } else if (type == "tgroup") { ui_interface->openTabBox(it.label.c_str()); } else if (type == "vslider") { ui_interface->addVerticalSlider(it.label.c_str(), &static_cast(fPathInputTable[countIn])->fZone, init, min, max, step); } else if (type == "hslider") { ui_interface->addHorizontalSlider(it.label.c_str(), &static_cast(fPathInputTable[countIn])->fZone, init, min, max, step); } else if (type == "checkbox") { ui_interface->addCheckButton(it.label.c_str(), &static_cast(fPathInputTable[countIn])->fZone); } else if (type == "soundfile") { // Nothing } else if (type == "hbargraph") { ui_interface->addHorizontalBargraph(it.label.c_str(), &static_cast(fPathOutputTable[countOut])->fZone, min, max); } else if (type == "vbargraph") { ui_interface->addVerticalBargraph(it.label.c_str(), &static_cast(fPathOutputTable[countOut])->fZone, min, max); } else if (type == "nentry") { ui_interface->addNumEntry(it.label.c_str(), &static_cast(fPathInputTable[countIn])->fZone, init, min, max, step); } else if (type == "button") { ui_interface->addButton(it.label.c_str(), &static_cast(fPathInputTable[countIn])->fZone); } else if (type == "close") { ui_interface->closeBox(); } if (isInput(type)) { countIn++; } else if (isOutput(type)) { countOut++; } else if (isSoundfile(type)) { countSound++; } } if (tmp_local != NULL) { setlocale(LC_ALL, tmp_local); free(tmp_local); } } void buildUserInterface(UI* ui_interface, char* memory_block) { // MANDATORY: to be sure floats or double are correctly parsed char* tmp_local = setlocale(LC_ALL, nullptr); if (tmp_local != NULL) { tmp_local = STRDUP(tmp_local); } setlocale(LC_ALL, "C"); for (const auto& it : fUiItems) { std::string type = it.type; int index = it.index; REAL init = REAL(it.init); REAL min = REAL(it.fmin); REAL max = REAL(it.fmax); REAL step = REAL(it.step); // Meta data declaration for input items if (isInput(type)) { for (size_t i = 0; i < it.meta.size(); i++) { REAL_UI(ui_interface)->declare(REAL_ADR(index), it.meta[i].first.c_str(), it.meta[i].second.c_str()); } } // Meta data declaration for output items else if (isOutput(type)) { for (size_t i = 0; i < it.meta.size(); i++) { REAL_UI(ui_interface)->declare(REAL_ADR(index), it.meta[i].first.c_str(), it.meta[i].second.c_str()); } } // Meta data declaration for group opening or closing else { for (size_t i = 0; i < it.meta.size(); i++) { REAL_UI(ui_interface)->declare(0, it.meta[i].first.c_str(), it.meta[i].second.c_str()); } } if (type == "hgroup") { REAL_UI(ui_interface)->openHorizontalBox(it.label.c_str()); } else if (type == "vgroup") { REAL_UI(ui_interface)->openVerticalBox(it.label.c_str()); } else if (type == "tgroup") { REAL_UI(ui_interface)->openTabBox(it.label.c_str()); } else if (type == "vslider") { REAL_UI(ui_interface)->addVerticalSlider(it.label.c_str(), REAL_ADR(index), init, min, max, step); } else if (type == "hslider") { REAL_UI(ui_interface)->addHorizontalSlider(it.label.c_str(), REAL_ADR(index), init, min, max, step); } else if (type == "checkbox") { REAL_UI(ui_interface)->addCheckButton(it.label.c_str(), REAL_ADR(index)); } else if (type == "soundfile") { REAL_UI(ui_interface)->addSoundfile(it.label.c_str(), it.url.c_str(), SOUNDFILE_ADR(index)); } else if (type == "hbargraph") { REAL_UI(ui_interface)->addHorizontalBargraph(it.label.c_str(), REAL_ADR(index), min, max); } else if (type == "vbargraph") { REAL_UI(ui_interface)->addVerticalBargraph(it.label.c_str(), REAL_ADR(index), min, max); } else if (type == "nentry") { REAL_UI(ui_interface)->addNumEntry(it.label.c_str(), REAL_ADR(index), init, min, max, step); } else if (type == "button") { REAL_UI(ui_interface)->addButton(it.label.c_str(), REAL_ADR(index)); } else if (type == "close") { REAL_UI(ui_interface)->closeBox(); } } if (tmp_local != NULL) { setlocale(LC_ALL, tmp_local); free(tmp_local); } } void buildUserInterface(UIGlue* ui_interface, char* memory_block) { // MANDATORY: to be sure floats or double are correctly parsed char* tmp_local = setlocale(LC_ALL, nullptr); if (tmp_local != NULL) { tmp_local = STRDUP(tmp_local); } setlocale(LC_ALL, "C"); for (const auto& it : fUiItems) { std::string type = it.type; int index = it.index; REAL init = REAL(it.init); REAL min = REAL(it.fmin); REAL max = REAL(it.fmax); REAL step = REAL(it.step); // Meta data declaration for input items if (isInput(type)) { for (size_t i = 0; i < it.meta.size(); i++) { ui_interface->declare(ui_interface->uiInterface, REAL_EXT_ADR(index), it.meta[i].first.c_str(), it.meta[i].second.c_str()); } } // Meta data declaration for output items else if (isOutput(type)) { for (size_t i = 0; i < it.meta.size(); i++) { ui_interface->declare(ui_interface->uiInterface, REAL_EXT_ADR(index), it.meta[i].first.c_str(), it.meta[i].second.c_str()); } } // Meta data declaration for group opening or closing else { for (size_t i = 0; i < it.meta.size(); i++) { ui_interface->declare(ui_interface->uiInterface, 0, it.meta[i].first.c_str(), it.meta[i].second.c_str()); } } if (type == "hgroup") { ui_interface->openHorizontalBox(ui_interface->uiInterface, it.label.c_str()); } else if (type == "vgroup") { ui_interface->openVerticalBox(ui_interface->uiInterface, it.label.c_str()); } else if (type == "tgroup") { ui_interface->openTabBox(ui_interface->uiInterface, it.label.c_str()); } else if (type == "vslider") { ui_interface->addVerticalSlider(ui_interface->uiInterface, it.label.c_str(), REAL_EXT_ADR(index), init, min, max, step); } else if (type == "hslider") { ui_interface->addHorizontalSlider(ui_interface->uiInterface, it.label.c_str(), REAL_EXT_ADR(index), init, min, max, step); } else if (type == "checkbox") { ui_interface->addCheckButton(ui_interface->uiInterface, it.label.c_str(), REAL_EXT_ADR(index)); } else if (type == "soundfile") { ui_interface->addSoundfile(ui_interface->uiInterface, it.label.c_str(), it.url.c_str(), SOUNDFILE_ADR(index)); } else if (type == "hbargraph") { ui_interface->addHorizontalBargraph(ui_interface->uiInterface, it.label.c_str(), REAL_EXT_ADR(index), min, max); } else if (type == "vbargraph") { ui_interface->addVerticalBargraph(ui_interface->uiInterface, it.label.c_str(), REAL_EXT_ADR(index), min, max); } else if (type == "nentry") { ui_interface->addNumEntry(ui_interface->uiInterface, it.label.c_str(), REAL_EXT_ADR(index), init, min, max, step); } else if (type == "button") { ui_interface->addButton(ui_interface->uiInterface, it.label.c_str(), REAL_EXT_ADR(index)); } else if (type == "close") { ui_interface->closeBox(ui_interface->uiInterface); } } if (tmp_local != NULL) { setlocale(LC_ALL, tmp_local); free(tmp_local); } } bool hasCompileOption(const std::string& option) { std::istringstream iss(fCompileOptions); std::string token; while (std::getline(iss, token, ' ')) { if (token == option) return true; } return false; } int getDSPSize() { return fDSPSize; } std::string getName() { return fName; } std::string getLibVersion() { return fVersion; } std::string getCompileOptions() { return fCompileOptions; } std::vector getLibraryList() { return fLibraryList; } std::vector getIncludePathnames() { return fIncludePathnames; } int getNumInputs() { return fNumInputs; } int getNumOutputs() { return fNumOutputs; } std::vector& getInputControls() { return fPathInputTable; } std::vector& getOutputControls() { return fPathOutputTable; } }; // FAUSTFLOAT templated decoder struct FAUST_API JSONUIDecoder : public JSONUIDecoderReal { JSONUIDecoder(const std::string& json):JSONUIDecoderReal(json) {} }; // Generic factory static JSONUIDecoderBase* createJSONUIDecoder(const std::string& json) { JSONUIDecoder decoder(json); if (decoder.hasCompileOption("-double")) { return new JSONUIDecoderReal(json); } else { return new JSONUIDecoderReal(json); } } #endif /************************** END JSONUIDecoder.h **************************/ /** * Proxy dsp definition created from the DSP JSON description. * This class allows a 'proxy' dsp to control a real dsp * possibly running somewhere else. */ class proxy_dsp : public dsp { protected: JSONUIDecoder* fDecoder; int fSampleRate; void init(const std::string& json) { fDecoder = new JSONUIDecoder(json); fSampleRate = -1; } public: proxy_dsp():fDecoder(nullptr), fSampleRate(-1) {} proxy_dsp(const std::string& json) { init(json); } proxy_dsp(dsp* dsp) { JSONUI builder(dsp->getNumInputs(), dsp->getNumOutputs()); dsp->metadata(&builder); dsp->buildUserInterface(&builder); fSampleRate = dsp->getSampleRate(); fDecoder = new JSONUIDecoder(builder.JSON()); } virtual ~proxy_dsp() { delete fDecoder; } virtual int getNumInputs() { return fDecoder->fNumInputs; } virtual int getNumOutputs() { return fDecoder->fNumOutputs; } virtual void buildUserInterface(UI* ui) { fDecoder->buildUserInterface(ui); } // To possibly implement in a concrete proxy dsp virtual void init(int sample_rate) { instanceInit(sample_rate); } virtual void instanceInit(int sample_rate) { instanceConstants(sample_rate); instanceResetUserInterface(); instanceClear(); } virtual void instanceConstants(int sample_rate) { fSampleRate = sample_rate; } virtual void instanceResetUserInterface() { fDecoder->resetUserInterface(); } virtual void instanceClear() {} virtual int getSampleRate() { return fSampleRate; } virtual proxy_dsp* clone() { return new proxy_dsp(fDecoder->fJSON); } virtual void metadata(Meta* m) { fDecoder->metadata(m); } virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) {} virtual void compute(double date_usec, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) {} }; #endif /************************** END proxy-dsp.h **************************/ /************************** BEGIN JSONControl.h ************************** FAUST Architecture File Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale --------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXCEPTION : As a special exception, you may create a larger work that contains this FAUST architecture section and distribute that work under terms of your choice, so long as this FAUST architecture section is not modified. *************************************************************************/ #ifndef __JSON_CONTROL__ #define __JSON_CONTROL__ #include #ifndef FAUSTFLOAT #define FAUSTFLOAT float #endif struct FAUST_API JSONControl { virtual std::string getJSON() { return ""; } virtual void setParamValue(const std::string& path, FAUSTFLOAT value) {} virtual FAUSTFLOAT getParamValue(const std::string& path) { return 0; } virtual ~JSONControl() {} }; #endif /************************** END JSONControl.h **************************/ #define kActiveVoice 0 #define kFreeVoice -1 #define kReleaseVoice -2 #define kLegatoVoice -3 #define kNoVoice -4 #define VOICE_STOP_LEVEL 0.0005 // -70 db #define MIX_BUFFER_SIZE 4096 /** * Allows to control zones in a grouped manner. */ class GroupUI : public GUI, public PathBuilder { private: // Map to associate labels with UI group items std::map fLabelZoneMap; // Insert a zone into the map based on the label folloing the freq/gain/gate polyphonic convention void insertMap(std::string label, FAUSTFLOAT* zone) { if (!MapUI::endsWith(label, "/gate") && !MapUI::endsWith(label, "/freq") && !MapUI::endsWith(label, "/key") && !MapUI::endsWith(label, "/gain") && !MapUI::endsWith(label, "/vel") && !MapUI::endsWith(label, "/velocity")) { // Groups all controllers except 'freq/key', 'gate', and 'gain/vel|velocity' if (fLabelZoneMap.find(label) != fLabelZoneMap.end()) { fLabelZoneMap[label]->addZone(zone); } else { fLabelZoneMap[label] = new uiGroupItem(this, zone); } } } uiCallbackItem* fPanic; public: GroupUI(FAUSTFLOAT* zone, uiCallback cb, void* arg) { fPanic = new uiCallbackItem(this, zone, cb, arg); } virtual ~GroupUI() { // 'fPanic' is kept and deleted in GUI, so do not delete here } // -- widget's layouts void openTabBox(const char* label) { pushLabel(label); } void openHorizontalBox(const char* label) { pushLabel(label); } void openVerticalBox(const char* label) { pushLabel(label); } void closeBox() { popLabel(); } // -- active widgets void addButton(const char* label, FAUSTFLOAT* zone) { insertMap(buildPath(label), zone); } void addCheckButton(const char* label, FAUSTFLOAT* zone) { insertMap(buildPath(label), zone); } void addVerticalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT fmin, FAUSTFLOAT fmax, FAUSTFLOAT step) { insertMap(buildPath(label), zone); } void addHorizontalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT fmin, FAUSTFLOAT fmax, FAUSTFLOAT step) { insertMap(buildPath(label), zone); } void addNumEntry(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT fmin, FAUSTFLOAT fmax, FAUSTFLOAT step) { insertMap(buildPath(label), zone); } // -- passive widgets void addHorizontalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT fmin, FAUSTFLOAT fmax) { insertMap(buildPath(label), zone); } void addVerticalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT fmin, FAUSTFLOAT fmax) { insertMap(buildPath(label), zone); } }; /** * One voice of polyphony. */ struct dsp_voice : public MapUI, public decorator_dsp { typedef std::function TransformFunction; // Convert MIDI note to frequency static double midiToFreq(double note) { return 440.0 * std::pow(2.0, (note-69.0)/12.0); } // Voice state and properties int fCurNote; // Current playing note pitch int fNextNote; // In kLegatoVoice state, next note to play int fNextVel; // In kLegatoVoice state, next velocity to play int fDate; // KeyOn date int fRelease; // Current number of samples used in release mode to detect end of note FAUSTFLOAT fLevel; // Last audio block level double fReleaseLengthSec; // Maximum release length in seconds (estimated time to silence after note release) std::vector fGatePath; // Paths of 'gate' control std::vector fGainPath; // Paths of 'gain/vel|velocity' control std::vector fFreqPath; // Paths of 'freq/key' control TransformFunction fKeyFun; // MIDI key to freq conversion function TransformFunction fVelFun; // MIDI velocity to gain conversion function FAUSTFLOAT** fInputsSlice; FAUSTFLOAT** fOutputsSlice; dsp_voice(dsp* dsp):decorator_dsp(dsp) { // Default conversion functions fVelFun = [](int velocity) { return double(velocity)/127.0; }; fKeyFun = [](int pitch) { return midiToFreq(pitch); }; dsp->buildUserInterface(this); fCurNote = kFreeVoice; fNextNote = fNextVel = -1; fLevel = FAUSTFLOAT(0); fDate = fRelease = 0; fReleaseLengthSec = 0.5; // A half second is a reasonable default maximum release length. extractPaths(fGatePath, fFreqPath, fGainPath); } virtual ~dsp_voice() {} // Compute a slice of audio void computeSlice(int offset, int slice, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { FAUSTFLOAT** inputsSlice = static_cast(alloca(sizeof(FAUSTFLOAT*) * getNumInputs())); for (int chan = 0; chan < getNumInputs(); chan++) { inputsSlice[chan] = &(inputs[chan][offset]); } FAUSTFLOAT** outputsSlice = static_cast(alloca(sizeof(FAUSTFLOAT*) * getNumOutputs())); for (int chan = 0; chan < getNumOutputs(); chan++) { outputsSlice[chan] = &(outputs[chan][offset]); } compute(slice, inputsSlice, outputsSlice); } // Compute audio in legato mode void computeLegato(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { int slice = count/2; // Reset envelops for (size_t i = 0; i < fGatePath.size(); i++) { setParamValue(fGatePath[i], FAUSTFLOAT(0)); } // Compute current voice on half buffer computeSlice(0, slice, inputs, outputs); // Start next keyOn keyOn(fNextNote, fNextVel); // Compute on second half buffer computeSlice(slice, slice, inputs, outputs); } // Extract control paths from fullpath map void extractPaths(std::vector& gate, std::vector& freq, std::vector& gain) { // Keep gain/vel|velocity, freq/key and gate labels for (const auto& it : getFullpathMap()) { std::string path = it.first; if (endsWith(path, "/gate")) { gate.push_back(path); } else if (endsWith(path, "/freq")) { fKeyFun = [](int pitch) { return midiToFreq(pitch); }; freq.push_back(path); } else if (endsWith(path, "/key")) { fKeyFun = [](int pitch) { return pitch; }; freq.push_back(path); } else if (endsWith(path, "/gain")) { fVelFun = [](int velocity) { return double(velocity)/127.0; }; gain.push_back(path); } else if (endsWith(path, "/vel") || endsWith(path, "/velocity")) { fVelFun = [](int velocity) { return double(velocity); }; gain.push_back(path); } } } // Reset voice void reset() { init(getSampleRate()); } // Clear instance state void instanceClear() { decorator_dsp::instanceClear(); fCurNote = kFreeVoice; fNextNote = fNextVel = -1; fLevel = FAUSTFLOAT(0); fDate = fRelease = 0; } // Keep 'pitch' and 'velocity' to fadeOut the current voice and start next one in the next buffer void keyOn(int pitch, int velocity, bool legato = false) { if (legato) { fNextNote = pitch; fNextVel = velocity; } else { keyOn(pitch, fVelFun(velocity)); } } // KeyOn with normalized MIDI velocity [0..1] void keyOn(int pitch, double velocity) { for (size_t i = 0; i < fFreqPath.size(); i++) { setParamValue(fFreqPath[i], fKeyFun(pitch)); } for (size_t i = 0; i < fGatePath.size(); i++) { setParamValue(fGatePath[i], FAUSTFLOAT(1)); } for (size_t i = 0; i < fGainPath.size(); i++) { setParamValue(fGainPath[i], velocity); } fCurNote = pitch; } void keyOff(bool hard = false) { // No use of velocity for now... for (size_t i = 0; i < fGatePath.size(); i++) { setParamValue(fGatePath[i], FAUSTFLOAT(0)); } if (hard) { // Immediately stop voice fCurNote = kFreeVoice; } else { // Release voice fRelease = fReleaseLengthSec * fDSP->getSampleRate(); fCurNote = kReleaseVoice; } } // Change the voice release void setReleaseLength(double sec) { fReleaseLengthSec = sec; } }; /** * A group of voices. */ struct dsp_voice_group { // GUI group for controlling voice parameters GroupUI fGroups; std::vector fVoiceTable; // Individual voices dsp* fVoiceGroup; // Voices group to be used for GUI grouped control FAUSTFLOAT fPanic; // Panic button value bool fVoiceControl; // Voice control mode bool fGroupControl; // Group control mode dsp_voice_group(uiCallback cb, void* arg, bool control, bool group) :fGroups(&fPanic, cb, arg), fVoiceGroup(0), fPanic(FAUSTFLOAT(0)), fVoiceControl(control), fGroupControl(group) {} virtual ~dsp_voice_group() { for (size_t i = 0; i < fVoiceTable.size(); i++) { delete fVoiceTable[i]; } delete fVoiceGroup; } // Add a voice to the group void addVoice(dsp_voice* voice) { fVoiceTable.push_back(voice); } // Clear all voices from the group void clearVoices() { fVoiceTable.clear(); } // Initialize the voice group void init() { // Groups all uiItem for a given path fVoiceGroup = new proxy_dsp(fVoiceTable[0]); fVoiceGroup->buildUserInterface(&fGroups); for (size_t i = 0; i < fVoiceTable.size(); i++) { fVoiceTable[i]->buildUserInterface(&fGroups); } } // Reset the user interface for each voice instance void instanceResetUserInterface() { for (size_t i = 0; i < fVoiceTable.size(); i++) { fVoiceTable[i]->instanceResetUserInterface(); } } // Build the user interface for the voice group void buildUserInterface(UI* ui_interface) { if (fVoiceTable.size() > 1) { ui_interface->openTabBox("Polyphonic"); // Grouped voices UI ui_interface->openVerticalBox("Voices"); ui_interface->addButton("Panic", &fPanic); fVoiceGroup->buildUserInterface(ui_interface); ui_interface->closeBox(); // If not grouped, also add individual voices UI if (!fGroupControl || dynamic_cast(ui_interface)) { for (size_t i = 0; i < fVoiceTable.size(); i++) { char buffer[32]; snprintf(buffer, 32, ((fVoiceTable.size() < 8) ? "Voice%ld" : "V%ld"), long(i+1)); ui_interface->openHorizontalBox(buffer); fVoiceTable[i]->buildUserInterface(ui_interface); ui_interface->closeBox(); } } ui_interface->closeBox(); } else { fVoiceTable[0]->buildUserInterface(ui_interface); } } }; /** * Base class for MIDI controllable polyphonic DSP. */ #ifdef EMCC #endif class dsp_poly : public decorator_dsp, public midi, public JSONControl { protected: #ifdef EMCC MapUI fMapUI; // Map for UI control std::string fJSON; // JSON representation of the UI midi_handler fMidiHandler; // MIDI handler for the UI MidiUI fMIDIUI; // MIDI UI for the DSP #endif public: #ifdef EMCC dsp_poly(dsp* dsp):decorator_dsp(dsp), fMIDIUI(&fMidiHandler) { JSONUI jsonui(getNumInputs(), getNumOutputs()); buildUserInterface(&jsonui); fJSON = jsonui.JSON(true); buildUserInterface(&fMapUI); buildUserInterface(&fMIDIUI); } #else dsp_poly(dsp* dsp):decorator_dsp(dsp) {} #endif virtual ~dsp_poly() {} // Reimplemented for EMCC #ifdef EMCC virtual int getNumInputs() { return decorator_dsp::getNumInputs(); } virtual int getNumOutputs() { return decorator_dsp::getNumOutputs(); } virtual void buildUserInterface(UI* ui_interface) { decorator_dsp::buildUserInterface(ui_interface); } virtual int getSampleRate() { return decorator_dsp::getSampleRate(); } virtual void init(int sample_rate) { decorator_dsp::init(sample_rate); } virtual void instanceInit(int sample_rate) { decorator_dsp::instanceInit(sample_rate); } virtual void instanceConstants(int sample_rate) { decorator_dsp::instanceConstants(sample_rate); } virtual void instanceResetUserInterface() { decorator_dsp::instanceResetUserInterface(); } virtual void instanceClear() { decorator_dsp::instanceClear(); } virtual dsp_poly* clone() { return new dsp_poly(fDSP->clone()); } virtual void metadata(Meta* m) { decorator_dsp::metadata(m); } // Additional API std::string getJSON() { return fJSON; } virtual void setParamValue(const std::string& path, FAUSTFLOAT value) { fMapUI.setParamValue(path, value); GUI::updateAllGuis(); } virtual FAUSTFLOAT getParamValue(const std::string& path) { return fMapUI.getParamValue(path); } virtual void computeJS(int count, uintptr_t inputs, uintptr_t outputs) { decorator_dsp::compute(count, reinterpret_cast(inputs),reinterpret_cast(outputs)); } #endif virtual MapUI* keyOn(int channel, int pitch, int velocity) { return midi::keyOn(channel, pitch, velocity); } virtual void keyOff(int channel, int pitch, int velocity) { midi::keyOff(channel, pitch, velocity); } virtual void keyPress(int channel, int pitch, int press) { midi::keyPress(channel, pitch, press); } virtual void chanPress(int channel, int press) { midi::chanPress(channel, press); } virtual void ctrlChange(int channel, int ctrl, int value) { midi::ctrlChange(channel, ctrl, value); } virtual void ctrlChange14bits(int channel, int ctrl, int value) { midi::ctrlChange14bits(channel, ctrl, value); } virtual void pitchWheel(int channel, int wheel) { #ifdef EMCC fMIDIUI.pitchWheel(0., channel, wheel); GUI::updateAllGuis(); #else midi::pitchWheel(channel, wheel); #endif } virtual void progChange(int channel, int pgm) { midi::progChange(channel, pgm); } // Change the voice release virtual void setReleaseLength(double seconds) {} }; /** * Polyphonic DSP: groups a set of DSP to be played together or triggered by MIDI. * * All voices are preallocated by cloning the single DSP voice given at creation time. * Dynamic voice allocation is done in 'getFreeVoice' */ class mydsp_poly : public dsp_voice_group, public dsp_poly { private: FAUSTFLOAT** fMixBuffer; // Intermediate buffer for mixing voices FAUSTFLOAT** fOutBuffer; // Intermediate buffer for output midi_interface* fMidiHandler; // The midi_interface the DSP is connected to int fDate; // Current date for managing voices // Fade out the audio in the buffer void fadeOut(int count, FAUSTFLOAT** outBuffer) { // FadeOut on half buffer for (int chan = 0; chan < getNumOutputs(); chan++) { double factor = 1., step = 1./double(count); for (int frame = 0; frame < count; frame++) { outBuffer[chan][frame] *= factor; factor -= step; } } } // Mix the audio from the mix buffer to the output buffer, and also calculate the maximum level on the buffer FAUSTFLOAT mixCheckVoice(int count, FAUSTFLOAT** mixBuffer, FAUSTFLOAT** outBuffer) { FAUSTFLOAT level = 0; for (int chan = 0; chan < getNumOutputs(); chan++) { FAUSTFLOAT* mixChannel = mixBuffer[chan]; FAUSTFLOAT* outChannel = outBuffer[chan]; for (int frame = 0; frame < count; frame++) { level = std::max(level, (FAUSTFLOAT)fabs(mixChannel[frame])); outChannel[frame] += mixChannel[frame]; } } return level; } // Mix the audio from the mix buffer to the output buffer void mixVoice(int count, FAUSTFLOAT** mixBuffer, FAUSTFLOAT** outBuffer) { for (int chan = 0; chan < getNumOutputs(); chan++) { FAUSTFLOAT* mixChannel = mixBuffer[chan]; FAUSTFLOAT* outChannel = outBuffer[chan]; for (int frame = 0; frame < count; frame++) { outChannel[frame] += mixChannel[frame]; } } } // Copy the audio from one buffer to another void copy(int count, FAUSTFLOAT** mixBuffer, FAUSTFLOAT** outBuffer) { for (int chan = 0; chan < getNumOutputs(); chan++) { memcpy(outBuffer[chan], mixBuffer[chan], count * sizeof(FAUSTFLOAT)); } } // Clear the audio buffer void clear(int count, FAUSTFLOAT** outBuffer) { for (int chan = 0; chan < getNumOutputs(); chan++) { memset(outBuffer[chan], 0, count * sizeof(FAUSTFLOAT)); } } // Get the index of a voice currently playing a specific pitch int getPlayingVoice(int pitch) { int voice_playing = kNoVoice; int oldest_date_playing = INT_MAX; for (size_t i = 0; i < fVoiceTable.size(); i++) { if (fVoiceTable[i]->fCurNote == pitch) { // Keeps oldest playing voice if (fVoiceTable[i]->fDate < oldest_date_playing) { oldest_date_playing = fVoiceTable[i]->fDate; voice_playing = int(i); } } } return voice_playing; } // Allocate a voice with a given type int allocVoice(int voice, int type) { fVoiceTable[voice]->fDate++; fVoiceTable[voice]->fCurNote = type; return voice; } // Get a free voice for allocation, always returns a voice int getFreeVoice() { // Looks for the first available voice for (size_t i = 0; i < fVoiceTable.size(); i++) { if (fVoiceTable[i]->fCurNote == kFreeVoice) { return allocVoice(i, kActiveVoice); } } // Otherwise steal one int voice_release = kNoVoice; int voice_playing = kNoVoice; int oldest_date_release = INT_MAX; int oldest_date_playing = INT_MAX; // Scan all voices for (size_t i = 0; i < fVoiceTable.size(); i++) { if (fVoiceTable[i]->fCurNote == kReleaseVoice) { // Keeps oldest release voice if (fVoiceTable[i]->fDate < oldest_date_release) { oldest_date_release = fVoiceTable[i]->fDate; voice_release = int(i); } } else { // Otherwise keeps oldest playing voice if (fVoiceTable[i]->fDate < oldest_date_playing) { oldest_date_playing = fVoiceTable[i]->fDate; voice_playing = int(i); } } } // Then decide which one to steal if (oldest_date_release != INT_MAX) { fprintf(stderr, "Steal release voice : voice_date = %d cur_date = %d voice = %d \n", fVoiceTable[voice_release]->fDate, fDate, voice_release); return allocVoice(voice_release, kLegatoVoice); } else if (oldest_date_playing != INT_MAX) { fprintf(stderr, "Steal playing voice : voice_date = %d cur_date = %d voice = %d \n", fVoiceTable[voice_playing]->fDate, fDate, voice_release); return allocVoice(voice_playing, kLegatoVoice); } else { assert(false); return kNoVoice; } } // Callback for panic button static void panic(FAUSTFLOAT val, void* arg) { if (val == FAUSTFLOAT(1)) { static_cast(arg)->allNotesOff(true); } } // Check if the DSP is polyphonic bool checkPolyphony() { if (fVoiceTable.size() > 0) { return true; } else { fprintf(stderr, "DSP is not polyphonic...\n"); return false; } } public: /** * Constructor. * * @param dsp - the dsp to be used for one voice. Beware: mydsp_poly will use and finally delete the pointer. * @param nvoices - number of polyphony voices, should be at least 1 * @param control - whether voices will be dynamically allocated and controlled (typically by a MIDI controler). * If false all voices are always running. * @param group - if true, voices are not individually accessible, a global "Voices" tab will automatically dispatch * a given control on all voices, assuming GUI::updateAllGuis() is called. * If false, all voices can be individually controlled. * */ mydsp_poly(dsp* dsp, int nvoices, bool control = false, bool group = true) : dsp_voice_group(panic, this, control, group), dsp_poly(dsp) // dsp parameter is deallocated by ~dsp_poly { fDate = 0; fMidiHandler = nullptr; // Create voices assert(nvoices > 0); for (int i = 0; i < nvoices; i++) { addVoice(new dsp_voice(dsp->clone())); } // Init audio output buffers fMixBuffer = new FAUSTFLOAT*[getNumOutputs()]; fOutBuffer = new FAUSTFLOAT*[getNumOutputs()]; for (int chan = 0; chan < getNumOutputs(); chan++) { fMixBuffer[chan] = new FAUSTFLOAT[MIX_BUFFER_SIZE]; fOutBuffer[chan] = new FAUSTFLOAT[MIX_BUFFER_SIZE]; } dsp_voice_group::init(); } virtual ~mydsp_poly() { // Remove from fMidiHandler if (fMidiHandler) fMidiHandler->removeMidiIn(this); for (int chan = 0; chan < getNumOutputs(); chan++) { delete[] fMixBuffer[chan]; delete[] fOutBuffer[chan]; } delete[] fMixBuffer; delete[] fOutBuffer; } // DSP API void buildUserInterface(UI* ui_interface) { // MidiUI ui_interface contains the midi_handler connected to the MIDI driver if (dynamic_cast(ui_interface)) { fMidiHandler = dynamic_cast(ui_interface); fMidiHandler->addMidiIn(this); } dsp_voice_group::buildUserInterface(ui_interface); } void init(int sample_rate) { decorator_dsp::init(sample_rate); fVoiceGroup->init(sample_rate); fPanic = FAUSTFLOAT(0); // Init voices for (size_t i = 0; i < fVoiceTable.size(); i++) { fVoiceTable[i]->init(sample_rate); } } void instanceInit(int samplingFreq) { instanceConstants(samplingFreq); instanceResetUserInterface(); instanceClear(); } void instanceConstants(int sample_rate) { decorator_dsp::instanceConstants(sample_rate); fVoiceGroup->instanceConstants(sample_rate); // Init voices for (size_t i = 0; i < fVoiceTable.size(); i++) { fVoiceTable[i]->instanceConstants(sample_rate); } } void instanceResetUserInterface() { decorator_dsp::instanceResetUserInterface(); fVoiceGroup->instanceResetUserInterface(); fPanic = FAUSTFLOAT(0); for (size_t i = 0; i < fVoiceTable.size(); i++) { fVoiceTable[i]->instanceResetUserInterface(); } } void instanceClear() { decorator_dsp::instanceClear(); fVoiceGroup->instanceClear(); for (size_t i = 0; i < fVoiceTable.size(); i++) { fVoiceTable[i]->instanceClear(); } } virtual mydsp_poly* clone() { return new mydsp_poly(fDSP->clone(), int(fVoiceTable.size()), fVoiceControl, fGroupControl); } void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { assert(count <= MIX_BUFFER_SIZE); // First clear the intermediate fOutBuffer clear(count, fOutBuffer); if (fVoiceControl) { // Mix all playing voices for (size_t i = 0; i < fVoiceTable.size(); i++) { dsp_voice* voice = fVoiceTable[i]; if (voice->fCurNote == kLegatoVoice) { // Play from current note and next note voice->computeLegato(count, inputs, fMixBuffer); // FadeOut on first half buffer fadeOut(count/2, fMixBuffer); // Mix it in result voice->fLevel = mixCheckVoice(count, fMixBuffer, fOutBuffer); } else if (voice->fCurNote != kFreeVoice) { // Compute current note voice->compute(count, inputs, fMixBuffer); // Mix it in result voice->fLevel = mixCheckVoice(count, fMixBuffer, fOutBuffer); // Check the level to possibly set the voice in kFreeVoice again voice->fRelease -= count; if ((voice->fCurNote == kReleaseVoice) && (voice->fRelease < 0) && (voice->fLevel < VOICE_STOP_LEVEL)) { voice->fCurNote = kFreeVoice; } } } } else { // Mix all voices for (size_t i = 0; i < fVoiceTable.size(); i++) { fVoiceTable[i]->compute(count, inputs, fMixBuffer); mixVoice(count, fMixBuffer, fOutBuffer); } } // Finally copy intermediate buffer to outputs copy(count, fOutBuffer, outputs); } void compute(double date_usec, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { compute(count, inputs, outputs); } // Terminate all active voices, gently or immediately (depending of 'hard' value) void allNotesOff(bool hard = false) { for (size_t i = 0; i < fVoiceTable.size(); i++) { fVoiceTable[i]->keyOff(hard); } } // Additional polyphonic API MapUI* newVoice() { return fVoiceTable[getFreeVoice()]; } void deleteVoice(MapUI* voice) { auto it = find(fVoiceTable.begin(), fVoiceTable.end(), reinterpret_cast(voice)); if (it != fVoiceTable.end()) { dsp_voice* voice = *it; voice->keyOff(); voice->reset(); } else { fprintf(stderr, "Voice not found\n"); } } // MIDI API MapUI* keyOn(int channel, int pitch, int velocity) { if (checkPolyphony()) { int voice = getFreeVoice(); fVoiceTable[voice]->keyOn(pitch, velocity, fVoiceTable[voice]->fCurNote == kLegatoVoice); return fVoiceTable[voice]; } else { return 0; } } void keyOff(int channel, int pitch, int velocity = 127) { if (checkPolyphony()) { int voice = getPlayingVoice(pitch); if (voice != kNoVoice) { fVoiceTable[voice]->keyOff(); } else { fprintf(stderr, "Playing pitch = %d not found\n", pitch); } } } void ctrlChange(int channel, int ctrl, int value) { if (ctrl == ALL_NOTES_OFF || ctrl == ALL_SOUND_OFF) { allNotesOff(); } } // Change the voice release void setReleaseLength(double seconds) { for (size_t i = 0; i < fVoiceTable.size(); i++) { fVoiceTable[i]->setReleaseLength(seconds); } } }; /** * Polyphonic DSP with an integrated effect. */ class dsp_poly_effect : public dsp_poly { private: // fPolyDSP will respond to MIDI messages. dsp_poly* fPolyDSP; public: dsp_poly_effect(dsp_poly* voice, dsp* combined) :dsp_poly(combined), fPolyDSP(voice) {} virtual ~dsp_poly_effect() { // dsp_poly_effect is also a decorator_dsp, which will free fPolyDSP } // MIDI API MapUI* keyOn(int channel, int pitch, int velocity) { return fPolyDSP->keyOn(channel, pitch, velocity); } void keyOff(int channel, int pitch, int velocity) { fPolyDSP->keyOff(channel, pitch, velocity); } void keyPress(int channel, int pitch, int press) { fPolyDSP->keyPress(channel, pitch, press); } void chanPress(int channel, int press) { fPolyDSP->chanPress(channel, press); } void ctrlChange(int channel, int ctrl, int value) { fPolyDSP->ctrlChange(channel, ctrl, value); } void ctrlChange14bits(int channel, int ctrl, int value) { fPolyDSP->ctrlChange14bits(channel, ctrl, value); } void pitchWheel(int channel, int wheel) { fPolyDSP->pitchWheel(channel, wheel); } void progChange(int channel, int pgm) { fPolyDSP->progChange(channel, pgm); } // Change the voice release void setReleaseLength(double sec) { fPolyDSP->setReleaseLength(sec); } }; /** * Polyphonic DSP factory class. Helper code to support polyphonic DSP source with an integrated effect. */ struct dsp_poly_factory : public dsp_factory { dsp_factory* fProcessFactory; dsp_factory* fEffectFactory; dsp* adaptDSP(dsp* dsp, bool is_double) { return (is_double) ? new dsp_sample_adapter(dsp) : dsp; } dsp_poly_factory(dsp_factory* process_factory = nullptr, dsp_factory* effect_factory = nullptr): fProcessFactory(process_factory) ,fEffectFactory(effect_factory) {} virtual ~dsp_poly_factory() {} std::string getName() { return fProcessFactory->getName(); } std::string getSHAKey() { return fProcessFactory->getSHAKey(); } std::string getDSPCode() { return fProcessFactory->getDSPCode(); } std::string getCompileOptions() { return fProcessFactory->getCompileOptions(); } std::vector getLibraryList() { return fProcessFactory->getLibraryList(); } std::vector getIncludePathnames() { return fProcessFactory->getIncludePathnames(); } std::vector getWarningMessages() { return fProcessFactory->getWarningMessages(); } std::string getEffectCode(const std::string& dsp_content) { std::stringstream effect_code; effect_code << "adapt(1,1) = _; adapt(2,2) = _,_; adapt(1,2) = _ <: _,_; adapt(2,1) = _,_ :> _;"; effect_code << "adaptor(F,G) = adapt(outputs(F),inputs(G)); dsp_code = environment{ " << dsp_content << " };"; effect_code << "process = adaptor(dsp_code.process, dsp_code.effect) : dsp_code.effect;"; return effect_code.str(); } virtual void setMemoryManager(dsp_memory_manager* manager) { fProcessFactory->setMemoryManager(manager); if (fEffectFactory) { fEffectFactory->setMemoryManager(manager); } } virtual dsp_memory_manager* getMemoryManager() { return fProcessFactory->getMemoryManager(); } /* Create a new polyphonic DSP instance with global effect, to be deleted with C++ 'delete' * * @param nvoices - number of polyphony voices, should be at least 1 * @param control - whether voices will be dynamically allocated and controlled (typically by a MIDI controler). * If false all voices are always running. * @param group - if true, voices are not individually accessible, a global "Voices" tab will automatically dispatch * a given control on all voices, assuming GUI::updateAllGuis() is called. * If false, all voices can be individually controlled. * @param is_double - if true, internally allocated DSPs will be adapted to receive 'double' samples. */ dsp_poly* createPolyDSPInstance(int nvoices, bool control, bool group, bool is_double = false) { dsp_poly* dsp_poly = new mydsp_poly(adaptDSP(fProcessFactory->createDSPInstance(), is_double), nvoices, control, group); if (fEffectFactory) { // the 'dsp_poly' object has to be controlled with MIDI, so kept separated from new dsp_sequencer(...) object return new dsp_poly_effect(dsp_poly, new dsp_sequencer(dsp_poly, adaptDSP(fEffectFactory->createDSPInstance(), is_double))); } else { return new dsp_poly_effect(dsp_poly, dsp_poly); } } /* Create a new DSP instance, to be deleted with C++ 'delete' */ dsp* createDSPInstance() { return fProcessFactory->createDSPInstance(); } }; #endif // __poly_dsp__ /************************** END poly-dsp.h **************************/ #endif #ifdef HAS_MAIN #include "WM8978.h" #endif /****************************************************************************** ******************************************************************************* VECTOR INTRINSICS ******************************************************************************* *******************************************************************************/ /********************END ARCHITECTURE SECTION (part 1/2)****************/ /**************************BEGIN USER SECTION **************************/ #ifndef FAUSTFLOAT #define FAUSTFLOAT float #endif #include #include #include #include #ifndef FAUSTCLASS #define FAUSTCLASS mydsp #endif #ifdef __APPLE__ #define exp10f __exp10f #define exp10 __exp10 #endif #if defined(_WIN32) #define RESTRICT __restrict #else #define RESTRICT __restrict__ #endif class mydsp : public dsp { private: int fSampleRate; float fConst0; float fConst1; FAUSTFLOAT fEntry0; float fConst2; float fRec0[2]; float fConst3; FAUSTFLOAT fEntry1; float fRec3[2]; float fRec1[2]; public: mydsp() {} void metadata(Meta* m) { m->declare("compile_options", "-a /opt/homebrew/Cellar/faust/2.70.3/share/faust/esp32/esp32.cpp -lang cpp -i -ct 1 -es 1 -mcd 16 -mdd 1024 -mdy 33 -single -ftz 0"); m->declare("filename", "FaustSawtooth.dsp"); m->declare("maths.lib/author", "GRAME"); m->declare("maths.lib/copyright", "GRAME"); m->declare("maths.lib/license", "LGPL with exception"); m->declare("maths.lib/name", "Faust Math Library"); m->declare("maths.lib/version", "2.7.0"); m->declare("name", "FaustSawtooth"); m->declare("oscillators.lib/name", "Faust Oscillator Library"); m->declare("oscillators.lib/saw2ptr:author", "Julius O. Smith III"); m->declare("oscillators.lib/saw2ptr:license", "STK-4.3"); m->declare("oscillators.lib/version", "1.5.0"); m->declare("platform.lib/name", "Generic Platform Library"); m->declare("platform.lib/version", "1.3.0"); m->declare("signals.lib/name", "Faust Signal Routing Library"); m->declare("signals.lib/version", "1.5.0"); } virtual int getNumInputs() { return 0; } virtual int getNumOutputs() { return 1; } static void classInit(int sample_rate) { } virtual void instanceConstants(int sample_rate) { fSampleRate = sample_rate; fConst0 = std::min(1.92e+05f, std::max(1.0f, float(fSampleRate))); fConst1 = 44.1f / fConst0; fConst2 = 1.0f - fConst1; fConst3 = 1.0f / fConst0; } virtual void instanceResetUserInterface() { fEntry0 = FAUSTFLOAT(1.0f); fEntry1 = FAUSTFLOAT(4.4e+02f); } virtual void instanceClear() { for (int l0 = 0; l0 < 2; l0 = l0 + 1) { fRec0[l0] = 0.0f; } for (int l1 = 0; l1 < 2; l1 = l1 + 1) { fRec3[l1] = 0.0f; } for (int l2 = 0; l2 < 2; l2 = l2 + 1) { fRec1[l2] = 0.0f; } } virtual void init(int sample_rate) { classInit(sample_rate); instanceInit(sample_rate); } virtual void instanceInit(int sample_rate) { instanceConstants(sample_rate); instanceResetUserInterface(); instanceClear(); } virtual mydsp* clone() { return new mydsp(); } virtual int getSampleRate() { return fSampleRate; } virtual void buildUserInterface(UI* ui_interface) { ui_interface->openVerticalBox("FaustSawtooth"); ui_interface->addNumEntry("freq", &fEntry1, FAUSTFLOAT(4.4e+02f), FAUSTFLOAT(2e+01f), FAUSTFLOAT(2e+04f), FAUSTFLOAT(0.01f)); ui_interface->addNumEntry("gain", &fEntry0, FAUSTFLOAT(1.0f), FAUSTFLOAT(0.0f), FAUSTFLOAT(1.0f), FAUSTFLOAT(0.01f)); ui_interface->closeBox(); } virtual void compute(int count, FAUSTFLOAT** RESTRICT inputs, FAUSTFLOAT** RESTRICT outputs) { FAUSTFLOAT* output0 = outputs[0]; float fSlow0 = fConst1 * float(fEntry0); float fSlow1 = fConst1 * float(fEntry1); for (int i0 = 0; i0 < count; i0 = i0 + 1) { fRec0[0] = fSlow0 + fConst2 * fRec0[1]; fRec3[0] = fSlow1 + fConst2 * fRec3[1]; float fTemp0 = std::max(1.1920929e-07f, std::fabs(fRec3[0])); float fTemp1 = fRec1[1] + fConst3 * fTemp0; float fTemp2 = fTemp1 + -1.0f; int iTemp3 = fTemp2 < 0.0f; fRec1[0] = ((iTemp3) ? fTemp1 : fTemp2); float fRec2 = ((iTemp3) ? fTemp1 : fTemp1 + (1.0f - fConst0 / fTemp0) * fTemp2); output0[i0] = FAUSTFLOAT(fRec0[0] * (2.0f * fRec2 + -1.0f)); fRec0[1] = fRec0[0]; fRec3[1] = fRec3[0]; fRec1[1] = fRec1[0]; } } }; /***************************END USER SECTION ***************************/ /*******************BEGIN ARCHITECTURE SECTION (part 2/2)***************/ using namespace std; #ifdef MIDICTRL list GUI::fGuiList; ztimedmap GUI::gTimedZoneMap; #endif FaustSawtooth::FaustSawtooth(int sample_rate, int buffer_size) { #ifdef NVOICES int nvoices = NVOICES; fDSP = new mydsp_poly(new mydsp(), nvoices, true, true); #else fDSP = new mydsp(); #endif fUI = new MapUI(); fDSP->buildUserInterface(fUI); fAudio = new esp32audio(sample_rate, buffer_size); fAudio->init("esp32", fDSP); #ifdef SOUNDFILE fSoundUI = new SoundUI("/sdcard/", sample_rate); fDSP->buildUserInterface(fSoundUI); #endif #ifdef MIDICTRL fMIDIHandler = new esp32_midi(); fMIDIInterface = new MidiUI(fMIDIHandler); fDSP->buildUserInterface(fMIDIInterface); #endif } FaustSawtooth::~FaustSawtooth() { delete fDSP; delete fUI; delete fAudio; #ifdef MIDICTRL delete fMIDIInterface; delete fMIDIHandler; #endif #ifdef SOUNDFILE delete fSoundUI; #endif } bool FaustSawtooth::start() { #ifdef MIDICTRL if (!fMIDIInterface->run()) return false; #endif return fAudio->start(); } void FaustSawtooth::stop() { #ifdef MIDICTRL fMIDIInterface->stop(); #endif fAudio->stop(); } void FaustSawtooth::setParamValue(const string& path, float value) { fUI->setParamValue(path, value); } float FaustSawtooth::getParamValue(const string& path) { return fUI->getParamValue(path); } // Entry point #ifdef HAS_MAIN extern "C" void app_main() { // Init audio codec WM8978 wm8978; wm8978.init(); wm8978.addaCfg(1,1); wm8978.inputCfg(1,0,0); wm8978.outputCfg(1,0); wm8978.micGain(30); wm8978.auxGain(0); wm8978.lineinGain(0); // Set gain wm8978.spkVolSet(60); // [0-63] wm8978.hpVolSet(40,40); wm8978.i2sCfg(2,0); // Allocate and start Faust DSP FaustSawtooth* DSP = new FaustSawtooth(48000, 32); DSP->start(); // Waiting forever vTaskSuspend(nullptr); } #endif /********************END ARCHITECTURE SECTION (part 2/2)****************/ #endif