2 ZynAddSubFX - a software synthesizer
4 MiddleWare.cpp - Glue Logic And Home Of Non-RT Operations
5 Copyright (C) 2016 Mark McCurry
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (at your option) any later version.
12 #include "MiddleWare.h"
23 #include <rtosc/undo-history.h>
24 #include <rtosc/thread-link.h>
25 #include <rtosc/ports.h>
30 #include "../UI/Connection.h"
31 #include "../UI/Fl_Osc_Interface.h"
36 #include "CallbackRepeater.h"
39 #include "PresetExtractor.h"
40 #include "../Containers/MultiPseudoStack.h"
41 #include "../Params/PresetsStore.h"
42 #include "../Params/ADnoteParameters.h"
43 #include "../Params/SUBnoteParameters.h"
44 #include "../Params/PADnoteParameters.h"
45 #include "../DSP/FFTwrapper.h"
46 #include "../Synth/OscilGen.h"
47 #include "../Nio/Nio.h"
65 /******************************************************************************
66 * LIBLO And Reflection Code *
68 * All messages that are handled are handled in a serial fashion. *
69 * Thus, changes in the current interface sending messages can be encoded *
70 * into the stream via events which simply echo back the active interface *
71 ******************************************************************************/
72 static void liblo_error_cb(int i
, const char *m
, const char *loc
)
74 fprintf(stderr
, "liblo :-( %d-%s@%s\n",i
,m
,loc
);
77 void path_search(const char *m
, const char *url
)
82 //assumed upper bound of 32 ports (may need to be resized)
84 rtosc_arg_t args
[256];
86 const Ports
*ports
= NULL
;
87 const char *str
= rtosc_argument(m
,0).s
;
88 const char *needle
= rtosc_argument(m
,1).s
;
91 memset(types
, 0, sizeof(types
));
92 memset(args
, 0, sizeof(args
));
95 ports
= &Master::ports
;
97 const Port
*port
= Master::ports
.apropos(rtosc_argument(m
,0).s
);
103 //RTness not confirmed here
104 for(const Port
&p
:*ports
) {
105 if(strstr(p
.name
, needle
) != p
.name
|| !p
.name
)
108 args
[pos
++].s
= p
.name
;
110 if(p
.metadata
&& *p
.metadata
) {
111 args
[pos
].b
.data
= (unsigned char*) p
.metadata
;
112 auto tmp
= rtosc::Port::MetaContainer(p
.metadata
);
113 args
[pos
++].b
.len
= tmp
.length();
115 args
[pos
].b
.data
= (unsigned char*) NULL
;
116 args
[pos
++].b
.len
= 0;
122 //Reply to requester [wow, these messages are getting huge...]
123 char buffer
[1024*20];
124 size_t length
= rtosc_amessage(buffer
, sizeof(buffer
), "/paths", types
, args
);
126 lo_message msg
= lo_message_deserialise((void*)buffer
, length
, NULL
);
127 lo_address addr
= lo_address_new_from_url(url
);
129 lo_send_message(addr
, buffer
, msg
);
130 lo_address_free(addr
);
131 lo_message_free(msg
);
135 static int handler_function(const char *path
, const char *types
, lo_arg
**argv
,
136 int argc
, lo_message msg
, void *user_data
)
141 MiddleWare
*mw
= (MiddleWare
*)user_data
;
142 lo_address addr
= lo_message_get_source(msg
);
144 const char *tmp
= lo_address_get_url(addr
);
145 if(tmp
!= mw
->activeUrl()) {
146 mw
->transmitMsg("/echo", "ss", "OSC_URL", tmp
);
153 memset(buffer
, 0, sizeof(buffer
));
155 lo_message_serialise(msg
, path
, buffer
, &size
);
156 if(!strcmp(buffer
, "/path-search") && !strcmp("ss", rtosc_argument_string(buffer
))) {
157 path_search(buffer
, mw
->activeUrl().c_str());
158 } else if(buffer
[0]=='/' && strrchr(buffer
, '/')[1]) {
159 mw
->transmitMsg(rtosc::Ports::collapsePath(buffer
));
165 typedef void(*cb_t
)(void*,const char*);
167 //utility method (should be moved to a better location)
168 template <class T
, class V
>
169 std::vector
<T
> keys(const std::map
<T
,V
> &m
)
173 vec
.push_back(kv
.first
);
178 /*****************************************************************************
179 * Memory Deallocation *
180 *****************************************************************************/
181 void deallocate(const char *str
, void *v
)
183 //printf("deallocating a '%s' at '%p'\n", str, v);
184 if(!strcmp(str
, "Part"))
186 else if(!strcmp(str
, "Master"))
188 else if(!strcmp(str
, "fft_t"))
190 else if(!strcmp(str
, "KbmInfo"))
192 else if(!strcmp(str
, "SclInfo"))
194 else if(!strcmp(str
, "Microtonal"))
195 delete (Microtonal
*)v
;
197 fprintf(stderr
, "Unknown type '%s', leaking pointer %p!!\n", str
, v
);
201 /*****************************************************************************
203 *****************************************************************************/
205 void preparePadSynth(string path
, PADnoteParameters
*p
, rtosc::RtData
&d
)
207 //printf("preparing padsynth parameters\n");
208 assert(!path
.empty());
212 unsigned num
= p
->sampleGenerator([&path
,&d
]
213 (unsigned N
, PADnoteParameters::Sample
&s
)
215 //printf("sending info to '%s'\n",
216 // (path+to_s(N)).c_str());
217 d
.chain((path
+to_s(N
)).c_str(), "ifb",
218 s
.size
, s
.basefreq
, sizeof(float*), &s
.smp
);
219 }, []{return false;}, 1);
221 std::mutex rtdata_mutex
;
222 unsigned num
= p
->sampleGenerator([&rtdata_mutex
, &path
,&d
]
223 (unsigned N
, PADnoteParameters::Sample
&s
)
225 //printf("sending info to '%s'\n",
226 // (path+to_s(N)).c_str());
228 d
.chain((path
+to_s(N
)).c_str(), "ifb",
229 s
.size
, s
.basefreq
, sizeof(float*), &s
.smp
);
230 rtdata_mutex
.unlock();
231 }, []{return false;});
234 //clear out unused samples
235 for(unsigned i
= num
; i
< PAD_MAX_SAMPLES
; ++i
) {
236 d
.chain((path
+to_s(i
)).c_str(), "ifb",
237 0, 440.0f
, sizeof(float*), NULL
);
241 /******************************************************************************
242 * Non-RealTime Object Store *
245 * Storage For Objects which need to be interfaced with outside the realtime *
246 * thread (aka they have long lived operations which can be done out-of-band) *
248 * - OscilGen instances as prepare() cannot be done in realtime and PAD *
249 * depends on these instances *
250 * - PADnoteParameter instances as applyparameters() cannot be done in *
253 * These instances are collected on every part change and kit change *
254 ******************************************************************************/
257 std::map
<std::string
, void*> objmap
;
259 void extractMaster(Master
*master
)
261 for(int i
=0; i
< NUM_MIDI_PARTS
; ++i
) {
262 extractPart(master
->part
[i
], i
);
266 void extractPart(Part
*part
, int i
)
268 for(int j
=0; j
< NUM_KIT_ITEMS
; ++j
) {
269 auto &obj
= part
->kit
[j
];
270 extractAD(obj
.adpars
, i
, j
);
271 extractPAD(obj
.padpars
, i
, j
);
275 void extractAD(ADnoteParameters
*adpars
, int i
, int j
)
277 std::string base
= "/part"+to_s(i
)+"/kit"+to_s(j
)+"/";
278 for(int k
=0; k
<NUM_VOICES
; ++k
) {
279 std::string nbase
= base
+"adpars/VoicePar"+to_s(k
)+"/";
281 auto &nobj
= adpars
->VoicePar
[k
];
282 objmap
[nbase
+"OscilSmp/"] = nobj
.OscilSmp
;
283 objmap
[nbase
+"FMSmp/"] = nobj
.FMSmp
;
285 objmap
[nbase
+"OscilSmp/"] = nullptr;
286 objmap
[nbase
+"FMSmp/"] = nullptr;
291 void extractPAD(PADnoteParameters
*padpars
, int i
, int j
)
293 std::string base
= "/part"+to_s(i
)+"/kit"+to_s(j
)+"/";
294 for(int k
=0; k
<NUM_VOICES
; ++k
) {
296 objmap
[base
+"padpars/"] = padpars
;
297 objmap
[base
+"padpars/oscilgen/"] = padpars
->oscilgen
;
299 objmap
[base
+"padpars/"] = nullptr;
300 objmap
[base
+"padpars/oscilgen/"] = nullptr;
310 bool has(std::string loc
)
312 return objmap
.find(loc
) != objmap
.end();
315 void *get(std::string loc
)
320 void handleOscil(const char *msg
, rtosc::RtData
&d
) {
321 string
obj_rl(d
.message
, msg
);
322 void *osc
= get(obj_rl
);
324 strcpy(d
.loc
, obj_rl
.c_str());
326 OscilGen::non_realtime_ports
.dispatch(msg
, d
);
328 void handlePad(const char *msg
, rtosc::RtData
&d
) {
329 string
obj_rl(d
.message
, msg
);
330 void *pad
= get(obj_rl
);
331 if(!strcmp(msg
, "prepare")) {
332 preparePadSynth(obj_rl
, (PADnoteParameters
*)pad
, d
);
334 d
.reply((obj_rl
+"needPrepare").c_str(), "F");
338 strcpy(d
.loc
, obj_rl
.c_str());
340 PADnoteParameters::non_realtime_ports
.dispatch(msg
, d
);
341 if(rtosc_narguments(msg
)) {
342 if(!strcmp(msg
, "oscilgen/prepare"))
345 d
.reply((obj_rl
+"needPrepare").c_str(), "T");
352 /******************************************************************************
353 * Realtime Parameter Store *
355 * Storage for AD/PAD/SUB parameters which are allocated as needed by kits. *
356 * Two classes of events affect this: *
357 * 1. When a message to enable a kit is observed, then the kit is allocated *
358 * and sent prior to the enable message. *
359 * 2. When a part is allocated all part information is rebuilt *
361 * (NOTE pointers aren't really needed here, just booleans on whether it has *
363 * This may be later utilized for copy/paste support *
364 ******************************************************************************/
369 memset(add
, 0, sizeof(add
));
370 memset(pad
, 0, sizeof(pad
));
371 memset(sub
, 0, sizeof(sub
));
374 void extractPart(Part
*part
, int i
)
376 for(int j
=0; j
< NUM_KIT_ITEMS
; ++j
) {
377 auto kit
= part
->kit
[j
];
378 add
[i
][j
] = kit
.adpars
;
379 sub
[i
][j
] = kit
.subpars
;
380 pad
[i
][j
] = kit
.padpars
;
384 ADnoteParameters
*add
[NUM_MIDI_PARTS
][NUM_KIT_ITEMS
];
385 SUBnoteParameters
*sub
[NUM_MIDI_PARTS
][NUM_KIT_ITEMS
];
386 PADnoteParameters
*pad
[NUM_MIDI_PARTS
][NUM_KIT_ITEMS
];
389 //XXX perhaps move this to Nio
390 //(there needs to be some standard Nio stub file for this sort of stuff)
394 rtosc::Ports ports
= {
395 {"sink-list:", 0, 0, [](const char *, rtosc::RtData
&d
) {
396 auto list
= Nio::getSinks();
397 char *ret
= rtosc_splat(d
.loc
, list
);
401 {"source-list:", 0, 0, [](const char *, rtosc::RtData
&d
) {
402 auto list
= Nio::getSources();
403 char *ret
= rtosc_splat(d
.loc
, list
);
407 {"source::s", 0, 0, [](const char *msg
, rtosc::RtData
&d
) {
408 if(rtosc_narguments(msg
) == 0)
409 d
.reply(d
.loc
, "s", Nio::getSource().c_str());
411 Nio::setSource(rtosc_argument(msg
,0).s
);}},
412 {"sink::s", 0, 0, [](const char *msg
, rtosc::RtData
&d
) {
413 if(rtosc_narguments(msg
) == 0)
414 d
.reply(d
.loc
, "s", Nio::getSink().c_str());
416 Nio::setSink(rtosc_argument(msg
,0).s
);}},
429 Config
* const config
;
430 MiddleWareImpl(MiddleWare
*mw
, SYNTH_T synth
, Config
* config
,
432 ~MiddleWareImpl(void);
434 //Check offline vs online mode in plugins
435 void heartBeat(Master
*m
);
436 int64_t start_time_sec
;
437 int64_t start_time_nsec
;
440 //Apply function while parameters are write locked
441 void doReadOnlyOp(std::function
<void()> read_only_fn
);
442 void doReadOnlyOpPlugin(std::function
<void()> read_only_fn
);
443 bool doReadOnlyOpNormal(std::function
<void()> read_only_fn
, bool canfail
=false);
445 void savePart(int npart
, const char *filename
)
447 //Copy is needed as filename WILL get trashed during the rest of the run
448 std::string fname
= filename
;
449 //printf("saving part(%d,'%s')\n", npart, filename);
450 doReadOnlyOp([this,fname
,npart
](){
451 int res
= master
->part
[npart
]->saveXML(fname
.c_str());
453 /*printf("results: '%s' '%d'\n",fname.c_str(), res);*/});
456 void loadPendingBank(int par
, Bank
&bank
)
458 if(((unsigned int)par
< bank
.banks
.size())
459 && (bank
.banks
[par
].dir
!= bank
.bankfiletitle
))
460 bank
.loadbank(bank
.banks
[par
].dir
);
463 void loadPart(int npart
, const char *filename
, Master
*master
)
465 actual_load
[npart
]++;
467 if(actual_load
[npart
] != pending_load
[npart
])
469 assert(actual_load
[npart
] <= pending_load
[npart
]);
471 //load part in async fashion when possible
473 auto alloc
= std::async(std::launch::async
,
474 [master
,filename
,this,npart
](){
475 Part
*p
= new Part(*master
->memory
, synth
,
477 config
->cfg
.GzipCompression
,
478 config
->cfg
.Interpolation
,
479 &master
->microtonal
, master
->fft
, &master
->watcher
,
480 ("/part"+to_s(npart
)+"/").c_str());
481 if(p
->loadXMLinstrument(filename
))
482 fprintf(stderr
, "Warning: failed to load part<%s>!\n", filename
);
484 auto isLateLoad
= [this,npart
]{
485 return actual_load
[npart
] != pending_load
[npart
];
488 p
->applyparameters(isLateLoad
);
493 while(alloc
.wait_for(std::chrono::seconds(0)) != std::future_status::ready
) {
498 Part
*p
= alloc
.get();
500 Part
*p
= new Part(*master
->memory
, synth
, master
->time
,
501 config
->cfg
.GzipCompression
,
502 config
->cfg
.Interpolation
,
503 &master
->microtonal
, master
->fft
);
505 if(p
->loadXMLinstrument(filename
))
506 fprintf(stderr
, "Warning: failed to load part<%s>!\n", filename
);
508 auto isLateLoad
= [this,npart
]{
509 return actual_load
[npart
] != pending_load
[npart
];
512 p
->applyparameters(isLateLoad
);
515 obj_store
.extractPart(p
, npart
);
516 kits
.extractPart(p
, npart
);
518 //Give it to the backend and wait for the old part to return for
520 parent
->transmitMsg("/load-part", "ib", npart
, sizeof(Part
*), &p
);
521 GUI::raiseUi(ui
, "/damage", "s", ("/part"+to_s(npart
)+"/").c_str());
524 //Load a new cleared Part instance
525 void loadClearPart(int npart
)
530 Part
*p
= new Part(*master
->memory
, synth
,
532 config
->cfg
.GzipCompression
,
533 config
->cfg
.Interpolation
,
534 &master
->microtonal
, master
->fft
);
535 p
->applyparameters();
536 obj_store
.extractPart(p
, npart
);
537 kits
.extractPart(p
, npart
);
539 //Give it to the backend and wait for the old part to return for
541 parent
->transmitMsg("/load-part", "ib", npart
, sizeof(Part
*), &p
);
542 GUI::raiseUi(ui
, "/damage", "s", ("/part" + to_s(npart
) + "/").c_str());
545 //Well, you don't get much crazier than changing out all of your RT
546 //structures at once... TODO error handling
547 void loadMaster(const char *filename
)
549 Master
*m
= new Master(synth
, config
);
553 if ( m
->loadXML(filename
) ) {
557 m
->applyparameters();
560 //Update resource locator table
565 //Give it to the backend and wait for the old part to return for
567 parent
->transmitMsg("/load-master", "b", sizeof(Master
*), &m
);
570 void loadXsz(const char *filename
, rtosc::RtData
&d
)
572 Microtonal
*micro
= new Microtonal(master
->gzip_compression
);
573 int err
= micro
->loadXML(filename
);
575 d
.reply("/alert", "s", "Error: Could not load the xsz file.");
578 d
.chain("/microtonal/paste", "b", sizeof(void*), µ
);
581 void saveXsz(const char *filename
, rtosc::RtData
&d
)
584 doReadOnlyOp([this,filename
,&err
](){
585 err
= master
->microtonal
.saveXML(filename
);});
587 d
.reply("/alert", "s", "Error: Could not save the xsz file.");
590 void loadScl(const char *filename
, rtosc::RtData
&d
)
592 SclInfo
*scl
= new SclInfo
;
593 int err
=Microtonal::loadscl(*scl
, filename
);
595 d
.reply("/alert", "s", "Error: Could not load the scl file.");
598 d
.chain("/microtonal/paste_scl", "b", sizeof(void*), &scl
);
601 void loadKbm(const char *filename
, rtosc::RtData
&d
)
603 KbmInfo
*kbm
= new KbmInfo
;
604 int err
=Microtonal::loadkbm(*kbm
, filename
);
606 d
.reply("/alert", "s", "Error: Could not load the kbm file.");
609 d
.chain("/microtonal/paste_kbm", "b", sizeof(void*), &kbm
);
612 void updateResources(Master
*m
)
615 obj_store
.extractMaster(m
);
616 for(int i
=0; i
<NUM_MIDI_PARTS
; ++i
)
617 kits
.extractPart(m
->part
[i
], i
);
620 //If currently broadcasting messages
621 bool broadcast
= false;
622 //If message should be forwarded through snoop ports
623 bool forward
= false;
624 //if message is in order or out-of-order execution
625 bool in_order
= false;
626 //If accepting undo events as user driven
627 bool recording_undo
= true;
628 void bToUhandle(const char *rtmsg
);
633 while(lo_server_recv_noblock(server
, 0));
635 while(bToU
->hasNext()) {
636 const char *rtmsg
= bToU
->read();
640 while(auto *m
= multi_thread_source
.read()) {
641 handleMsg(m
->memory
);
642 multi_thread_source
.free(m
);
649 //XXX This might have problems with a master swap operation
651 master
->runOSC(0,0,true);
656 void kitEnable(const char *msg
);
657 void kitEnable(int part
, int kit
, int type
);
659 // Handle an event with special cases
660 void handleMsg(const char *msg
);
662 void write(const char *path
, const char *args
, ...);
663 void write(const char *path
, const char *args
, va_list va
);
665 void currentUrl(string addr
)
668 known_remotes
.insert(addr
);
671 // Send a message to a remote client
672 void sendToRemote(const char *msg
, std::string dest
);
673 // Send a message to the current remote client
674 void sendToCurrentRemote(const char *msg
)
676 sendToRemote(msg
, in_order
? curr_url
: last_url
);
678 // Broadcast a message to all listening remote clients
679 void broadcastToRemote(const char *msg
);
683 * Provides a mapping for non-RT objects stored inside the backend
684 * - Oscilgen almost all parameters can be safely set
685 * - Padnote can have anything set on its oscilgen and a very small set
686 * of general parameters
688 NonRtObjStore obj_store
;
690 //This code will own the pointer to master, be prepared for odd things if
691 //this assumption is broken
694 //The ONLY means that any chunk of UI code should have for interacting with the
696 Fl_Osc_Interface
*osc
;
697 //Synth Engine Parameters
700 //Callback When Waiting on async events
704 //General UI callback
709 std::atomic_int pending_load
[NUM_MIDI_PARTS
];
710 std::atomic_int actual_load
[NUM_MIDI_PARTS
];
713 rtosc::UndoHistory undo
;
716 //rtosc::MidiMappernRT midi_mapper;
718 //Link To the Realtime
719 rtosc::ThreadLink
*bToU
;
720 rtosc::ThreadLink
*uToB
;
722 //Link to the unknown
723 MultiQueue multi_thread_source
;
727 string last_url
, curr_url
;
728 std::set
<string
> known_remotes
;
730 //Synthesis Rate Parameters
733 PresetsStore presetsstore
;
735 CallbackRepeater autoSave
;
738 /*****************************************************************************
739 * Data Object for Non-RT Class Dispatch *
740 *****************************************************************************/
742 class MwDataObj
:public rtosc::RtData
745 MwDataObj(MiddleWareImpl
*mwi_
)
748 loc
= new char[loc_size
];
749 memset(loc
, 0, loc_size
);
750 buffer
= new char[4*4096];
751 memset(buffer
, 0, 4*4096);
763 //Replies and broadcasts go to the remote
765 //Chain calls repeat the call into handle()
767 //Forward calls send the message directly to the realtime
768 virtual void reply(const char *path
, const char *args
, ...) override
770 //printf("reply building '%s'\n", path);
773 if(!strcmp(path
, "/forward")) { //forward the information to the backend
775 path
= va_arg(va
, const char *);
776 rtosc_vmessage(buffer
,4*4096,path
,args
,va
);
778 rtosc_vmessage(buffer
,4*4096,path
,args
,va
);
783 virtual void replyArray(const char *path
, const char *args
, rtosc_arg_t
*argd
) override
785 //printf("reply building '%s'\n", path);
786 if(!strcmp(path
, "/forward")) { //forward the information to the backend
788 rtosc_amessage(buffer
,4*4096,path
,args
,argd
);
790 rtosc_amessage(buffer
,4*4096,path
,args
,argd
);
794 virtual void reply(const char *msg
) override
{
795 mwi
->sendToCurrentRemote(msg
);
797 //virtual void broadcast(const char *path, const char *args, ...){(void)path;(void)args;};
798 //virtual void broadcast(const char *msg){(void)msg;};
800 virtual void chain(const char *msg
) override
803 // printf("chain call on <%s>\n", msg);
807 virtual void chain(const char *path
, const char *args
, ...) override
812 rtosc_vmessage(buffer
,4*4096,path
,args
,va
);
817 virtual void forward(const char *) override
828 static std::vector
<std::string
> getFiles(const char *folder
, bool finddir
)
830 DIR *dir
= opendir(folder
);
837 std::vector
<string
> files
;
839 while((fn
= readdir(dir
))) {
841 bool is_dir
= fn
->d_type
& DT_DIR
;
842 //it could still be a symbolic link
844 string path
= string(folder
) + "/" + fn
->d_name
;
846 memset((void*)&buf
, 0, sizeof(buf
));
847 int err
= stat(path
.c_str(), &buf
);
849 printf("[Zyn:Error] stat cannot handle <%s>:%d\n", path
.c_str(), err
);
850 if(S_ISDIR(buf
.st_mode
)) {
855 std::string darn_windows
= folder
+ std::string("/") + std::string(fn
->d_name
);
856 //printf("attr on <%s> => %x\n", darn_windows.c_str(), GetFileAttributes(darn_windows.c_str()));
857 //printf("desired mask = %x\n", mask);
858 //printf("error = %x\n", INVALID_FILE_ATTRIBUTES);
859 bool is_dir
= GetFileAttributes(darn_windows
.c_str()) & FILE_ATTRIBUTE_DIRECTORY
;
861 if(finddir
== is_dir
&& strcmp(".", fn
->d_name
))
862 files
.push_back(fn
->d_name
);
866 std::sort(begin(files
), end(files
));
872 static int extractInt(const char *msg
)
874 const char *mm
= msg
;
875 while(*mm
&& !isdigit(*mm
)) ++mm
;
881 static const char *chomp(const char *msg
)
883 while(*msg
&& *msg
!='/') ++msg
; \
884 msg
= *msg
? msg
+1 : msg
;
890 #define rBegin [](const char *msg, RtData &d) { (void)msg;(void)d;\
891 rObject &impl = *((rObject*)d.obj);(void)impl;
893 /*****************************************************************************
896 * Banks and presets in general are not classed as realtime safe *
898 * The supported operations are: *
901 * - Refresh List of Banks *
902 *****************************************************************************/
903 extern const rtosc::Ports bankPorts
;
904 const rtosc::Ports bankPorts
= {
908 impl
.rescanforbanks();
911 for(auto &elm
: impl
.banks
)
912 d
.reply("/bank/bank_select", "iss", i
++, elm
.name
.c_str(), elm
.dir
.c_str());
913 d
.reply("/bank/bank_select", "i", impl
.bankpos
);
915 impl
.loadbank(impl
.banks
[0].dir
);
918 for(int i
=0; i
<BANK_SIZE
; ++i
) {
919 d
.reply("/bankview", "iss",
920 i
, impl
.ins
[i
].name
.c_str(),
921 impl
.ins
[i
].filename
.c_str());
924 //Clear all bank slots
925 for(int i
=0; i
<BANK_SIZE
; ++i
) {
926 d
.reply("/bankview", "iss", i
, "", "");
932 #define MAX_BANKS 256
933 char types
[MAX_BANKS
*2+1]={0};
934 rtosc_arg_t args
[MAX_BANKS
*2];
936 for(auto &elm
: impl
.banks
) {
937 types
[i
] = types
[i
+ 1] = 's';
938 args
[i
++].s
= elm
.name
.c_str();
939 args
[i
++].s
= elm
.dir
.c_str();
941 d
.replyArray("/bank/bank_list", types
, args
);
946 const char *types
[17];
949 types
[ 2] = "Chromatic Percussion";
951 types
[ 4] = "Guitar";
953 types
[ 6] = "Solo Strings";
954 types
[ 7] = "Ensemble";
958 types
[11] = "Synth Lead";
959 types
[12] = "Synth Pad";
960 types
[13] = "Synth Effects";
961 types
[14] = "Ethnic";
962 types
[15] = "Percussive";
963 types
[16] = "Sound Effects";
965 rtosc_arg_t args
[17];
966 for(int i
=0; i
<17; ++i
) {
968 args
[i
].s
= types
[i
];
970 d
.replyArray("/bank/types", t
, args
);
974 const char *types
[8];
980 types
[ 5] = "ambient";
985 for(int i
=0; i
<8; ++i
) {
987 args
[i
].s
= types
[i
];
989 d
.replyArray(d
.loc
, t
, args
);
993 const int loc
= extractInt(msg
);
997 d
.reply("/bankview", "iss",
998 loc
, impl
.ins
[loc
].name
.c_str(),
999 impl
.ins
[loc
].filename
.c_str());
1004 for(auto &elm
: impl
.banks
)
1005 d
.reply("/bank/bank_select", "iss", i
++, elm
.name
.c_str(), elm
.dir
.c_str());
1007 {"bank_select::i", 0, 0,
1009 if(rtosc_narguments(msg
)) {
1010 const int pos
= rtosc_argument(msg
, 0).i
;
1011 d
.reply(d
.loc
, "i", pos
);
1012 if(impl
.bankpos
!= pos
) {
1014 impl
.loadbank(impl
.banks
[pos
].dir
);
1017 for(int i
=0; i
<BANK_SIZE
; ++i
)
1018 d
.reply("/bankview", "iss",
1019 i
, impl
.ins
[i
].name
.c_str(),
1020 impl
.ins
[i
].filename
.c_str());
1023 d
.reply("/bank/bank_select", "i", impl
.bankpos
);
1025 {"rename_slot:is", 0, 0,
1027 const int slot
= rtosc_argument(msg
, 0).i
;
1028 const char *name
= rtosc_argument(msg
, 1).s
;
1029 const int err
= impl
.setname(slot
, name
, -1);
1031 d
.reply("/alert", "s",
1032 "Failed To Rename Bank Slot, please check file permissions");
1035 {"swap_slots:ii", 0, 0,
1037 const int slota
= rtosc_argument(msg
, 0).i
;
1038 const int slotb
= rtosc_argument(msg
, 1).i
;
1039 const int err
= impl
.swapslot(slota
, slotb
);
1041 d
.reply("/alert", "s",
1042 "Failed To Swap Bank Slots, please check file permissions");
1044 {"clear_slot:i", 0, 0,
1046 const int slot
= rtosc_argument(msg
, 0).i
;
1047 const int err
= impl
.clearslot(slot
);
1049 d
.reply("/alert", "s",
1050 "Failed To Clear Bank Slot, please check file permissions");
1054 if(rtosc_narguments(msg
))
1055 impl
.setMsb(rtosc_argument(msg
, 0).i
);
1057 d
.reply(d
.loc
, "i", impl
.bank_msb
);
1061 if(rtosc_narguments(msg
))
1062 impl
.setLsb(rtosc_argument(msg
, 0).i
);
1064 d
.reply(d
.loc
, "i", impl
.bank_lsb
);
1068 int err
= impl
.newbank(rtosc_argument(msg
, 0).s
);
1070 d
.reply("/alert", "s", "Error: Could not make a new bank (directory)..");
1074 auto res
= impl
.search(rtosc_argument(msg
, 0).s
);
1075 #define MAX_SEARCH 300
1076 char res_type
[MAX_SEARCH
+1] = {};
1077 rtosc_arg_t res_dat
[MAX_SEARCH
] = {};
1078 for(unsigned i
=0; i
<res
.size() && i
<MAX_SEARCH
; ++i
) {
1080 res_dat
[i
].s
= res
[i
].c_str();
1082 d
.replyArray("/bank/search_results", res_type
, res_dat
);
1087 auto res
= impl
.blist(rtosc_argument(msg
, 0).s
);
1088 #define MAX_SEARCH 300
1089 char res_type
[MAX_SEARCH
+1] = {};
1090 rtosc_arg_t res_dat
[MAX_SEARCH
] = {};
1091 for(unsigned i
=0; i
<res
.size() && i
<MAX_SEARCH
; ++i
) {
1093 res_dat
[i
].s
= res
[i
].c_str();
1095 d
.replyArray("/bank/search_results", res_type
, res_dat
);
1098 {"search_results:", 0, 0,
1100 d
.reply("/bank/search_results", "");
1104 /******************************************************************************
1105 * MiddleWare Snooping Ports *
1107 * These ports handle: *
1108 * - Events going to the realtime thread which cannot be safely handled *
1110 * - Events generated by the realtime thread which are not destined for a *
1112 ******************************************************************************/
1115 #define rObject MiddleWareImpl
1118 #define STRINGIFY2(a) #a
1119 #define STRINGIFY(a) STRINGIFY2(a)
1123 * BASE/part#/kititem#
1124 * BASE/part#/kit#/adpars/voice#/oscil/\*
1125 * BASE/part#/kit#/adpars/voice#/mod-oscil/\*
1126 * BASE/part#/kit#/padpars/prepare
1127 * BASE/part#/kit#/padpars/oscil/\*
1129 static rtosc::Ports middwareSnoopPorts
= {
1130 {"part#" STRINGIFY(NUM_MIDI_PARTS
)
1131 "/kit#" STRINGIFY(NUM_KIT_ITEMS
) "/adpars/VoicePar#"
1132 STRINGIFY(NUM_VOICES
) "/OscilSmp/", 0, &OscilGen::non_realtime_ports
,
1134 impl
.obj_store
.handleOscil(chomp(chomp(chomp(chomp(chomp(msg
))))), d
);
1136 {"part#" STRINGIFY(NUM_MIDI_PARTS
)
1137 "/kit#" STRINGIFY(NUM_KIT_ITEMS
)
1138 "/adpars/VoicePar#" STRINGIFY(NUM_VOICES
) "/FMSmp/", 0, &OscilGen::non_realtime_ports
,
1140 impl
.obj_store
.handleOscil(chomp(chomp(chomp(chomp(chomp(msg
))))), d
);
1142 {"part#" STRINGIFY(NUM_MIDI_PARTS
)
1143 "/kit#" STRINGIFY(NUM_KIT_ITEMS
) "/padpars/", 0, &PADnoteParameters::non_realtime_ports
,
1145 impl
.obj_store
.handlePad(chomp(chomp(chomp(msg
))), d
);
1147 {"bank/", 0, &bankPorts
,
1149 d
.obj
= &impl
.master
->bank
;
1150 bankPorts
.dispatch(chomp(msg
),d
);
1152 {"bank/save_to_slot:ii", 0, 0,
1154 const int part_id
= rtosc_argument(msg
, 0).i
;
1155 const int slot
= rtosc_argument(msg
, 1).i
;
1158 impl
.doReadOnlyOp([&impl
,slot
,part_id
,&err
](){
1159 err
= impl
.master
->bank
.savetoslot(slot
, impl
.master
->part
[part_id
]);});
1162 rtosc_message(buffer
, 1024, "/alert", "s",
1163 "Failed To Save To Bank Slot, please check file permissions");
1164 GUI::raiseUi(impl
.ui
, buffer
);
1167 {"config/", 0, &Config::ports
,
1169 d
.obj
= impl
.config
;
1170 Config::ports
.dispatch(chomp(msg
), d
);
1172 {"presets/", 0, &real_preset_ports
, [](const char *msg
, RtData
&d
) {
1173 MiddleWareImpl
*obj
= (MiddleWareImpl
*)d
.obj
;
1174 d
.obj
= (void*)obj
->parent
;
1175 real_preset_ports
.dispatch(chomp(msg
), d
);
1176 if(strstr(msg
, "paste") && rtosc_argument_string(msg
)[0] == 's')
1177 d
.reply("/damage", "s", rtosc_argument(msg
, 0).s
);
1179 {"io/", 0, &Nio::ports
, [](const char *msg
, RtData
&d
) {
1180 Nio::ports
.dispatch(chomp(msg
), d
);}},
1181 {"part*/kit*/{Padenabled,Ppadenabled,Psubenabled}:T:F", 0, 0,
1183 impl
.kitEnable(msg
);
1186 {"save_xlz:s", 0, 0,
1188 impl
.doReadOnlyOp([&]() {
1189 const char *file
= rtosc_argument(msg
, 0).s
;
1191 Master::saveAutomation(xml
, impl
.master
->automate
);
1192 xml
.saveXMLfile(file
, impl
.master
->gzip_compression
);
1195 {"load_xlz:s", 0, 0,
1197 const char *file
= rtosc_argument(msg
, 0).s
;
1199 xml
.loadXMLfile(file
);
1200 rtosc::AutomationMgr
*mgr
= new rtosc::AutomationMgr(16,4,8);
1201 mgr
->set_ports(Master::ports
);
1202 Master::loadAutomation(xml
, *mgr
);
1203 d
.chain("/automate/load-blob", "b", sizeof(void*), &mgr
);
1205 {"clear_xlz:", 0, 0,
1207 d
.chain("/automate/clear", "");
1210 {"load_xsz:s", 0, 0,
1212 const char *file
= rtosc_argument(msg
, 0).s
;
1213 impl
.loadXsz(file
, d
);
1215 {"save_xsz:s", 0, 0,
1217 const char *file
= rtosc_argument(msg
, 0).s
;
1218 impl
.saveXsz(file
, d
);
1220 {"load_scl:s", 0, 0,
1222 const char *file
= rtosc_argument(msg
, 0).s
;
1223 impl
.loadScl(file
, d
);
1225 {"load_kbm:s", 0, 0,
1227 const char *file
= rtosc_argument(msg
, 0).s
;
1228 impl
.loadKbm(file
, d
);
1230 {"save_xmz:s", 0, 0,
1232 const char *file
= rtosc_argument(msg
, 0).s
;
1233 //Copy is needed as filename WILL get trashed during the rest of the run
1234 impl
.doReadOnlyOp([&impl
,file
](){
1235 int res
= impl
.master
->saveXML(file
);
1238 {"save_xiz:is", 0, 0,
1240 const int part_id
= rtosc_argument(msg
,0).i
;
1241 const char *file
= rtosc_argument(msg
,1).s
;
1242 impl
.savePart(part_id
, file
);
1244 {"file_home_dir:", 0, 0,
1246 const char *home
= getenv("PWD");
1248 home
= getenv("HOME");
1250 home
= getenv("USERPROFILE");
1252 home
= getenv("HOMEPATH");
1256 string home_
= home
;
1258 if(home_
[home_
.length()-1] != '/')
1261 d
.reply(d
.loc
, "s", home_
.c_str());
1263 {"file_list_files:s", 0, 0,
1265 const char *folder
= rtosc_argument(msg
, 0).s
;
1267 auto files
= getFiles(folder
, false);
1269 const int N
= files
.size();
1270 rtosc_arg_t
*args
= new rtosc_arg_t
[N
];
1271 char *types
= new char[N
+1];
1273 for(int i
=0; i
<N
; ++i
) {
1274 args
[i
].s
= files
[i
].c_str();
1278 d
.replyArray(d
.loc
, types
, args
);
1282 {"file_list_dirs:s", 0, 0,
1284 const char *folder
= rtosc_argument(msg
, 0).s
;
1286 auto files
= getFiles(folder
, true);
1288 const int N
= files
.size();
1289 rtosc_arg_t
*args
= new rtosc_arg_t
[N
];
1290 char *types
= new char[N
+1];
1292 for(int i
=0; i
<N
; ++i
) {
1293 args
[i
].s
= files
[i
].c_str();
1297 d
.replyArray(d
.loc
, types
, args
);
1301 {"reload_auto_save:i", 0, 0,
1303 const int save_id
= rtosc_argument(msg
,0).i
;
1304 const string save_dir
= string(getenv("HOME")) + "/.local";
1305 const string save_file
= "zynaddsubfx-"+to_s(save_id
)+"-autosave.xmz";
1306 const string save_loc
= save_dir
+ "/" + save_file
;
1307 impl
.loadMaster(save_loc
.c_str());
1308 //XXX it would be better to remove the autosave after there is a new
1309 //autosave, but this method should work for non-immediate crashes :-|
1310 remove(save_loc
.c_str());
1312 {"delete_auto_save:i", 0, 0,
1314 const int save_id
= rtosc_argument(msg
,0).i
;
1315 const string save_dir
= string(getenv("HOME")) + "/.local";
1316 const string save_file
= "zynaddsubfx-"+to_s(save_id
)+"-autosave.xmz";
1317 const string save_loc
= save_dir
+ "/" + save_file
;
1318 remove(save_loc
.c_str());
1320 {"load_xmz:s", 0, 0,
1322 const char *file
= rtosc_argument(msg
, 0).s
;
1323 impl
.loadMaster(file
);
1324 d
.reply("/damage", "s", "/");
1326 {"reset_master:", 0, 0,
1328 impl
.loadMaster(NULL
);
1329 d
.reply("/damage", "s", "/");
1331 {"load_xiz:is", 0, 0,
1333 const int part_id
= rtosc_argument(msg
,0).i
;
1334 const char *file
= rtosc_argument(msg
,1).s
;
1335 impl
.pending_load
[part_id
]++;
1336 impl
.loadPart(part_id
, file
, impl
.master
);
1338 {"load-part:is", 0, 0,
1340 const int part_id
= rtosc_argument(msg
,0).i
;
1341 const char *file
= rtosc_argument(msg
,1).s
;
1342 impl
.pending_load
[part_id
]++;
1343 impl
.loadPart(part_id
, file
, impl
.master
);
1345 {"load-part:iss", 0, 0,
1347 const int part_id
= rtosc_argument(msg
,0).i
;
1348 const char *file
= rtosc_argument(msg
,1).s
;
1349 const char *name
= rtosc_argument(msg
,2).s
;
1350 impl
.pending_load
[part_id
]++;
1351 impl
.loadPart(part_id
, file
, impl
.master
);
1352 impl
.uToB
->write(("/part"+to_s(part_id
)+"/Pname").c_str(), "s",
1355 {"setprogram:i:c", 0, 0,
1357 Bank
&bank
= impl
.master
->bank
;
1358 const int slot
= rtosc_argument(msg
, 0).i
+ 128*bank
.bank_lsb
;
1359 if(slot
< BANK_SIZE
) {
1360 impl
.pending_load
[0]++;
1361 impl
.loadPart(0, impl
.master
->bank
.ins
[slot
].filename
.c_str(), impl
.master
);
1362 impl
.uToB
->write("/part0/Pname", "s", impl
.master
->bank
.ins
[slot
].name
.c_str());
1365 {"part#16/clear:", 0, 0,
1367 int id
= extractInt(msg
);
1368 impl
.loadClearPart(id
);
1369 d
.reply("/damage", "s", ("/part"+to_s(id
)).c_str());
1373 impl
.undo
.seekHistory(-1);
1377 impl
.undo
.seekHistory(+1);
1379 //port to observe the midi mappings
1380 //{"midi-learn-values:", 0, 0,
1382 // auto &midi = impl.midi_mapper;
1383 // auto key = keys(midi.inv_map);
1384 // //cc-id, path, min, max
1385 //#define MAX_MIDI 32
1386 // rtosc_arg_t args[MAX_MIDI*4];
1387 // char argt[MAX_MIDI*4+1] = {};
1389 // for(unsigned i=0; i<key.size() && i<MAX_MIDI; ++i) {
1390 // auto val = midi.inv_map[key[i]];
1391 // if(std::get<1>(val) == -1)
1393 // argt[4*j+0] = 'i';
1394 // args[4*j+0].i = std::get<1>(val);
1395 // argt[4*j+1] = 's';
1396 // args[4*j+1].s = key[i].c_str();
1397 // argt[4*j+2] = 'i';
1398 // args[4*j+2].i = 0;
1399 // argt[4*j+3] = 'i';
1400 // args[4*j+3].i = 127;
1404 // d.replyArray(d.loc, argt, args);
1409 // string addr = rtosc_argument(msg, 0).s;
1410 // auto &midi = impl.midi_mapper;
1411 // auto map = midi.getMidiMappingStrings();
1412 // if(map.find(addr) != map.end())
1413 // midi.map(addr.c_str(), false);
1415 // midi.map(addr.c_str(), true);
1417 //{"unlearn:s", 0, 0,
1419 // string addr = rtosc_argument(msg, 0).s;
1420 // auto &midi = impl.midi_mapper;
1421 // auto map = midi.getMidiMappingStrings();
1422 // midi.unMap(addr.c_str(), false);
1423 // midi.unMap(addr.c_str(), true);
1425 //drop this message into the abyss
1426 {"ui/title:", 0, 0, [](const char *msg
, RtData
&d
) {}},
1427 {"quit:", 0, 0, [](const char *, RtData
&) {Pexitprogram
= 1;}},
1430 static rtosc::Ports middlewareReplyPorts
= {
1433 const char *type
= rtosc_argument(msg
, 0).s
;
1434 const char *url
= rtosc_argument(msg
, 1).s
;
1435 if(!strcmp(type
, "OSC_URL"))
1436 impl
.currentUrl(url
);
1440 const char *type
= rtosc_argument(msg
, 0).s
;
1441 void *ptr
= *(void**)rtosc_argument(msg
, 1).b
.data
;
1442 deallocate(type
, ptr
);
1444 {"request-memory:", 0, 0,
1446 //Generate out more memory for the RT memory pool
1448 size_t N
= 5*1024*1024;
1449 void *mem
= malloc(N
);
1450 impl
.uToB
->write("/add-rt-memory", "bi", sizeof(void*), &mem
, N
);
1452 {"setprogram:cc:ii", 0, 0,
1454 Bank
&bank
= impl
.master
->bank
;
1455 const int part
= rtosc_argument(msg
, 0).i
;
1456 const int program
= rtosc_argument(msg
, 1).i
+ 128*bank
.bank_lsb
;
1457 impl
.loadPart(part
, impl
.master
->bank
.ins
[program
].filename
.c_str(), impl
.master
);
1458 impl
.uToB
->write(("/part"+to_s(part
)+"/Pname").c_str(), "s", impl
.master
->bank
.ins
[program
].name
.c_str());
1462 impl
.loadPendingBank(rtosc_argument(msg
,0).i
, impl
.master
->bank
);
1464 {"undo_pause:", 0, 0, rBegin
; impl
.recording_undo
= false; rEnd
},
1465 {"undo_resume:", 0, 0, rBegin
; impl
.recording_undo
= true; rEnd
},
1466 {"undo_change", 0, 0,
1468 if(impl
.recording_undo
)
1469 impl
.undo
.recordEvent(msg
);
1471 {"broadcast:", 0, 0, rBegin
; impl
.broadcast
= true; rEnd
},
1472 {"forward:", 0, 0, rBegin
; impl
.forward
= true; rEnd
},
1477 /******************************************************************************
1478 * MiddleWare Implementation *
1479 ******************************************************************************/
1481 MiddleWareImpl::MiddleWareImpl(MiddleWare
*mw
, SYNTH_T synth_
,
1482 Config
* config
, int preferrred_port
)
1483 :parent(mw
), config(config
), ui(nullptr), synth(std::move(synth_
)),
1484 presetsstore(*config
), autoSave(-1, [this]() {
1485 auto master
= this->master
;
1486 this->doReadOnlyOp([master
](){
1487 std::string home
= getenv("HOME");
1488 std::string save_file
= home
+"/.local/zynaddsubfx-"+to_s(getpid())+"-autosave.xmz";
1489 printf("doing an autosave <%s>...\n", save_file
.c_str());
1490 int res
= master
->saveXML(save_file
.c_str());
1493 bToU
= new rtosc::ThreadLink(4096*2*16,1024/16);
1494 uToB
= new rtosc::ThreadLink(4096*2*16,1024/16);
1495 //midi_mapper.base_ports = &Master::ports;
1496 //midi_mapper.rt_cb = [this](const char *msg){handleMsg(msg);};
1497 if(preferrred_port
!= -1)
1498 server
= lo_server_new_with_proto(to_s(preferrred_port
).c_str(),
1499 LO_UDP
, liblo_error_cb
);
1501 server
= lo_server_new_with_proto(NULL
, LO_UDP
, liblo_error_cb
);
1504 lo_server_add_method(server
, NULL
, NULL
, handler_function
, mw
);
1505 fprintf(stderr
, "lo server running on %d\n", lo_server_get_port(server
));
1507 fprintf(stderr
, "lo server could not be started :-/\n");
1510 //dummy callback for starters
1511 cb
= [](void*, const char*){};
1515 master
= new Master(synth
, config
);
1516 master
->bToU
= bToU
;
1517 master
->uToB
= uToB
;
1518 osc
= GUI::genOscInterface(mw
);
1520 //Grab objects of interest from master
1521 updateResources(master
);
1524 for(int i
=0; i
< NUM_MIDI_PARTS
; ++i
) {
1525 pending_load
[i
] = 0;
1530 undo
.setCallback([this](const char *msg
) {
1531 // printf("undo callback <%s>\n", msg);
1533 rtosc_message(buf
, 1024, "/undo_pause","");
1536 rtosc_message(buf
, 1024, "/undo_resume","");
1540 //Setup starting time
1541 struct timespec time
;
1542 clock_gettime(CLOCK_MONOTONIC
, &time
);
1543 start_time_sec
= time
.tv_sec
;
1544 start_time_nsec
= time
.tv_nsec
;
1549 MiddleWareImpl::~MiddleWareImpl(void)
1553 lo_server_free(server
);
1562 /** Threading When Saving
1563 * ----------------------
1565 * Procedure Middleware:
1566 * 1) Middleware sends /freeze_state to backend
1567 * 2) Middleware waits on /state_frozen from backend
1568 * All intervening commands are held for out of order execution
1570 * At this time by the memory barrier we are guarenteed that all old
1571 * writes are done and assuming the freezing logic is sound, then it is
1572 * impossible for any other parameter to change at this time
1573 * 3) Middleware performs saving operation
1574 * 4) Middleware sends /thaw_state to backend
1575 * 5) Restore in order execution
1577 * Procedure Backend:
1578 * 1) Observe /freeze_state and disable all mutating events (MIDI CC)
1579 * 2) Run a memory release to ensure that all writes are complete
1580 * 3) Send /state_frozen to Middleware
1582 * 4) Observe /thaw_state and resume normal processing
1585 void MiddleWareImpl::doReadOnlyOp(std::function
<void()> read_only_fn
)
1588 uToB
->write("/freeze_state","");
1590 std::list
<const char *> fico
;
1592 while(tries
++ < 10000) {
1593 if(!bToU
->hasNext()) {
1597 const char *msg
= bToU
->read();
1598 if(!strcmp("/state_frozen", msg
))
1600 size_t bytes
= rtosc_message_length(msg
, bToU
->buffer_size());
1601 char *save_buf
= new char[bytes
];
1602 memcpy(save_buf
, msg
, bytes
);
1603 fico
.push_back(save_buf
);
1606 assert(tries
< 10000);//if this happens, the backend must be dead
1608 std::atomic_thread_fence(std::memory_order_acquire
);
1610 //Now it is safe to do any read only operation
1613 //Now to resume normal operations
1614 uToB
->write("/thaw_state","");
1621 //Offline detection code:
1622 // - Assume that the audio callback should be run at least once every 50ms
1623 // - Atomically provide the number of ms since start to Master
1624 // - Every time middleware ticks provide a heart beat
1625 // - If when the heart beat is provided the backend is more than 200ms behind
1626 // the last heartbeat then it must be offline
1627 // - When marked offline the backend doesn't receive another heartbeat until it
1628 // registers the current beat that it's behind on
1629 void MiddleWareImpl::heartBeat(Master
*master
)
1632 //Last provided beat
1633 //Last acknowledged beat
1634 //Current offline status
1636 struct timespec time
;
1637 clock_gettime(CLOCK_MONOTONIC
, &time
);
1638 uint32_t now
= (time
.tv_sec
-start_time_sec
)*100 +
1639 (time
.tv_nsec
-start_time_nsec
)*1e-9*100;
1640 int32_t last_ack
= master
->last_ack
;
1641 int32_t last_beat
= master
->last_beat
;
1643 //everything is considered online for the first second
1648 if(last_beat
== last_ack
) {
1649 //XXX INSERT MESSAGE HERE ABOUT TRANSITION TO ONLINE
1652 //Send new heart beat
1653 master
->last_beat
= now
;
1656 //it's unquestionably alive
1657 if(last_beat
== last_ack
) {
1659 //Send new heart beat
1660 master
->last_beat
= now
;
1664 //it's pretty likely dead
1665 if(last_beat
-last_ack
> 0 && now
-last_beat
> 20) {
1666 //The backend has had 200 ms to acquire a new beat
1667 //The backend instead has an older beat
1668 //XXX INSERT MESSAGE HERE ABOUT TRANSITION TO OFFLINE
1673 //who knows if it's alive or not here, give it a few ms to acquire or
1679 void MiddleWareImpl::doReadOnlyOpPlugin(std::function
<void()> read_only_fn
)
1684 std::atomic_thread_fence(std::memory_order_acquire
);
1686 //Now it is safe to do any read only operation
1688 } else if(!doReadOnlyOpNormal(read_only_fn
, true)) {
1689 //check if we just transitioned to offline mode
1691 std::atomic_thread_fence(std::memory_order_acquire
);
1693 //Now it is safe to do any read only operation
1698 bool MiddleWareImpl::doReadOnlyOpNormal(std::function
<void()> read_only_fn
, bool canfail
)
1701 uToB
->write("/freeze_state","");
1703 std::list
<const char *> fico
;
1705 while(tries
++ < 2000) {
1706 if(!bToU
->hasNext()) {
1710 const char *msg
= bToU
->read();
1711 if(!strcmp("/state_frozen", msg
))
1713 size_t bytes
= rtosc_message_length(msg
, bToU
->buffer_size());
1714 char *save_buf
= new char[bytes
];
1715 memcpy(save_buf
, msg
, bytes
);
1716 fico
.push_back(save_buf
);
1720 //Now to resume normal operations
1721 uToB
->write("/thaw_state","");
1729 assert(tries
< 10000);//if this happens, the backend must be dead
1731 std::atomic_thread_fence(std::memory_order_acquire
);
1733 //Now it is safe to do any read only operation
1736 //Now to resume normal operations
1737 uToB
->write("/thaw_state","");
1745 void MiddleWareImpl::broadcastToRemote(const char *rtmsg
)
1747 //Always send to the local UI
1748 sendToRemote(rtmsg
, "GUI");
1750 //Send to remote UI if there's one listening
1751 for(auto rem
:known_remotes
)
1753 sendToRemote(rtmsg
, rem
);
1758 void MiddleWareImpl::sendToRemote(const char *rtmsg
, std::string dest
)
1760 if(!rtmsg
|| rtmsg
[0] != '/' || !rtosc_message_length(rtmsg
, -1)) {
1761 printf("[Warning] Invalid message in sendToRemote <%s>...\n", rtmsg
);
1765 //printf("sendToRemote(%s:%s,%s)\n", rtmsg, rtosc_argument_string(rtmsg),
1769 } else if(!dest
.empty()) {
1770 lo_message msg
= lo_message_deserialise((void*)rtmsg
,
1771 rtosc_message_length(rtmsg
, bToU
->buffer_size()), NULL
);
1773 printf("[ERROR] OSC to <%s> Failed To Parse In Liblo\n", rtmsg
);
1778 lo_address addr
= lo_address_new_from_url(dest
.c_str());
1780 lo_send_message(addr
, rtmsg
, msg
);
1781 lo_address_free(addr
);
1782 lo_message_free(msg
);
1787 * Handle all events coming from the backend
1789 * This includes forwarded events which need to be retransmitted to the backend
1790 * after the snooping code inspects the message
1792 void MiddleWareImpl::bToUhandle(const char *rtmsg
)
1794 //Verify Message isn't a known corruption bug
1795 assert(strcmp(rtmsg
, "/part0/kit0/Ppadenableda"));
1796 assert(strcmp(rtmsg
, "/ze_state"));
1798 //Dump Incomming Events For Debugging
1799 if(strcmp(rtmsg
, "/vu-meter") && false) {
1800 fprintf(stdout
, "%c[%d;%d;%dm", 0x1B, 0, 1 + 30, 0 + 40);
1801 fprintf(stdout
, "frontend[%c]: '%s'<%s>\n", forward
?'f':broadcast
?'b':'N',
1802 rtmsg
, rtosc_argument_string(rtmsg
));
1803 fprintf(stdout
, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
1807 //printf(".");fflush(stdout);
1810 middlewareReplyPorts
.dispatch(rtmsg
, d
, true);
1813 fprintf(stderr
, "[ERROR] Unexpected Null OSC In Zyn\n");
1818 //Normal message not captured by the ports
1819 if(d
.matches
== 0) {
1824 broadcastToRemote(rtmsg
);
1826 sendToCurrentRemote(rtmsg
);
1832 //Allocate kits on a as needed basis
1833 void MiddleWareImpl::kitEnable(const char *msg
)
1835 const string argv
= rtosc_argument_string(msg
);
1838 //Extract fields from:
1839 //BASE/part#/kit#/Pxxxenabled
1841 if(strstr(msg
, "Padenabled"))
1843 else if(strstr(msg
, "Ppadenabled"))
1845 else if(strstr(msg
, "Psubenabled"))
1850 const char *tmp
= strstr(msg
, "part");
1855 const int part
= atoi(tmp
+4);
1857 tmp
= strstr(msg
, "kit");
1862 const int kit
= atoi(tmp
+3);
1864 kitEnable(part
, kit
, type
);
1867 void MiddleWareImpl::kitEnable(int part
, int kit
, int type
)
1869 //printf("attempting a kit enable<%d,%d,%d>\n", part, kit, type);
1870 string url
= "/part"+to_s(part
)+"/kit"+to_s(kit
)+"/";
1872 if(type
== 0 && kits
.add
[part
][kit
] == NULL
) {
1873 ptr
= kits
.add
[part
][kit
] = new ADnoteParameters(synth
, master
->fft
,
1875 url
+= "adpars-data";
1876 obj_store
.extractAD(kits
.add
[part
][kit
], part
, kit
);
1877 } else if(type
== 1 && kits
.pad
[part
][kit
] == NULL
) {
1878 ptr
= kits
.pad
[part
][kit
] = new PADnoteParameters(synth
, master
->fft
,
1880 url
+= "padpars-data";
1881 obj_store
.extractPAD(kits
.pad
[part
][kit
], part
, kit
);
1882 } else if(type
== 2 && kits
.sub
[part
][kit
] == NULL
) {
1883 ptr
= kits
.sub
[part
][kit
] = new SUBnoteParameters(&master
->time
);
1884 url
+= "subpars-data";
1887 //Send the new memory
1889 uToB
->write(url
.c_str(), "b", sizeof(void*), &ptr
);
1894 * Handle all messages traveling to the realtime side.
1896 void MiddleWareImpl::handleMsg(const char *msg
)
1898 //Check for known bugs
1899 assert(msg
&& *msg
&& strrchr(msg
, '/')[1]);
1900 assert(strstr(msg
,"free") == NULL
|| strstr(rtosc_argument_string(msg
), "b") == NULL
);
1901 assert(strcmp(msg
, "/part0/Psysefxvol"));
1902 assert(strcmp(msg
, "/Penabled"));
1903 assert(strcmp(msg
, "part0/part0/Ppanning"));
1904 assert(strcmp(msg
, "sysefx0sysefx0/preset"));
1905 assert(strcmp(msg
, "/sysefx0preset"));
1906 assert(strcmp(msg
, "Psysefxvol0/part0"));
1908 if(strcmp("/get-vu", msg
) && false) {
1909 fprintf(stdout
, "%c[%d;%d;%dm", 0x1B, 0, 6 + 30, 0 + 40);
1910 fprintf(stdout
, "middleware: '%s':%s\n", msg
, rtosc_argument_string(msg
));
1911 fprintf(stdout
, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
1914 const char *last_path
= strrchr(msg
, '/');
1916 printf("Bad message in handleMsg() <%s>\n", msg
);
1922 middwareSnoopPorts
.dispatch(msg
, d
, true);
1924 //A message unmodified by snooping
1925 if(d
.matches
== 0 || d
.forwarded
) {
1926 //if(strcmp("/get-vu", msg)) {
1927 // printf("Message Continuing on<%s:%s>...\n", msg, rtosc_argument_string(msg));
1929 uToB
->raw_write(msg
);
1931 //printf("Message Handled<%s:%s>...\n", msg, rtosc_argument_string(msg));
1935 void MiddleWareImpl::write(const char *path
, const char *args
, ...)
1937 //We have a free buffer in the threadlink, so use it
1940 write(path
, args
, va
);
1944 void MiddleWareImpl::write(const char *path
, const char *args
, va_list va
)
1946 //printf("is that a '%s' I see there?\n", path);
1947 char *buffer
= uToB
->buffer();
1948 unsigned len
= uToB
->buffer_size();
1949 bool success
= rtosc_vmessage(buffer
, len
, path
, args
, va
);
1950 //printf("working on '%s':'%s'\n",path, args);
1955 warnx("Failed to write message to '%s'", path
);
1958 /******************************************************************************
1959 * MidleWare Forwarding Stubs *
1960 ******************************************************************************/
1961 MiddleWare::MiddleWare(SYNTH_T synth
, Config
* config
,
1963 :impl(new MiddleWareImpl(this, std::move(synth
), config
, preferred_port
))
1966 MiddleWare::~MiddleWare(void)
1971 void MiddleWare::updateResources(Master
*m
)
1973 impl
->updateResources(m
);
1976 Master
*MiddleWare::spawnMaster(void)
1978 assert(impl
->master
);
1979 assert(impl
->master
->uToB
);
1980 return impl
->master
;
1983 void MiddleWare::enableAutoSave(int interval_sec
)
1985 impl
->autoSave
.dt
= interval_sec
;
1988 int MiddleWare::checkAutoSave(void)
1990 //save spec zynaddsubfx-PID-autosave.xmz
1991 const std::string home
= getenv("HOME");
1992 const std::string save_dir
= home
+"/.local/";
1994 DIR *dir
= opendir(save_dir
.c_str());
2000 int reload_save
= -1;
2002 while((fn
= readdir(dir
))) {
2003 const char *filename
= fn
->d_name
;
2004 const char *prefix
= "zynaddsubfx-";
2006 //check for manditory prefix
2007 if(strstr(filename
, prefix
) != filename
)
2010 int id
= atoi(filename
+strlen(prefix
));
2012 bool in_use
= false;
2014 std::string proc_file
= "/proc/" + to_s(id
) + "/comm";
2015 std::ifstream
ifs(proc_file
);
2017 std::string comm_name
;
2019 in_use
= (comm_name
== "zynaddsubfx");
2033 void MiddleWare::removeAutoSave(void)
2035 std::string home
= getenv("HOME");
2036 std::string save_file
= home
+"/.local/zynaddsubfx-"+to_s(getpid())+"-autosave.xmz";
2037 remove(save_file
.c_str());
2040 Fl_Osc_Interface
*MiddleWare::spawnUiApi(void)
2045 void MiddleWare::tick(void)
2050 void MiddleWare::doReadOnlyOp(std::function
<void()> fn
)
2052 impl
->doReadOnlyOp(fn
);
2055 void MiddleWare::setUiCallback(void(*cb
)(void*,const char *), void *ui
)
2061 void MiddleWare::setIdleCallback(void(*cb
)(void*), void *ptr
)
2064 impl
->idle_ptr
= ptr
;
2067 void MiddleWare::transmitMsg(const char *msg
)
2069 impl
->handleMsg(msg
);
2072 void MiddleWare::transmitMsg(const char *path
, const char *args
, ...)
2077 if(rtosc_vmessage(buffer
,1024,path
,args
,va
))
2078 transmitMsg(buffer
);
2080 fprintf(stderr
, "Error in transmitMsg(...)\n");
2084 void MiddleWare::transmitMsg_va(const char *path
, const char *args
, va_list va
)
2087 if(rtosc_vmessage(buffer
, 1024, path
, args
, va
))
2088 transmitMsg(buffer
);
2090 fprintf(stderr
, "Error in transmitMsg(va)n");
2093 void MiddleWare::messageAnywhere(const char *path
, const char *args
, ...)
2095 auto *mem
= impl
->multi_thread_source
.alloc();
2097 fprintf(stderr
, "Middleware::messageAnywhere memory pool out of memory...\n");
2101 if(rtosc_vmessage(mem
->memory
,mem
->size
,path
,args
,va
))
2102 impl
->multi_thread_source
.write(mem
);
2104 fprintf(stderr
, "Middleware::messageAnywhere message too big...\n");
2105 impl
->multi_thread_source
.free(mem
);
2110 void MiddleWare::pendingSetBank(int bank
)
2112 impl
->bToU
->write("/setbank", "c", bank
);
2114 void MiddleWare::pendingSetProgram(int part
, int program
)
2116 impl
->pending_load
[part
]++;
2117 impl
->bToU
->write("/setprogram", "cc", part
, program
);
2120 std::string
MiddleWare::activeUrl(void)
2122 return impl
->last_url
;
2125 void MiddleWare::activeUrl(std::string u
)
2130 const SYNTH_T
&MiddleWare::getSynth(void) const
2135 const char* MiddleWare::getServerAddress(void) const
2138 return lo_server_get_url(impl
->server
);
2143 const PresetsStore
& MiddleWare::getPresetsStore() const
2145 return impl
->presetsstore
;
2148 PresetsStore
& MiddleWare::getPresetsStore()
2150 return impl
->presetsstore
;