zynaddsubfx

ZynAddSubFX open source synthesizer
Log | Files | Refs | Submodules | LICENSE

commit 93e3e6e08e3e4aba56afe468a2f2005d8d518375
parent 0c43f66fea7935ef8f50f4fbd84178a6105a1493
Author: Johannes Lorenz <johannes89@ist-einmalig.de>
Date:   Tue, 10 Oct 2017 19:58:17 +0200

Merge from master

Diffstat:
M.travis.yml | 8--------
MCMakeLists.txt | 13+++++++++++--
MHISTORY.txt | 11+++++++++++
Mdoc/Doxyfile.in | 4++--
Adoc/automation-proposal/automation1.png | 0
Adoc/automation-proposal/current_slot.png | 0
Adoc/automation-proposal/macro.png | 0
Adoc/automation-proposal/macro_view.png | 0
Adoc/automation-proposal/parameter-automation-documentation.txt | 208+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Effects/EQ.cpp | 4++--
Msrc/Effects/Effect.h | 2+-
Msrc/Effects/EffectMgr.cpp | 7++++++-
Msrc/Effects/EffectMgr.h | 24++++++++++++++++++++++++
Msrc/Misc/Bank.cpp | 17++++++++++++++++-
Msrc/Misc/BankDb.cpp | 32++++++++++++++++++++------------
Msrc/Misc/Config.cpp | 31+++++++++++++++++++++++--------
Msrc/Misc/Master.cpp | 130++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/Misc/Master.h | 3+++
Msrc/Misc/Microtonal.cpp | 1+
Msrc/Misc/MiddleWare.cpp | 159++++++++++++++++++++++++++++++++-----------------------------------------------
Msrc/Misc/Part.cpp | 4++++
Msrc/Misc/Schema.cpp | 35+++++++++++++++++++++++++++++++++++
Msrc/Misc/Util.cpp | 23++++++++++++++++++++++-
Msrc/Misc/Util.h | 2+-
Msrc/Misc/XMLwrapper.cpp | 8+++++++-
Msrc/Nio/NulEngine.cpp | 4++--
Msrc/Params/Controller.cpp | 1+
Msrc/Params/EnvelopeParams.h | 4++--
Msrc/Params/FilterParams.cpp | 3+--
Msrc/Params/PADnoteParameters.cpp | 186+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/Params/PADnoteParameters.h | 42++++++++++++++++++++++++++++++------------
Msrc/Plugin/AlienWah/CMakeLists.txt | 2++
Msrc/Plugin/Chorus/CMakeLists.txt | 2++
Msrc/Plugin/Distortion/CMakeLists.txt | 2++
Msrc/Plugin/DynamicFilter/CMakeLists.txt | 2++
Msrc/Plugin/Echo/CMakeLists.txt | 2++
Msrc/Plugin/Phaser/CMakeLists.txt | 2++
Msrc/Plugin/Reverb/CMakeLists.txt | 2++
Msrc/Plugin/ZynAddSubFX/CMakeLists.txt | 41++++++++++++++++++++++++++++-------------
Msrc/Plugin/ZynAddSubFX/ZynAddSubFX-UI-Zest.cpp | 48++++++++++++++++++++++++++++++++++++++++++++----
Msrc/Plugin/ZynAddSubFX/ZynAddSubFX.cpp | 8+++-----
Msrc/Synth/ADnote.cpp | 43+++++++++++++++++++++++++++++++++++++++----
Msrc/Synth/ADnote.h | 2+-
Msrc/Tests/CMakeLists.txt | 3++-
Msrc/Tests/InstrumentStats.cpp | 261+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/Tests/MessageTest.h | 41++++++++++++++++++++++++++++++++++++++---
Msrc/Tests/PadNoteTest.h | 6+++---
Msrc/Tests/PluginTest.h | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/Tests/guitar-adnote.xmz | 5++++-
Msrc/UI/BankUI.fl | 21+++++++++++++++++++++
Msrc/UI/BankView.cpp | 31+++++++++++++++++++++++++------
Msrc/UI/BankView.h | 3---
Msrc/main.cpp | 39++++++++++++++++++++++++++++++++++++---
Azynaddsubfx-oss.desktop | 10++++++++++
54 files changed, 1296 insertions(+), 316 deletions(-)

