| @@ -108,6 +108,7 @@ data/windows/Carla-*-win64/ | |||
| source/bridges/jackplugin/libjack.so.0 | |||
| source/frontend/Makefile | |||
| source/tests/ansi-pedantic-test_* | |||
| source/tests/CachedPlugins | |||
| source/tests/CarlaRingBuffer | |||
| source/tests/CarlaPipeUtils | |||
| source/tests/CarlaString | |||
| @@ -331,17 +331,17 @@ $(OBJDIR)/zynaddsubfx-fx.cpp.o: zynaddsubfx-fx.cpp $(ZYN_UI_FILES_H) | |||
| $(OBJDIR)/zynaddsubfx-synth.cpp.o: zynaddsubfx-synth.cpp $(ZYN_UI_FILES_H) | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $<" | |||
| @$(CXX) $< $(ZYN_CXX_FLAGS) -c -o $@ | |||
| @$(CXX) $< $(ZYN_CXX_FLAGS) -Wno-unused-parameter -c -o $@ | |||
| $(OBJDIR)/zynaddsubfx-src.cpp.o: zynaddsubfx-src.cpp $(ZYN_UI_FILES_H) | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $<" | |||
| @$(CXX) $< $(ZYN_CXX_FLAGS) -Wno-unused-parameter -c -o $@ | |||
| @$(CXX) $< $(ZYN_CXX_FLAGS) -Wno-unused-parameter -Wno-unused-variable -c -o $@ | |||
| $(OBJDIR)/zynaddsubfx-ui.cpp.o: zynaddsubfx-ui.cpp $(ZYN_UI_FILES_H) $(ZYN_UI_FILES_CPP) | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $<" | |||
| $(CXX) $< $(ZYN_CXX_FLAGS) -Wno-unused-parameter -Wno-unused-variable -c -o $@ | |||
| @$(CXX) $< $(ZYN_CXX_FLAGS) -Wno-unused-parameter -Wno-unused-variable -c -o $@ | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| @@ -220,6 +220,13 @@ extern "C" { | |||
| #undef rChangeCb | |||
| #define rChangeCb | |||
| #include "zynaddsubfx/Misc/CallbackRepeater.cpp" | |||
| #undef rObject | |||
| #undef rStdString | |||
| #undef rStdStringCb | |||
| #undef rChangeCb | |||
| #define rChangeCb | |||
| #include "zynaddsubfx/Misc/Config.cpp" | |||
| #undef rObject | |||
| #undef rStdString | |||
| @@ -286,6 +286,7 @@ public: | |||
| fMiddleWare(nullptr), | |||
| fMaster(nullptr), | |||
| fSynth(), | |||
| fDefaultState(nullptr), | |||
| fMutex(), | |||
| fMiddleWareThread(new MiddleWareThread()) | |||
| { | |||
| @@ -324,6 +325,8 @@ public: | |||
| _initMaster(); | |||
| _setMasterParameters(); | |||
| fMaster->getalldata(&fDefaultState); | |||
| fMiddleWareThread->start(fMiddleWare); | |||
| } | |||
| @@ -331,6 +334,7 @@ public: | |||
| { | |||
| fMiddleWareThread->stop(); | |||
| _deleteMaster(); | |||
| std::free(fDefaultState); | |||
| } | |||
| protected: | |||
| @@ -594,7 +598,7 @@ protected: | |||
| if (bank == 0) | |||
| { | |||
| // reset part to default | |||
| // TODO | |||
| setState(fDefaultState); | |||
| return; | |||
| } | |||
| @@ -828,6 +832,7 @@ private: | |||
| Master* fMaster; | |||
| SYNTH_T fSynth; | |||
| Config fConfig; | |||
| char* fDefaultState; | |||
| float fParameters[kParamCount]; | |||
| @@ -1,18 +1,69 @@ | |||
| #include "NotePool.h" | |||
| //XXX eliminate dependence on Part.h | |||
| #include "../Misc/Part.h" | |||
| #include "../Misc/Allocator.h" | |||
| #include "../Synth/SynthNote.h" | |||
| #include <cstring> | |||
| #include <cassert> | |||
| #include <iostream> | |||
| #define SUSTAIN_BIT 0x04 | |||
| #define NOTE_MASK 0x03 | |||
| enum NoteStatus { | |||
| KEY_OFF = 0x00, | |||
| KEY_PLAYING = 0x01, | |||
| KEY_RELEASED_AND_SUSTAINED = 0x02, | |||
| KEY_RELEASED = 0x03 | |||
| }; | |||
| NotePool::NotePool(void) | |||
| :needs_cleaning(0) | |||
| { | |||
| memset(ndesc, 0, sizeof(ndesc)); | |||
| memset(sdesc, 0, sizeof(sdesc)); | |||
| } | |||
| bool NotePool::NoteDescriptor::playing(void) const | |||
| { | |||
| return (status&NOTE_MASK) == KEY_PLAYING; | |||
| } | |||
| bool NotePool::NoteDescriptor::sustained(void) const | |||
| { | |||
| return (status&NOTE_MASK) == KEY_RELEASED_AND_SUSTAINED; | |||
| } | |||
| bool NotePool::NoteDescriptor::released(void) const | |||
| { | |||
| return (status&NOTE_MASK) == KEY_RELEASED; | |||
| } | |||
| bool NotePool::NoteDescriptor::off(void) const | |||
| { | |||
| return (status&NOTE_MASK) == KEY_OFF; | |||
| } | |||
| void NotePool::NoteDescriptor::setStatus(uint8_t s) | |||
| { | |||
| status &= ~NOTE_MASK; | |||
| status |= (NOTE_MASK&s); | |||
| } | |||
| void NotePool::NoteDescriptor::doSustain(void) | |||
| { | |||
| setStatus(KEY_RELEASED_AND_SUSTAINED); | |||
| } | |||
| bool NotePool::NoteDescriptor::canSustain(void) const | |||
| { | |||
| return !(status & SUSTAIN_BIT); | |||
| } | |||
| void NotePool::NoteDescriptor::makeUnsustainable(void) | |||
| { | |||
| status |= SUSTAIN_BIT; | |||
| } | |||
| NotePool::activeNotesIter NotePool::activeNotes(NoteDescriptor &n) | |||
| { | |||
| const int off_d1 = &n-ndesc; | |||
| @@ -35,18 +86,18 @@ static int getMergeableDescriptor(uint8_t note, uint8_t sendto, bool legato, | |||
| { | |||
| int desc_id = 0; | |||
| for(int i=0; i<POLYPHONY; ++i, ++desc_id) | |||
| if(ndesc[desc_id].status == Part::KEY_OFF) | |||
| if(ndesc[desc_id].off()) | |||
| break; | |||
| if(desc_id != 0) { | |||
| auto &nd = ndesc[desc_id-1]; | |||
| if(nd.age == 0 && nd.note == note && nd.sendto == sendto | |||
| && nd.status == Part::KEY_PLAYING && nd.legatoMirror == legato) | |||
| && nd.playing() && nd.legatoMirror == legato && nd.canSustain()) | |||
| return desc_id-1; | |||
| } | |||
| //Out of free descriptors | |||
| if(desc_id >= POLYPHONY || ndesc[desc_id].status != Part::KEY_OFF) { | |||
| if(desc_id >= POLYPHONY || !ndesc[desc_id].off()) { | |||
| return -1; | |||
| } | |||
| @@ -65,6 +116,28 @@ NotePool::constActiveDescIter NotePool::activeDesc(void) const | |||
| return constActiveDescIter{*this}; | |||
| } | |||
| int NotePool::usedNoteDesc(void) const | |||
| { | |||
| if(needs_cleaning) | |||
| const_cast<NotePool*>(this)->cleanup(); | |||
| int cnt = 0; | |||
| for(int i=0; i<POLYPHONY; ++i) | |||
| cnt += (ndesc[i].size != 0); | |||
| return cnt; | |||
| } | |||
| int NotePool::usedSynthDesc(void) const | |||
| { | |||
| if(needs_cleaning) | |||
| const_cast<NotePool*>(this)->cleanup(); | |||
| int cnt = 0; | |||
| for(int i=0; i<POLYPHONY*EXPECTED_USAGE; ++i) | |||
| cnt += (bool)sdesc[i].note; | |||
| return cnt; | |||
| } | |||
| void NotePool::insertNote(uint8_t note, uint8_t sendto, SynthDescriptor desc, bool legato) | |||
| { | |||
| //Get first free note descriptor | |||
| @@ -74,7 +147,7 @@ void NotePool::insertNote(uint8_t note, uint8_t sendto, SynthDescriptor desc, bo | |||
| ndesc[desc_id].note = note; | |||
| ndesc[desc_id].sendto = sendto; | |||
| ndesc[desc_id].size += 1; | |||
| ndesc[desc_id].status = Part::KEY_PLAYING; | |||
| ndesc[desc_id].status = KEY_PLAYING; | |||
| ndesc[desc_id].legatoMirror = legato; | |||
| //Get first free synth descriptor | |||
| @@ -90,7 +163,7 @@ void NotePool::insertNote(uint8_t note, uint8_t sendto, SynthDescriptor desc, bo | |||
| void NotePool::upgradeToLegato(void) | |||
| { | |||
| for(auto &d:activeDesc()) | |||
| if(d.status == Part::KEY_PLAYING) | |||
| if(d.playing()) | |||
| for(auto &s:activeNotes(d)) | |||
| insertLegatoNote(d.note, d.sendto, s); | |||
| } | |||
| @@ -118,12 +191,23 @@ void NotePool::applyLegato(LegatoParams &par) | |||
| std::cerr << "failed to create legato note: " << ba.what() << std::endl; | |||
| } | |||
| } | |||
| }; | |||
| } | |||
| void NotePool::makeUnsustainable(uint8_t note) | |||
| { | |||
| for(auto &desc:activeDesc()) { | |||
| if(desc.note == note) { | |||
| desc.makeUnsustainable(); | |||
| if(desc.sustained()) | |||
| release(desc); | |||
| } | |||
| } | |||
| } | |||
| bool NotePool::full(void) const | |||
| { | |||
| for(int i=0; i<POLYPHONY; ++i) | |||
| if(ndesc[i].status == Part::KEY_OFF) | |||
| if(ndesc[i].off()) | |||
| return false; | |||
| return true; | |||
| } | |||
| @@ -149,8 +233,7 @@ int NotePool::getRunningNotes(void) const | |||
| bool running[256] = {0}; | |||
| for(auto &desc:activeDesc()) { | |||
| //printf("note!(%d)\n", desc.note); | |||
| if(desc.status == Part::KEY_PLAYING || | |||
| desc.status == Part::KEY_RELEASED_AND_SUSTAINED) | |||
| if(desc.playing() || desc.sustained()) | |||
| running[desc.note] = true; | |||
| } | |||
| @@ -160,30 +243,44 @@ int NotePool::getRunningNotes(void) const | |||
| return running_count; | |||
| } | |||
| int NotePool::enforceKeyLimit(int limit) const | |||
| { | |||
| //{ | |||
| //int oldestnotepos = -1; | |||
| //if(notecount > keylimit) //find out the oldest note | |||
| // for(int i = 0; i < POLYPHONY; ++i) { | |||
| // int maxtime = 0; | |||
| // if(((partnote[i].status == KEY_PLAYING) || (partnote[i].status == KEY_RELEASED_AND_SUSTAINED)) && (partnote[i].time > maxtime)) { | |||
| // maxtime = partnote[i].time; | |||
| // oldestnotepos = i; | |||
| // } | |||
| // } | |||
| //if(oldestnotepos != -1) | |||
| // ReleaseNotePos(oldestnotepos); | |||
| //} | |||
| //printf("Unimplemented enforceKeyLimit()\n"); | |||
| return -1; | |||
| void NotePool::enforceKeyLimit(int limit) | |||
| { | |||
| int notes_to_kill = getRunningNotes() - limit; | |||
| if(notes_to_kill <= 0) | |||
| return; | |||
| NoteDescriptor *to_kill = NULL; | |||
| unsigned oldest = 0; | |||
| for(auto &nd : activeDesc()) { | |||
| if(to_kill == NULL) { | |||
| //There must be something to kill | |||
| oldest = nd.age; | |||
| to_kill = &nd; | |||
| } else if(to_kill->released() && nd.playing()) { | |||
| //Prefer to kill off a running note | |||
| oldest = nd.age; | |||
| to_kill = &nd; | |||
| } else if(nd.age > oldest && !(to_kill->playing() && nd.released())) { | |||
| //Get an older note when it doesn't move from running to released | |||
| oldest = nd.age; | |||
| to_kill = &nd; | |||
| } | |||
| } | |||
| if(to_kill) { | |||
| auto &tk = *to_kill; | |||
| if(tk.released() || tk.sustained()) | |||
| kill(*to_kill); | |||
| else | |||
| entomb(*to_kill); | |||
| } | |||
| } | |||
| void NotePool::releasePlayingNotes(void) | |||
| { | |||
| for(auto &d:activeDesc()) { | |||
| if(d.status == Part::KEY_PLAYING) { | |||
| d.status = Part::KEY_RELEASED; | |||
| if(d.playing()) { | |||
| d.setStatus(KEY_RELEASED); | |||
| for(auto s:activeNotes(d)) | |||
| s.note->releasekey(); | |||
| } | |||
| @@ -192,7 +289,7 @@ void NotePool::releasePlayingNotes(void) | |||
| void NotePool::release(NoteDescriptor &d) | |||
| { | |||
| d.status = Part::KEY_RELEASED; | |||
| d.setStatus(KEY_RELEASED); | |||
| for(auto s:activeNotes(d)) | |||
| s.note->releasekey(); | |||
| } | |||
| @@ -213,7 +310,7 @@ void NotePool::killNote(uint8_t note) | |||
| void NotePool::kill(NoteDescriptor &d) | |||
| { | |||
| d.status = Part::KEY_OFF; | |||
| d.setStatus(KEY_OFF); | |||
| for(auto &s:activeNotes(d)) | |||
| kill(s); | |||
| } | |||
| @@ -225,6 +322,13 @@ void NotePool::kill(SynthDescriptor &s) | |||
| needs_cleaning = true; | |||
| } | |||
| void NotePool::entomb(NoteDescriptor &d) | |||
| { | |||
| d.setStatus(KEY_RELEASED); | |||
| for(auto &s:activeNotes(d)) | |||
| s.note->entomb(); | |||
| } | |||
| const char *getStatus(int status_bits) | |||
| { | |||
| switch(status_bits) | |||
| @@ -252,7 +356,7 @@ void NotePool::cleanup(void) | |||
| int last_valid_desc = 0; | |||
| for(int i=0; i<POLYPHONY; ++i) | |||
| if(ndesc[i].status != Part::KEY_OFF) | |||
| if(!ndesc[i].off()) | |||
| last_valid_desc = i; | |||
| //Find the real numbers of allocated notes | |||
| @@ -275,7 +379,7 @@ void NotePool::cleanup(void) | |||
| if(new_length[i] != 0) | |||
| ndesc[cum_new++] = ndesc[i]; | |||
| else | |||
| ndesc[i].status = Part::KEY_OFF; | |||
| ndesc[i].setStatus(KEY_OFF); | |||
| } | |||
| memset(ndesc+cum_new, 0, sizeof(*ndesc)*(POLYPHONY-cum_new)); | |||
| } | |||
| @@ -24,6 +24,19 @@ class NotePool | |||
| uint8_t status; | |||
| bool legatoMirror; | |||
| bool operator==(NoteDescriptor); | |||
| //status checks | |||
| bool playing(void) const; | |||
| bool off(void) const; | |||
| bool sustained(void) const; | |||
| bool released(void) const; | |||
| //status transitions | |||
| void setStatus(uint8_t s); | |||
| void doSustain(void); | |||
| bool canSustain(void) const; | |||
| void makeUnsustainable(void); | |||
| }; | |||
| //To be pedantic this wastes 2 or 6 bytes per descriptor | |||
| @@ -84,6 +97,10 @@ class NotePool | |||
| activeDescIter activeDesc(void); | |||
| constActiveDescIter activeDesc(void) const; | |||
| //Counts of descriptors used for tests | |||
| int usedNoteDesc(void) const; | |||
| int usedSynthDesc(void) const; | |||
| NotePool(void); | |||
| //Operations | |||
| @@ -93,13 +110,15 @@ class NotePool | |||
| void upgradeToLegato(void); | |||
| void applyLegato(LegatoParams &par); | |||
| void makeUnsustainable(uint8_t note); | |||
| bool full(void) const; | |||
| bool synthFull(int sdesc_count) const; | |||
| //Note that isn't KEY_PLAYING or KEY_RELASED_AND_SUSTAINING | |||
| bool existsRunningNote(void) const; | |||
| int getRunningNotes(void) const; | |||
| int enforceKeyLimit(int limit) const; | |||
| void enforceKeyLimit(int limit); | |||
| void releasePlayingNotes(void); | |||
| void releaseNote(note_t note); | |||
| @@ -109,6 +128,7 @@ class NotePool | |||
| void killNote(note_t note); | |||
| void kill(NoteDescriptor &d); | |||
| void kill(SynthDescriptor &s); | |||
| void entomb(NoteDescriptor &d); | |||
| void cleanup(void); | |||
| @@ -0,0 +1,13 @@ | |||
| #include "CallbackRepeater.h" | |||
| CallbackRepeater::CallbackRepeater(int interval, cb_t cb_) | |||
| :last(time(0)), dt(interval), cb(cb_) | |||
| {} | |||
| void CallbackRepeater::tick(void) | |||
| { | |||
| auto now = time(0); | |||
| if(now-last > dt && dt >= 0) { | |||
| cb(); | |||
| last = now; | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| #pragma once | |||
| #include <functional> | |||
| #include <ctime> | |||
| struct CallbackRepeater | |||
| { | |||
| typedef std::function<void(void)> cb_t ; | |||
| //Call interval in seconds and callback | |||
| CallbackRepeater(int interval, cb_t cb_); | |||
| //Time Check | |||
| void tick(void); | |||
| std::time_t last; | |||
| std::time_t dt; | |||
| cb_t cb; | |||
| }; | |||
| @@ -237,6 +237,19 @@ static const Ports master_ports = { | |||
| SNIP | |||
| preset_ports.dispatch(msg, data); | |||
| rBOIL_END}, | |||
| {"HDDRecorder/preparefile:s", rDoc("Init WAV file"), 0, [](const char *msg, RtData &d) { | |||
| Master *m = (Master*)d.obj; | |||
| m->HDDRecorder.preparefile(rtosc_argument(msg, 0).s, 1);}}, | |||
| {"HDDRecorder/start:", rDoc("Start recording"), 0, [](const char *, RtData &d) { | |||
| Master *m = (Master*)d.obj; | |||
| m->HDDRecorder.start();}}, | |||
| {"HDDRecorder/stop:", rDoc("Stop recording"), 0, [](const char *, RtData &d) { | |||
| Master *m = (Master*)d.obj; | |||
| m->HDDRecorder.stop();}}, | |||
| {"HDDRecorder/pause:", rDoc("Pause recording"), 0, [](const char *, RtData &d) { | |||
| Master *m = (Master*)d.obj; | |||
| m->HDDRecorder.pause();}}, | |||
| }; | |||
| const Ports &Master::ports = master_ports; | |||
| @@ -618,7 +631,7 @@ int msg_id=0; | |||
| /* | |||
| * Master audio out (the final sound) | |||
| */ | |||
| bool Master::AudioOut(float *outl, float *outr) | |||
| bool Master::AudioOut(float *outr, float *outl) | |||
| { | |||
| //Danger Limits | |||
| if(memory->lowMemory(2,1024*1024)) | |||
| @@ -5,6 +5,7 @@ | |||
| #include <cstdlib> | |||
| #include <fstream> | |||
| #include <iostream> | |||
| #include <dirent.h> | |||
| #include <rtosc/undo-history.h> | |||
| #include <rtosc/thread-link.h> | |||
| @@ -19,6 +20,7 @@ | |||
| #include <map> | |||
| #include "Util.h" | |||
| #include "CallbackRepeater.h" | |||
| #include "Master.h" | |||
| #include "Part.h" | |||
| #include "PresetExtractor.h" | |||
| @@ -576,14 +578,18 @@ public: | |||
| { | |||
| if(server) | |||
| while(lo_server_recv_noblock(server, 0)); | |||
| while(bToU->hasNext()) { | |||
| const char *rtmsg = bToU->read(); | |||
| bToUhandle(rtmsg); | |||
| } | |||
| while(auto *m = multi_thread_source.read()) { | |||
| handleMsg(m->memory); | |||
| multi_thread_source.free(m); | |||
| } | |||
| autoSave.tick(); | |||
| } | |||
| @@ -659,6 +665,8 @@ public: | |||
| const SYNTH_T synth; | |||
| PresetsStore presetsstore; | |||
| CallbackRepeater autoSave; | |||
| }; | |||
| /***************************************************************************** | |||
| @@ -965,6 +973,25 @@ static rtosc::Ports middwareSnoopPorts = { | |||
| const char *file = rtosc_argument(msg,1).s; | |||
| impl.savePart(part_id, file); | |||
| rEnd}, | |||
| {"reload_auto_save:i", 0, 0, | |||
| rBegin | |||
| const int save_id = rtosc_argument(msg,0).i; | |||
| const string save_dir = string(getenv("HOME")) + "/.local"; | |||
| const string save_file = "zynaddsubfx-"+to_s(save_id)+"-autosave.xmz"; | |||
| const string save_loc = save_dir + "/" + save_file; | |||
| impl.loadMaster(save_loc.c_str()); | |||
| //XXX it would be better to remove the autosave after there is a new | |||
| //autosave, but this method should work for non-immediate crashes :-| | |||
| remove(save_loc.c_str()); | |||
| rEnd}, | |||
| {"delete_auto_save:i", 0, 0, | |||
| rBegin | |||
| const int save_id = rtosc_argument(msg,0).i; | |||
| const string save_dir = string(getenv("HOME")) + "/.local"; | |||
| const string save_file = "zynaddsubfx-"+to_s(save_id)+"-autosave.xmz"; | |||
| const string save_loc = save_dir + "/" + save_file; | |||
| remove(save_loc.c_str()); | |||
| rEnd}, | |||
| {"load_xmz:s", 0, 0, | |||
| rBegin; | |||
| const char *file = rtosc_argument(msg, 0).s; | |||
| @@ -1100,7 +1127,14 @@ static rtosc::Ports middlewareReplyPorts = { | |||
| MiddleWareImpl::MiddleWareImpl(MiddleWare *mw, SYNTH_T synth_, | |||
| Config* config, int preferrred_port) | |||
| :parent(mw), config(config), ui(nullptr), synth(std::move(synth_)), | |||
| presetsstore(*config) | |||
| presetsstore(*config), autoSave(-1, [this]() { | |||
| auto master = this->master; | |||
| this->doReadOnlyOp([master](){ | |||
| std::string home = getenv("HOME"); | |||
| std::string save_file = home+"/.local/zynaddsubfx-"+to_s(getpid())+"-autosave.xmz"; | |||
| printf("doing an autosave <%s>...\n", save_file.c_str()); | |||
| int res = master->saveXML(save_file.c_str()); | |||
| (void)res;});}) | |||
| { | |||
| bToU = new rtosc::ThreadLink(4096*2,1024); | |||
| uToB = new rtosc::ThreadLink(4096*2,1024); | |||
| @@ -1425,24 +1459,86 @@ MiddleWare::MiddleWare(SYNTH_T synth, Config* config, | |||
| int preferred_port) | |||
| :impl(new MiddleWareImpl(this, std::move(synth), config, preferred_port)) | |||
| {} | |||
| MiddleWare::~MiddleWare(void) | |||
| { | |||
| delete impl; | |||
| } | |||
| void MiddleWare::updateResources(Master *m) | |||
| { | |||
| impl->updateResources(m); | |||
| } | |||
| Master *MiddleWare::spawnMaster(void) | |||
| { | |||
| assert(impl->master); | |||
| assert(impl->master->uToB); | |||
| return impl->master; | |||
| } | |||
| void MiddleWare::enableAutoSave(int interval_sec) | |||
| { | |||
| impl->autoSave.dt = interval_sec; | |||
| } | |||
| int MiddleWare::checkAutoSave(void) | |||
| { | |||
| //save spec zynaddsubfx-PID-autosave.xmz | |||
| const std::string home = getenv("HOME"); | |||
| const std::string save_dir = home+"/.local/"; | |||
| DIR *dir = opendir(save_dir.c_str()); | |||
| if(dir == NULL) | |||
| return -1; | |||
| struct dirent *fn; | |||
| int reload_save = -1; | |||
| while((fn = readdir(dir))) { | |||
| const char *filename = fn->d_name; | |||
| const char *prefix = "zynaddsubfx-"; | |||
| //check for manditory prefix | |||
| if(strstr(filename, prefix) != filename) | |||
| continue; | |||
| int id = atoi(filename+strlen(prefix)); | |||
| bool in_use = false; | |||
| std::string proc_file = "/proc/" + to_s(id) + "/comm"; | |||
| std::ifstream ifs(proc_file); | |||
| if(ifs.good()) { | |||
| std::string comm_name; | |||
| ifs >> comm_name; | |||
| in_use = (comm_name == "zynaddsubfx"); | |||
| } | |||
| if(!in_use) { | |||
| reload_save = id; | |||
| break; | |||
| } | |||
| } | |||
| closedir(dir); | |||
| return reload_save; | |||
| } | |||
| void MiddleWare::removeAutoSave(void) | |||
| { | |||
| std::string home = getenv("HOME"); | |||
| std::string save_file = home+"/.local/zynaddsubfx-"+to_s(getpid())+"-autosave.xmz"; | |||
| remove(save_file.c_str()); | |||
| } | |||
| Fl_Osc_Interface *MiddleWare::spawnUiApi(void) | |||
| { | |||
| return impl->osc; | |||
| } | |||
| void MiddleWare::tick(void) | |||
| { | |||
| impl->tick(); | |||
| @@ -17,6 +17,18 @@ class MiddleWare | |||
| void updateResources(Master *m); | |||
| //returns internal master pointer | |||
| class Master *spawnMaster(void); | |||
| //Enable AutoSave Functionality | |||
| void enableAutoSave(int interval_sec=60); | |||
| //Check for old automatic saves which should only exist if multiple | |||
| //instances are in use OR when there was a crash | |||
| // | |||
| //When an old save is found return the id of the save file | |||
| int checkAutoSave(void); | |||
| void removeAutoSave(void); | |||
| //return UI interface | |||
| class Fl_Osc_Interface *spawnUiApi(void); | |||
| //Set callback to push UI events to | |||
| @@ -40,6 +52,7 @@ class MiddleWare | |||
| //Indicate that a bank will be loaded | |||
| //NOTE: Can only be called by realtime thread | |||
| void pendingSetBank(int bank); | |||
| //Indicate that a program will be loaded on a known part | |||
| //NOTE: Can only be called by realtime thread | |||
| void pendingSetProgram(int part, int program); | |||
| @@ -312,7 +312,7 @@ void Part::defaultsinstrument() | |||
| Pdrummode = 0; | |||
| for(int n = 0; n < NUM_KIT_ITEMS; ++n) { | |||
| kit[n].Penabled = false; | |||
| //kit[n].Penabled = false; | |||
| kit[n].Pmuted = false; | |||
| kit[n].Pminkey = 0; | |||
| kit[n].Pmaxkey = 127; | |||
| @@ -475,6 +475,9 @@ bool Part::NoteOn(unsigned char note, | |||
| return true; | |||
| } | |||
| if(Ppolymode) | |||
| notePool.makeUnsustainable(note); | |||
| //Create New Notes | |||
| for(uint8_t i = 0; i < NUM_KIT_ITEMS; ++i) { | |||
| auto &item = kit[i]; | |||
| @@ -522,7 +525,7 @@ void Part::NoteOff(unsigned char note) //release the key | |||
| monomemPop(note); | |||
| for(auto &desc:notePool.activeDesc()) { | |||
| if(desc.note != note || desc.status != KEY_PLAYING) | |||
| if(desc.note != note || !desc.playing()) | |||
| continue; | |||
| if(!ctl.sustain.sustain) { //the sustain pedal is not pushed | |||
| if((isMonoMode() || isLegatoMode()) && !monomemEmpty()) | |||
| @@ -530,8 +533,12 @@ void Part::NoteOff(unsigned char note) //release the key | |||
| else | |||
| notePool.release(desc); | |||
| } | |||
| else //the sustain pedal is pushed | |||
| desc.status = KEY_RELEASED_AND_SUSTAINED; | |||
| else { //the sustain pedal is pushed | |||
| if(desc.canSustain()) | |||
| desc.doSustain(); | |||
| else | |||
| notePool.release(desc); | |||
| } | |||
| } | |||
| } | |||
| @@ -550,7 +557,7 @@ void Part::PolyphonicAftertouch(unsigned char note, | |||
| const float vel = getVelocity(velocity, Pvelsns, Pveloffs); | |||
| for(auto &d:notePool.activeDesc()) { | |||
| if(d.note == note && d.status == KEY_PLAYING) | |||
| if(d.note == note && d.playing()) | |||
| for(auto &s:notePool.activeNotes(d)) | |||
| s.note->setVelocity(vel); | |||
| } | |||
| @@ -659,7 +666,7 @@ void Part::ReleaseSustainedKeys() | |||
| MonoMemRenote(); // To play most recent still held note. | |||
| for(auto &d:notePool.activeDesc()) | |||
| if(d.status == KEY_RELEASED_AND_SUSTAINED) | |||
| if(d.sustained()) | |||
| for(auto &s:notePool.activeNotes(d)) | |||
| s.note->releasekey(); | |||
| } | |||
| @@ -671,7 +678,7 @@ void Part::ReleaseSustainedKeys() | |||
| void Part::ReleaseAllKeys() | |||
| { | |||
| for(auto &d:notePool.activeDesc()) | |||
| if(d.status != KEY_RELEASED) | |||
| if(!d.released()) | |||
| for(auto &s:notePool.activeNotes(d)) | |||
| s.note->releasekey(); | |||
| } | |||
| @@ -726,7 +733,7 @@ void Part::setkeylimit(unsigned char Pkeylimit_) | |||
| if(keylimit == 0) | |||
| keylimit = POLYPHONY - 5; | |||
| if(notePool.getRunningNotes() > keylimit) | |||
| if(notePool.getRunningNotes() >= keylimit) | |||
| notePool.enforceKeyLimit(keylimit); | |||
| } | |||
| @@ -840,6 +847,9 @@ void Part::setkititemstatus(unsigned kititem, bool Penabled_) | |||
| delete kkit.adpars; | |||
| delete kkit.subpars; | |||
| delete kkit.padpars; | |||
| kkit.adpars = nullptr; | |||
| kkit.subpars = nullptr; | |||
| kkit.padpars = nullptr; | |||
| kkit.Pname[0] = '\0'; | |||
| notePool.killAllNotes(); | |||
| @@ -147,9 +147,6 @@ class Part | |||
| float *partfxinputl[NUM_PART_EFX + 1], //Left and right signal that pass thru part effects; | |||
| *partfxinputr[NUM_PART_EFX + 1]; //partfxinput l/r [NUM_PART_EFX] is for "no effect" buffer | |||
| enum NoteStatus { | |||
| KEY_OFF, KEY_PLAYING, KEY_RELEASED_AND_SUSTAINED, KEY_RELEASED | |||
| }; | |||
| float volume, oldvolumel, oldvolumer; //this is applied by Master | |||
| float panning; //this is applied by Master, too | |||
| @@ -1845,7 +1845,7 @@ void ADnote::releasekey() | |||
| /* | |||
| * Check if the note is finished | |||
| */ | |||
| int ADnote::finished() const | |||
| bool ADnote::finished() const | |||
| { | |||
| if(NoteEnabled == ON) | |||
| return 0; | |||
| @@ -1853,6 +1853,11 @@ int ADnote::finished() const | |||
| return 1; | |||
| } | |||
| void ADnote::entomb(void) | |||
| { | |||
| NoteGlobalPar.AmpEnvelope->forceFinish(); | |||
| } | |||
| void ADnote::Voice::releasekey() | |||
| { | |||
| if(!Enabled) | |||
| @@ -52,7 +52,9 @@ class ADnote:public SynthNote | |||
| int noteout(float *outl, float *outr); | |||
| void releasekey(); | |||
| int finished() const; | |||
| bool finished() const; | |||
| void entomb(void); | |||
| virtual SynthNote *cloneLegato(void) override; | |||
| private: | |||
| @@ -101,6 +101,11 @@ void Envelope::releasekey() | |||
| t = 0.0f; | |||
| } | |||
| void Envelope::forceFinish(void) | |||
| { | |||
| envfinish = true; | |||
| } | |||
| /* | |||
| * Envelope Output | |||
| */ | |||
| @@ -35,6 +35,8 @@ class Envelope | |||
| /**Destructor*/ | |||
| ~Envelope(); | |||
| void releasekey(); | |||
| /**Push Envelope to finishing state*/ | |||
| void forceFinish(void); | |||
| float envout(); | |||
| float envout_dB(); | |||
| /**Determines the status of the Envelope | |||
| @@ -428,11 +428,16 @@ int PADnote::noteout(float *outl, float *outr) | |||
| return 1; | |||
| } | |||
| int PADnote::finished() const | |||
| bool PADnote::finished() const | |||
| { | |||
| return finished_; | |||
| } | |||
| void PADnote::entomb(void) | |||
| { | |||
| NoteGlobalPar.AmpEnvelope->forceFinish(); | |||
| } | |||
| void PADnote::releasekey() | |||
| { | |||
| NoteGlobalPar.FreqEnvelope->releasekey(); | |||
| @@ -38,7 +38,9 @@ class PADnote:public SynthNote | |||
| void legatonote(LegatoParams pars); | |||
| int noteout(float *outl, float *outr); | |||
| int finished() const; | |||
| bool finished() const; | |||
| void entomb(void); | |||
| void releasekey(); | |||
| private: | |||
| void setup(float freq, float velocity, int portamento_, | |||
| @@ -619,10 +619,15 @@ void SUBnote::releasekey() | |||
| /* | |||
| * Check if the note is finished | |||
| */ | |||
| int SUBnote::finished() const | |||
| bool SUBnote::finished() const | |||
| { | |||
| if(NoteEnabled == OFF) | |||
| return 1; | |||
| else | |||
| return 0; | |||
| } | |||
| void SUBnote::entomb(void) | |||
| { | |||
| AmpEnvelope->forceFinish(); | |||
| } | |||
| @@ -38,7 +38,8 @@ class SUBnote:public SynthNote | |||
| int noteout(float *outl, float *outr); //note output,return 0 if the note is finished | |||
| void releasekey(); | |||
| int finished() const; | |||
| bool finished() const; | |||
| void entomb(void); | |||
| private: | |||
| void setup(float freq, | |||
| @@ -63,7 +63,10 @@ class SynthNote | |||
| /**Return if note is finished. | |||
| * @return finished=1 unfinished=0*/ | |||
| virtual int finished() const = 0; | |||
| virtual bool finished() const = 0; | |||
| /**Make a note die off next buffer compute*/ | |||
| virtual void entomb(void) = 0; | |||
| virtual void legatonote(LegatoParams pars) = 0; | |||
| @@ -134,7 +134,7 @@ void GUI::destroyUi(ui_handle_t ui) | |||
| delete static_cast<MasterUI*>(ui); | |||
| } | |||
| #define BEGIN(x) {x,":non-realtime\0",NULL,[](const char *m, rtosc::RtData d){ \ | |||
| #define BEGIN(x) {x,":non-realtime\0",NULL,[](const char *m, rtosc::RtData &d){ \ | |||
| MasterUI *ui = static_cast<MasterUI*>(d.obj); \ | |||
| rtosc_arg_t a0 = {0}, a1 = {0}; \ | |||
| if(rtosc_narguments(m) > 0) \ | |||
| @@ -157,6 +157,18 @@ rtosc::Ports uiPorts::ports = { | |||
| BEGIN("alert:s") { | |||
| fl_alert("%s",a0.s); | |||
| } END | |||
| BEGIN("alert-reload:i") { | |||
| int res = fl_choice("Old autosave found, do you want to reload?", | |||
| "Delete", "Reload", "Ignore"); | |||
| // 0 1 2 | |||
| if(1==res) { | |||
| d.reply("/reload_auto_save", "i", a0.i); | |||
| ui->refresh_master_ui(); | |||
| ui->updatepanel(); | |||
| } else if(0==res) { | |||
| d.reply("/delete_auto_save", "i", a0.i); | |||
| } | |||
| } END | |||
| BEGIN("session-type:s") { | |||
| if(strcmp(a0.s,"LASH")) | |||
| return; | |||
| @@ -188,6 +200,26 @@ rtosc::Ports uiPorts::ports = { | |||
| } END | |||
| }; | |||
| //very tiny rtdata ext | |||
| class RtDataUI: public rtosc::RtData { | |||
| public: | |||
| RtDataUI(Fl_Osc_Interface *osc_) | |||
| :osc(osc_) | |||
| {} | |||
| void reply(const char *path, const char *args, ...) override | |||
| { | |||
| va_list va; | |||
| va_start(va,args); | |||
| char buf[2048]; | |||
| rtosc_vmessage(buf,sizeof(buf),path,args,va); | |||
| osc->writeRaw(buf); | |||
| va_end(va); | |||
| } | |||
| Fl_Osc_Interface *osc; | |||
| }; | |||
| void GUI::raiseUi(ui_handle_t gui, const char *message) | |||
| { | |||
| @@ -209,7 +241,7 @@ void GUI::raiseUi(ui_handle_t gui, const char *message) | |||
| //printf("got message for UI '%s'\n", message); | |||
| char buffer[1024]; | |||
| memset(buffer, 0, sizeof(buffer)); | |||
| rtosc::RtData d; | |||
| RtDataUI d(mui->osc); | |||
| d.loc = buffer; | |||
| d.loc_size = 1024; | |||
| d.obj = gui; | |||
| @@ -178,18 +178,13 @@ bankui->show();} | |||
| } | |||
| Fl_Check_Button partenabled { | |||
| label 01 | |||
| callback {o->oscWrite("Penabled", o->value() ? "T" : "F"); | |||
| if ((int) o->value()==0) panellistitemgroup->deactivate(); | |||
| else { | |||
| panellistitemgroup->activate(); | |||
| /* | |||
| if ((int)bankui->cbwig->value()!=(npart+1)){ | |||
| bankui->cbwig->value(npart+1); | |||
| bankui->cbwig->do_callback(); | |||
| };*/ | |||
| }; | |||
| callback { | |||
| if ((int) o->value()==0) panellistitemgroup->deactivate(); | |||
| else { | |||
| panellistitemgroup->activate(); | |||
| }; | |||
| o->redraw();} | |||
| o->redraw();} | |||
| private xywh {5 0 45 20} down_box DOWN_BOX labeltype EMBOSSED_LABEL labelfont 1 labelsize 13 align 24 | |||
| code0 {char tmp[10];snprintf(tmp,10,"%d",npart+1);o->copy_label(tmp);} | |||
| code1 {o->init("Penabled");} | |||
| @@ -472,7 +467,7 @@ fl_filename_setext(filename,".wav"); | |||
| //TODO TODO Test if a file exists | |||
| if (fl_choice("The file *might* exist. \\nOverwrite it?","No","Yes",NULL)) { | |||
| osc->write("/HDDRecorder/preparefile", "T"); | |||
| osc->write("/HDDRecorder/preparefile", "s", filename); | |||
| recordbutton->activate();//TODO make this button osc controlled | |||
| } | |||
| @@ -765,7 +760,7 @@ stopbutton->activate(); | |||
| pausebutton->activate(); | |||
| pauselabel->activate(); | |||
| o->oscWrite("HDDRecorder/start"); | |||
| o->oscWrite("resetvu"); | |||
| o->oscWrite("reset-vu"); | |||
| mastermenu->redraw();} | |||
| tooltip {Start Recording} xywh {159 46 21 21} box ROUND_UP_BOX color 88 labelfont 1 labelsize 10 align 2 deactivate | |||
| class Fl_Osc_Button | |||
| @@ -109,6 +109,7 @@ void exitprogram(const Config& config) | |||
| { | |||
| Nio::stop(); | |||
| config.save(); | |||
| middleware->removeAutoSave(); | |||
| GUI::destroyUi(gui); | |||
| delete middleware; | |||
| @@ -193,6 +194,9 @@ int main(int argc, char *argv[]) | |||
| { | |||
| "auto-connect", 0, NULL, 'a' | |||
| }, | |||
| { | |||
| "auto-save", 0, NULL, 'A' | |||
| }, | |||
| { | |||
| "pid-in-client-name", 0, NULL, 'p' | |||
| }, | |||
| @@ -221,6 +225,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; | |||
| string loadfile, loadinstrument, execAfterInit, ui_title; | |||
| @@ -230,7 +235,7 @@ int main(int argc, char *argv[]) | |||
| /**\todo check this process for a small memory leak*/ | |||
| opt = getopt_long(argc, | |||
| argv, | |||
| "l:L:r:b:o:I:O:N:e:P:u:hvapSDUY", | |||
| "l:L:r:b:o:I:O:N:e:P:A:u:hvapSDUY", | |||
| opts, | |||
| &option_index); | |||
| char *optarguments = optarg; | |||
| @@ -321,6 +326,10 @@ int main(int argc, char *argv[]) | |||
| if(optarguments) | |||
| prefered_port = atoi(optarguments); | |||
| break; | |||
| case 'A': | |||
| if(optarguments) | |||
| auto_save_interval = atoi(optarguments); | |||
| break; | |||
| case 'e': | |||
| GETOP(execAfterInit); | |||
| break; | |||
| @@ -370,6 +379,7 @@ int main(int argc, char *argv[]) | |||
| " -U , --no-gui\t\t\t\t Run ZynAddSubFX without user interface\n" | |||
| << " -N , --named\t\t\t\t Postfix IO Name when possible\n" | |||
| << " -a , --auto-connect\t\t\t AutoConnect when using JACK\n" | |||
| << " -A , --auto-save=INTERVAL\t\t Automatically save at interval (disabled for negative intervals)\n" | |||
| << " -p , --pid-in-client-name\t\t Append PID to (JACK) " | |||
| "client name\n" | |||
| << " -P , --preferred-port\t\t\t Preferred OSC Port\n" | |||
| @@ -476,6 +486,13 @@ int main(int argc, char *argv[]) | |||
| "Default IO did not initialize.\nDefaulting to NULL backend."); | |||
| } | |||
| if(auto_save_interval >= 0) { | |||
| int old_save = middleware->checkAutoSave(); | |||
| if(old_save > 0) | |||
| GUI::raiseUi(gui, "/alert-reload", "i", old_save); | |||
| middleware->enableAutoSave(auto_save_interval); | |||
| } | |||
| #if USE_NSM | |||
| char *nsm_url = getenv("NSM_URL"); | |||