zynaddsubfx

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

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:
MREADME.adoc | 3+++
Msrc/CMakeLists.txt | 31+++++++++++++++++++++++++++++--
Msrc/Effects/EQ.cpp | 4++--
Msrc/Effects/Phaser.cpp | 34++++++++++++++++++++++------------
Msrc/Effects/Reverb.cpp | 2+-
Msrc/Misc/Master.cpp | 70++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/Misc/Master.h | 8++++++++
Msrc/Misc/MiddleWare.cpp | 149++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/Params/Controller.cpp | 7++++---
Msrc/Params/FilterParams.cpp | 2+-
Msrc/Plugin/ZynAddSubFX/CMakeLists.txt | 2+-
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}