diff --git a/.travis.yml b/.travis.yml @@ -4,14 +4,6 @@ compiler: - gcc before_install: - - sudo apt-get --purge remove gcc - - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - - sudo apt-add-repository -y ppa:dhart/ppa - - sudo apt-get -qq update - - sudo apt-get -qq install g++-4.8 - - sudo apt-get -qq install gcc-4.8 - - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 90 - - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 90 - sudo apt-get install zlib1g-dev libmxml-dev libfftw3-dev dssi-dev libfltk1.3-dev libxpm-dev - sudo apt-get install --force-yes cxxtest - wget http://downloads.sourceforge.net/liblo/liblo-0.28.tar.gz diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -3,8 +3,12 @@ set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") project(zynaddsubfx) set(VERSION_MAJOR "3") set(VERSION_MINOR "0") -set(VERSION_REVISION "1") +set(VERSION_REVISION "2") +#Set data directory, if any +if(DEFINED ZYN_DATADIR) +add_definitions(-DZYN_DATADIR="${ZYN_DATADIR}") +endif() #Include RTOSC if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/rtosc/CMakeLists.txt") @@ -34,14 +38,19 @@ add_subdirectory(doc) # Doxygen only install(FILES AUTHORS.txt COPYING HISTORY.txt README.adoc DESTINATION share/doc/zynaddsubfx ) -install(FILES zynaddsubfx-jack.desktop zynaddsubfx-alsa.desktop +install(FILES zynaddsubfx-jack.desktop zynaddsubfx-alsa.desktop zynaddsubfx-oss.desktop DESTINATION share/applications) install(FILES zynaddsubfx.svg DESTINATION share/pixmaps) install(DIRECTORY instruments/banks DESTINATION share/zynaddsubfx) +if(DEFINED ZYN_EXAMPLESDIR) +install(DIRECTORY instruments/examples + DESTINATION ${ZYN_EXAMPLESDIR}) +else() install(DIRECTORY instruments/examples DESTINATION share/zynaddsubfx) +endif() install(DIRECTORY instruments/ZynAddSubFX.lv2presets DESTINATION ${PluginLibDir}/lv2) diff --git a/HISTORY.txt b/HISTORY.txt @@ -1,3 +1,14 @@ +3.0.2 (21 July 2017) + - Upgrade MIDI learn system to include host automations and macro + learned controls + - Upgrade analog filter parameters to floating point resolution + - Add default values to OSC metadata + - Fix exit when closing zyn-fusion subprocess + - Fix crash with large number of pad synth samples + - Silence 0 volume effects + - Silence 0 volume add synth voices + - Fix minor bugs + 3.0.1 (28 November 2016) - Fix bank screen with Zyn-Fusion - Fix crash on startup with GL 2.1 to 3.1 diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in @@ -28,8 +28,8 @@ FULL_PATH_NAMES = YES STRIP_FROM_PATH = @CMAKE_DOXYGEN_INPUT_LIST@ STRIP_FROM_INC_PATH = SHORT_NAMES = NO -JAVADOC_AUTOBRIEF = NO -QT_AUTOBRIEF = NO +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = YES MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO diff --git a/doc/automation-proposal/automation1.png b/doc/automation-proposal/automation1.png Binary files differ. diff --git a/doc/automation-proposal/current_slot.png b/doc/automation-proposal/current_slot.png Binary files differ. diff --git a/doc/automation-proposal/macro.png b/doc/automation-proposal/macro.png Binary files differ. diff --git a/doc/automation-proposal/macro_view.png b/doc/automation-proposal/macro_view.png Binary files differ. diff --git a/doc/automation-proposal/parameter-automation-documentation.txt b/doc/automation-proposal/parameter-automation-documentation.txt @@ -0,0 +1,208 @@ +An Introduction to the ZynAddSubFX Macro System +=============================================== +:author: Mark McCurry, Spencer Jackson +:toc: + + +Motivation +---------- + +One of the major issues expressed by users around the time of the initial +3.0.0 launch was that they were dissatisfied with automations when using +ZynAddSubFX within their preferred VST host. This was a combination of: + +1. Hosts providing a poor workflow for sending MIDI CC events compared to VST + parameters +2. Confusion with the use of MIDI learn within Zyn +3. Issues regarding saving/loading parameters exposed through MIDI learn +4. Parameter resolution for MIDI CCs +5. Some parameters only produce updates on the next note + +This feature attempts to address points 1, 2, & 3. Issue 4 is a large undertaking +but is expected to become complete for the 3.1.x release. Issue 5 is being fixed +one set of parameters at a time and should be addressed in the 3.0.x series. + +Overview & Terminology +---------------------- + +The Macro System boils down to replacing the existing MIDI learn implementation +with a series of 'slots' for parameter automation and automation +macros. In its current state it does that and a little more but as it matures it +should become a powerful system for automation and live tweaking of sounds. + +What is an automation? +~~~~~~~~~~~~~~~~~~~~~~ +image::automation1.png[] + +- A single parameter, e.g. the addsynth base frequency in part 1, kit 1 +- A gain and offset to adjust the range of values the automation will reach +(e.g. -50 cents to +150 cents) +- A mapping function (currently only linear, log, custom, etc coming soon) + +What is a macro? +~~~~~~~~~~~~~~~~ + +- A group of automations that get assigned to an automation slot + +All automations in the macro are operated together. Thus turning one knob on +your MIDI controller or a single automation lane in a DAW can change up to +4 parameters (see <<FAQ>>) +at a time. Using the automation mappings allows for all the parameters to be adjusted +in different amounts and/or directions, but they are all moved by actuating the macro. + +Macros are assembled by adding automations to a macro slot. + +What is a macro slot? +~~~~~~~~~~~~~~~~~~~~~~ + +- A set of one or more automations (a single macro) +- An optional MIDI CC binding or plugin host automation + +In Zynaddsubfx 3.0.2 all macro slots are global. This means that loading new +instruments does not switch the automations in any way. Only loading and saving +Zyn master files will allow you to quickly switch between macro slot configurations. +In future versions there +will be global macro slots and instrument macro slots that can be saved +and loaded independently. + +The global macro slots are exposed to VST/LV2 hosts. +This keeps the total number of parameters exposed to a host somewhat +reasonable: 16 slots for automation of up to 64 parameters. + + +At the level of a Zyn master file (.xmz) the automation slots +can be bound to MIDI CCs; loading the file will reload the midi bindings. +Zyn MIDI-learn files (.xlz) can store the MIDI CC+Channel linkage to all +automation slots. + +The Macros View +--------------- + +image::macro_view.png[] + +The Macro view shows: + +. List of Slots +.. Slot state (active, inactive) - currently this button is disabled, all slots are always active +.. A "clear slot" button (the x) - removes all automations in the slot +.. Slot name - click name to edit +. Macro selector buttons +.. highlighted button is the slot currently selected +.. use these when macro learning to select which slot to add to +. Macro sliders - (green bars under names) +.. indicates current macro value +.. slide to adjust through the UI +. Learning Mode selectors +.. Normal Learn - each learned automation is added to the next available slot +.. Macro Learn - learned automations are added to the currently selected slot (if not full) + +image::current_slot.png[] +Currently selected slot is selected/indicated with these buttons. + +The selected macro shows: + +. MIDI binding if any e.g. "Chan 2, CC 14" (or "None" or "Learning Queue N") +. 4 Automations + +image::automation1.png[] + +Each automation shows + +. Parameter path/name - clicking a section of the path shows a dropdown menu allowing easy switching between parts, voices, engines, etc. +. Minimum, maximum, center value of parameter +. Graph of map function +.. X is the macro value +.. Y is value of parameter being automated +. Gain knob - adjust to change slope of map function +. Offset knob adjust to move map function up or down +. Remove button (X) - click to remove the automation + + +Workflow examples +----------------- + +Automating a parameter in plugin mode +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +. Learn mode should be set to Normal Learn (this is the default) +. Use CTRL or click the [Learn] button to start learning an automation +. Click on one or more controls (knobs/sliders/buttons) to assign them to + automation slots +.. After clicking, each control is allocated to the next free Master + automation slot +.. the automation slot is put into the unbound state (it can be put into + "MIDI learn" mode later. +. The automation can be adjusted in the automation view + +Binding parameters to MIDI in standalone mode +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +. Learn mode should be set to Normal Learn (this is the default) +. Use CTRL or click the [Learn] button to start learning an automation +. Click on one or more controls (knobs/sliders/buttons) to assign them to + automation slots +.. After clicking, each control is allocated to the next free Master + automation slot +.. Each automation slot is then placed into the "MIDI learn" mode, watching for + the next unbound MIDI CC that arrives in standalone mode + (MIDI CCs are identified by Channel+CC# in Master automation slots) +. The user optionally sends CCs to be bound by wiggling the controls on their + physical (or virtual) MIDI device +. The automation can be adjusted in the automation view + +Setting up a macro +~~~~~~~~~~~~~~~~~~ + +image::macro.png[] +A macro with 3 automations + +. Enter the Macro view +. Select the Macro Learn button (the Normal Learn button will turn off) +. Select the macro slot you would like to add new automations to +. Hold CTRL or double click the [Learn] button to enter learn mode +. Click the parameter(s) that you wish to add the the macro slot +. To add parameters to another macro slot switch the currently selected + macro slot in the Macro view + +Adjusting automation ranges +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +. Go to the macro view +. Find the right macro slot and automation +. Adjust gain +.. High gain will make the automation's parameter traverse the whole range of travel with just a little movement of the macro. +.. Low gain will make the automation's parameter travel less range through the knob than the knob in the UI +.. Negative gain makes the macro adjust the parameter down when the macro is turned up +. Adjust offset +.. This moves the automation's parameter range of travel up or down +. If an automation was unintentional the user can use the X button to remove that automation + +Learning/changing MIDI CC bindings after loading an instrument/binding +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +. Go to the [Macro] view +. Find the right macro slot and automation +. Single click on the MIDI CC widget +.. The automation switches to the "learning CC" state +. Wiggle some MIDI controller +. OR Double click on the MIDI CC widget +.. this shows a dialog to manually choose the channel and CC id +. The CC has been remapped + +FAQ +--- +Q: Why only 4 automations to a slot? + +A: Limiting it to only 4 allowed for an easier design in the UI. There is no +limitation in the software and if many users request more capability, we will +extend it to more. + + +Q: What happens if a single parameter is in multiple automation slots? + +A: Currently the engine will set the parameter to the value from the macro that +was most recently changed. Future versions may allow for macro fusion to combine +multiple values in different ways. + + +Q: Why didn't you add _feature X_ + +A: We love well documented feature requests. Please leave your idea in our bugtracker at https://sourceforge.net/p/zynaddsubfx/feature-requests/ diff --git a/src/Effects/EQ.cpp b/src/Effects/EQ.cpp @@ -80,8 +80,8 @@ rtosc::Ports EQ::ports = { memset(b, 0, sizeof(b)); eq->getFilter(a,b); - char type[MAX_EQ_BANDS*MAX_FILTER_STAGES*3*2+1] = {0}; - rtosc_arg_t val[MAX_EQ_BANDS*MAX_FILTER_STAGES*3*2] = {0}; + char type[MAX_EQ_BANDS*MAX_FILTER_STAGES*3*2+1] = {}; + rtosc_arg_t val[MAX_EQ_BANDS*MAX_FILTER_STAGES*3*2] = {}; for(int i=0; i<MAX_EQ_BANDS*MAX_FILTER_STAGES*3; ++i) { int stride = MAX_EQ_BANDS*MAX_FILTER_STAGES*3; type[i] = type[i+stride] = 'f'; diff --git a/src/Effects/Effect.h b/src/Effects/Effect.h @@ -45,7 +45,7 @@ #endif #define rEffParCommon(pname, rshort, rdoc, idx, ...) \ -{STRINGIFY(pname) ":", rProp(parameter) rLinear(0,127) \ +{STRINGIFY(pname) "::i", rProp(parameter) rLinear(0,127) \ rShort(rshort) rDoc(rdoc), \ 0, \ [](const char *msg, rtosc::RtData &d) \ diff --git a/src/Effects/EffectMgr.cpp b/src/Effects/EffectMgr.cpp @@ -466,7 +466,12 @@ void EffectMgr::add2XML(XMLwrapper& xml) xml.beginbranch("EFFECT_PARAMETERS"); for(int n = 0; n < 128; ++n) { - int par = geteffectpar(n); + int par = 0; + if(efx) + par = efx->getpar(n); + else if(n<128) + par = settings[n]; + if(par == 0) continue; xml.beginbranch("par_no", n); diff --git a/src/Effects/EffectMgr.h b/src/Effects/EffectMgr.h @@ -77,6 +77,30 @@ class EffectMgr:public Presets //Parameters Prior to initialization char preset; + + /** + * When loading an effect from XML the child effect cannot be loaded + * directly as it would require access to the realtime memory pool, + * which cannot be done outside of the realtime thread. + * Therefore, parameters are loaded to this array which can then be used + * to construct the full effect (via init()) once the object is in the + * realtime context. + * + * Additionally this structure is used in the case of pasting effects as + * pasted effect object are *not* fully initialized when they're put on + * the middleware -> backend ringbuffer, but settings has the values + * loaded from the XML serialization. + * The settings values can be pasted once they're on the realtime thread + * and then they can be applied. + * + * The requirement that the realtime memory pool is used to create the + * effect is in place as it is possible to change the effect type in the + * realtime thread and thus the new effect would draw from the realtime + * memory pool and the old object would be expected to be freed to the + * realtime memory pool. + * + * See also: PresetExtractor.cpp + */ char settings[128]; bool dryonly; diff --git a/src/Misc/Bank.cpp b/src/Misc/Bank.cpp @@ -30,6 +30,9 @@ #include "Util.h" #include "Part.h" #include "BankDb.h" +#ifdef WIN32 +#include <windows.h> +#endif using namespace std; @@ -362,6 +365,18 @@ void Bank::rescanforbanks() for(int i = 0; i < MAX_BANK_ROOT_DIRS; ++i) if(!config->cfg.bankRootDirList[i].empty()) scanrootdir(config->cfg.bankRootDirList[i]); +#ifdef WIN32 + { + //Search the VST Directory for banks/preset/etc + char path[1024]; + GetModuleFileName(GetModuleHandle("ZynAddSubFX.dll"), path, sizeof(path)); + if(strstr(path, "ZynAddSubFX.dll")) { + strstr(path, "ZynAddSubFX.dll")[0] = 0; + strcat(path, "banks"); + scanrootdir(path); + } + } +#endif //sort the banks sort(banks.begin(), banks.end()); @@ -477,7 +492,7 @@ std::vector<std::string> Bank::search(std::string s) const std::vector<std::string> Bank::blist(std::string s) { std::vector<std::string> out; - int result = loadbank(s); + loadbank(s); for(int i=0; i<128; ++i) { if(ins[i].filename.empty()) out.push_back("Empty Preset"); diff --git a/src/Misc/BankDb.cpp b/src/Misc/BankDb.cpp @@ -80,17 +80,17 @@ static svec split(string s) return vec; } -static string line(string s) -{ - string ss; - for(char c:s) { - if(c != '\n') - ss.push_back(c); - else - return ss; - } - return ss; -} +//static string line(string s) +//{ +// string ss; +// for(char c:s) { +// if(c != '\n') +// ss.push_back(c); +// else +// return ss; +// } +// return ss; +//} bvec BankDb::search(std::string ss) const { @@ -231,10 +231,18 @@ BankEntry BankDb::processXiz(std::string filename, //Grab a timestamp struct stat st; - int ret = lstat(fname.c_str(), &st); int time = 0; + + //gah windows, just implement the darn standard APIs +#ifndef WIN32 + int ret = lstat(fname.c_str(), &st); if(ret != -1) time = st.st_mtim.tv_sec; +#else + int ret = 0; + time = rand(); +#endif + //quickly check if the file exists in the cache and if it is up-to-date if(cache.find(fname) != cache.end() && diff --git a/src/Misc/Config.cpp b/src/Misc/Config.cpp @@ -138,19 +138,26 @@ static const rtosc::Ports ports = { c.cfg.OscilSize = val; d.broadcast(d.loc, "i", (int)(log(c.cfg.OscilSize*1.0)/log(2.0))); }}, + {"clear-favorites:", rDoc("Clear favorite directories"), 0, + [](const char *msg, rtosc::RtData &d) { + Config &c = *(Config*)d.obj; + for(int i=0; i<MAX_BANK_ROOT_DIRS; ++i) + c.cfg.favoriteList[i] = ""; + }}, {"add-favorite:s", rDoc("Add favorite directory"), 0, [](const char *msg, rtosc::RtData &d) { Config &c = *(Config*)d.obj; + const char *path = rtosc_argument(msg, 0).s; for(int i=0; i<MAX_BANK_ROOT_DIRS; ++i) { - if(c.cfg.favoriteList[i].empty()) { - c.cfg.favoriteList[i] = rtosc_argument(msg, 0).s; + if(c.cfg.favoriteList[i].empty() || c.cfg.favoriteList[i] == path) { + c.cfg.favoriteList[i] = path; return; } } }}, - {"favorites:", rProp(parameter), 0, + {"favorites:", /*rProp(parameter)*/ 0, 0, [](const char *msg, rtosc::RtData &d) { Config &c = *(Config*)d.obj; @@ -227,14 +234,18 @@ void Config::init() //banks cfg.bankRootDirList[0] = "~/banks"; cfg.bankRootDirList[1] = "./"; - cfg.bankRootDirList[2] = "/usr/share/zynaddsubfx/banks"; - cfg.bankRootDirList[3] = "/usr/local/share/zynaddsubfx/banks"; #ifdef __APPLE__ - cfg.bankRootDirList[4] = "../Resources/banks"; + cfg.bankRootDirList[2] = "../Resources/banks"; +#else + cfg.bankRootDirList[2] = "../banks"; +#endif + cfg.bankRootDirList[3] = "banks"; +#ifdef ZYN_DATADIR + cfg.bankRootDirList[4] = ZYN_DATADIR "/banks"; #else - cfg.bankRootDirList[4] = "../banks"; + cfg.bankRootDirList[4] = "/usr/share/zynaddsubfx/banks"; + cfg.bankRootDirList[5] = "/usr/local/share/zynaddsubfx/banks"; #endif - cfg.bankRootDirList[5] = "banks"; } if(cfg.presetsDirList[0].empty()) { @@ -246,8 +257,12 @@ void Config::init() cfg.presetsDirList[1] = "../presets"; #endif cfg.presetsDirList[2] = "presets"; +#ifdef ZYN_DATADIR + cfg.presetsDirList[3] = ZYN_DATADIR "/presets"; +#else cfg.presetsDirList[3] = "/usr/share/zynaddsubfx/presets"; cfg.presetsDirList[4] = "/usr/local/share/zynaddsubfx/presets"; +#endif } cfg.LinuxALSAaudioDev = "default"; cfg.nameTag = ""; diff --git a/src/Misc/Master.cpp b/src/Misc/Master.cpp @@ -131,7 +131,7 @@ static int get_next_int(const char *msg) } static const Ports mapping_ports = { - {"offset::f", rProp(parameter) rShort("off") rLinear(-50, 50) rMap(unit, percent), 0, + {"offset::f", rProp(parameter) rDefault(0) rShort("off") rLinear(-50, 50) rMap(unit, percent), 0, rBegin; int slot = d.idx[1]; int param = d.idx[0]; @@ -142,7 +142,7 @@ static const Ports mapping_ports = { } else d.reply(d.loc, "f", a.getSlotSubOffset(slot, param)); rEnd}, - {"gain::f", rProp(parameter) rShort("gain") rLinear(-200, 200) rMap(unit, percent), 0, + {"gain::f", rProp(parameter) rDefault(100) rShort("gain") rLinear(-200, 200) rMap(unit, percent), 0, rBegin; int slot = d.idx[1]; int param = d.idx[0]; @@ -156,7 +156,7 @@ static const Ports mapping_ports = { }; static const Ports auto_param_ports = { - {"used:", rProp(parameter) rProp(read-only) rDoc("If automation is assigned to anything"), 0, + {"used::T:F", rProp(parameter) rProp(read-only) rDoc("If automation is assigned to anything"), 0, rBegin; int slot = d.idx[1]; int param = d.idx[0]; @@ -172,13 +172,13 @@ static const Ports auto_param_ports = { else d.reply(d.loc, a.slots[slot].automations[param].active ? "T" : "F"); rEnd}, - {"path:", rProp(parameter) rProp(read-only) rDoc("Path of parameter"), 0, + {"path::s", rProp(parameter) rProp(read-only) rDoc("Path of parameter"), 0, rBegin; int slot = d.idx[1]; int param = d.idx[0]; d.reply(d.loc, "s", a.slots[slot].automations[param].param_path); rEnd}, - {"clear:", 0, 0, + {"clear:", rDoc("Clear automation param"), 0, rBegin; int slot = d.idx[1]; int param = d.idx[0]; @@ -186,6 +186,7 @@ static const Ports auto_param_ports = { rEnd}, {"mapping/", 0, &mapping_ports, rBegin; + (void) a; SNIP; mapping_ports.dispatch(msg, d); rEnd}, @@ -219,7 +220,7 @@ static const Ports slot_ports = { // rtosc_argument(msg, 1).s, // rtosc_argument(msg, 2).T); // rEnd}, - {"value::f", rProp(parameter) rMap(default, 0.5) rLinear(0, 1) rDoc("Access current value in slot 'i' (0..1)"), 0, + {"value::f", rProp(no learn) rProp(parameter) rMap(default, 0.5) rLinear(0, 1) rDoc("Access current value in slot 'i' (0..1)"), 0, rBegin; int num = d.idx[0]; if(!strcmp("f",rtosc_argument_string(msg))) { @@ -255,12 +256,12 @@ static const Ports slot_ports = { else d.reply(d.loc, a.slots[slot].active ? "T" : "F"); rEnd}, - {"learning:", rProp(parameter) rMap(default, -1) rDoc("If slot is trying to find a midi learn binding"), 0, + {"learning::i", rProp(parameter) rMap(default, -1) rDoc("If slot is trying to find a midi learn binding"), 0, rBegin; int slot = d.idx[0]; d.reply(d.loc, "i", a.slots[slot].learning); rEnd}, - {"clear:", 0, 0, + {"clear:", rDoc("Clear automation slot"), 0, rBegin; int slot = d.idx[0]; a.clearSlot(slot); @@ -306,6 +307,40 @@ static const Ports automate_ports = { slot_ports.dispatch(msg, d); d.pop_index(); rEnd}, + {"clear", rDoc("Clear all automation slots"), 0, + rBegin; + for(int i=0; i<a.nslots; ++i) + a.clearSlot(i); + rEnd}, + {"load-blob:b", rProp(internal) rDoc("Load blob from middleware"), 0, + rBegin; + auto &b = **(rtosc::AutomationMgr **)rtosc_argument(msg, 0).b.data; + //XXX this code should likely be in rtosc + for(int i=0; i<a.nslots; ++i) { + auto &slota = a.slots[i]; + auto &slotb = b.slots[i]; + std::swap(slota.learning, slotb.learning); + std::swap(slota.midi_cc, slotb.midi_cc); + std::swap(slota.used, slotb.used); + std::swap(slota.active, slotb.active); + for(int j=0; j<a.per_slot; ++j) { + auto &aa = slota.automations[j]; + auto &ab = slotb.automations[j]; + std::swap(aa.used, ab.used); + std::swap(aa.active, ab.active); + std::swap(aa.param_path, ab.param_path); + std::swap(aa.param_min, ab.param_min); + std::swap(aa.param_max, ab.param_max); + std::swap(aa.param_step, ab.param_step); + std::swap(aa.param_type, ab.param_type); + std::swap(aa.map.offset, ab.map.offset); + std::swap(aa.map.gain, ab.map.gain); + std::swap(aa.map.upoints, ab.map.upoints); + for(int k=0; k<aa.map.npoints; ++k) + std::swap(aa.map.control_points[k], ab.map.control_points[k]); + } + } + rEnd}, }; #undef rBegin @@ -584,6 +619,81 @@ vuData::vuData(void) rmspeakl(0.0f), rmspeakr(0.0f), clipped(0) {} +void Master::saveAutomation(XMLwrapper &xml, const rtosc::AutomationMgr &midi) +{ + xml.beginbranch("automation"); + { + XmlNode metadata("mgr-info"); + metadata["nslots"] = to_s(midi.nslots); + metadata["nautomations"] = to_s(midi.per_slot); + metadata["ncontrol"] = to_s(midi.slots[0].automations[0].map.npoints); + xml.add(metadata); + + for(int i=0; i<midi.nslots; ++i) { + const auto &slot = midi.slots[i]; + if(!slot.used) + continue; + xml.beginbranch("slot", i); + XmlNode params("params"); + params["midi-cc"] = to_s(slot.midi_cc); + xml.add(params); + for(int j=0; j<midi.per_slot; ++j) { + const auto &au = slot.automations[j]; + if(!au.used) + continue; + xml.beginbranch("automation", j); + XmlNode automation("params"); + automation["path"] = au.param_path; + XmlNode mapping("mapping"); + mapping["gain"] = to_s(au.map.gain); + mapping["offset"] = to_s(au.map.offset); + xml.add(automation); + xml.add(mapping); + xml.endbranch(); + } + + xml.endbranch(); + } + } + xml.endbranch(); +} + +void Master::loadAutomation(XMLwrapper &xml, rtosc::AutomationMgr &midi) +{ + if(xml.enterbranch("automation")) { + for(int i=0; i<midi.nslots; ++i) { + auto &slot = midi.slots[i]; + if(xml.enterbranch("slot", i)) { + for(int j=0; j<midi.per_slot; ++j) { + if(xml.enterbranch("automation", j)) { + float gain = 1.0; + float offset = 0.0; + std::string path = ""; + for(auto node:xml.getBranch()) { + if(node.name == "params") + path = node["path"]; + else if(node.name == "mapping") { + gain = atof(node["gain"].c_str()); + offset = atof(node["offset"].c_str()); + } + } + printf("createBinding(%d, %s, false)\n", i, path.c_str()); + midi.createBinding(i, path.c_str(), false); + midi.setSlotSubGain(i, j, gain); + midi.setSlotSubOffset(i, j, offset); + xml.exitbranch(); + } + } + for(auto node:xml.getBranch()) + if(node.name == "params") + slot.midi_cc = atoi(node["midi-cc"].c_str()); + xml.exitbranch(); + } + } + xml.exitbranch(); + } +} + Master::Master(const SYNTH_T &synth_, Config* config) :HDDRecorder(synth_), time(synth_), ctl(synth_, &time), microtonal(config->cfg.GzipCompression), bank(config), @@ -1349,6 +1459,8 @@ void Master::add2XML(XMLwrapper& xml) microtonal.add2XML(xml); xml.endbranch(); + saveAutomation(xml, automate); + for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) { xml.beginbranch("PART", npart); part[npart]->add2XML(xml); @@ -1473,6 +1585,8 @@ void Master::getfromXML(XMLwrapper& xml) xml.exitbranch(); } + loadAutomation(xml, automate); + sysefx[0]->changeeffect(0); if(xml.enterbranch("SYSTEM_EFFECTS")) { for(int nefx = 0; nefx < NUM_SYS_EFX; ++nefx) { diff --git a/src/Misc/Master.h b/src/Misc/Master.h @@ -62,6 +62,9 @@ class Master /**This adds the parameters to the XML data*/ void add2XML(XMLwrapper& xml); + static void saveAutomation(XMLwrapper &xml, const rtosc::AutomationMgr &midi); + static void loadAutomation(XMLwrapper &xml, rtosc::AutomationMgr &midi); + void defaults(); /**loads all settings from a XML file diff --git a/src/Misc/Microtonal.cpp b/src/Misc/Microtonal.cpp @@ -870,6 +870,7 @@ void Microtonal::apply(void) strncat(buf, tmpbuf, sizeof(buf)-1); } int err = texttotunings(buf); + (void) err; } } diff --git a/src/Misc/MiddleWare.cpp b/src/Misc/MiddleWare.cpp @@ -18,6 +18,7 @@ #include <iostream> #include <dirent.h> #include <sys/stat.h> +#include <mutex> #include <rtosc/undo-history.h> #include <rtosc/thread-link.h> @@ -59,6 +60,7 @@ namespace zyn { using std::string; +using std::mutex; int Pexitprogram = 0; /****************************************************************************** @@ -207,72 +209,25 @@ void preparePadSynth(string path, PADnoteParameters *p, rtosc::RtData &d) assert(!path.empty()); path += "sample"; - unsigned max = 0; - p->sampleGenerator([&max,&path,&d] - (unsigned N, PADnoteParameters::Sample &s) - { - max = max<N ? N : max; - //printf("sending info to '%s'\n", (path+to_s(N)).c_str()); - d.chain((path+to_s(N)).c_str(), "ifb", - s.size, s.basefreq, sizeof(float*), &s.smp); - }, []{return false;}); + mutex rtdata_mutex; + unsigned num = p->sampleGenerator([&rtdata_mutex,&path,&d] + (unsigned N, PADnoteParameters::Sample &s) + { + //printf("sending info to '%s'\n", + // (path+to_s(N)).c_str()); + rtdata_mutex.lock(); + d.chain((path+to_s(N)).c_str(), "ifb", + s.size, s.basefreq, sizeof(float*), &s.smp); + rtdata_mutex.unlock(); + }, []{return false;}); //clear out unused samples - for(unsigned i = max+1; i < PAD_MAX_SAMPLES; ++i) { + for(unsigned i = num; i < PAD_MAX_SAMPLES; ++i) { d.chain((path+to_s(i)).c_str(), "ifb", 0, 440.0f, sizeof(float*), NULL); } } /****************************************************************************** - * MIDI Serialization * - * * - ******************************************************************************/ -//void saveMidiLearn(XMLwrapper &xml, const rtosc::MidiMappernRT &midi) -//{ -// xml.beginbranch("midi-learn"); -// for(auto value:midi.inv_map) { -// XmlNode binding("midi-binding"); -// auto biject = std::get<3>(value.second); -// binding["osc-path"] = value.first; -// binding["coarse-CC"] = to_s(std::get<1>(value.second)); -// binding["fine-CC"] = to_s(std::get<2>(value.second)); -// binding["type"] = "i"; -// binding["minimum"] = to_s(biject.min); -// binding["maximum"] = to_s(biject.max); -// xml.add(binding); -// } -// xml.endbranch(); -//} -// -//void loadMidiLearn(XMLwrapper &xml, rtosc::MidiMappernRT &midi) -//{ -// using rtosc::Port; -// if(xml.enterbranch("midi-learn")) { -// auto nodes = xml.getBranch(); -// -// //TODO clear mapper -// -// for(auto node:nodes) { -// if(node.name != "midi-binding" || -// !node.has("osc-path") || -// !node.has("coarse-CC")) -// continue; -// const string path = node["osc-path"]; -// const int CC = atoi(node["coarse-CC"].c_str()); -// const Port *p = Master::ports.apropos(path.c_str()); -// if(p) { -// printf("loading midi port...\n"); -// midi.addNewMapper(CC, *p, path); -// } else { -// printf("unknown midi bindable <%s>\n", path.c_str()); -// } -// } -// xml.exitbranch(); -// } else -// printf("cannot find 'midi-learn' branch...\n"); -//} - -/****************************************************************************** * Non-RealTime Object Store * * * * * @@ -826,7 +781,7 @@ class MwDataObj:public rtosc::RtData //Chain calls repeat the call into handle() //Forward calls send the message directly to the realtime - virtual void reply(const char *path, const char *args, ...) + virtual void reply(const char *path, const char *args, ...) override { //printf("reply building '%s'\n", path); va_list va; @@ -852,7 +807,7 @@ class MwDataObj:public rtosc::RtData reply(buffer); } } - virtual void reply(const char *msg){ + virtual void reply(const char *msg) override{ mwi->sendToCurrentRemote(msg); }; //virtual void broadcast(const char *path, const char *args, ...){(void)path;(void)args;}; @@ -914,9 +869,9 @@ static std::vector<std::string> getFiles(const char *folder, bool finddir) } #else std::string darn_windows = folder + std::string("/") + std::string(fn->d_name); - printf("attr on <%s> => %x\n", darn_windows.c_str(), GetFileAttributes(darn_windows.c_str())); - printf("desired mask = %x\n", mask); - printf("error = %x\n", INVALID_FILE_ATTRIBUTES); + //printf("attr on <%s> => %x\n", darn_windows.c_str(), GetFileAttributes(darn_windows.c_str())); + //printf("desired mask = %x\n", mask); + //printf("error = %x\n", INVALID_FILE_ATTRIBUTES); bool is_dir = GetFileAttributes(darn_windows.c_str()) & FILE_ATTRIBUTE_DIRECTORY; #endif if(finddir == is_dir && strcmp(".", fn->d_name)) @@ -965,13 +920,28 @@ extern const rtosc::Ports bankPorts; const rtosc::Ports bankPorts = { {"rescan:", 0, 0, rBegin; + impl.bankpos = 0; impl.rescanforbanks(); //Send updated banks int i = 0; for(auto &elm : impl.banks) d.reply("/bank/bank_select", "iss", i++, elm.name.c_str(), elm.dir.c_str()); d.reply("/bank/bank_select", "i", impl.bankpos); - + if (i > 0) { + impl.loadbank(impl.banks[0].dir); + + //Reload bank slots + for(int i=0; i<BANK_SIZE; ++i) { + d.reply("/bankview", "iss", + i, impl.ins[i].name.c_str(), + impl.ins[i].filename.c_str()); + } + } else { + //Clear all bank slots + for(int i=0; i<BANK_SIZE; ++i) { + d.reply("/bankview", "iss", i, "", ""); + } + } rEnd}, {"bank_list:", 0, 0, rBegin; @@ -1119,8 +1089,8 @@ const rtosc::Ports bankPorts = { rBegin; auto res = impl.search(rtosc_argument(msg, 0).s); #define MAX_SEARCH 300 - char res_type[MAX_SEARCH+1] = {0}; - rtosc_arg_t res_dat[MAX_SEARCH] = {0}; + char res_type[MAX_SEARCH+1] = {}; + rtosc_arg_t res_dat[MAX_SEARCH] = {}; for(unsigned i=0; i<res.size() && i<MAX_SEARCH; ++i) { res_type[i] = 's'; res_dat[i].s = res[i].c_str(); @@ -1132,8 +1102,8 @@ const rtosc::Ports bankPorts = { rBegin; auto res = impl.blist(rtosc_argument(msg, 0).s); #define MAX_SEARCH 300 - char res_type[MAX_SEARCH+1] = {0}; - rtosc_arg_t res_dat[MAX_SEARCH] = {0}; + char res_type[MAX_SEARCH+1] = {}; + rtosc_arg_t res_dat[MAX_SEARCH] = {}; for(unsigned i=0; i<res.size() && i<MAX_SEARCH; ++i) { res_type[i] = 's'; res_dat[i].s = res[i].c_str(); @@ -1229,24 +1199,29 @@ static rtosc::Ports middwareSnoopPorts = { impl.kitEnable(msg); d.forward(); rEnd}, - //{"save_xlz:s", 0, 0, - // rBegin; - // const char *file = rtosc_argument(msg, 0).s; - // XMLwrapper xml; - // saveMidiLearn(xml, impl.midi_mapper); - // xml.saveXMLfile(file, impl.master->gzip_compression); - // rEnd}, - //{"load_xlz:s", 0, 0, - // rBegin; - // const char *file = rtosc_argument(msg, 0).s; - // XMLwrapper xml; - // xml.loadXMLfile(file); - // loadMidiLearn(xml, impl.midi_mapper); - // rEnd}, - //{"clear_xlz:", 0, 0, - // rBegin; - // impl.midi_mapper.clear(); - // rEnd}, + {"save_xlz:s", 0, 0, + rBegin; + impl.doReadOnlyOp([&]() { + const char *file = rtosc_argument(msg, 0).s; + XMLwrapper xml; + Master::saveAutomation(xml, impl.master->automate); + xml.saveXMLfile(file, impl.master->gzip_compression); + }); + rEnd}, + {"load_xlz:s", 0, 0, + rBegin; + const char *file = rtosc_argument(msg, 0).s; + XMLwrapper xml; + xml.loadXMLfile(file); + rtosc::AutomationMgr *mgr = new rtosc::AutomationMgr(16,4,8); + mgr->set_ports(Master::ports); + Master::loadAutomation(xml, *mgr); + d.chain("/automate/load-blob", "b", sizeof(void*), &mgr); + rEnd}, + {"clear_xlz:", 0, 0, + rBegin; + d.chain("/automate/clear", ""); + rEnd}, //scale file stuff {"load_xsz:s", 0, 0, rBegin; @@ -1455,7 +1430,7 @@ static rtosc::Ports middwareSnoopPorts = { // //cc-id, path, min, max //#define MAX_MIDI 32 // rtosc_arg_t args[MAX_MIDI*4]; - // char argt[MAX_MIDI*4+1] = {0}; + // char argt[MAX_MIDI*4+1] = {}; // int j=0; // for(unsigned i=0; i<key.size() && i<MAX_MIDI; ++i) { // auto val = midi.inv_map[key[i]]; @@ -1539,10 +1514,6 @@ static rtosc::Ports middlewareReplyPorts = { if(impl.recording_undo) impl.undo.recordEvent(msg); rEnd}, - //{"midi-use-CC:i", 0, 0, - // rBegin; - // impl.midi_mapper.useFreeID(rtosc_argument(msg, 0).i); - // rEnd}, {"broadcast:", 0, 0, rBegin; impl.broadcast = true; rEnd}, {"forward:", 0, 0, rBegin; impl.forward = true; rEnd}, }; @@ -1666,7 +1637,7 @@ void MiddleWareImpl::doReadOnlyOp(std::function<void()> read_only_fn) int tries = 0; while(tries++ < 10000) { if(!bToU->hasNext()) { - usleep(500); + os_usleep(500); continue; } const char *msg = bToU->read(); @@ -1779,7 +1750,7 @@ bool MiddleWareImpl::doReadOnlyOpNormal(std::function<void()> read_only_fn, bool int tries = 0; while(tries++ < 2000) { if(!bToU->hasNext()) { - usleep(500); + os_usleep(500); continue; } const char *msg = bToU->read(); diff --git a/src/Misc/Part.cpp b/src/Misc/Part.cpp @@ -49,9 +49,13 @@ static const Ports partPorts = { rRecur(ctl, "Controller"), rParamZyn(partno, rProp(internal), "How many parts are before this in the Master"), +#undef rChangeCb +#define rChangeCb if(obj->Penabled == false) obj->AllNotesOff(); rToggle(Penabled, rShort("enable"), rDefaultDepends(partno), rPresets(true), rDefault(false), "Part enable"), #undef rChangeCb +#define rChangeCb +#undef rChangeCb #define rChangeCb obj->setPvolume(obj->Pvolume); rParamZyn(Pvolume, rShort("Vol"), rDefault(96),"Part Volume"), #undef rChangeCb diff --git a/src/Misc/Schema.cpp b/src/Misc/Schema.cpp @@ -11,6 +11,30 @@ void walk_ports2(const rtosc::Ports *base, namespace zyn { +static const char *escape_string(const char *msg) +{ + if(!msg) + return NULL; + char *out = (char*)malloc(strlen(msg)*2+1); + memset(out, 0, strlen(msg)*2+1); + char *itr = out; + while(*msg) { + if(*msg == '"') { + *itr++ = '\\'; + *itr++ = '\"'; + } else if(*msg == '\\') { + *itr++ = '\\'; + *itr++ = '\\'; + } else { + *itr++ = *msg; + } + + msg++; + + } + return out; +} + /* * root : * - 'parameters' : [parameter...] @@ -25,6 +49,8 @@ namespace zyn { * - 'scale' : scale-type * - 'domain' : range [OPTIONAL] * - 'options' : [option...] [OPTIONAL] + * - 'default' : string + * - 'defaults' : defaults * type : {'int', 'float', 'boolean'} * action : * - 'path' : path-id @@ -35,10 +61,14 @@ namespace zyn { * option : * - 'id' : id-number * - 'value' : string-rep + * defaults : + * - 'id' : id-number + * - 'value' : string-rep */ using std::ostream; using std::string; +#if 0 static int enum_min(Port::MetaContainer meta) { int min = 0; @@ -96,6 +126,7 @@ static ostream &add_options(ostream &o, Port::MetaContainer meta) return o; } +#endif /* * parameter : @@ -175,6 +206,8 @@ void dump_param_cb(const rtosc::Port *p, const char *full_name, const char*, const char *min = meta["min"]; const char *max = meta["max"]; + const char *def = meta["default"]; + def = escape_string(def); for(auto m:meta) { if(strlen(m.title) >= 5 && !memcmp(m.title, "map ", 4)) { @@ -202,6 +235,8 @@ void dump_param_cb(const rtosc::Port *p, const char *full_name, const char*, o << " \"type\" : \"" << type << "\""; if(min && max) o << ",\n \"range\" : [" << min << "," << max << "]"; + if(def) + o << ",\n \"default\" : \"" << def << "\"\n"; if(!options.empty()) { o << ",\n \"options\" : [\n"; int N = options.size(); diff --git a/src/Misc/Util.cpp b/src/Misc/Util.cpp @@ -129,10 +129,31 @@ void set_realtime() #endif } -void os_sleep(long length) + + +#ifdef WIN32 +#include <windows.h> + +//https://stackoverflow.com/questions/5801813/c-usleep-is-obsolete-workarounds-for-windows-mingw +void os_usleep(long usec) +{ + HANDLE timer; + LARGE_INTEGER ft; + + ft.QuadPart = -(10*usec); // Convert to 100 nanosecond interval, negative value indicates relative time + + timer = CreateWaitableTimer(NULL, TRUE, NULL); + SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); +} +#else + +void os_usleep(long length) { usleep(length); } +#endif //!< maximum lenght a pid has on any POSIX system //!< this is an estimation, but more than 12 looks insane diff --git a/src/Misc/Util.h b/src/Misc/Util.h @@ -46,7 +46,7 @@ extern float getdetune(unsigned char type, void set_realtime(); /**Os independent sleep in microsecond*/ -void os_sleep(long length); +void os_usleep(long length); //! returns pid padded to maximum pid lenght, posix conform std::string os_pid_as_padded_string(); diff --git a/src/Misc/XMLwrapper.cpp b/src/Misc/XMLwrapper.cpp @@ -253,7 +253,7 @@ void XMLwrapper::addparreal(const string &name, float val) union { float in; uint32_t out; } convert; char buf[11]; convert.in = val; - sprintf(buf, "0x%0.8X", convert.out); + sprintf(buf, "0x%.8X", convert.out); addparams("par_real", 3, "name", name.c_str(), "value", stringFrom<float>(val).c_str(), "exact_value", buf); } @@ -397,6 +397,12 @@ bool XMLwrapper::putXMLdata(const char *xmldata) if(root == NULL) return false; + //fetch version information + _fileversion.set_major(stringTo<int>(mxmlElementGetAttr(root, "version-major"))); + _fileversion.set_minor(stringTo<int>(mxmlElementGetAttr(root, "version-minor"))); + _fileversion.set_revision( + stringTo<int>(mxmlElementGetAttr(root, "version-revision"))); + return true; } diff --git a/src/Nio/NulEngine.cpp b/src/Nio/NulEngine.cpp @@ -13,8 +13,8 @@ #include "NulEngine.h" #include "../globals.h" +#include "../Misc/Util.h" -#include <unistd.h> #include <iostream> using namespace std; @@ -50,7 +50,7 @@ void *NulEngine::AudioThread() + (playing_until.tv_sec - now.tv_sec) * 1000000; if(remaining > 10000) //Don't sleep() less than 10ms. //This will add latency... - usleep(remaining - 10000); + os_usleep(remaining - 10000); if(remaining < 0) cerr << "WARNING - too late" << endl; } diff --git a/src/Params/Controller.cpp b/src/Params/Controller.cpp @@ -46,6 +46,7 @@ const rtosc::Ports Controller::ports = { rToggle(pitchwheel.is_split, rDefault(false), "If PitchWheel Has unified bendrange or not"), rParamI(pitchwheel.bendrange, rShort("pch.d"), rDefault(200), + rLinear(-6400, 6400), "Range of MIDI Pitch Wheel"), rParamI(pitchwheel.bendrange_down, rDefault(0), "Lower Range of MIDI Pitch Wheel"), diff --git a/src/Params/EnvelopeParams.h b/src/Params/EnvelopeParams.h @@ -55,8 +55,8 @@ class EnvelopeParams:public Presets static float dt(char val); static char inv_dt(float val); - //! @brief defines where it is used and its default settings - //! corresponds to envelope_type_t + //! Defines where it is used and its default settings. + //! Corresponds to envelope_type_t int envelope_type; /* MIDI Parameters */ diff --git a/src/Params/FilterParams.cpp b/src/Params/FilterParams.cpp @@ -172,7 +172,6 @@ const rtosc::Ports FilterParams::ports = { 0.0, cf.d[1]); } } else if(obj->Pcategory == 2) { - int order = 0; float gain = dB2rap(obj->getgain()); auto cf = SVFilter::computeResponse(obj->Ptype, Filter::getrealfreq(obj->getfreq()), @@ -570,7 +569,7 @@ void FilterParams::getfromXML(XMLwrapper& xml) basefreq = xml.getparreal("basefreq", 1000); baseq = xml.getparreal("baseq", 10); gain = xml.getparreal("gain", 0); - freqtracking = xml.getparreal("freq_track", 0); + freqtracking = xml.getparreal("freq_tracking", 0); } //formant filter parameters diff --git a/src/Params/PADnoteParameters.cpp b/src/Params/PADnoteParameters.cpp @@ -10,6 +10,7 @@ as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ +#include <limits> #include <cmath> #include "PADnoteParameters.h" #include "FilterParams.h" @@ -20,6 +21,7 @@ #include "../Misc/WavFile.h" #include "../Misc/Time.h" #include <cstdio> +#include <thread> #include <rtosc/ports.h> #include <rtosc/port-sugar.h> @@ -192,7 +194,7 @@ static const rtosc::Ports non_realtime_ports = //Harmonic Bandwidth rOption(Pbwscale, rShort("bw scale"), rOptions(Normal, - EqualHz, Quater, + EqualHz, Quarter, Half, 75%, 150%, Double, Inv. Half), rDefault(Normal), @@ -304,8 +306,6 @@ PADnoteParameters::PADnoteParameters(const SYNTH_T &synth_, FFTwrapper *fft_, { setpresettype("Ppadsynth"); - fft = fft_; - resonance = new Resonance(); oscilgen = new OscilGen(synth, fft_, resonance); oscilgen->ADvsPAD = true; @@ -596,7 +596,7 @@ float PADnoteParameters::setPbandwidth(int Pbandwidth) /* * Get the harmonic(overtone) position */ -float PADnoteParameters::getNhr(int n) +float PADnoteParameters::getNhr(int n) const { float result = 1.0f; const float par1 = powf(10.0f, -(1.0f - Phrpos.par1 / 255.0f) * 3.0f); @@ -696,9 +696,9 @@ static float Pbwscale_translate(char Pbwscale) void PADnoteParameters::generatespectrum_bandwidthMode(float *spectrum, int size, float basefreq, - float *profile, + const float *profile, int profilesize, - float bwadjust) + float bwadjust) const { float harmonics[synth.oscilsize]; memset(spectrum, 0, sizeof(float) * size); @@ -712,7 +712,7 @@ void PADnoteParameters::generatespectrum_bandwidthMode(float *spectrum, //Constants across harmonics const float power = Pbwscale_translate(Pbwscale); - const float bandwidthcents = setPbandwidth(Pbandwidth); + const float bandwidthcents = const_cast<PADnoteParameters*>(this)->setPbandwidth(Pbandwidth); for(int nh = 1; nh < synth.oscilsize / 2; ++nh) { //for each harmonic const float realfreq = getNhr(nh) * basefreq; @@ -773,7 +773,7 @@ void PADnoteParameters::generatespectrum_bandwidthMode(float *spectrum, */ void PADnoteParameters::generatespectrum_otherModes(float *spectrum, int size, - float basefreq) + float basefreq) const { float harmonics[synth.oscilsize]; memset(spectrum, 0, sizeof(float) * size); @@ -830,21 +830,20 @@ void PADnoteParameters::applyparameters() applyparameters([]{return false;}); } -void PADnoteParameters::applyparameters(std::function<bool()> do_abort) +void PADnoteParameters::applyparameters(std::function<bool()> do_abort, + unsigned max_threads) { if(do_abort()) return; - unsigned max = 0; - sampleGenerator([&max,this] - (unsigned N, PADnoteParameters::Sample &smp) { - delete[] sample[N].smp; - sample[N] = smp; - max = max < N ? N : max; - }, - do_abort); + unsigned num = sampleGenerator([this] + (unsigned N, PADnoteParameters::Sample &smp) { + delete[] sample[N].smp; + sample[N] = smp; + }, + do_abort, max_threads); //Delete remaining unused samples - for(unsigned i = max; i < PAD_MAX_SAMPLES; ++i) + for(unsigned i = num; i < PAD_MAX_SAMPLES; ++i) deletesample(i); } @@ -854,13 +853,17 @@ void PADnoteParameters::applyparameters(std::function<bool()> do_abort) // - Pquality.oct // - Pquality.smpoct // - spectrum at various frequencies (oodles of data) -void PADnoteParameters::sampleGenerator(PADnoteParameters::callback cb, - std::function<bool()> do_abort) +int PADnoteParameters::sampleGenerator(PADnoteParameters::callback cb, + std::function<bool()> do_abort, + unsigned max_threads) { + if(!max_threads) + max_threads = std::numeric_limits<unsigned>::max(); + const int samplesize = (((int) 1) << (Pquality.samplesize + 14)); const int spectrumsize = samplesize / 2; - float *spectrum = new float[spectrumsize]; const int profilesize = 512; + float profile[profilesize]; @@ -885,71 +888,92 @@ void PADnoteParameters::sampleGenerator(PADnoteParameters::callback cb, if(samplemax > PAD_MAX_SAMPLES) samplemax = PAD_MAX_SAMPLES; - //prepare a BIG FFT - FFTwrapper *fft = new FFTwrapper(samplesize); - fft_t *fftfreqs = new fft_t[samplesize / 2]; - //this is used to compute frequency relation to the base frequency float adj[samplemax]; for(int nsample = 0; nsample < samplemax; ++nsample) adj[nsample] = (Pquality.oct + 1.0f) * (float)nsample / samplemax; - for(int nsample = 0; nsample < samplemax; ++nsample) { - if(do_abort()) - goto exit; - const float basefreqadjust = - powf(2.0f, adj[nsample] - adj[samplemax - 1] * 0.5f); - - if(Pmode == 0) - generatespectrum_bandwidthMode(spectrum, - spectrumsize, - basefreq * basefreqadjust, - profile, - profilesize, - bwadjust); - else - generatespectrum_otherModes(spectrum, spectrumsize, - basefreq * basefreqadjust); - - //the last samples contains the first samples - //(used for linear/cubic interpolation) - const int extra_samples = 5; - PADnoteParameters::Sample newsample; - newsample.smp = new float[samplesize + extra_samples]; - - newsample.smp[0] = 0.0f; - for(int i = 1; i < spectrumsize; ++i) //randomize the phases - fftfreqs[i] = FFTpolar(spectrum[i], (float)RND * 2 * PI); - //that's all; here is the only ifft for the whole sample; - //no windows are used ;-) - fft->freqs2smps(fftfreqs, newsample.smp); - - - //normalize(rms) - float rms = 0.0f; - for(int i = 0; i < samplesize; ++i) - rms += newsample.smp[i] * newsample.smp[i]; - rms = sqrt(rms); - if(rms < 0.000001f) - rms = 1.0f; - rms *= sqrt(262144.0f / samplesize);//262144=2^18 - for(int i = 0; i < samplesize; ++i) - newsample.smp[i] *= 1.0f / rms * 50.0f; - - //prepare extra samples used by the linear or cubic interpolation - for(int i = 0; i < extra_samples; ++i) - newsample.smp[i + samplesize] = newsample.smp[i]; - - //yield new sample - newsample.size = samplesize; - newsample.basefreq = basefreq * basefreqadjust; - cb(nsample, newsample); - } -exit: - //Cleanup - delete (fft); - delete[] fftfreqs; - delete[] spectrum; + const PADnoteParameters* this_c = this; + + auto thread_cb = [basefreq, bwadjust, &cb, do_abort, + samplesize, samplemax, spectrumsize, + &adj, &profile, this_c]( + unsigned nthreads, unsigned threadno) + { + //prepare a BIG IFFT + FFTwrapper *fft = new FFTwrapper(samplesize); + fft_t *fftfreqs = new fft_t[samplesize / 2]; + float *spectrum = new float[spectrumsize]; + + for(int nsample = 0; nsample < samplemax; ++nsample) + if(nsample % nthreads == threadno) + { + if(do_abort()) + break; + const float basefreqadjust = + powf(2.0f, adj[nsample] - adj[samplemax - 1] * 0.5f); + + if(this_c->Pmode == 0) + this_c->generatespectrum_bandwidthMode(spectrum, + spectrumsize, + basefreq*basefreqadjust, + profile, + profilesize, + bwadjust); + else + this_c->generatespectrum_otherModes(spectrum, spectrumsize, + basefreq * basefreqadjust); + + //the last samples contains the first samples + //(used for linear/cubic interpolation) + const int extra_samples = 5; + PADnoteParameters::Sample newsample; + newsample.smp = new float[samplesize + extra_samples]; + + newsample.smp[0] = 0.0f; + for(int i = 1; i < spectrumsize; ++i) //randomize the phases + fftfreqs[i] = FFTpolar(spectrum[i], (float)RND * 2 * PI); + //that's all; here is the only ifft for the whole sample; + //no windows are used ;-) + fft->freqs2smps(fftfreqs, newsample.smp); + + + //normalize(rms) + float rms = 0.0f; + for(int i = 0; i < samplesize; ++i) + rms += newsample.smp[i] * newsample.smp[i]; + rms = sqrt(rms); + if(rms < 0.000001f) + rms = 1.0f; + rms *= sqrt(262144.0f / samplesize);//262144=2^18 + for(int i = 0; i < samplesize; ++i) + newsample.smp[i] *= 1.0f / rms * 50.0f; + + //prepare extra samples used by the linear or cubic interpolation + for(int i = 0; i < extra_samples; ++i) + newsample.smp[i + samplesize] = newsample.smp[i]; + + //yield new sample + newsample.size = samplesize; + newsample.basefreq = basefreq * basefreqadjust; + cb(nsample, newsample); + } + + //Cleanup + delete (fft); + delete[] fftfreqs; + delete[] spectrum; + }; + + unsigned nthreads = std::min(max_threads, + std::thread::hardware_concurrency()); + std::vector<std::thread> threads(nthreads); + for(unsigned i = 0; i < nthreads; ++i) + threads[i] = std::thread(thread_cb, nthreads, i); + for(unsigned i = 0; i < nthreads; ++i) + threads[i].join(); + + return samplemax; } void PADnoteParameters::export2wav(std::string basefilename) diff --git a/src/Params/PADnoteParameters.h b/src/Params/PADnoteParameters.h @@ -25,7 +25,7 @@ namespace zyn { /** * Parameters for PAD synthesis * - * Note - unlike most other parameter objects significant portions of this + * @note unlike most other parameter objects significant portions of this * object are `owned' by the non-realtime context. The realtime context only * needs the samples generated by the PADsynth algorithm and modulators (ie * envelopes/filters/LFOs) for amplitude, frequency, and filters. @@ -146,26 +146,45 @@ class PADnoteParameters:public Presets - float setPbandwidth(int Pbandwidth); //returns the BandWidth in cents - float getNhr(int n); //gets the n-th overtone position relatively to N harmonic + float setPbandwidth(int Pbandwidth); //!< Return the BandWidth in cents + //! Get the n-th overtone position relatively to N harmonic + float getNhr(int n) const; void applyparameters(void); - void applyparameters(std::function<bool()> do_abort); + //! Compute the #sample array from the other parameters. + //! For the function's parameters, see sampleGenerator() + void applyparameters(std::function<bool()> do_abort, + unsigned max_threads = 0); void export2wav(std::string basefilename); OscilGen *oscilgen; Resonance *resonance; - //RT sample data struct Sample { int size; float basefreq; float *smp; - } sample[PAD_MAX_SAMPLES]; + }; + + //! RT sample data + Sample sample[PAD_MAX_SAMPLES]; typedef std::function<void(int,PADnoteParameters::Sample&)> callback; - void sampleGenerator(PADnoteParameters::callback cb, - std::function<bool()> do_abort); + + //! PAD synth main function + //! Generate spectrum and run IFFTs on it + //! @param cb A callback that will be executed for each sample buffer + //! Note that this function can be executed by multiple + //! threads at the same time, so make sure you use mutexes + //! etc where required + //! @param do_abort Function that decides whether the calculation should + //! be aborted (probably because of interruptions by the + //! user) + //! @param max_threads Maximum number of threads for computation, or + //! zero if no maximum shall be set + int sampleGenerator(PADnoteParameters::callback cb, + std::function<bool()> do_abort, + unsigned max_threads = 0); const AbsTime *time; int64_t last_update_timestamp; @@ -178,16 +197,15 @@ class PADnoteParameters:public Presets void generatespectrum_bandwidthMode(float *spectrum, int size, float basefreq, - float *profile, + const float *profile, int profilesize, - float bwadjust); + float bwadjust) const; void generatespectrum_otherModes(float *spectrum, int size, - float basefreq); + float basefreq) const; void deletesamples(); void deletesample(int n); - FFTwrapper *fft; public: const SYNTH_T &synth; }; diff --git a/src/Plugin/AlienWah/CMakeLists.txt b/src/Plugin/AlienWah/CMakeLists.txt @@ -24,6 +24,8 @@ add_custom_command(TARGET ZynAlienWah_lv2 POST_BUILD COMMAND ../../lv2-ttl-generator $<TARGET_FILE:ZynAlienWah_lv2> WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lv2) +add_dependencies(ZynAlienWah_lv2 lv2-ttl-generator) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lv2/manifest.ttl ${CMAKE_CURRENT_BINARY_DIR}/lv2/presets.ttl diff --git a/src/Plugin/Chorus/CMakeLists.txt b/src/Plugin/Chorus/CMakeLists.txt @@ -24,6 +24,8 @@ add_custom_command(TARGET ZynChorus_lv2 POST_BUILD COMMAND ../../lv2-ttl-generator $<TARGET_FILE:ZynChorus_lv2> WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lv2) +add_dependencies(ZynChorus_lv2 lv2-ttl-generator) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lv2/manifest.ttl ${CMAKE_CURRENT_BINARY_DIR}/lv2/presets.ttl diff --git a/src/Plugin/Distortion/CMakeLists.txt b/src/Plugin/Distortion/CMakeLists.txt @@ -24,6 +24,8 @@ add_custom_command(TARGET ZynDistortion_lv2 POST_BUILD COMMAND ../../lv2-ttl-generator $<TARGET_FILE:ZynDistortion_lv2> WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lv2) +add_dependencies(ZynDistortion_lv2 lv2-ttl-generator) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lv2/manifest.ttl ${CMAKE_CURRENT_BINARY_DIR}/lv2/presets.ttl diff --git a/src/Plugin/DynamicFilter/CMakeLists.txt b/src/Plugin/DynamicFilter/CMakeLists.txt @@ -24,6 +24,8 @@ add_custom_command(TARGET ZynDynamicFilter_lv2 POST_BUILD COMMAND ../../lv2-ttl-generator $<TARGET_FILE:ZynDynamicFilter_lv2> WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lv2) +add_dependencies(ZynDynamicFilter_lv2 lv2-ttl-generator) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lv2/manifest.ttl ${CMAKE_CURRENT_BINARY_DIR}/lv2/presets.ttl diff --git a/src/Plugin/Echo/CMakeLists.txt b/src/Plugin/Echo/CMakeLists.txt @@ -24,6 +24,8 @@ add_custom_command(TARGET ZynEcho_lv2 POST_BUILD COMMAND ../../lv2-ttl-generator $<TARGET_FILE:ZynEcho_lv2> WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lv2) +add_dependencies(ZynEcho_lv2 lv2-ttl-generator) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lv2/manifest.ttl ${CMAKE_CURRENT_BINARY_DIR}/lv2/presets.ttl diff --git a/src/Plugin/Phaser/CMakeLists.txt b/src/Plugin/Phaser/CMakeLists.txt @@ -24,6 +24,8 @@ add_custom_command(TARGET ZynPhaser_lv2 POST_BUILD COMMAND ../../lv2-ttl-generator $<TARGET_FILE:ZynPhaser_lv2> WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lv2) +add_dependencies(ZynPhaser_lv2 lv2-ttl-generator) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lv2/manifest.ttl ${CMAKE_CURRENT_BINARY_DIR}/lv2/presets.ttl diff --git a/src/Plugin/Reverb/CMakeLists.txt b/src/Plugin/Reverb/CMakeLists.txt @@ -24,6 +24,8 @@ add_custom_command(TARGET ZynReverb_lv2 POST_BUILD COMMAND ../../lv2-ttl-generator $<TARGET_FILE:ZynReverb_lv2> WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lv2) +add_dependencies(ZynReverb_lv2 lv2-ttl-generator) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lv2/manifest.ttl ${CMAKE_CURRENT_BINARY_DIR}/lv2/presets.ttl diff --git a/src/Plugin/ZynAddSubFX/CMakeLists.txt b/src/Plugin/ZynAddSubFX/CMakeLists.txt @@ -48,6 +48,7 @@ add_library(ZynAddSubFX_vst SHARED elseif(ZestGui) # UI Enabled using Zest: internal only +if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows") add_library(ZynAddSubFX_lv2 SHARED ${CMAKE_SOURCE_DIR}/src/globals.cpp ${CMAKE_SOURCE_DIR}/src/UI/ConnectionDummy.cpp @@ -61,6 +62,7 @@ add_library(ZynAddSubFX_lv2_ui SHARED ${CMAKE_SOURCE_DIR}/DPF/dgl/src/Application.cpp ${CMAKE_SOURCE_DIR}/DPF/distrho/DistrhoUIMain.cpp ZynAddSubFX-UI-Zest.cpp) +endif() add_library(ZynAddSubFX_vst SHARED ${CMAKE_SOURCE_DIR}/src/globals.cpp @@ -91,10 +93,12 @@ add_library(ZynAddSubFX_vst SHARED endif() -set_target_properties(ZynAddSubFX_lv2 PROPERTIES COMPILE_DEFINITIONS "DISTRHO_PLUGIN_TARGET_LV2") -set_target_properties(ZynAddSubFX_lv2 PROPERTIES LIBRARY_OUTPUT_DIRECTORY "lv2") -set_target_properties(ZynAddSubFX_lv2 PROPERTIES OUTPUT_NAME "ZynAddSubFX") -set_target_properties(ZynAddSubFX_lv2 PROPERTIES PREFIX "") +if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + set_target_properties(ZynAddSubFX_lv2 PROPERTIES COMPILE_DEFINITIONS "DISTRHO_PLUGIN_TARGET_LV2") + set_target_properties(ZynAddSubFX_lv2 PROPERTIES LIBRARY_OUTPUT_DIRECTORY "lv2") + set_target_properties(ZynAddSubFX_lv2 PROPERTIES OUTPUT_NAME "ZynAddSubFX") + set_target_properties(ZynAddSubFX_lv2 PROPERTIES PREFIX "") +endif() set_target_properties(ZynAddSubFX_vst PROPERTIES COMPILE_DEFINITIONS "DISTRHO_PLUGIN_TARGET_VST") set_target_properties(ZynAddSubFX_vst PROPERTIES LIBRARY_OUTPUT_DIRECTORY "vst") @@ -104,29 +108,40 @@ set_target_properties(ZynAddSubFX_vst PROPERTIES PREFIX "") if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") set(PLATFORM_LIBRARIES ws2_32 winmm + glu32 + gdi32 + opengl32 wsock32 "-static" iphlpapi "-static" winpthread) elseif(ZestGui) set(PLATFORM_LIBRARIES X11 GL rt) -else() +elseif(NtkGui OR FltkGui) set(PLATFORM_LIBRARIES X11 rt) +else() + set(PLATFORM_LIBRARIES rt) endif() -target_link_libraries(ZynAddSubFX_lv2 zynaddsubfx_core ${OS_LIBRARIES} ${LIBLO_LIBRARIES} - ${PLATFORM_LIBRARIES}) +if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + target_link_libraries(ZynAddSubFX_lv2 zynaddsubfx_core ${OS_LIBRARIES} ${LIBLO_LIBRARIES} + ${PLATFORM_LIBRARIES}) +endif() target_link_libraries(ZynAddSubFX_vst zynaddsubfx_core ${OS_LIBRARIES} ${LIBLO_LIBRARIES} ${PLATFORM_LIBRARIES}) -if(ZestGui) +if(ZestGui AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows") target_link_libraries(ZynAddSubFX_lv2_ui X11 GL) endif() -install(TARGETS ZynAddSubFX_lv2 LIBRARY DESTINATION ${PluginLibDir}/lv2/ZynAddSubFX.lv2/) -install(TARGETS ZynAddSubFX_vst LIBRARY DESTINATION ${PluginLibDir}/vst/) +if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + install(TARGETS ZynAddSubFX_lv2 LIBRARY DESTINATION ${PluginLibDir}/lv2/ZynAddSubFX.lv2/) + install(TARGETS ZynAddSubFX_vst LIBRARY DESTINATION ${PluginLibDir}/vst/) -add_custom_command(TARGET ZynAddSubFX_lv2 POST_BUILD - COMMAND ../../lv2-ttl-generator $<TARGET_FILE:ZynAddSubFX_lv2> - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lv2) + add_custom_command(TARGET ZynAddSubFX_lv2 POST_BUILD + COMMAND ../../lv2-ttl-generator $<TARGET_FILE:ZynAddSubFX_lv2> + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lv2) + + add_dependencies(ZynAddSubFX_lv2 lv2-ttl-generator) +endif() install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lv2/manifest.ttl diff --git a/src/Plugin/ZynAddSubFX/ZynAddSubFX-UI-Zest.cpp b/src/Plugin/ZynAddSubFX/ZynAddSubFX-UI-Zest.cpp @@ -13,7 +13,14 @@ // DPF includes #include "DistrhoUI.hpp" +#ifdef WIN32 +#include <windows.h> +#else +#ifndef __USE_GNU +#define __USE_GNU +#endif #include <dlfcn.h> +#endif typedef void *zest_t; struct zest_handles { @@ -41,13 +48,37 @@ public: : UI(1181, 659) { printf("[INFO] Opened the zynaddsubfx UI...\n"); - handle = dlopen("/opt/zyn-fusion/libzest.so", RTLD_LAZY); +#ifdef WIN32 + char path[1024]; + GetModuleFileName(GetModuleHandle("ZynAddSubFX.dll"), path, sizeof(path)); + if(strstr(path, "ZynAddSubFX.dll")) + strstr(path, "ZynAddSubFX.dll")[0] = 0; + strcat(path, "libzest.dll"); + printf("[DEBUG] Loading zest library from <%s>\n", path); + handle = LoadLibrary(path); + if(!handle) + handle = LoadLibrary("./libzest.dll"); + if(!handle) + handle = LoadLibrary("libzest.dll"); +#else + handle = dlopen("./libzest.so", RTLD_LAZY); + if(!handle) + handle = dlopen("/opt/zyn-fusion/libzest.so", RTLD_LAZY); + if(!handle) + handle = dlopen("libzest.so", RTLD_LAZY); +#endif if(!handle) { printf("[ERROR] Cannot Open libzest.so\n"); +#ifndef WIN32 printf("[ERROR] '%s'\n", dlerror()); +#endif } memset(&z, 0, sizeof(z)); +#ifdef WIN32 +#define get_sym(x) z.zest_##x = (decltype(z.zest_##x))GetProcAddress(handle, "zest_"#x) +#else #define get_sym(x) z.zest_##x = (decltype(z.zest_##x))dlsym(handle, "zest_"#x) +#endif if(handle) { get_sym(open); get_sym(setup); @@ -70,8 +101,11 @@ public: printf("[INFO:Zyn] zest_close()\n"); if(z.zest) z.zest_close(z.zest); +#ifdef WIN32 +#else if(handle) - dlclose(handle); + dlclose(handle); +#endif } protected: @@ -156,13 +190,15 @@ protected: if(!z.zest) { if(!z.zest_open) return; - //printf("[INFO:Zyn] zest_open()\n"); +if(!oscPort) + return; + printf("[INFO:Zyn] zest_open()\n"); char address[1024]; snprintf(address, sizeof(address), "osc.udp://127.0.0.1:%d",oscPort); printf("[INFO:Zyn] zest_open(%s)\n", address); z.zest = z.zest_open(address); - printf("[INFO:Zyn] zest_setup()\n"); + printf("[INFO:Zyn] zest_setup(%s)\n", address); z.zest_setup(z.zest); } @@ -195,7 +231,11 @@ protected: private: int oscPort; zest_handles z; +#ifdef WIN32 + HMODULE handle; +#else void *handle; +#endif DISTRHO_DECLARE_NON_COPY_CLASS(ZynAddSubFXUI) diff --git a/src/Plugin/ZynAddSubFX/ZynAddSubFX.cpp b/src/Plugin/ZynAddSubFX/ZynAddSubFX.cpp @@ -224,7 +224,7 @@ protected: parameter.ranges.def = 0.0f; break; } - if(kParamSlot1 <= index && index <= kParamSlot16) { + if(index <= kParamSlot16) { parameter.hints = kParameterIsAutomable; parameter.name = ("Slot " + zyn::to_s(index-kParamSlot1 + 1)).c_str(); parameter.symbol = ("slot" + zyn::to_s(index-kParamSlot1 + 1)).c_str(); @@ -246,7 +246,7 @@ protected: case kParamOscPort: return oscPort; } - if(kParamSlot1 <= index && index <= kParamSlot16) { + if(index <= kParamSlot16) { return master->automate.getSlot(index - kParamSlot1); } return 0.0f; @@ -260,10 +260,8 @@ protected: */ void setParameterValue(uint32_t index, float value) noexcept override { - // only an output port for now - if(kParamSlot1 <= index && index <= kParamSlot16) { + if(index <= kParamSlot16) master->automate.setSlot(index - kParamSlot1, value); - } } /* -------------------------------------------------------------------------------------------------------- diff --git a/src/Synth/ADnote.cpp b/src/Synth/ADnote.cpp @@ -77,9 +77,8 @@ ADnote::ADnote(ADnoteParameters *pars_, SynthParams &spars, else NoteGlobalPar.Punch.Enabled = 0; - for(int nvoice = 0; nvoice < NUM_VOICES; ++nvoice) { + for(int nvoice = 0; nvoice < NUM_VOICES; ++nvoice) setupVoice(nvoice); - } max_unison = 1; for(int nvoice = 0; nvoice < NUM_VOICES; ++nvoice) @@ -414,7 +413,7 @@ void ADnote::setupVoiceDetune(int nvoice) pars.VoicePar[nvoice].PFMDetune); } -void ADnote::setupVoiceMod(int nvoice) +void ADnote::setupVoiceMod(int nvoice, bool first_run) { auto &param = pars.VoicePar[nvoice]; auto &voice = NoteVoicePar[nvoice]; @@ -443,6 +442,42 @@ void ADnote::setupVoiceMod(int nvoice) voice.FMFreqFixed = param.PFMFixedFreq; + //Triggers when a user enables modulation on a running voice + if(!first_run && voice.FMEnabled != NONE && voice.FMSmp == NULL && voice.FMVoice < 0) { + param.FMSmp->newrandseed(prng()); + voice.FMSmp = memory.valloc<float>(synth.oscilsize + OSCIL_SMP_EXTRA_SAMPLES); + memset(voice.FMSmp, 0, sizeof(float)*(synth.oscilsize + OSCIL_SMP_EXTRA_SAMPLES)); + int vc = nvoice; + if(param.PextFMoscil != -1) + vc = param.PextFMoscil; + + float tmp = 1.0f; + if((pars.VoicePar[vc].FMSmp->Padaptiveharmonics != 0) + || (voice.FMEnabled == MORPH) + || (voice.FMEnabled == RING_MOD)) + tmp = getFMvoicebasefreq(nvoice); + + if(!pars.GlobalPar.Hrandgrouping) + pars.VoicePar[vc].FMSmp->newrandseed(prng()); + + for(int k = 0; k < unison_size[nvoice]; ++k) + oscposhiFM[nvoice][k] = (oscposhi[nvoice][k] + + pars.VoicePar[vc].FMSmp->get( + voice.FMSmp, tmp)) + % synth.oscilsize; + + for(int i = 0; i < OSCIL_SMP_EXTRA_SAMPLES; ++i) + voice.FMSmp[synth.oscilsize + i] = voice.FMSmp[i]; + int oscposhiFM_add = + (int)((param.PFMoscilphase + - 64.0f) / 128.0f * synth.oscilsize + + synth.oscilsize * 4); + for(int k = 0; k < unison_size[nvoice]; ++k) { + oscposhiFM[nvoice][k] += oscposhiFM_add; + oscposhiFM[nvoice][k] %= synth.oscilsize; + } + } + //Compute the Voice's modulator volume (incl. damping) float fmvoldamp = powf(440.0f / getvoicebasefreq(nvoice), @@ -1574,7 +1609,7 @@ int ADnote::noteout(float *outl, float *outr) || (NoteVoicePar[nvoice].DelayTicks > 0)) continue; setupVoiceDetune(nvoice); - setupVoiceMod(nvoice); + setupVoiceMod(nvoice, false); } computecurrentparameters(); diff --git a/src/Synth/ADnote.h b/src/Synth/ADnote.h @@ -56,7 +56,7 @@ class ADnote:public SynthNote void setupVoice(int nvoice); int setupVoiceUnison(int nvoice); void setupVoiceDetune(int nvoice); - void setupVoiceMod(int nvoice); + void setupVoiceMod(int nvoice, bool first_run = true); /**Changes the frequency of an oscillator. * @param nvoice voice to run computations on diff --git a/src/Tests/CMakeLists.txt b/src/Tests/CMakeLists.txt @@ -29,7 +29,8 @@ CXXTEST_ADD_TEST(MemoryStressTest MemoryStressTest.cpp #Extra libraries added to make test and full compilation use the same library #links for quirky compilers -set(test_lib zynaddsubfx_core ${GUI_LIBRARIES} ${ZLIB_LIBRARY} ${FFTW_LIBRARIES} ${MXML_LIBRARIES} pthread) +set(test_lib zynaddsubfx_core ${GUI_LIBRARIES} ${ZLIB_LIBRARY} ${FFTW_LIBRARIES} + ${MXML_LIBRARIES} pthread "-Wl,--no-as-needed -lpthread") message(STATUS "Linking tests with: ${test_lib}") target_link_libraries(ADnoteTest ${test_lib}) diff --git a/src/Tests/InstrumentStats.cpp b/src/Tests/InstrumentStats.cpp @@ -22,6 +22,13 @@ #include "../Misc/Microtonal.h" #include "../DSP/FFTwrapper.h" #include "../globals.h" + +#include "../Effects/EffectMgr.h" +#include "../Params/LFOParams.h" +#include "../Params/EnvelopeParams.h" +#include "../Params/ADnoteParameters.h" +#include "../Params/PADnoteParameters.h" +#include "../Params/SUBnoteParameters.h" using namespace std; using namespace zyn; @@ -143,22 +150,250 @@ void memUsage() printf("%lld", alloc.totalAlloced()); } +/* + * Kit fields used + * + * Kit type + * + * Add synth engines used + * Add synth voices used + * Sub synth engines used + * Pad synth engines used + * + * + * Total Envelopes + * Optional Envelopes + * + * Total Free mode Envelopes + * + * Total LFO + * Optional LFO + * + * Total Filters + * + * Total 'Analog' Filters + * Total SVF Filters + * Total Formant Filters + * + * Total Effects + */ +void usage_stats(void) +{ + int kit_type = 0; + int kits_used = 0; + int add_engines = 0; + int add_voices = 0; + int sub_engines = 0; + int pad_engines = 0; + + int env_total = 0; + int env_optional = 0; + int env_free = 0; + + int lfo_total = 0; + int lfo_optional = 0; + + int filter_total = 0; + int filter_analog = 0; + int filter_svf = 0; + int filter_formant = 0; + + int effects_total = 0; + + kit_type = p->Pkitmode; + for(int i=0; i<NUM_KIT_ITEMS; ++i) { + auto &k = p->kit[i]; + if(!(k.Penabled || (i==0 && p->Pkitmode == 0l))) + continue; + + if(k.Padenabled) { + auto &e = *k.adpars; + add_engines += 1; + for(int j=0; j<NUM_VOICES; ++j) { + auto &v = k.adpars->VoicePar[j]; + if(!v.Enabled) + continue; + add_voices += 1; + if(v.PFilterEnabled) { + auto &f = *v.VoiceFilter; + filter_total += 1; + if(f.Pcategory == 0) + filter_analog += 1; + else if(f.Pcategory == 1) + filter_formant += 1; + else + filter_svf += 1; + } + if(v.PFreqLfoEnabled && v.FreqLfo->Pintensity) { + lfo_optional += 1; + lfo_total += 1; + } + if(v.PFilterLfoEnabled && v.FilterLfo->Pintensity) { + lfo_optional += 1; + lfo_total += 1; + } + if(v.PAmpLfoEnabled && v.AmpLfo->Pintensity) { + lfo_optional += 1; + lfo_total += 1; + } + if(v.PFreqEnvelopeEnabled) { + env_optional += 1; + env_total += 1; + env_free += !!v.FreqEnvelope->Pfreemode; + } + if(v.PFilterEnvelopeEnabled) { + env_optional += 1; + env_total += 1; + env_free += !!v.FilterEnvelope->Pfreemode; + } + if(v.PAmpEnvelopeEnabled) { + env_optional += 1; + env_total += 1; + env_free += !!v.AmpEnvelope->Pfreemode; + } + } + + + + if(e.GlobalPar.GlobalFilter) { + auto &f = *e.GlobalPar.GlobalFilter; + filter_total += 1; + if(f.Pcategory == 0) + filter_analog += 1; + else if(f.Pcategory == 1) + filter_formant += 1; + else + filter_svf += 1; + } + if(e.GlobalPar.FreqLfo->Pintensity) + lfo_total += 1; + if(e.GlobalPar.FilterLfo->Pintensity) + lfo_total += 1; + if(e.GlobalPar.AmpLfo->Pintensity) + lfo_total += 1; + env_total += 3; + env_free += !!e.GlobalPar.FreqEnvelope->Pfreemode; + env_free += !!e.GlobalPar.FilterEnvelope->Pfreemode; + env_free += !!e.GlobalPar.AmpEnvelope->Pfreemode; + } + + if(k.Ppadenabled) { + pad_engines += 1; + auto &e = *k.padpars; + if(e.GlobalFilter) { + auto &f = *e.GlobalFilter; + filter_total += 1; + if(f.Pcategory == 0) + filter_analog += 1; + else if(f.Pcategory == 1) + filter_formant += 1; + else + filter_svf += 1; + } + if(e.FreqLfo->Pintensity) + lfo_total += 1; + if(e.FilterLfo->Pintensity) + lfo_total += 1; + if(e.AmpLfo->Pintensity) + lfo_total += 1; + env_total += 3; + env_free += !!e.FreqEnvelope->Pfreemode; + env_free += !!e.FilterEnvelope->Pfreemode; + env_free += !!e.AmpEnvelope->Pfreemode; + } + + if(k.Psubenabled) { + sub_engines += 1; + auto &e = *k.subpars; + + if(e.PGlobalFilterEnabled) { + auto &f = *e.GlobalFilter; + filter_total += 1; + if(f.Pcategory == 0) + filter_analog += 1; + else if(f.Pcategory == 1) + filter_formant += 1; + else + filter_svf += 1; + } + if(e.PFreqEnvelopeEnabled) { + env_total += 1; + env_optional += 1; + env_free += !!e.FreqEnvelope->Pfreemode; + } + if(e.PGlobalFilterEnabled) { + env_total += 1; + env_optional += 1; + env_free += !!e.GlobalFilterEnvelope->Pfreemode; + } + if(e.PBandWidthEnvelopeEnabled) { + env_total += 1; + env_optional += 1; + env_free += !!e.BandWidthEnvelope->Pfreemode; + } + } + + kits_used += 1; + } + + for(int i=0; i<NUM_PART_EFX; ++i) { + if(p->partefx[i]->efx) + effects_total += 1; + } + + printf("Kit type: %d\n", kit_type); + printf("Kits used: %d\n", kits_used); + printf("Add engines: %d\n", add_engines); + printf(" Add voices: %d\n", add_voices); + printf("Sub engines: %d\n", sub_engines); + printf("Pad engines: %d\n", pad_engines); + + printf("\n"); + + printf("Env total: %d\n", env_total); + printf("Env optional: %d\n", env_optional); + printf("Env free: %d\n", env_free); + + printf("\n"); + + printf("LFO total: %d\n", lfo_total); + printf("LFO optional: %d\n", lfo_optional); + + printf("\n"); + + printf("Filter total: %d\n", filter_total); + printf("Filter analog: %d\n", filter_analog); + printf("Filter svf: %d\n", filter_svf); + printf("Filter formant: %d\n", filter_formant); + + printf("\n"); + + printf("Effects Total: %d\n", effects_total); +} + int main(int argc, char **argv) { - if(argc != 2) { + if(argc < 2) { fprintf(stderr, "Please supply a xiz file\n"); return 1; } - - mode = MODE_PROFILE; - setup(); - xml(argv[1]); - load(); - memUsage(); - printf(", "); - noteOn(); - speed(); - noteOff(); - memUsage(); - printf("\n"); + if(argc == 2) { + mode = MODE_PROFILE; + setup(); + xml(argv[1]); + load(); + memUsage(); + printf(", "); + noteOn(); + speed(); + noteOff(); + memUsage(); + printf("\n"); + } else if(argc == 3) { + mode = MODE_TEST; + setup(); + xml(argv[2]); + load(); + usage_stats(); + } } diff --git a/src/Tests/MessageTest.h b/src/Tests/MessageTest.h @@ -184,14 +184,48 @@ class MessageTest:public CxxTest::TestSuite mw->transmitMsg("/learn", "s", "/Pvolume"); mw->transmitMsg("/virtual_midi_cc", "iii", 0, 23, 108); + //param is at default until rt-thread is run + TS_ASSERT_EQUALS(ms->Pvolume, 80); + + //Perform a learning operation + run_realtime(); - run_realtime(); //1. runs learning and identifies a CC to bind - mw->tick(); //2. produces new binding table - run_realtime(); //3. applies new binding table + //Verify binding affects control + TS_ASSERT_EQUALS(ms->Pvolume, 108); + + printf("# Trying to save automations\n"); + start_realtime(); mw->transmitMsg("/save_xlz", "s", "test-midi-learn.xlz"); + stop_realtime(); + + //Verify that some file exists + printf("# Verifying file exists\n"); + FILE *f = fopen("test-midi-learn.xlz", "r"); + TS_ASSERT(f); + + if(f) + fclose(f); + + printf("# Clearing automation\n"); + //Clear out state + mw->transmitMsg("/clear_xlz", ""); + //Send dummy message + mw->transmitMsg("/virtual_midi_cc", "iii", 0, 23, 27); + run_realtime(); + + //Verify automation table is clear + TS_ASSERT_EQUALS(ms->Pvolume, 108); + + printf("# Loading automation\n"); mw->transmitMsg("/load_xlz", "s", "test-midi-learn.xlz"); + //Send message + mw->transmitMsg("/virtual_midi_cc", "iii", 0, 23, 28); + run_realtime(); + + //Verify automation table is restored + TS_ASSERT_EQUALS(ms->Pvolume, 28); } void testLfoPaste(void) @@ -284,6 +318,7 @@ class MessageTest:public CxxTest::TestSuite state = 0; } + (void) id; //printf("Message #%d %s:%s\n", id++, msg, rtosc_argument_string(msg)); //if(rtosc_narguments(msg)) // printf(" %d\n", rtosc_argument(msg, 0).i); diff --git a/src/Tests/PadNoteTest.h b/src/Tests/PadNoteTest.h @@ -92,7 +92,7 @@ class PadNoteTest:public CxxTest::TestSuite //defaultPreset->defaults(); - pars->applyparameters(); + pars->applyparameters([]{return false;}, 1); //verify xml was loaded ///TS_ASSERT(defaultPreset->VoicePar[1].Enabled); @@ -197,9 +197,9 @@ class PadNoteTest:public CxxTest::TestSuite TS_ASSERT_DELTA(note->NoteGlobalPar.Panning, 0.500000f, 0.01f); - for(int i=0; i<7; ++i) + for(int i=0; i<8; ++i) TS_ASSERT(pars->sample[i].smp); - for(int i=7; i<PAD_MAX_SAMPLES; ++i) + for(int i=8; i<PAD_MAX_SAMPLES; ++i) TS_ASSERT(!pars->sample[i].smp); TS_ASSERT_DELTA(pars->sample[0].smp[0], -0.057407f, 0.0005f); diff --git a/src/Tests/PluginTest.h b/src/Tests/PluginTest.h @@ -56,7 +56,7 @@ void print_string_differences(string orig, string next) //Insertion is 2 cost and moves +2 State (+2 if symbols are different) //Deletion is 1 cost and moves +0 State (+2 if symbols are different) char *transition = new char[N*M]; - int *cost = new int[N*M]; + float *cost = new float[N*M]; const int match = 1; const int insert = 2; @@ -69,8 +69,8 @@ void print_string_differences(string orig, string next) } //Just assume the -1 line is the same - cost[0*M + 0] = (a[0] == b[0])*3; - cost[0*M + 1] = (a[1] == b[0])*2 + 2; + cost[0*M + 0] = (a[0] != b[0])*3; + cost[0*M + 1] = (a[1] != b[0])*2 + 2; for(int i=1; i<N; ++i) { for(int j=0; j<M; ++j) { int cost_match = 0xffffff; @@ -80,7 +80,7 @@ void print_string_differences(string orig, string next) if(j > 1) cost_ins = cost[(i-1)*M + (j-2)] + 1 + 2*(a[i] != b[j]); if(j > 0) - cost_match = cost[(i-1)*M + (j-1)] + 2*(a[i] != b[j]); + cost_match = cost[(i-1)*M + (j-1)] + 3*(a[i] != b[j]); if(cost_match >= 0xffff && cost_ins >= 0xffff && cost_del >= 0xffff) { ; @@ -97,28 +97,68 @@ void print_string_differences(string orig, string next) } } + //int off = 0; + //int len = N; + //for(int i=off; i<off+len; ++i) { + // for(int j=off; j<off+len; ++j) { + // printf("%4d ", (int)(cost[i*M+j] > 4000 ? -1 : cost[i*M+j])); + // } + // printf("\n"); + //} + //for(int i=off; i<off+len; ++i) { + // for(int j=off; j<off+len; ++j) { + // printf("%d ", transition[i*M+j]); + // } + // printf("\n"); + //} + + //for(int i=off; i<off+len; ++i) + // printf("%d: %s\n", i, a[i].c_str()); + //for(int i=off; i<off+len; ++i) + // printf("%d: %s\n", i, b[i].c_str()); + //exit(1); + int total_cost = cost[(N-1)*M + (M-1)]; if(total_cost < 500) { - printf("total cost = %d\n", cost[(N-1)*M + (M-1)]); + printf("total cost = %f\n", cost[(N-1)*M + (M-1)]); - //int b_pos = b.size()-1; + int b_pos = b.size()-1; int a_pos = a.size()-1; - for(int i=(M-1); i >= 0; --i) { - if(a[a_pos] != b[i]) { - printf("- %s\n", a[a_pos].c_str()); - printf("+ %s\n", b[i].c_str()); - } - if(transition[i*M+a_pos] == match) { + while(a_pos > 0 && b_pos > 0) { + //printf("state = (%d, %d) => %f\n", a_pos, b_pos, cost[a_pos*M+b_pos]); + if(transition[a_pos*M+b_pos] == match) { + if(a[a_pos] != b[b_pos]) { + printf("REF - %s\n", a[a_pos].c_str()); + printf("NEW + %s\n", b[b_pos].c_str()); + } //printf("R"); a_pos -= 1; - } else if(transition[i*M+a_pos] == del) { + b_pos -= 1; + } else if(transition[a_pos*M+b_pos] == del) { //printf("D"); - } else if(transition[i*M+a_pos] == insert) { + //if(a[a_pos] != b[b_pos]) { + //printf("- %s\n", a[a_pos].c_str()); + printf("NEW - %s\n", b[b_pos].c_str()); + //} + b_pos -= 1; + } else if(transition[a_pos*M+b_pos] == insert) { + //if(a[a_pos] != b[b_pos]) { + printf("REF - %s\n", a[a_pos].c_str()); + printf("NEW + %s\n", b[b_pos].c_str()); + printf("NEW + %s\n", b[b_pos-1].c_str()); + //} //printf("I"); - a_pos -= 2; + a_pos -= 1; + b_pos -= 2; + } else { + printf("ERROR STATE @(%d, %d)\n", a_pos, b_pos); + exit(1); } + } //printf("%d vs %d\n", N, M); + } else { + printf("[WARNING] XML File appears to be radically different\n"); } } diff --git a/src/Tests/guitar-adnote.xmz b/src/Tests/guitar-adnote.xmz @@ -2,7 +2,7 @@ <?xml version="1.0f" encoding="UTF-8"?> <!DOCTYPE ZynAddSubFX-data> <ZynAddSubFX-data version-major="3" version-minor="0" -version-revision="1" ZynAddSubFX-author="Nasca Octavian Paul"> +version-revision="2" ZynAddSubFX-author="Nasca Octavian Paul"> <INFORMATION> <par_bool name="PADsynth_used" value="yes" /> </INFORMATION> @@ -28,6 +28,9 @@ version-revision="1" ZynAddSubFX-author="Nasca Octavian Paul"> <par name="a_note" value="69" /> <par_real name="a_freq" value="440" exact_value="0x43DC0000" /> </MICROTONAL> +<automation> +<mgr-info nslots="16" nautomations="4" ncontrol="8" /> +</automation> <PART id="0"> <par_bool name="enabled" value="yes" /> <par name="volume" value="96" /> diff --git a/src/UI/BankUI.fl b/src/UI/BankUI.fl @@ -23,6 +23,9 @@ decl {\#include <FL/Fl_Button.H>} {public local decl {\#include <FL/Fl_File_Chooser.H>} {public local } +decl {\#include <FL/Fl_Input.H>} {public local +} + decl {\#include "Fl_Osc_Interface.h"} {public local } @@ -95,6 +98,15 @@ refreshmainwindow();} banklist->value(0);} tooltip {Refresh the bank list (rescan)} xywh {230 8 105 20} box THIN_UP_BOX color 50 labelsize 11 } + Fl_Input {} { + label {Search by name: } + code0 {o->when(FL_WHEN_CHANGED);} + callback { + std::string str = o->value(); + update_search(str)} + tooltip {Enter text to search for} + xywh {460 8 105 20} box THIN_UP_BOX color 50 labelsize 11 + } } } Function {BankUI(int *npart_, Fl_Osc_Interface *osc_)} {open @@ -139,6 +151,15 @@ bankview->refresh();} {} if (banklist->size() == 0) banklist->add(" ");} {} } + Function {update_search(std::string search_string)} {open + } { + code {if (search_string.empty()) { + refreshmainwindow(); +} else { + osc->write("/bank/search", "s", search_string.c_str()); +} + } {} + } decl {Fl_Osc_Interface *osc;} {private local } decl {Fl_Valuator *cbwig;} {public local diff --git a/src/UI/BankView.cpp b/src/UI/BankView.cpp @@ -225,15 +225,17 @@ void BankViewControls::mode(int m) BankView::BankView(int x,int y, int w, int h, const char *label) - :Fl_Group(x,y,w,h,label), bvc(NULL), slots{0}, osc(0), - loc(""), nselected(-1), npart(0), cbwig_(0) + :Fl_Group(x,y,w,h,label), Fl_Osc_Widget(), + bvc(NULL), slots{0}, nselected(-1), npart(0), cbwig_(0) {} BankView::~BankView(void) { - if(osc) - osc->removeLink("/bankview", this); + if(osc) { + osc->removeLink("/bankview", this); + osc->removeLink("/bank/search_results", this); + } } void BankView::init(Fl_Osc_Interface *osc_, BankViewControls *bvc_, int *npart_) @@ -245,6 +247,7 @@ void BankView::init(Fl_Osc_Interface *osc_, BankViewControls *bvc_, int *npart_) npart = npart_; osc->createLink("/bankview", this); + osc->createLink("/bank/search_results", this); //Element Size const float width = w()/5.0; @@ -346,14 +349,30 @@ void BankView::react(int event, int nslot) void BankView::OSC_raw(const char *msg) { - if(!strcmp(rtosc_argument_string(msg), "iss")) { + if(!strcmp(msg, "/bank/search_results")) { + const char *ptr = rtosc_argument_string(msg); + int slot = 0; + + while (ptr[0] == 's' && ptr[1] == 's') { + const char *bank = rtosc_argument(msg, 2*slot).s; + const char *fname = rtosc_argument(msg, 2*slot + 1).s; + + /* store search results directly into slot */ + slots[slot]->update(bank, fname); + if (++slot == 160) + break; + ptr += 2; + } + while (slot < 160) + slots[slot++]->update("", ""); + } else if(!strcmp(rtosc_argument_string(msg), "iss")) { int nslot = rtosc_argument(msg,0).i; const char *name = rtosc_argument(msg,1).s; const char *fname = rtosc_argument(msg,2).s; if(0 <= nslot && nslot < 160) slots[nslot]->update(name, fname); - } if(!strcmp(rtosc_argument_string(msg), "ss")) { + } else if(!strcmp(rtosc_argument_string(msg), "ss")) { while(*msg && !isdigit(*msg)) msg++; int nslot = atoi(msg); const char *name = rtosc_argument(msg,0).s; diff --git a/src/UI/BankView.h b/src/UI/BankView.h @@ -96,9 +96,6 @@ class BankView: public Fl_Group, public Fl_Osc_Widget BankViewControls *bvc; BankSlot *slots[160]; - Fl_Osc_Interface *osc; - std::string loc; - //XXX TODO locked banks... int nselected; int *npart; diff --git a/src/main.cpp b/src/main.cpp @@ -3,7 +3,7 @@ main.cpp - Main file of the synthesizer Copyright (C) 2002-2005 Nasca Octavian Paul - Copyright (C) 2012-2016 Mark McCurry + Copyright (C) 2012-2017 Mark McCurry This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -21,7 +21,9 @@ #include <algorithm> #include <signal.h> +#ifndef WIN32 #include <err.h> +#endif #include <unistd.h> #include <getopt.h> @@ -46,8 +48,10 @@ GUI::ui_handle_t gui; #ifdef ZEST_GUI +#ifndef WIN32 #include <sys/wait.h> #endif +#endif //Glue Layer #include "Misc/MiddleWare.h" @@ -133,7 +137,9 @@ void exitprogram(const Config& config) #ifdef WIN32 #include <windows.h> #include <mmsystem.h> +namespace zyn{ extern InMgr *in; +} HMIDIIN winmidiinhandle = 0; void CALLBACK WinMidiInProc(HMIDIIN hMidiIn,UINT wMsg,DWORD dwInstance, @@ -310,7 +316,7 @@ int main(int argc, char *argv[]) opterr = 0; int option_index = 0, opt, exitwithhelp = 0, exitwithversion = 0; int prefered_port = -1; - int auto_save_interval = 60; + int auto_save_interval = 0; int wmidi = -1; string loadfile, loadinstrument, execAfterInit, loadmidilearn; @@ -321,7 +327,7 @@ int wmidi = -1; /**\todo check this process for a small memory leak*/ opt = getopt_long(argc, argv, - "l:L:M:r:b:o:I:O:N:e:P:A:D:hvapSDUYZ", + "l:L:M:r:b:o:I:O:N:e:P:A:d:D:hvapSDUYZ", opts, &option_index); char *optarguments = optarg; @@ -641,10 +647,13 @@ int wmidi = -1; } #ifdef ZEST_GUI +#ifndef WIN32 pid_t gui_pid = 0; +#endif if(!noui) { printf("[INFO] Launching Zyn-Fusion...\n"); const char *addr = middleware->getServerAddress(); +#ifndef WIN32 gui_pid = fork(); if(gui_pid == 0) { execlp("zyn-fusion", "zyn-fusion", addr, "--builtin", "--no-hotload", 0); @@ -652,6 +661,28 @@ int wmidi = -1; err(1,"Failed to launch Zyn-Fusion"); } +#else + STARTUPINFO si; +PROCESS_INFORMATION pi; +memset(&si, 0, sizeof(si)); +memset(&pi, 0, sizeof(pi)); +char *why_windows = strrchr(addr, ':'); +char *seriously_why = why_windows + 1; +char start_line[256] = {0}; +if(why_windows) + snprintf(start_line, sizeof(start_line), "zyn-fusion.exe osc.udp://127.0.0.1:%s", seriously_why); +else { + printf("COULD NOT PARSE <%s>\n", addr); + exit(1); +} +printf("[INFO] starting subprocess via <%s>\n", start_line); +if(!CreateProcess(NULL, start_line, +NULL, NULL, 0, 0, NULL, NULL, &si, &pi)) { + printf("Failed to launch Zyn-Fusion...\n"); + exit(1); +} + +#endif } #endif @@ -695,6 +726,7 @@ done: #endif #ifdef ZEST_GUI +#ifndef WIN32 if(!noui) { int status = 0; int ret = waitpid(gui_pid, &status, WNOHANG); @@ -702,6 +734,7 @@ done: Pexitprogram = 1; } #endif +#endif } exitprogram(config); diff --git a/zynaddsubfx-oss.desktop b/zynaddsubfx-oss.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=ZynAddSubFX - OSS +Comment=A powerful realtime software synthesizer +Comment[fr]=Un synthétiseur logiciel temps-réel puissant +Keywords=audio;sound;alsa;midi;synth;synthesizer; +Exec=zynaddsubfx -I OSS -O OSS +Icon=zynaddsubfx +Terminal=false +Type=Application +Categories=AudioVideo;Audio;