1 /************************************************************************
2  FAUST Architecture File
3  Copyright (C) 2016-2021 GRAME, Centre National de Creation Musicale
4  ---------------------------------------------------------------------
5  This Architecture section is free software; you can redistribute it
6  and/or modify it under the terms of the GNU General Public License
7  as published by the Free Software Foundation; either version 3 of
8  the License, or (at your option) any later version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program; If not, see <http://www.gnu.org/licenses/>.
17 
18  EXCEPTION : As a special exception, you may create a larger work
19  that contains this FAUST architecture section and distribute
20  that work under terms of your choice, so long as this FAUST
21  architecture section is not modified.
22 
23  ************************************************************************/
24 
25 #include <libgen.h>
26 #include <iostream>
27 #include <fstream>
28 #include <sstream>
29 #include <string>
30 #include <vector>
31 #include <thread>
32 #include <mutex>
33 
34 #ifdef JACK
35 #include "faust/audio/jack-dsp.h"
36 #else
37 #include "faust/audio/coreaudio-dsp.h"
38 #include "faust/midi/rt-midi.h"
39 #include "faust/midi/RtMidi.cpp"
40 #endif
41 #include "faust/dsp/llvm-dsp.h"
42 #include "faust/dsp/interpreter-dsp.h"
43 #include "faust/dsp/dsp-adapter.h"
44 #include "faust/dsp/poly-dsp.h"
45 #include "faust/dsp/libfaust.h"
46 #include "faust/gui/meta.h"
47 #include "faust/gui/FUI.h"
48 #include "faust/gui/GTKUI.h"
49 #include "faust/gui/MidiUI.h"
50 #include "faust/gui/httpdUI.h"
51 #include "faust/gui/OSCUI.h"
52 #include "faust/gui/SoundUI.h"
53 #include "faust/misc.h"
54 
55 using namespace std;
56 
57 list<GUI*> GUI::fGuiList;
58 ztimedmap GUI::gTimedZoneMap;
59 
endWith(const string & str,const string & suffix)60 static bool endWith(const string& str, const string& suffix)
61 {
62     size_t i = str.rfind(suffix);
63     return (i != string::npos) && (i == (str.length() - suffix.length()));
64 }
65 
printList(const vector<string> & list)66 static void printList(const vector<string>& list)
67 {
68     for (size_t i = 0; i < list.size(); i++) {
69         cout << "item: " << list[i] << "\n";
70     }
71 }
72 
splitTarget(const string & target,string & triple,string & cpu)73 static void splitTarget(const string& target, string& triple, string& cpu)
74 {
75     size_t pos1 = target.find_first_of(':');
76     triple = target.substr(0, pos1);
77     if (pos1 != string::npos) {
78         cpu = target.substr(pos1 + 1);
79     }
80 }
81 
82 struct DynamicDSP {
83 
84     dsp_factory* fFactory = nullptr;
85     dsp* fDSP = nullptr;
86     MidiUI* fMIDIInterface = nullptr;
87     httpdUI* fHTTPDinterface = nullptr;
88     GUI* fInterface = nullptr;
89     GUI* fOSCinterface = nullptr;
90     FUI* fFInterface = nullptr;
91     SoundUI* fSoundinterface = nullptr;
92 #ifdef JACK
93     jackaudio_midi fAudio;
94 #else
95     coreaudio fAudio;
96 #endif
97     string fRCfilename;
98     bool is_llvm = false;
99 
100 #ifdef JACK
DynamicDSPDynamicDSP101     DynamicDSP(int argc, char* argv[], bool is_dsp_only = false)
102 #else
103     DynamicDSP(int argc, char* argv[], bool is_dsp_only = false):fAudio(44100, 512)
104 #endif
105     {
106         char name[256];
107         char filename[256];
108         char rcfilename[256];
109         char* home = getenv("HOME");
110         int nvoices = 0;
111         bool midi_sync = false;
112         string error_msg;
113 
114         snprintf(name, 255, "%s", basename(argv[0]));
115         snprintf(filename, 255, "%s", basename(argv[argc-1]));
116         snprintf(rcfilename, 255, "%s/.%s-%src", home, name, filename);
117         fRCfilename = rcfilename;
118 
119         is_llvm = isopt(argv, "-llvm");
120         bool is_interp = isopt(argv, "-interp");
121         bool is_midi = isopt(argv, "-midi");
122         bool is_osc = isopt(argv, "-osc");
123         bool is_all = isopt(argv, "-all");
124         bool is_generic = isopt(argv, "-generic");
125         bool is_httpd = isopt(argv, "-httpd");
126         bool is_resample = isopt(argv, "-resample");
127         bool is_double = isopt(argv, "-double");
128 
129         if (isopt(argv, "-h") || isopt(argv, "-help") || (!is_llvm && !is_interp)) {
130         #ifdef JACK
131             cout << "dynamic-jack-gtk [-llvm|interp] [-edit] [-generic] [-nvoices <num>] [-all] [-midi] [-osc] [-httpd] [-resample] [additional Faust options (-vec -vs 8...)] foo.dsp/foo.fbc/foo.ll/foo.bc/foo.mc" << endl;
132         #else
133             cout << "dynamic-coreaudio-gtk [-llvm|interp] [-edit] [-generic] [-nvoices <num>] [-all] [-midi] [-osc] [-httpd] [-resample] [additional Faust options (-vec -vs 8...)] foo.dsp/foo.fbc/foo.ll/foo.bc/foo.mc" << endl;
134         #endif
135             cout << "Use '-llvm' to use LLVM backend, using either .dsp, .ll, .bc or .mc files\n";
136             cout << "Use '-interp' to use Interpreter backend, using either .dsp or .fbc (Faust Byte Code) files\n";
137             cout << "Use '-edit' to start an edit/compile/run loop, using a foo.dsp kind of source file\n";
138             cout << "Use '-generic' to JIT for a generic CPU (otherwise 'native' mode is used)\n";
139             cout << "Use '-nvoices <num>' to produce a polyphonic self-contained DSP with <num> voices, ready to be used with MIDI or OSC\n";
140             cout << "Use '-all' to active the 'all voices always playing' mode when polyphony is used\n";
141             cout << "Use '-midi' to activate MIDI control\n";
142             cout << "Use '-osc' to activate OSC control\n";
143             cout << "Use '-httpd' to activate HTTP control\n";
144             cout << "Use '-resample' to resample soundfiles to the audio driver sample rate\n";
145             exit(EXIT_FAILURE);
146         }
147 
148         cout << "Libfaust version : " << getCLibFaustVersion() << endl;
149 
150         int argc1 = 0;
151         const char* argv1[64];
152 
153         cout << "Compiled with additional options : ";
154         for (int i = 1; i < argc-1; i++) {
155             if ((string(argv[i]) == "-llvm")
156                 || (string(argv[i]) == "-interp")
157                 || (string(argv[i]) == "-edit")
158                 || (string(argv[i]) == "-generic")
159                 || (string(argv[i]) == "-midi")
160                 || (string(argv[i]) == "-osc")
161                 || (string(argv[i]) == "-all")
162                 || (string(argv[i]) == "-httpd")
163                 || (string(argv[i]) == "-resample")) {
164                 continue;
165             } else if (string(argv[i]) == "-nvoices") {
166                 i++;
167                 continue;
168             }
169             argv1[argc1++] = argv[i];
170             cout << argv[i] << " ";
171         }
172         cout << endl;
173         argv1[argc1] = nullptr;  // NULL terminated argv
174 
175         if (is_llvm) {
176 
177             string opt_target;
178             if (is_generic) {
179                 string triple, cpu;
180                 splitTarget(getDSPMachineTarget(), triple, cpu);
181                 opt_target = triple + ":generic";
182                 cout << "Using LLVM backend in 'generic' mode\n";
183             } else {
184                 cout << "Using LLVM backend in 'native' mode\n";
185             }
186 
187             // argc : without the filename (last element);
188             fFactory = createDSPFactoryFromFile(argv[argc-1], argc1, argv1, opt_target, error_msg, -1);
189 
190             if (!is_dsp_only) {
191                 if (!fFactory) {
192                     cerr << error_msg;
193                     cout << "Trying to use readDSPFactoryFromIRFile..." << endl;
194                     fFactory = readDSPFactoryFromIRFile(argv[argc-1], "", error_msg, -1);
195                 }
196                 if (!fFactory) {
197                     cerr << error_msg;
198                     cout << "Trying to use readDSPFactoryFromBitcodeFile..." << endl;
199                     fFactory = readDSPFactoryFromBitcodeFile(argv[argc-1], "", error_msg, -1);
200                 }
201                 if (!fFactory) {
202                     cerr << error_msg;
203                     cout << "Trying to use readDSPFactoryFromMachineFile..." << endl;
204                     fFactory = readDSPFactoryFromMachineFile(argv[argc-1], "", error_msg);
205                 }
206             }
207 
208         } else {
209             cout << "Using interpreter backend" << endl;
210             // argc : without the filename (last element);
211             fFactory = createInterpreterDSPFactoryFromFile(argv[argc-1], argc1, argv1, error_msg);
212 
213             if (!is_dsp_only) {
214                 if (!fFactory) {
215                     cerr << error_msg;
216                     cout << "Trying to use readDSPFactoryFromBitcodeFile..." << endl;
217                     fFactory = readInterpreterDSPFactoryFromBitcodeFile(argv[argc-1], error_msg);
218                 }
219             }
220         }
221 
222         if (!fFactory) {
223             cerr << error_msg;
224             throw bad_alloc();
225         }
226 
227         cout << "getCompileOptions " << fFactory->getCompileOptions() << endl;
228         printList(fFactory->getLibraryList());
229         printList(fFactory->getIncludePathnames());
230 
231         fDSP = fFactory->createDSPInstance();
232         if (!fDSP) {
233             cerr << "Cannot create instance "<< endl;
234             throw bad_alloc();
235         }
236 
237         if (is_double) {
238             cout << "Running in double..." << endl;
239             fDSP = new dsp_sample_adapter<double, float>(fDSP);
240         }
241 
242         cout << "getName " << fFactory->getName() << endl;
243         cout << "getSHAKey " << fFactory->getSHAKey() << endl;
244 
245         // Before reading the -nvoices parameter
246         MidiMeta::analyse(fDSP, midi_sync, nvoices);
247         nvoices = lopt(argv, "-nvoices", nvoices);
248 
249         if (nvoices > 0) {
250             cout << "Starting polyphonic mode 'nvoices' : " << nvoices << " and 'all' : " << is_all << endl;
251             fDSP = new mydsp_poly(fDSP, nvoices, !is_all, true);
252         }
253 
254         fInterface = new GTKUI(filename, &argc, &argv);
255         fDSP->buildUserInterface(fInterface);
256 
257         fFInterface = new FUI();
258         fDSP->buildUserInterface(fFInterface);
259 
260         if (!fAudio.init(filename, fDSP)) {
261             throw bad_alloc();
262         }
263 
264         // After audio init to get SR
265         fSoundinterface = new SoundUI("", ((is_resample) ? fAudio.getSampleRate() : -1), nullptr, is_double);
266         fDSP->buildUserInterface(fSoundinterface);
267 
268         if (is_httpd) {
269             fHTTPDinterface = new httpdUI(name, fDSP->getNumInputs(), fDSP->getNumOutputs(), argc, argv);
270             fDSP->buildUserInterface(fHTTPDinterface);
271         }
272 
273         if (is_osc) {
274             fOSCinterface = new OSCUI(filename, argc, argv);
275             fDSP->buildUserInterface(fOSCinterface);
276         }
277 
278         if (is_midi) {
279         #ifdef JACK
280             fMIDIInterface = new MidiUI(&fAudio);
281         #else
282             rt_midi midi_handler(name);
283             fMIDIInterface = new MidiUI(&midi_handler);
284         #endif
285             fDSP->buildUserInterface(fMIDIInterface);
286         }
287 
288         // State (after UI construction)
289         fFInterface->recallState(rcfilename);
290         fAudio.start();
291 
292         if (is_httpd) {
293             fHTTPDinterface->run();
294         }
295 
296         if (is_osc) {
297             fOSCinterface->run();
298         }
299 
300         if (is_midi) {
301             fMIDIInterface->run();
302         }
303     }
304 
startDynamicDSP305     bool start()
306     {
307         fInterface->run();
308         return fInterface->stopped();
309     }
310 
stopDynamicDSP311     void stop()
312     {
313         fInterface->stop();
314     }
315 
~DynamicDSPDynamicDSP316     ~DynamicDSP()
317     {
318         fAudio.stop();
319         fFInterface->saveState(fRCfilename.c_str());
320 
321         delete fInterface;
322         delete fDSP;
323         delete fFInterface;
324         delete fMIDIInterface;
325         delete fHTTPDinterface;
326         delete fOSCinterface;
327         delete fSoundinterface;
328 
329         if (is_llvm) {
330             deleteDSPFactory(static_cast<llvm_dsp_factory*>(fFactory));
331         } else {
332             deleteInterpreterDSPFactory(static_cast<interpreter_dsp_factory*>(fFactory));
333         }
334     }
335 
336 };
337 
338 // Global context shared between 'main' and the additional thread
339 struct Context {
340     int fArgc;
341     char** fArgv;
342     string fSHAKey;
ContextContext343     Context(int argc, char** argv):fArgc(argc), fArgv(argv) {}
344 };
345 
346 static mutex gMutex;
347 static DynamicDSP* gDynamicDSP = nullptr;
348 
349 // Stop the current gDynamicDSP if file content is different, the main thread will then allocate a new one
run(Context * context)350 static void run(Context* context)
351 {
352     while (true) {
353         string sha_key = generateSHA1(pathToContent(context->fArgv[context->fArgc-1]));
354         if (sha_key != context->fSHAKey) {
355             context->fSHAKey = sha_key;
356             gMutex.lock();
357             if (gDynamicDSP) gDynamicDSP->stop();
358             gMutex.unlock();
359         }
360         usleep(500000);
361     }
362 }
363 
runDynamicDSP(int argc,char * argv[],bool is_dsp_only=false)364 static bool runDynamicDSP(int argc, char* argv[], bool is_dsp_only = false)
365 {
366     try {
367         // Allocate a new DynamicDSP and start it
368         gDynamicDSP = new DynamicDSP(argc, argv, is_dsp_only);
369         bool res = gDynamicDSP->start();
370         // 'start' returns either because the DSP file content has changed or the window was closed by the user
371         gMutex.lock();
372         delete gDynamicDSP;
373         gDynamicDSP = nullptr;
374         gMutex.unlock();
375         // 'res' is true when gDynamicDSP was stopped by the additional thread
376         return res;
377     } catch (...) {
378         gDynamicDSP = nullptr;
379         return true;
380     }
381 }
382 
main(int argc,char * argv[])383 int main(int argc, char* argv[])
384 {
385     if (isopt(argv, "-edit")) {
386         if (!endWith(argv[argc-1], ".dsp")) {
387             cout << "Cannot use -edit mode with '" << argv[argc-1] << "', use a foo.dsp kind of source file!" << endl;
388             exit(EXIT_FAILURE);
389         }
390         // Start an additional thread that continuously check if the DSP file content has changed
391         new thread(run, new Context(argc, argv));
392         // And edit/compile/run forever
393         while (runDynamicDSP(argc, argv, true)) {};
394     } else {
395         // Run once
396         runDynamicDSP(argc, argv);
397     }
398 
399     return 0;
400 }
401 
402