commit 313a13e6b7a5750b873217f7760c57e1fb738f29
parent e8a656ade4e651a54c67d200015ea47f6a4c64c4
Author: fundamental <mark.d.mccurry@gmail.com>
Date: Tue, 15 Nov 2016 19:56:34 -0500
Merge branch 'master' of ssh://git.code.sf.net/p/zynaddsubfx/code
Diffstat:
11 files changed, 257 insertions(+), 55 deletions(-)
diff --git a/README.adoc b/README.adoc
@@ -1,5 +1,8 @@
ZynAddSubFX
-----------
+
+image::https://travis-ci.org/zynaddsubfx/zynaddsubfx.svg?branch=master[alt="Build status", link="https://travis-ci.org/zynaddsubfx/zynaddsubfx"]
+
It is a feature heavy realtime software synthesizer for Linux, OSX,
and Windows.
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
@@ -260,8 +260,35 @@ endif()
add_definitions(
-Wall
-Wextra
- -Wno-inconsistent-missing-override
)
+
+# macro similar to "check_cxx_compiler_flag_extra", however,
+# it also checks for warnings that are only output if other warnings are active
+macro (check_cxx_compiler_flag_extra _FLAG _FAILREGEX _RESULT)
+ set(SAFE_CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS}")
+ set(CMAKE_REQUIRED_DEFINITIONS "${_FLAG}")
+
+ check_cxx_source_compiles("int main() { int x; return x; }" ${_RESULT}
+ FAIL_REGEX "unrecognized|option"
+ )
+
+ set (CMAKE_REQUIRED_DEFINITIONS "${SAFE_CMAKE_REQUIRED_DEFINITIONS}")
+endmacro ()
+
+check_cxx_compiler_flag_extra("-Wall -Wextra -Wno-inconsistent-missing-override"
+ "unrecognized|option"
+ COMPILER_SUPPORTS_NO_INCONSISTENT_MISSING_OVERRIDE
+ )
+if(COMPILER_SUPPORTS_NO_INCONSISTENT_MISSING_OVERRIDE)
+ add_definitions(-Wno-inconsistent-missing-override)
+endif()
+
+check_cxx_compiler_flag("-Wall -Wextra -Werror --system-header-prefix='FL/'"
+ COMPILER_SUPPORTS_SYSTEM_HDR_PREFIX)
+if(COMPILER_SUPPORTS_SYSTEM_HDR_PREFIX)
+ add_definitions(--system-header-prefix="FL/")
+endif()
+
if(NOT AVOID_ASM)
message(STATUS "Compiling with x86 opcode support")
add_definitions(-DASM_F2I_YES)
@@ -441,7 +468,7 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
"-static" iphlpapi
"-static" winpthread)
else()
- set(PLATFORM_LIBRARIES "")
+ set(PLATFORM_LIBRARIES rt)
set(PTHREAD_LIBRARY pthread)
endif()
diff --git a/src/Effects/EQ.cpp b/src/Effects/EQ.cpp
@@ -50,12 +50,12 @@ static rtosc::Ports filterports {
rEQ(2);
rEnd},
{"Pq::i", rProp(parameter) rMap(min, 0) rMap(max, 127)
- rShort("q"), 0,
+ rShort("q") rDoc("Resonance/Bandwidth"), 0,
rBegin;
rEQ(3);
rEnd},
{"Pstages::i", rProp(parameter) rMap(min, 0) rMap(max, 4)
- rShort("stages"), 0,
+ rShort("stages") rDoc("Additional filter stages"), 0,
rBegin;
rEQ(4);
rEnd},
diff --git a/src/Effects/Phaser.cpp b/src/Effects/Phaser.cpp
@@ -28,6 +28,16 @@ using namespace std;
#define rBegin [](const char *msg, rtosc::RtData &d) {
#define rEnd }
+#define ucharParamCb(pname) rBegin \
+ rObject &p = *(rObject*)d.obj; \
+ if(rtosc_narguments(msg)) \
+ p.set##pname(rtosc_argument(msg, 0).i); \
+ else \
+ d.reply(d.loc, "i", p.P##pname); \
+ rEnd
+#define rParamPhaser(name, ...) \
+ {STRINGIFY(P##name) "::i", rProp(parameter) rMap(min, 0) rMap(max, 127) DOC(__VA_ARGS__), NULL, ucharParamCb(name)}
+
rtosc::Ports Phaser::ports = {
{"preset::i", rProp(parameter)
rOptions(Phaser 1, Phaser 2, Phaser 3, Phaser 4,
@@ -43,22 +53,22 @@ rtosc::Ports Phaser::ports = {
d.reply(d.loc, "i", o->Ppreset);
rEnd},
//Pvolume/Ppanning are common
- rEffPar(lfo.Pfreq, 2, rShort("freq"), ""),
- rEffPar(lfo.Prandomness, 3, rShort("rnd."), ""),
+ rEffPar(lfo.Pfreq, 2, rShort("freq"), "LFO frequency"),
+ rEffPar(lfo.Prandomness, 3, rShort("rnd."), "LFO randomness"),
rEffPar(lfo.PLFOtype, 4, rShort("type"),
rOptions(sine, tri), "lfo shape"),
- rEffPar(lfo.Pstereo, 5, rShort("stereo"), ""),
- rEffPar(Pdepth, 6, rShort("depth"), ""),
- rEffPar(Pfb, 7, rShort("fb"), ""),
+ rEffPar(lfo.Pstereo, 5, rShort("stereo"), "Left/right channel phase shift"),
+ rEffPar(Pdepth, 6, rShort("depth"), "LFP depth"),
+ rEffPar(Pfb, 7, rShort("fb"), "Feedback"),
rEffPar(Pstages, 8, rLinear(1,12), rShort("stages"), ""),
- rEffPar(Plrcross, 9, rShort("cross"), ""),
- rEffPar(Poffset, 9, rShort("off"), "Offset"),
- rEffParTF(Poutsub, 10, rShort("sub"), ""),
- rEffPar(Pphase, 11, rShort("phase"), ""),
- rEffPar(Pwidth, 11, rShort("width"), ""),
- rEffParTF(Phyper, 12, rShort("hyp."), ""),
+ rParamPhaser(lrcross, rShort("cross"), "Channel routing"),
+ rParamPhaser(offset, rShort("off"), "Offset"),
+ rEffParTF(Poutsub, 10, rShort("sub"), "Invert output"),
+ rParamPhaser(phase, rShort("phase"), ""),
+ rParamPhaser(width, rShort("width"), ""),
+ rEffParTF(Phyper, 12, rShort("hyp."), "Square the LFO"),
rEffPar(Pdistortion, 13, rShort("distort"), "Distortion"),
- rEffParTF(Panalog, 14, rShort("analog"), ""),
+ rEffParTF(Panalog, 14, rShort("analog"), "Use analog phaser"),
};
#undef rBegin
#undef rEnd
diff --git a/src/Effects/Reverb.cpp b/src/Effects/Reverb.cpp
@@ -42,7 +42,7 @@ rtosc::Ports Reverb::ports = {
rEffPar(Pidelay, 3, rShort("i.time"), "Delay for first impulse"),
rEffPar(Pidelayfb,4, rShort("i.fb"), "Feedback for first impulse"),
rEffPar(Plpf, 7, rShort("lpf"), "Low pass filter"),
- rEffPar(Phpf, 8, rShort("lpf"), "High pass filter"),
+ rEffPar(Phpf, 8, rShort("hpf"), "High pass filter"),
rEffPar(Plohidamp,9, rShort("damp"), "Dampening"),
//Todo make this a selector
rEffPar(Ptype, 10,rShort("type"),
diff --git a/src/Misc/Master.cpp b/src/Misc/Master.cpp
@@ -682,27 +682,8 @@ void dump_msg(const char* ptr, std::ostream& os = std::cerr)
#endif
int msg_id=0;
-/*
- * Master audio out (the final sound)
- */
-bool Master::AudioOut(float *outr, float *outl)
+bool Master::runOSC(float *outl, float *outr, bool offline)
{
- //Danger Limits
- if(memory->lowMemory(2,1024*1024))
- printf("QUITE LOW MEMORY IN THE RT POOL BE PREPARED FOR WEIRD BEHAVIOR!!\n");
- //Normal Limits
- if(!pendingMemory && memory->lowMemory(4,1024*1024)) {
- printf("Requesting more memory\n");
- bToU->write("/request-memory", "");
- pendingMemory = true;
- }
-
-
- //Handle watch points
- if(bToU)
- watcher.write_back = bToU;
- watcher.tick();
-
//Handle user events TODO move me to a proper location
char loc_buf[1024];
DataObj d{loc_buf, 1024, this, bToU};
@@ -714,7 +695,8 @@ bool Master::AudioOut(float *outr, float *outl)
if(!strcmp(msg, "/load-master")) {
Master *this_master = this;
Master *new_master = *(Master**)rtosc_argument(msg, 0).b.data;
- new_master->AudioOut(outl, outr);
+ if(!offline)
+ new_master->AudioOut(outl, outr);
Nio::masterSwap(new_master);
if (mastercb)
mastercb(mastercb_ptr, new_master);
@@ -733,8 +715,6 @@ bool Master::AudioOut(float *outr, float *outl)
events++;
if(!d.matches) {
//workaround for requesting voice status
- //gtk_hscale_new_with_range
- //
int a=0, b=0, c=0;
char e=0;
if(4 == sscanf(msg, "/part%d/kit%d/adpars/VoicePar%d/Enable%c", &a, &b, &c, &e)) {
@@ -744,21 +724,44 @@ bool Master::AudioOut(float *outr, float *outl)
}
if(!d.matches) {// && !ports.apropos(msg)) {
fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 1, 7 + 30, 0 + 40);
- fprintf(stderr, "Unknown address<BACKEND> '%s:%s'\n", uToB->peak(), rtosc_argument_string(uToB->peak()));
-#if 0
- if(strstr(msg, "PFMVelocity"))
- dump_msg(msg);
- if(ports.apropos(msg))
- fprintf(stderr, " -> best match: '%s'\n", ports.apropos(msg)->name);
- if(ports.apropos(msg+1))
- fprintf(stderr, " -> best match: '%s'\n", ports.apropos(msg+1)->name);
-#endif
+ fprintf(stderr, "Unknown address<BACKEND:%s> '%s:%s'\n",
+ offline ? "offline" : "online",
+ uToB->peak(),
+ rtosc_argument_string(uToB->peak()));
fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
}
}
if(events>1 && false)
fprintf(stderr, "backend: %d events per cycle\n",events);
+ return true;
+}
+
+/*
+ * Master audio out (the final sound)
+ */
+bool Master::AudioOut(float *outr, float *outl)
+{
+ //Danger Limits
+ if(memory->lowMemory(2,1024*1024))
+ printf("QUITE LOW MEMORY IN THE RT POOL BE PREPARED FOR WEIRD BEHAVIOR!!\n");
+ //Normal Limits
+ if(!pendingMemory && memory->lowMemory(4,1024*1024)) {
+ printf("Requesting more memory\n");
+ bToU->write("/request-memory", "");
+ pendingMemory = true;
+ }
+
+ //work through events
+ if(!runOSC(outl, outr, false))
+ return false;
+
+
+ //Handle watch points
+ if(bToU)
+ watcher.write_back = bToU;
+ watcher.tick();
+
//Swaps the Left channel with Right Channel
if(swaplr)
@@ -914,6 +917,9 @@ bool Master::AudioOut(float *outr, float *outl)
}
#endif
+ //Update pulse
+ last_ack = last_beat;
+
return true;
}
diff --git a/src/Misc/Master.h b/src/Misc/Master.h
@@ -91,6 +91,9 @@ class Master
void vuUpdate(const float *outl, const float *outr);
+ //Process a set of OSC events in the bToU buffer
+ bool runOSC(float *outl, float *outr, bool offline=false);
+
/**Audio Output*/
bool AudioOut(float *outl, float *outr) REALTIME;
/**Audio Output (for callback mode).
@@ -175,6 +178,11 @@ class Master
bool pendingMemory;
const SYNTH_T &synth;
const int& gzip_compression; //!< value from config
+
+ //Heartbeat for identifying plugin offline modes
+ //in units of 10 ms (done s.t. overflow is in 497 days)
+ uint32_t last_beat;
+ uint32_t last_ack;
private:
float sysefxvol[NUM_SYS_EFX][NUM_MIDI_PARTS];
float sysefxsend[NUM_SYS_EFX][NUM_SYS_EFX];
diff --git a/src/Misc/MiddleWare.cpp b/src/Misc/MiddleWare.cpp
@@ -463,8 +463,16 @@ public:
int preferred_port);
~MiddleWareImpl(void);
+ //Check offline vs online mode in plugins
+ void heartBeat(Master *m);
+ int64_t start_time_sec;
+ int64_t start_time_nsec;
+ bool offline;
+
//Apply function while parameters are write locked
void doReadOnlyOp(std::function<void()> read_only_fn);
+ void doReadOnlyOpPlugin(std::function<void()> read_only_fn);
+ bool doReadOnlyOpNormal(std::function<void()> read_only_fn, bool canfail=false);
void savePart(int npart, const char *filename)
{
@@ -493,7 +501,7 @@ public:
assert(actual_load[npart] <= pending_load[npart]);
//load part in async fashion when possible
-#if 0
+#ifndef WIN32
auto alloc = std::async(std::launch::async,
[master,filename,this,npart](){
Part *p = new Part(*master->memory, synth,
@@ -667,6 +675,13 @@ public:
}
autoSave.tick();
+
+ heartBeat(master);
+
+ //XXX This might have problems with a master swap operation
+ if(offline)
+ master->runOSC(0,0,true);
+
}
@@ -1537,6 +1552,14 @@ MiddleWareImpl::MiddleWareImpl(MiddleWare *mw, SYNTH_T synth_,
rtosc_message(buf, 1024, "/undo_resume","");
handleMsg(buf);
});
+
+ //Setup starting time
+ struct timespec time;
+ clock_gettime(CLOCK_MONOTONIC, &time);
+ start_time_sec = time.tv_sec;
+ start_time_nsec = time.tv_nsec;
+
+ offline = false;
}
MiddleWareImpl::~MiddleWareImpl(void)
@@ -1611,6 +1634,130 @@ void MiddleWareImpl::doReadOnlyOp(std::function<void()> read_only_fn)
}
}
+//Offline detection code:
+// - Assume that the audio callback should be run at least once every 50ms
+// - Atomically provide the number of ms since start to Master
+// - Every time middleware ticks provide a heart beat
+// - If when the heart beat is provided the backend is more than 200ms behind
+// the last heartbeat then it must be offline
+// - When marked offline the backend doesn't receive another heartbeat until it
+// registers the current beat that it's behind on
+void MiddleWareImpl::heartBeat(Master *master)
+{
+ //Current time
+ //Last provided beat
+ //Last acknowledged beat
+ //Current offline status
+
+ struct timespec time;
+ clock_gettime(CLOCK_MONOTONIC, &time);
+ uint32_t now = (time.tv_sec-start_time_sec)*100 +
+ (time.tv_nsec-start_time_nsec)*1e-9*100;
+ int32_t last_ack = master->last_ack;
+ int32_t last_beat = master->last_beat;
+
+ //everything is considered online for the first second
+ if(now < 100)
+ return;
+
+ if(offline) {
+ if(last_beat == last_ack) {
+ //XXX INSERT MESSAGE HERE ABOUT TRANSITION TO ONLINE
+ offline = false;
+
+ //Send new heart beat
+ master->last_beat = now;
+ }
+ } else {
+ //it's unquestionably alive
+ if(last_beat == last_ack) {
+
+ //Send new heart beat
+ master->last_beat = now;
+ return;
+ }
+
+ //it's pretty likely dead
+ if(last_beat-last_ack > 0 && now-last_beat > 20) {
+ //The backend has had 200 ms to acquire a new beat
+ //The backend instead has an older beat
+ //XXX INSERT MESSAGE HERE ABOUT TRANSITION TO OFFLINE
+ offline = true;
+ return;
+ }
+
+ //who knows if it's alive or not here, give it a few ms to acquire or
+ //not
+ }
+
+}
+
+void MiddleWareImpl::doReadOnlyOpPlugin(std::function<void()> read_only_fn)
+{
+ assert(uToB);
+ int offline = 0;
+ if(offline) {
+ std::atomic_thread_fence(std::memory_order_acquire);
+
+ //Now it is safe to do any read only operation
+ read_only_fn();
+ } else if(!doReadOnlyOpNormal(read_only_fn, true)) {
+ //check if we just transitioned to offline mode
+
+ std::atomic_thread_fence(std::memory_order_acquire);
+
+ //Now it is safe to do any read only operation
+ read_only_fn();
+ }
+}
+
+bool MiddleWareImpl::doReadOnlyOpNormal(std::function<void()> read_only_fn, bool canfail)
+{
+ assert(uToB);
+ uToB->write("/freeze_state","");
+
+ std::list<const char *> fico;
+ int tries = 0;
+ while(tries++ < 2000) {
+ if(!bToU->hasNext()) {
+ usleep(500);
+ continue;
+ }
+ const char *msg = bToU->read();
+ if(!strcmp("/state_frozen", msg))
+ break;
+ size_t bytes = rtosc_message_length(msg, bToU->buffer_size());
+ char *save_buf = new char[bytes];
+ memcpy(save_buf, msg, bytes);
+ fico.push_back(save_buf);
+ }
+
+ if(canfail) {
+ //Now to resume normal operations
+ uToB->write("/thaw_state","");
+ for(auto x:fico) {
+ uToB->raw_write(x);
+ delete [] x;
+ }
+ return false;
+ }
+
+ assert(tries < 10000);//if this happens, the backend must be dead
+
+ std::atomic_thread_fence(std::memory_order_acquire);
+
+ //Now it is safe to do any read only operation
+ read_only_fn();
+
+ //Now to resume normal operations
+ uToB->write("/thaw_state","");
+ for(auto x:fico) {
+ uToB->raw_write(x);
+ delete [] x;
+ }
+ return true;
+}
+
void MiddleWareImpl::broadcastToRemote(const char *rtmsg)
{
//Always send to the local UI
diff --git a/src/Params/Controller.cpp b/src/Params/Controller.cpp
@@ -42,10 +42,11 @@ const rtosc::Ports Controller::ports = {
rToggle(volume.receive, rShort("vol.rcv"), "Volume MIDI Receive"),
rToggle(sustain.receive, rShort("sus.rcv"), "Sustain MIDI Receive"),
rToggle(portamento.receive, rShort("prt.rcv"), "Portamento MIDI Receive"),
- rToggle(portamento.portamento, "UNDOCUMENTED"),
+ rToggle(portamento.portamento, "Portamento Enable"),
rParamZyn(portamento.time, rShort("time"), "Portamento Length"),
- rToggle(portamento.proportional, rShort("propt."), "If all portamentos are proportional to the distance they span"),
- rParamZyn(portamento.propRate, rShort("rate"), "Portamento proportional rate"),
+ rToggle(portamento.proportional, rShort("propt."), "Whether the portamento time is proportional"
+ "to the size of the interval between two notes."),
+ rParamZyn(portamento.propRate, rShort("scale"), "Portamento proportional scale"),
rParamZyn(portamento.propDepth, rShort("depth"), "Portamento proportional depth"),
rParamZyn(portamento.pitchthresh, rShort("thresh"), "Threshold for portamento"),
rToggle(portamento.pitchthreshtype, rShort("tr.type"), "Type of threshold"),
diff --git a/src/Params/FilterParams.cpp b/src/Params/FilterParams.cpp
@@ -78,7 +78,7 @@ const rtosc::Ports FilterParams::ports = {
rParamZyn(Pformantslowness, rShort("slew"),
"Rate that formants change"),
rParamZyn(Pvowelclearness, rShort("clarity"),
- "Cost for mixing vowels"),
+ "How much each vowel is smudged with the next in sequence. A high clarity will avoid smudging."),
rParamZyn(Pcenterfreq, rShort("cutoff"),
"Center Freq (formant)"),
rParamZyn(Poctavesfreq, rShort("octaves"),
diff --git a/src/Plugin/ZynAddSubFX/CMakeLists.txt b/src/Plugin/ZynAddSubFX/CMakeLists.txt
@@ -108,7 +108,7 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
"-static" iphlpapi
"-static" winpthread)
else()
- set(PLATFORM_LIBRARIES X11 GL)
+ set(PLATFORM_LIBRARIES X11 GL rt)
endif()
target_link_libraries(ZynAddSubFX_lv2 zynaddsubfx_core ${OS_LIBRARIES} ${LIBLO_LIBRARIES}