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