commit 93e3e6e08e3e4aba56afe468a2f2005d8d518375
parent 0c43f66fea7935ef8f50f4fbd84178a6105a1493
Author: Johannes Lorenz <johannes89@ist-einmalig.de>
Date: Tue, 10 Oct 2017 19:58:17 +0200
Merge from master
Diffstat:
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 ¶m = 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;