@@ -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"); | |||