1 /************************************************************************
2  FAUST Architecture File
3  Copyright (C) 2012-2020 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 #ifdef WIN32
26 #pragma warning (disable: 4244 4800 4267)
27 #define _CRT_SECURE_NO_WARNINGS
28 #else
29 #include <Carbon/Carbon.h>
30 #endif
31 
32 #include "faustgen~.h"
33 #include "faust/dsp/libfaust.h"
34 
35 #define LLVM_DSP
36 #include "faust/dsp/poly-dsp.h"
37 
38 // Globals
39 int faustgen_factory::gFaustCounter = 0;
40 map<string, faustgen_factory*> faustgen_factory::gFactoryMap;
41 t_jrgba faustgen::gDefaultColor = {-1., -1., -1., -1.};
42 
43 std::list<GUI*> GUI::fGuiList;
44 ztimedmap GUI::gTimedZoneMap;
45 
46 //===================
47 // Faust DSP Factory
48 //===================
49 
50 struct MyMeta : public Meta, public std::map<std::string, std::string>
51 {
declareMyMeta52     void declare(const char* key, const char* value)
53     {
54         (*this)[key] = value;
55     }
getMyMeta56     const std::string get(const char* key, const char* def)
57     {
58         return (this->find(key) != this->end()) ? (*this)[key] : def;
59     }
60 };
61 
getCodeSize()62 static const char* getCodeSize()
63 {
64     int tmp;
65     return (sizeof(&tmp) == 8) ? "64 bits" : "32 bits";
66 }
67 
68 #ifdef __APPLE__
69 
70 #include <CoreFoundation/CoreFoundation.h>
71 #include <IOKit/IOKitLib.h>
72 
getTarget()73 static string getTarget()
74 {
75     int tmp;
76     return (sizeof(&tmp) == 8) ? "" : "i386-apple-darwin10.6.0";
77 }
78 
79 // Returns the serial number as a CFString.
80 // It is the caller's responsibility to release the returned CFString when done with it.
getSerialNumber()81 static string getSerialNumber()
82 {
83     io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
84 
85     if (platformExpert) {
86         CFTypeRef serialNumberAsCFString =
87         IORegistryEntryCreateCFProperty(platformExpert,
88                                         CFSTR(kIOPlatformSerialNumberKey),
89                                         kCFAllocatorDefault, 0);
90         if (serialNumberAsCFString) {
91             char serial_name[256];
92             CFStringGetCString((CFStringRef)serialNumberAsCFString, serial_name, 256, kCFStringEncodingMacRoman);
93             string res = string(serial_name) + string(getCodeSize());
94             CFRelease(serialNumberAsCFString);
95             return res;
96         }
97         IOObjectRelease(platformExpert);
98     }
99     return "";
100 }
101 
102 #else
103 
getTarget()104 static string getTarget() { return ""; }
105 
getComputerName(char * computer_name,DWORD * computer_name_lg)106 static int getComputerName(char* computer_name, DWORD* computer_name_lg)
107 {
108     HKEY hKey;
109     if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
110                      "SYSTEM\\CurrentControlSet\\Control\\ComputerName\\ComputerName",
111                      0, KEY_QUERY_VALUE, &hKey ) != ERROR_SUCCESS)
112         return FALSE;
113     if (RegQueryValueEx(hKey, "ComputerName", NULL, NULL,
114                         (LPBYTE) computer_name,
115                         (LPDWORD) computer_name_lg) != ERROR_SUCCESS) {
116         RegCloseKey(hKey);
117         return FALSE;
118     }
119     RegCloseKey(hKey);
120     return TRUE;
121 }
122 
getSerialNumber()123 static string getSerialNumber()
124 {
125     char serial_name[256] = "Default Windows name";
126     DWORD name_lg = 256;
127     if (getComputerName(serial_name, &name_lg) == TRUE) {
128         return string(serial_name) + string(getCodeSize());
129     } else {
130         return "Windows";
131     }
132 }
133 
134 #endif
135 
getFolderFromFilename(const string & fullpath)136 static string getFolderFromFilename(const string& fullpath)
137 {
138     size_t first = fullpath.find_first_of(SEPARATOR);
139     size_t last = fullpath.find_last_of(SEPARATOR);
140     return (first != string::npos && last != string::npos) ? fullpath.substr(first, last - first) : "";
141 }
142 
getFolderFromPath(const string & fullpath)143 static string getFolderFromPath(const string& fullpath)
144 {
145     size_t first = fullpath.find_first_of(SEPARATOR);
146     return (first != string::npos) ? fullpath.substr(first, fullpath.size() - first) : "";
147 }
148 
149 struct Max_Meta : public Meta
150 {
declareMax_Meta151     void declare(const char* key, const char* value)
152     {
153         if ((strcmp("name", key) == 0) || (strcmp("author", key) == 0)) {
154             post("%s : %s", key, value);
155         }
156     }
157 };
158 
faustgen_factory(const string & name)159 faustgen_factory::faustgen_factory(const string& name)
160 {
161     m_siginlets = 0;
162     m_sigoutlets = 0;
163 
164     fDefaultPath = path_getdefault();
165     fName = name;
166     fDSPfactory = NULL;
167     fBitCodeSize = 0;
168     fBitCode = NULL;
169     fSourceCodeSize = 0;
170     fSourceCode = NULL;
171     gFaustCounter++;
172     fFaustNumber = gFaustCounter;
173     fOptLevel = LLVM_OPTIMIZATION;
174     fPolyphonic = false;
175     fSoundUI = NULL;
176 
177 #ifdef __APPLE__
178     // OSX only : access to the fautgen~ bundle
179     CFBundleRef faustgen_bundle = CFBundleGetBundleWithIdentifier(CFSTR("com.grame.faustgen-"));  // - character added since SDK 7.3.3
180     CFURLRef faustgen_ref = CFBundleCopyBundleURL(faustgen_bundle);
181     UInt8 bundle_path[512];
182     Boolean res = CFURLGetFileSystemRepresentation(faustgen_ref, true, bundle_path, 512);
183     assert(res);
184 
185     // Built the complete resource path
186     fLibraryPath.insert(string((const char*)bundle_path) + string(FAUST_LIBRARY_PATH));
187 
188     // Draw path in temporary folder
189     fDrawPath = string(FAUST_DRAW_PATH);
190 #endif
191 
192 #ifdef WIN32
193     HMODULE handle = LoadLibrary("faustgen~.mxe64");
194     if (handle) {
195         // Get faustgen~.mxe64 path
196         char name[512];
197         GetModuleFileName(handle, name, 512);
198         string str_name = string(name);
199         str_name = str_name.substr(0, str_name.find_last_of("\\"));
200         // Built the complete resource path
201         fLibraryPath.insert(string(str_name) + string(FAUST_LIBRARY_PATH));
202         // Draw path in temporary folder
203         TCHAR lpTempPathBuffer[MAX_PATH];
204         // Gets the temp path env string (no guarantee it's a valid path).
205         DWORD dwRetVal = GetTempPath(MAX_PATH, lpTempPathBuffer);
206         if (dwRetVal > MAX_PATH || (dwRetVal == 0)) {
207             post("GetTempPath failed...");
208             // Try our value instead...
209             fDrawPath = string(str_name) + string(FAUST_DRAW_PATH);
210         } else {
211             fDrawPath = string(lpTempPathBuffer);
212         }
213         FreeLibrary(handle);
214     } else {
215         post("Error : cannot locate faustgen~.mxe64...");
216         fDrawPath = "";
217     }
218 #endif
219 }
220 
~faustgen_factory()221 faustgen_factory::~faustgen_factory()
222 {
223     free_dsp_factory();
224     free_sourcecode();
225     free_bitcode();
226 
227     remove_svg();
228 
229     delete fSoundUI;
230     fSoundUI = NULL;
231 }
232 
free_sourcecode()233 void faustgen_factory::free_sourcecode()
234 {
235     if (fSourceCode) {
236         sysmem_freehandle(fSourceCode);
237         fSourceCodeSize = 0;
238         fSourceCode = NULL;
239     }
240 }
241 
free_bitcode()242 void faustgen_factory::free_bitcode()
243 {
244     if (fBitCode) {
245         sysmem_freehandle(fBitCode);
246         fBitCodeSize = 0;
247         fBitCode = NULL;
248     }
249 }
250 
free_dsp_factory()251 void faustgen_factory::free_dsp_factory()
252 {
253     lock_audio();
254     lock_ui();
255     {
256         // Free all instances
257         for (auto& it : fInstances) {
258             it->free_dsp();
259         }
260 
261         //deleteDSPFactory(fDSPfactory);
262         fDSPfactory = NULL;
263     }
264     unlock_ui();
265     unlock_audio();
266 }
267 
create_factory_from_bitcode()268 llvm_dsp_factory* faustgen_factory::create_factory_from_bitcode()
269 {
270     // Alternate model using machine code
271     string error_msg;
272     llvm_dsp_factory* factory = readDSPFactoryFromMachine(*fBitCode, getTarget(), error_msg);
273     if (factory) {
274         // Reset fSoundUI with the new factory getIncludePathnames
275         delete fSoundUI;
276         fSoundUI = new SoundUI(factory->getIncludePathnames(), -1, nullptr, true);
277         /*
278          std::vector<std::string> sound_directories = factory->getIncludePathnames();
279          for (int i = 0; i < sound_directories.size(); i++) {
280             post("sound_directories %d %s", i, sound_directories[i].c_str());
281          }
282         */
283     } else {
284         post("%s", error_msg.c_str());
285     }
286     return factory;
287 }
288 
create_factory_from_sourcecode()289 llvm_dsp_factory* faustgen_factory::create_factory_from_sourcecode()
290 {
291     char name_app[64];
292     sprintf(name_app, "faustgen-%d", fFaustNumber);
293 
294     // To be sure we get a correct SVG diagram...
295     remove_svg();
296 
297     default_compile_options();
298     print_compile_options();
299 
300     // Prepare compile options
301     string error_msg;
302     const char* argv[64];
303 
304     assert(fCompileOptions.size() < 64);
305     StringVectorIt it;
306     int i = 0;
307     for (it = fCompileOptions.begin(); it != fCompileOptions.end(); it++) {
308         argv[i++] = (char*)(*it).c_str();
309     }
310     argv[fCompileOptions.size()] = 0;  // NULL terminated argv
311 
312     // Generate SVG file
313     if (!generateAuxFilesFromString(name_app, *fSourceCode, fCompileOptions.size(), argv, error_msg)) {
314         post("Generate SVG error : %s", error_msg.c_str());
315     }
316 
317     llvm_dsp_factory* factory = createDSPFactoryFromString(name_app, *fSourceCode, fCompileOptions.size(), argv, getTarget(), error_msg, fOptLevel);
318 
319     if (factory) {
320         // Reset fSoundUI with the new factory getIncludePathnames
321         delete fSoundUI;
322         fSoundUI = new SoundUI(factory->getIncludePathnames(), -1, nullptr, true);
323         /*
324          std::vector<std::string> sound_directories = factory->getIncludePathnames();
325          for (int i = 0; i < sound_directories.size(); i++) {
326             post("sound_directories %d %s", i, sound_directories[i].c_str());
327          }
328         */
329         return factory;
330     } else {
331         // Update all instances
332         for (auto& it : fInstances) {
333             it->hilight_on();
334         }
335         if (fInstances.begin() != fInstances.end()) {
336             (*fInstances.begin())->hilight_error(error_msg);
337         }
338         post("Invalid Faust code or compile options : %s", error_msg.c_str());
339         return 0;
340     }
341 }
342 
create_dsp_instance(int nvoices)343 ::dsp* faustgen_factory::create_dsp_instance(int nvoices)
344 {
345     ::dsp* mono = fDSPfactory->createDSPInstance();
346 
347     // Check 'nvoices' metadata
348     if (nvoices == 0) {
349         MyMeta meta;
350         mono->metadata(&meta);
351         std::string numVoices = meta.get("nvoices", "0");
352         nvoices = atoi(numVoices.c_str());
353         if (nvoices < 0) nvoices = 0;
354     }
355 
356     if (nvoices > 0) {
357         fPolyphonic = true;
358         return new mydsp_poly(mono, nvoices, true);
359     } else {
360         fPolyphonic = false;
361         return mono;
362     }
363 }
364 
create_dsp_aux()365 ::dsp* faustgen_factory::create_dsp_aux()
366 {
367     ::dsp* dsp = 0;
368     Max_Meta meta;
369     string error;
370 
371     // Factory already allocated
372     if (fDSPfactory) {
373         dsp = create_dsp_instance();
374         post("Factory already allocated, %i input(s), %i output(s)", dsp->getNumInputs(), dsp->getNumOutputs());
375         goto end;
376     }
377 
378     // Tries to create from bitcode
379     if (fBitCodeSize > 0) {
380         fDSPfactory = create_factory_from_bitcode();
381         if (fDSPfactory) {
382             dsp = create_dsp_instance();
383             dsp->metadata(&meta);
384             post("Compilation from bitcode succeeded, %i input(s), %i output(s)", dsp->getNumInputs(), dsp->getNumOutputs());
385             goto end;
386         } else {
387             post("Compilation from bitcode failed...");
388         }
389     }
390 
391     // Otherwise tries to create from source code
392     if (fSourceCodeSize > 0) {
393         fDSPfactory = create_factory_from_sourcecode();
394         if (fDSPfactory) {
395             dsp = create_dsp_instance();
396             dsp->metadata(&meta);
397             post("Compilation from source code succeeded, %i input(s), %i output(s)", dsp->getNumInputs(), dsp->getNumOutputs());
398             goto end;
399         } else {
400             post("Compilation from source code failed...");
401         }
402     }
403 
404     // Otherwise creates default DSP keeping the same input/output number
405     fDSPfactory = createDSPFactoryFromString("default", DEFAULT_CODE, 0, 0, getTarget(), error, 0);
406     dsp = create_dsp_instance();
407     post("Allocation of default DSP succeeded, %i input(s), %i output(s)", dsp->getNumInputs(), dsp->getNumOutputs());
408 
409 end:
410     assert(dsp);
411     m_siginlets = dsp->getNumInputs();
412     m_sigoutlets = dsp->getNumOutputs();
413 
414     // Prepare JSON
415     make_json(dsp);
416     return dsp;
417 }
418 
make_json(::dsp * dsp)419 void faustgen_factory::make_json(::dsp* dsp)
420 {
421     // Prepare JSON
422     JSONUI builder(m_siginlets, m_sigoutlets);
423     dsp->buildUserInterface(&builder);
424     dsp->metadata(&builder);
425     fJSON = builder.JSON();
426 }
427 
add_library_path(const string & library_path)428 void faustgen_factory::add_library_path(const string& library_path)
429 {
430     fLibraryPath.insert(library_path);
431 }
432 
add_compile_option(const string & key,const string & value)433 void faustgen_factory::add_compile_option(const string& key, const string& value)
434 {
435     if ((key != "") && (value != "")) {
436         fCompileOptions.push_back(key);
437         fCompileOptions.push_back(value);
438     }
439 }
440 
add_compile_option(const string & value)441 void faustgen_factory::add_compile_option(const string& value)
442 {
443     if (value != "") {
444         fCompileOptions.push_back(value);
445     }
446 }
447 
print_compile_options()448 void faustgen_factory::print_compile_options()
449 {
450     if (fCompileOptions.size() > 0) {
451         post("-----------------------------");
452         StringVectorIt it;
453         for (it = fCompileOptions.begin(); it != fCompileOptions.end(); it++) {
454             post("Compile option = %s", (*it).c_str());
455         }
456         post("-----------------------------");
457     }
458 }
459 
default_compile_options()460 void faustgen_factory::default_compile_options()
461 {
462     // Clear and set default value
463     fCompileOptions.clear();
464 
465     // By default when double
466     if (sizeof(FAUSTFLOAT) == 8) {
467         add_compile_option("-double");
468     }
469 
470     // Add -svg to current compile options
471     add_compile_option("-svg");
472 
473     // All library paths
474     StringSetIt it1;
475     for (it1 = fLibraryPath.begin(); it1 != fLibraryPath.end(); it1++) {
476         add_compile_option("-I", *it1);
477     }
478 
479     // Draw path
480     add_compile_option("-O", fDrawPath);
481 
482     // All options set in the 'compileoptions' message
483     StringVectorIt it;
484     for (it = fOptions.begin(); it != fOptions.end(); it++) {
485         // '-opt v' : parsed for LLVM optimization level
486         if (*it == "-opt") {
487             it++;
488             fOptLevel = atoi((*it).c_str());
489         } else {
490             add_compile_option(*it);
491         }
492     }
493 
494     // Vector mode by default
495     /*
496      add_compile_option("-vec");
497      add_compile_option("-lv");
498      add_compile_option("1");
499      */
500     /*
501      Seems not necessary...
502      fCompileOptions.push_back("-vs");
503      stringstream num;
504      num << sys_getblksize();
505      add_compile_option(num.str());
506      */
507 }
508 
getfromdictionary(t_dictionary * d)509 void faustgen_factory::getfromdictionary(t_dictionary* d)
510 {
511     // Read machine serial number
512     const char* serial_number;
513     t_max_err err = dictionary_getstring(d, gensym("serial_number"), &serial_number);
514     if (err != MAX_ERR_NONE || strcmp(serial_number, getSerialNumber().c_str()) != 0) {
515         post("Patch compiled on another machine or another CPU architecture, so ignore bitcode, force recompilation and use default compileoptions");
516         goto read_sourcecode;
517     }
518 
519     // Read sourcecode "version" key
520     const char* faustgen_version;
521     err = dictionary_getstring(d, gensym("version"), &faustgen_version);
522 
523     if (err != MAX_ERR_NONE) {
524         post("Cannot read \"version\" key, so ignore bitcode, force recompilation and use default compileoptions");
525         goto read_sourcecode;
526     } else if (strcmp(faustgen_version, FAUSTGEN_VERSION) != 0) {
527         post("Older version of faustgen~ (%s versus %s), so ignore bitcode, force recompilation and use default compileoptions", FAUSTGEN_VERSION, faustgen_version);
528         goto read_sourcecode;
529     }
530 
531     // Read bitcode size key
532     err = dictionary_getlong(d, gensym("machinecode_size"), (t_atom_long*)&fBitCodeSize);
533     if (err != MAX_ERR_NONE) {
534         fBitCodeSize = 0;
535         goto read_sourcecode;
536     }
537 
538     // If OK read bitcode
539     fBitCode = sysmem_newhandleclear(fBitCodeSize + 1);             // We need to use a size larger by one for the null terminator
540     const char* bitcode;
541     err = dictionary_getstring(d, gensym("machinecode"), &bitcode); // The retrieved pointer references the string in the dictionary, it is not a copy.
542     sysmem_copyptr(bitcode, *fBitCode, fBitCodeSize);
543     if (err != MAX_ERR_NONE) {
544         fBitCodeSize = 0;
545     }
546 
547 read_sourcecode:
548     // Load fLibraryPath
549     int i = 0;
550     const char* read_library_path;
551     char library_path[32];
552 
553 loop:
554     snprintf(library_path, 32, "library_path%d", i++);
555     err = dictionary_getstring(d, gensym(library_path), &read_library_path);
556     if (err == MAX_ERR_NONE) {
557         fLibraryPath.insert(read_library_path);
558         goto loop;
559     }
560 
561     // Read sourcecode size key
562     err = dictionary_getlong(d, gensym("sourcecode_size"), (t_atom_long*)&fSourceCodeSize);
563     if (err != MAX_ERR_NONE) {
564         goto default_sourcecode;
565     }
566 
567     // If OK read sourcecode
568     fSourceCode = sysmem_newhandleclear(fSourceCodeSize + 1);           // We need to use a size larger by one for the null terminator
569     const char* sourcecode;
570     err = dictionary_getstring(d, gensym("sourcecode"), &sourcecode);   // The retrieved pointer references the string in the dictionary, it is not a copy.
571     sysmem_copyptr(sourcecode, *fSourceCode, fSourceCodeSize);
572     if (err == MAX_ERR_NONE) {
573         return;
574     }
575 
576 default_sourcecode:
577     // Otherwise tries to create from default source code
578     fSourceCodeSize = strlen(DEFAULT_SOURCE_CODE);
579     fSourceCode = sysmem_newhandleclear(fSourceCodeSize + 1);
580     sysmem_copyptr(DEFAULT_SOURCE_CODE, *fSourceCode, fSourceCodeSize);
581 }
582 
583 // Called when saving the Max patcher
584 // This function saves the necessary data inside the JSON file (Faust sourcecode)
appendtodictionary(t_dictionary * d)585 void faustgen_factory::appendtodictionary(t_dictionary* d)
586 {
587     post("Saving object version, library_path, sourcecode and bitcode...");
588 
589     // Save machine serial number
590     dictionary_appendstring(d, gensym("serial_number"), getSerialNumber().c_str());
591 
592     // Save faustgen~ version
593     dictionary_appendstring(d, gensym("version"), FAUSTGEN_VERSION);
594 
595     // Save fLibraryPath
596     StringSetIt it;
597     int i = 0;
598     for (it = fLibraryPath.begin(); it != fLibraryPath.end(); it++) {
599         char library_path[32];
600         snprintf(library_path, 32, "library_path%d", i++);
601         dictionary_appendstring(d, gensym(library_path), (*it).c_str());
602     }
603 
604     // Save source code
605     if (fSourceCodeSize) {
606         dictionary_appendlong(d, gensym("sourcecode_size"), fSourceCodeSize);
607         dictionary_appendstring(d, gensym("sourcecode"), *fSourceCode);
608     }
609 
610     // Save bitcode
611     if (fDSPfactory) {
612         // Alternate model using machine code
613         string machinecode = writeDSPFactoryToMachine(fDSPfactory, getTarget());
614         dictionary_appendlong(d, gensym("machinecode_size"), machinecode.size());
615         dictionary_appendstring(d, gensym("machinecode"), machinecode.c_str());
616     }
617 }
618 
assist(void * b,long msg,long a,char * dst)619 void faustgen::assist(void* b, long msg, long a, char* dst)
620 {
621     if (msg == ASSIST_INLET) {
622         if (a == 0) {
623             if (fDSP->getNumInputs() == 0) {
624                 sprintf(dst, "(messages)");
625             } else {
626                 sprintf(dst, "(messages/signal) : Audio Input %ld", (a+1));
627             }
628         } else if (a < fDSP->getNumInputs()) {
629             sprintf(dst, "(signal) : Audio Input %ld", (a+1));
630         }
631     } else if (msg == ASSIST_OUTLET) {
632         if (a < fDSP->getNumOutputs()) {
633             sprintf(dst, "(signal) : Audio Output %ld", (a+1));
634         } else if (a == fDSP->getNumOutputs()) {
635             sprintf(dst, "(list) : [path, cur|init, min, max]*");
636         } else {
637             sprintf(dst, "(int) : raw MIDI bytes*");
638         }
639     }
640 }
641 
try_open_svg()642 bool faustgen_factory::try_open_svg()
643 {
644     // Open the svg diagram file inside a web browser
645     char command[512];
646 #ifdef WIN32
647     sprintf(command, "type \"file:///%sfaustgen-%d-svg/process.svg\"", fDrawPath.c_str(), fFaustNumber);
648 #else
649     sprintf(command, "open \"file://%sfaustgen-%d-svg/process.svg\"", fDrawPath.c_str(), fFaustNumber);
650 #endif
651     return (system(command) == 0);
652 }
653 
open_svg()654 void faustgen_factory::open_svg()
655 {
656     // Open the svg diagram file inside a web browser
657     char command[512];
658 #ifdef WIN32
659     sprintf(command, "start \"\" \"file:///%sfaustgen-%d-svg/process.svg\"", fDrawPath.c_str(), fFaustNumber);
660 #else
661     sprintf(command, "open \"file://%sfaustgen-%d-svg/process.svg\"", fDrawPath.c_str(), fFaustNumber);
662 #endif
663     system(command);
664 }
665 
remove_svg()666 void faustgen_factory::remove_svg()
667 {
668     // Possibly done by "compileoptions" or display_svg
669     char command[512];
670 #ifdef WIN32
671     sprintf(command, "rmdir /S/Q \"%sfaustgen-%d-svg\"", fDrawPath.c_str(), fFaustNumber);
672 #else
673     sprintf(command, "rm -r \"%sfaustgen-%d-svg\"", fDrawPath.c_str(), fFaustNumber);
674 #endif
675     system(command);
676 }
677 
display_svg()678 void faustgen_factory::display_svg()
679 {
680     // Try to open SVG svg diagram file inside a web browser
681     if (!try_open_svg()) {
682 
683         post("SVG diagram not available, recompile to produce it");
684 
685         // Force recompilation to produce it
686         llvm_dsp_factory* factory = create_factory_from_sourcecode();
687         //deleteDSPFactory(factory);
688 
689         // Open the SVG diagram file inside a web browser
690         open_svg();
691     }
692 }
693 
open_file(const char * file)694 bool faustgen_factory::open_file(const char* file)
695 {
696     char command[512];
697 #ifdef WIN32
698     sprintf(command, "start \"\" \"%s%s\"", (*fLibraryPath.begin()).c_str(), file);
699 #else
700     sprintf(command, "open \"%s%s\"", (*fLibraryPath.begin()).c_str(), file);
701 #endif
702     post(command);
703     return (system(command) == 0);
704 }
705 
open_file(const char * appl,const char * file)706 bool faustgen_factory::open_file(const char* appl, const char* file)
707 {
708     char command[512];
709 #ifdef WIN32
710     sprintf(command, "start \"\" %s \"%s%s\"", appl, (*fLibraryPath.begin()).c_str(), file);
711 #else
712     sprintf(command, "open -a %s \"%s%s\"", appl, (*fLibraryPath.begin()).c_str(), file);
713 #endif
714     return (system(command) == 0);
715 }
716 
display_documentation()717 void faustgen_factory::display_documentation()
718 {
719     // Open the Web documentation
720     char command[512];
721 #ifdef WIN32
722     sprintf(command, "start \"\" \"https://faustdoc.grame.fr/manual/introduction\"");
723 #else
724     sprintf(command, "open \"https://faustdoc.grame.fr/manual/introduction\"");
725 #endif
726     system(command);
727 }
728 
display_libraries_aux(const char * lib)729 void faustgen_factory::display_libraries_aux(const char* lib)
730 {
731     const char* appl;
732     int i = 0;
733 
734     while ((appl = TEXT_APPL_LIST[i++]) && (strcmp(appl, "") != 0)) {
735         if (open_file(appl, lib)) {
736             break;
737         }
738     }
739 }
740 
display_libraries()741 void faustgen_factory::display_libraries()
742 {
743     // Open the libraries
744 #ifdef WIN32
745     open_file(FAUST_PDF_LIBRARY);
746     open_file("all.lib");
747     open_file("aanl.lib");
748     open_file("analyzers.lib");
749     open_file("basics.lib");
750     open_file("compressors.lib");
751     open_file("delays.lib");
752     open_file("demos.lib");
753     open_file("double.lib");
754     open_file("dx7.lib");
755     open_file("envelopes.lib");
756     open_file("filters.lib");
757     open_file("fds.lib");
758     open_file("hoa.lib");
759     open_file("instruments.lib");
760     open_file("interpolators.lib");
761     open_file("maths.lib");
762     open_file("maxmsp.lib");
763     open_file("mi.lib");
764     open_file("misceffects.lib");
765     open_file("noises.lib");
766     open_file("oscillators.lib");
767     open_file("phaflangers.lib");
768     open_file("physmodels.lib");
769     open_file("platform.lib");
770     open_file("quantizers.lib");
771     open_file("reducemaps.lib");
772     open_file("reverbs.lib");
773     open_file("routes.lib");
774     open_file("runtime.lib");
775     open_file("sf.lib");
776     open_file("signals.lib");
777     open_file("soundfiles.lib");
778     open_file("spats.lib");
779     open_file("stdfaust.lib");
780     open_file("synths.lib");
781     open_file("tonestacks.lib");
782     open_file("tubes.lib");
783     open_file("vaeffects.lib");
784     open_file("version.lib");
785     open_file("wdmodels.lib");
786     open_file("webaudio.lib");
787 #else
788     display_libraries_aux("all.lib");
789     display_libraries_aux("aanl.lib");
790     display_libraries_aux("analyzers.lib");
791     display_libraries_aux("basics.lib");
792     display_libraries_aux("compressors.lib");
793     display_libraries_aux("delays.lib");
794     display_libraries_aux("demos.lib");
795     display_libraries_aux("double.lib");
796     display_libraries_aux("dx7.lib");
797     display_libraries_aux("envelopes.lib");
798     display_libraries_aux("filters.lib");
799     display_libraries_aux("fds.lib");
800     display_libraries_aux("hoa.lib");
801     display_libraries_aux("instruments.lib");
802     display_libraries_aux("interpolators.lib");
803     display_libraries_aux("maths.lib");
804     display_libraries_aux("maxmsp.lib");
805     display_libraries_aux("mi.lib");
806     display_libraries_aux("misceffects.lib");
807     display_libraries_aux("noises.lib");
808     display_libraries_aux("oscillators.lib");
809     display_libraries_aux("phaflangers.lib");
810     display_libraries_aux("physmodels.lib");
811     display_libraries_aux("platform.lib");
812     display_libraries_aux("quantizers.lib");
813     display_libraries_aux("reducemaps.lib");
814     display_libraries_aux("reverbs.lib");
815     display_libraries_aux("routes.lib");
816     display_libraries_aux("runtime.lib");
817     display_libraries_aux("sf.lib");
818     display_libraries_aux("signals.lib");
819     display_libraries_aux("soundfiles.lib");
820     display_libraries_aux("spats.lib");
821     display_libraries_aux("stdfaust.lib");
822     display_libraries_aux("synths.lib");
823     display_libraries_aux("tonestacks.lib");
824     display_libraries_aux("tubes.lib");
825     display_libraries_aux("vaeffects.lib");
826     display_libraries_aux("version.lib");
827     display_libraries_aux("wdmodels.lib");
828     display_libraries_aux("webaudio.lib");
829 #endif
830 }
831 
update_sourcecode(int size,char * source_code)832 void faustgen_factory::update_sourcecode(int size, char* source_code)
833 {
834     // Recompile only if text has been changed
835     if (strcmp(source_code, *fSourceCode) != 0) {
836 
837         // Update all instances
838         for (auto& it : fInstances) {
839             it->hilight_off();
840         }
841 
842         // Delete the existing Faust module
843         free_dsp_factory();
844 
845         // Free the memory allocated for fSourceCode
846         free_sourcecode();
847 
848         // Free the memory allocated for fBitCode
849         free_bitcode();
850 
851         // Allocate the right memory for fSourceCode
852         fSourceCode = sysmem_newhandleclear(size + 1);
853         sysmem_copyptr(source_code, *fSourceCode, size);
854         fSourceCodeSize = size;
855 
856         // Update all instances
857         for (auto& it : fInstances) {
858             it->update_sourcecode();
859         }
860 
861     } else {
862         post("DSP code has not been changed...");
863     }
864 }
865 
librarypath(long inlet,t_symbol * s)866 void faustgen_factory::librarypath(long inlet, t_symbol* s)
867 {
868     if (s != gensym("")) {
869         add_library_path(getFolderFromPath(s->s_name));
870     }
871 }
872 
read(long inlet,t_symbol * s)873 void faustgen_factory::read(long inlet, t_symbol* s)
874 {
875     char filename[MAX_FILENAME_CHARS];
876     short path = 0;
877     long type = 'TEXT';
878     t_max_err err;
879     t_filehandle fh;
880 
881     // No filename, so open load dialog
882     if (s == gensym("")) {
883         filename[0] = 0;
884         if (open_dialog(filename, &path, (t_fourcc*)&type, (t_fourcc*)&type, 1)) {
885             post("Faust DSP file not found");
886             return;
887         }
888         // Otherwise locate the file
889     } else {
890         strncpy_zero(filename, s->s_name, MAX_FILENAME_CHARS);
891         // Set default path with saved value
892         path_setdefault(fDefaultPath, 0);
893         if (locatefile_extended(filename, &path, (t_fourcc*)&type, (t_fourcc*)&type, 1)) {
894             post("Faust DSP file '%s' not found", filename);
895             return;
896         }
897     }
898 
899     // File found, open it and recompile DSP
900     err = path_opensysfile(filename, path, &fh, READ_PERM);
901     if (err) {
902         post("Faust DSP file '%s' cannot be opened", filename);
903         return;
904     }
905 
906     // Delete the existing Faust module
907     free_dsp_factory();
908 
909     // Free the memory allocated for fBitCode
910     free_bitcode();
911 
912     err = sysfile_readtextfile(fh, fSourceCode, 0, (t_sysfile_text_flags)(TEXT_LB_UNIX | TEXT_NULL_TERMINATE));
913     if (err) {
914         post("Faust DSP file '%s' cannot be read", filename);
915     }
916 
917     sysfile_close(fh);
918     fSourceCodeSize = sysmem_handlesize(fSourceCode);
919 
920     // Add DSP file enclosing folder pathname in the '-I' list
921     char full_path[MAX_FILENAME_CHARS];
922     if ((err = path_topathname(path, filename, full_path)) == 0) {
923         add_library_path(getFolderFromFilename(full_path));
924     } else {
925         post("path_topathname err = %d", err);
926     }
927 
928     // Update all instances
929     for (auto& it : fInstances) {
930         it->update_sourcecode();
931     }
932 }
933 
write(long inlet,t_symbol * s)934 void faustgen_factory::write(long inlet, t_symbol* s)
935 {
936     char filename[MAX_FILENAME_CHARS];
937     short path = 0;
938     long type = 'TEXT';
939     t_max_err err;
940     t_filehandle fh;
941 
942     // No filename, so open save dialog
943     if (s == gensym("")) {
944         filename[0] = 0;
945         if (saveas_dialog(filename, &path, NULL)) {
946             post("Faust DSP file not found");
947             return;
948         } else {
949             err = path_createsysfile(filename, path, type, &fh);
950             if (err) {
951                 post("Faust DSP file '%s' cannot be created", filename);
952                 return;
953             }
954         }
955         // Otherwise locate or create the file
956     } else {
957         strncpy_zero(filename, s->s_name, MAX_FILENAME_CHARS);
958         // Set default path with saved value
959         path_setdefault(fDefaultPath, 0);
960         if (locatefile_extended(filename, &path, (t_fourcc*)&type, (t_fourcc*)&type, 1)) {
961             post("Faust DSP file '%s' not found, so tries to create it", filename);
962             err = path_createsysfile(filename, path, type, &fh);
963             if (err) {
964                 post("Faust DSP file '%s' cannot be created", filename);
965                 return;
966             }
967         } else {
968             err = path_opensysfile(filename, path, &fh, WRITE_PERM);
969             if (err) {
970                 post("Faust DSP file '%s' cannot be opened", filename);
971                 return;
972             }
973         }
974     }
975 
976     err = sysfile_writetextfile(fh, fSourceCode, (t_sysfile_text_flags)(TEXT_LB_UNIX | TEXT_NULL_TERMINATE));
977     if (err) {
978         post("Faust DSP file '%s' cannot be written", filename);
979     }
980     sysfile_close(fh);
981 }
982 
compileoptions(long inlet,t_symbol * s,long argc,t_atom * argv)983 void faustgen_factory::compileoptions(long inlet, t_symbol* s, long argc, t_atom* argv)
984 {
985     post("Compiler options modified for faustgen");
986 
987     if (argc == 0) {
988         post("No argument entered, no additional compilation option will be used");
989     }
990 
991     // Clear options
992     fOptions.clear();
993     int i;
994     t_atom* ap;
995 
996     // Increment ap each time to get to the next atom
997     for (i = 0, ap = argv; i < argc; i++, ap++) {
998         switch (atom_gettype(ap)) {
999 
1000             case A_LONG: {
1001                 stringstream num;
1002                 num << atom_getlong(ap);
1003                 string res = num.str();
1004                 fOptions.push_back(res.c_str());
1005                 break;
1006             }
1007 
1008             case A_FLOAT:
1009                 post("Invalid compiler option argument - float");
1010                 break;
1011 
1012             case A_SYM:
1013                 // Add options to default ones
1014                 fOptions.push_back(atom_getsym(ap)->s_name);
1015                 break;
1016 
1017             default:
1018                 post("Invalid compiler option argument - unknown");
1019                 break;
1020         }
1021     }
1022 
1023     /*
1024      if (optimize) {
1025      post("Start looking for optimal compilation options...");
1026      #ifdef __APPLE__
1027      double best;
1028      dsp_optimizer optimizer(string(*fSourceCode), (*fLibraryPath.begin()).c_str(), getTarget(), sys_getblksize());
1029      fOptions = optimizer.findOptimizedParameters(best);
1030      #endif
1031      post("Optimal compilation options found");
1032      }
1033      */
1034 
1035     // Delete the existing Faust module
1036     free_dsp_factory();
1037 
1038     // Free the memory allocated for fBitCode
1039     free_bitcode();
1040 
1041     // Update all instances
1042     for (auto& it : fInstances) {
1043         it->update_sourcecode();
1044     }
1045 }
1046 
1047 //====================
1048 // Faust DSP Instance
1049 //====================
1050 
allocate_factory(const string & effect_name)1051 bool faustgen::allocate_factory(const string& effect_name)
1052 {
1053     bool res = false;
1054 
1055     if (faustgen_factory::gFactoryMap.find(effect_name) != faustgen_factory::gFactoryMap.end()) {
1056         fDSPfactory = faustgen_factory::gFactoryMap[effect_name];
1057     } else {
1058         fDSPfactory = new faustgen_factory(effect_name);
1059         faustgen_factory::gFactoryMap[effect_name] = fDSPfactory;
1060         res = true;
1061     }
1062 
1063     fDSPfactory->add_instance(this);
1064     return res;
1065 }
1066 
faustgen(t_symbol * sym,long ac,t_atom * argv)1067 faustgen::faustgen(t_symbol* sym, long ac, t_atom* argv)
1068 {
1069     m_siginlets = 0;
1070     m_sigoutlets = 0;
1071 
1072     fDSP = NULL;
1073     fDSPUI = NULL;
1074     fMidiUI = NULL;
1075     fOSCUI = NULL;
1076     fSavedUI = NULL;
1077     fDSPfactory = NULL;
1078     fEditor = NULL;
1079     fMute = false;
1080 
1081     int i;
1082     t_atom* ap;
1083     bool res = false;
1084 
1085     fMidiHandler.startMidi();
1086 
1087     // Allocate factory with a given "name"
1088     for (i = 0, ap = argv; i < ac; i++, ap++) {
1089         if (atom_gettype(ap) == A_SYM) {
1090             res = allocate_factory(atom_getsym(ap)->s_name);
1091             break;
1092         }
1093     }
1094 
1095     // Empty (= no name) faustgen~ will be internally separated as groups with different names
1096     if (!fDSPfactory) {
1097         string effect_name;
1098         stringstream num;
1099         num << faustgen_factory::gFaustCounter;
1100         effect_name = "faustgen_factory-" + num.str();
1101         res = allocate_factory(effect_name);
1102     }
1103 
1104     t_object* box;
1105     object_obex_lookup((t_object*)&m_ob, gensym("#B"), &box);
1106     if (gDefaultColor.red == -1.) {
1107         jbox_get_color(box, &gDefaultColor);
1108     }
1109 
1110     // Needed to script objects
1111     char name[64];
1112     sprintf(name, "faustgen-%lld", (long long)this);
1113     jbox_set_varname(box, gensym(name));
1114 
1115     // Fetch the data inside the max patcher using the dictionary
1116     t_dictionary* d = 0;
1117     if ((d = (t_dictionary*)gensym("#D")->s_thing) && res) {
1118         fDSPfactory->getfromdictionary(d);
1119     }
1120 
1121     create_dsp(true);
1122 }
1123 
1124 // Called upon deleting the object inside the patcher
~faustgen()1125 faustgen::~faustgen()
1126 {
1127     free_dsp();
1128 
1129     if (fEditor) {
1130         object_free(fEditor);
1131         fEditor = NULL;
1132     }
1133 
1134     fDSPfactory->remove_instance(this);
1135     fMidiHandler.stopMidi();
1136 }
1137 
free_dsp()1138 void faustgen::free_dsp()
1139 {
1140     // Save controller state
1141     fSavedUI->save();
1142 
1143     delete fMidiUI;
1144     fMidiUI = NULL;
1145 
1146     delete fOSCUI;
1147     fOSCUI = NULL;
1148 
1149     delete fSavedUI;
1150     fSavedUI = NULL;
1151 
1152     delete fDSPUI;
1153     fDSPUI = NULL;
1154 
1155     delete fDSP;
1156     fDSP = NULL;
1157 }
1158 
json_reader(const char * jsontext)1159 t_dictionary* faustgen::json_reader(const char* jsontext)
1160 {
1161     t_dictionary *d = NULL;
1162     t_max_err err;
1163     t_atom result[1];
1164     t_object *jsonreader = (t_object*)object_new(_sym_nobox, _sym_jsonreader);
1165 
1166     err = (t_max_err)object_method(jsonreader, _sym_parse, jsontext, result);
1167     if (!err) {
1168         t_object *ro = (t_object*)atom_getobj(result);
1169         if (ro) {
1170             if (object_classname_compare(ro, _sym_dictionary)) {
1171                 d = (t_dictionary*)ro;
1172             } else {
1173                 object_free(ro);
1174             }
1175         }
1176     }
1177     object_free(jsonreader);
1178     return d;
1179 }
1180 
1181 // Called upon sending the object a message inside the max patcher
1182 // Allows to set a value to the Faust module's parameter, or a list of values
anything(long inlet,t_symbol * s,long ac,t_atom * av)1183 void faustgen::anything(long inlet, t_symbol* s, long ac, t_atom* av)
1184 {
1185     fDSPfactory->lock_ui();
1186     {
1187         bool res = false;
1188         string name = string((s)->s_name);
1189 
1190         // If no argument is there, consider it as a toggle message for a button
1191         if (ac == 0 && fDSPUI->isValue(name)) {
1192 
1193             FAUSTFLOAT off = FAUSTFLOAT(0.0);
1194             FAUSTFLOAT on = FAUSTFLOAT(1.0);
1195             fDSPUI->setValue(name, off);
1196             fDSPUI->setValue(name, on);
1197 
1198             av[0].a_type = A_FLOAT;
1199             av[0].a_w.w_float = off;
1200             anything(inlet, s, 1, av);
1201 
1202             goto unlock;
1203 
1204         } else if (mspUI::checkDigit(name)) { // List of values
1205 
1206             int pos, ndigit = 0;
1207             for (pos = name.size() - 1; pos >= 0; pos--) {
1208                 if (isdigit(name[pos]) || name[pos] == ' ') {
1209                     ndigit++;
1210                 } else {
1211                     break;
1212                 }
1213             }
1214             pos++;
1215 
1216             string prefix = name.substr(0, pos);
1217             string num_base = name.substr(pos);
1218             int num = atoi(num_base.c_str());
1219 
1220             int i;
1221             t_atom* ap;
1222 
1223             // Increment ap each time to get to the next atom
1224             for (i = 0, ap = av; i < ac; i++, ap++) {
1225                 FAUSTFLOAT value;
1226                 switch (atom_gettype(ap)) {
1227                     case A_LONG:
1228                         value = FAUSTFLOAT(ap[0].a_w.w_long);
1229                         break;
1230                     case A_FLOAT:
1231                         value = FAUSTFLOAT(ap[0].a_w.w_float);
1232                         break;
1233                     default:
1234                         post("Invalid argument in parameter setting");
1235                         goto unlock;
1236                 }
1237 
1238                 string num_val = to_string(num + i);
1239                 stringstream param_name; param_name << prefix;
1240                 for (int i = 0; i < ndigit - mspUI::countDigit(num_val); i++) {
1241                     param_name << ' ';
1242                 }
1243                 param_name << num_val;
1244 
1245                 // Try special naming scheme for list of parameters
1246                 res = fDSPUI->setValue(param_name.str(), value);
1247 
1248                 // Otherwise try standard name
1249                 if (!res) {
1250                     res = fDSPUI->setValue(name, value);
1251                 }
1252                 if (!res) {
1253                     post("Unknown parameter : %s", (s)->s_name);
1254                 }
1255             }
1256 
1257         } else {
1258             // Standard parameter name
1259             FAUSTFLOAT value = (av[0].a_type == A_LONG) ? FAUSTFLOAT(av[0].a_w.w_long) : FAUSTFLOAT(av[0].a_w.w_float);
1260             res = fDSPUI->setValue(name, value);
1261             if (!res) {
1262                 post("Unknown parameter : %s", (s)->s_name);
1263             }
1264         }
1265     }
1266 
1267     unlock:
1268         fDSPfactory->unlock_ui();
1269 }
1270 
compileoptions(long inlet,t_symbol * s,long argc,t_atom * argv)1271 void faustgen::compileoptions(long inlet, t_symbol* s, long argc, t_atom* argv)
1272 {
1273     fDSPfactory->compileoptions(inlet, s, argc, argv);
1274 }
1275 
read(long inlet,t_symbol * s)1276 void faustgen::read(long inlet, t_symbol* s)
1277 {
1278     fDSPfactory->read(inlet, s);
1279 }
1280 
write(long inlet,t_symbol * s)1281 void faustgen::write(long inlet, t_symbol* s)
1282 {
1283     fDSPfactory->write(inlet, s);
1284 }
1285 
polyphony(long inlet,t_symbol * s,long ac,t_atom * av)1286 void faustgen::polyphony(long inlet, t_symbol* s, long ac, t_atom* av)
1287 {
1288     fDSPfactory->lock_audio();
1289     fDSPfactory->lock_ui();
1290     {
1291         free_dsp();
1292         fDSP = fDSPfactory->create_dsp_instance(av[0].a_w.w_long);
1293         assert(fDSP);
1294 
1295         // Init all controller (UI, MIDI, Soundfile)
1296         init_controllers();
1297 
1298         // Prepare JSON
1299         fDSPfactory->make_json(fDSP);
1300 
1301         // Send JSON to JS script
1302         create_jsui();
1303 
1304         // Initialize at the system's sampling rate
1305         fDSP->init(sys_getsr());
1306     }
1307     fDSPfactory->unlock_audio();
1308     fDSPfactory->unlock_ui();
1309 }
1310 
1311 // Reset controllers to init value and send [path, init, min, max]
init(long inlet,t_symbol * s,long ac,t_atom * av)1312 void faustgen::init(long inlet, t_symbol* s, long ac, t_atom* av)
1313 {
1314     // Reset internal state
1315     fSavedUI->reset();
1316 
1317     // Input controllers
1318     for (mspUI::iterator it = fDSPUI->begin2(); it != fDSPUI->end2(); it++) {
1319         t_atom myList[4];
1320         atom_setsym(&myList[0], gensym((*it).first.c_str()));
1321         atom_setfloat(&myList[1], (*it).second->getInitValue());    // init value
1322         atom_setfloat(&myList[2], (*it).second->getMinValue());
1323         atom_setfloat(&myList[3], (*it).second->getMaxValue());
1324         outlet_list(m_control_outlet, 0, 4, myList);
1325     }
1326     // Output controllers
1327     for (mspUI::iterator it = fDSPUI->begin4(); it != fDSPUI->end4(); it++) {
1328         t_atom myList[4];
1329         atom_setsym(&myList[0], gensym((*it).first.c_str()));
1330         atom_setfloat(&myList[1], (*it).second->getInitValue());    // init value
1331         atom_setfloat(&myList[2], (*it).second->getMinValue());
1332         atom_setfloat(&myList[3], (*it).second->getMaxValue());
1333         outlet_list(m_control_outlet, 0, 4, myList);
1334     }
1335 }
1336 
dump_inputs()1337 void faustgen::dump_inputs()
1338 {
1339     // Input controllers
1340     for (mspUI::iterator it = fDSPUI->begin2(); it != fDSPUI->end2(); it++) {
1341         t_atom myList[4];
1342         atom_setsym(&myList[0], gensym((*it).first.c_str()));
1343         atom_setfloat(&myList[1], (*it).second->getValue());    // cur value
1344         atom_setfloat(&myList[2], (*it).second->getMinValue());
1345         atom_setfloat(&myList[3], (*it).second->getMaxValue());
1346         outlet_list(m_control_outlet, 0, 4, myList);
1347     }
1348 }
dump_outputs()1349 void faustgen::dump_outputs()
1350 {
1351     // Output controllers
1352     for (mspUI::iterator it = fDSPUI->begin4(); it != fDSPUI->end4(); it++) {
1353         t_atom myList[4];
1354         atom_setsym(&myList[0], gensym((*it).first.c_str()));
1355         atom_setfloat(&myList[1], (*it).second->getValue());    // cur value
1356         atom_setfloat(&myList[2], (*it).second->getMinValue());
1357         atom_setfloat(&myList[3], (*it).second->getMaxValue());
1358         outlet_list(m_control_outlet, 0, 4, myList);
1359     }
1360 }
1361 
1362 // Dump controllers as list of [path, cur, min, max]
dump(long inlet,t_symbol * s,long ac,t_atom * av)1363 void faustgen::dump(long inlet, t_symbol* s, long ac, t_atom* av)
1364 {
1365     dump_inputs();
1366     dump_outputs();
1367 }
1368 
1369 // osc 'IP inport outport xmit bundle'
osc(long inlet,t_symbol * s,long ac,t_atom * av)1370 void faustgen::osc(long inlet, t_symbol* s, long ac, t_atom* av)
1371 {
1372     if (ac == 5) {
1373         fDSPfactory->lock_audio();
1374         fDSPfactory->lock_ui();
1375         {
1376             delete fOSCUI;
1377 
1378             const char* argv1[32];
1379             int argc1 = 0;
1380 
1381             argv1[argc1++] = "Faust";
1382 
1383             argv1[argc1++]  = "-desthost";
1384             argv1[argc1++]  = atom_getsym(&av[0])->s_name;
1385 
1386             char inport[32];
1387             snprintf(inport, 32, "%ld", long(av[1].a_w.w_long));
1388             argv1[argc1++] = "-port";
1389             argv1[argc1++] = inport;
1390 
1391             char outport[32];
1392             snprintf(outport, 32, "%ld", long(av[2].a_w.w_long));
1393             argv1[argc1++] = "-outport";
1394             argv1[argc1++] = outport;
1395 
1396             char xmit[32];
1397             snprintf(xmit, 32, "%ld", long(av[3].a_w.w_long));
1398             argv1[argc1++] = "-xmit";
1399             argv1[argc1++] = xmit;
1400 
1401             char bundle[32];
1402             snprintf(bundle, 32, "%ld", long(av[4].a_w.w_long));
1403             argv1[argc1++] = "-bundle";
1404             argv1[argc1++] = bundle;
1405 
1406             fOSCUI = new OSCUI("Faust", argc1, (char**)argv1);
1407             fDSP->buildUserInterface(fOSCUI);
1408             fOSCUI->run();
1409 
1410             post(fOSCUI->getInfos().c_str());
1411         }
1412         fDSPfactory->unlock_ui();
1413         fDSPfactory->unlock_audio();
1414     } else {
1415         post("Should be : osc 'IP inport outport xmit(0|1|2) bundle(0|1)'");
1416     }
1417 }
1418 
midievent(long inlet,t_symbol * s,long ac,t_atom * av)1419 void faustgen::midievent(long inlet, t_symbol* s, long ac, t_atom* av)
1420 {
1421     if (ac > 0) {
1422         int type = (int)av[0].a_w.w_long & 0xf0;
1423         int channel = (int)av[0].a_w.w_long & 0x0f;
1424 
1425         if (ac == 1) {
1426             fMidiHandler.handleSync(0.0, av[0].a_w.w_long);
1427         } else if (ac == 2) {
1428             fMidiHandler.handleData1(0.0, type, channel, av[1].a_w.w_long);
1429         } else if (ac == 3) {
1430             fMidiHandler.handleData2(0.0, type, channel, av[1].a_w.w_long, av[2].a_w.w_long);
1431         }
1432     }
1433 }
1434 
librarypath(long inlet,t_symbol * s)1435 void faustgen::librarypath(long inlet, t_symbol* s)
1436 {
1437     fDSPfactory->librarypath(inlet, s);
1438 }
1439 
1440 // Called when saving the Max patcher, this function saves the necessary data inside the json file (faust sourcecode)
appendtodictionary(t_dictionary * d)1441 void faustgen::appendtodictionary(t_dictionary* d)
1442 {
1443     fDSPfactory->appendtodictionary(d);
1444 }
1445 
getfromdictionary(t_dictionary * d)1446 void faustgen::getfromdictionary(t_dictionary* d)
1447 {
1448     fDSPfactory->getfromdictionary(d);
1449 }
1450 
1451 // Called when the user double-clicks on the faustgen object inside the Max patcher
dblclick(long inlet)1452 void faustgen::dblclick(long inlet)
1453 {
1454     // Create a popup menu inside the Max patcher
1455     t_jpopupmenu* popup = jpopupmenu_create();
1456     jpopupmenu_additem(popup, 1, "Edit DSP code", NULL, 0, 0, NULL);
1457     jpopupmenu_additem(popup, 2, "View DSP parameters", NULL, 0, 0, NULL);
1458     jpopupmenu_additem(popup, 3, "View compile options", NULL, 0, 0, NULL);
1459     jpopupmenu_additem(popup, 4, "View SVG diagram", NULL, 0, 0, NULL);
1460     jpopupmenu_additem(popup, 5, "View Web documentation", NULL, 0, 0, NULL);
1461     jpopupmenu_additem(popup, 6, "View libraries", NULL, 0, 0, NULL);
1462 
1463     // Get mouse position
1464     int x,y;
1465     jmouse_getposition_global(&x, &y);
1466     t_pt coordinate;
1467     coordinate.x = x;
1468     coordinate.y = y;
1469 
1470     int choice = jpopupmenu_popup(popup, coordinate, 0);
1471 
1472     switch (choice) {
1473 
1474         case 1:
1475             // Open the text editor to allow the user to input Faust sourcecode
1476             display_dsp_source();
1477             break;
1478 
1479         case 2:
1480             // Display inside the max window the current values of the module's parameters, as well as their ranges
1481             display_dsp_params();
1482             break;
1483 
1484         case 3:
1485             // Display compiler options
1486             fDSPfactory->print_compile_options();
1487             break;
1488 
1489         case 4:
1490             // Open the SVG diagram file inside a web browser
1491             display_svg();
1492             break;
1493 
1494         case 5:
1495             // Open the documentation
1496             display_documentation();
1497             break;
1498 
1499         case 6:
1500             // Open the libraries
1501             display_libraries();
1502             break;
1503 
1504         default:
1505             break;
1506     }
1507 
1508     // Destroy the popup menu once this is done
1509     jpopupmenu_destroy(popup);
1510 }
1511 
1512 // Called when closing the text editor, calls for the creation of a new Faust module with the updated sourcecode
edclose(long inlet,char ** source_code,long size)1513 void faustgen::edclose(long inlet, char** source_code, long size)
1514 {
1515     // Edclose "may" be called with an already deallocated object (like closing the patcher with a still opened editor)
1516     if (fDSP && fEditor) {
1517         fDSPfactory->update_sourcecode(size, *source_code);
1518         fEditor = NULL;
1519     }
1520 }
1521 
update_sourcecode()1522 void faustgen::update_sourcecode()
1523 {
1524     // Create a new DSP instance
1525     create_dsp(false);
1526 
1527     // faustgen~ state is modified...
1528     set_dirty();
1529 
1530     // Send a bang
1531     outlet_bang(m_control_outlet);
1532 }
1533 
1534 // Process the signal data with the Faust module
perform(int vs,t_sample ** inputs,long numins,t_sample ** outputs,long numouts)1535 inline void faustgen::perform(int vs, t_sample** inputs, long numins, t_sample** outputs, long numouts)
1536 {
1537     if (!fMute && fDSPfactory->try_lock_audio()) {
1538         // Has to be tested again when the lock has been taken...
1539         if (fDSP) {
1540             fDSP->compute(vs, static_cast<FAUSTFLOAT**>(inputs), static_cast<FAUSTFLOAT**>(outputs));
1541             if (fOSCUI) fOSCUI->endBundle();
1542             //update_outputs();
1543             // Use the right outlet to output messages
1544             dump_outputs();
1545             // Done for fMidiUI and fOSCUI
1546             GUI::updateAllGuis();
1547         }
1548         fDSPfactory->unlock_audio();
1549     } else {
1550         // Write null buffers to outs
1551         for (int i = 0; i < numouts; i++) {
1552             memset(outputs[i], 0, sizeof(t_sample) * vs);
1553         }
1554     }
1555 }
1556 
init(double samplerate)1557 inline void faustgen::init(double samplerate)
1558 {
1559     fDSP->init(samplerate);
1560 }
1561 
1562 // Display source code
display_dsp_source()1563 void faustgen::display_dsp_source()
1564 {
1565     if (fEditor) {
1566         // Editor already open, set it to to foreground
1567         object_attr_setchar(fEditor, gensym("visible"), 1);
1568     } else {
1569         // Create a text editor object
1570         fEditor = (t_object*)object_new(CLASS_NOBOX, gensym("jed"), this, 0);
1571 
1572         // Set the text inside the text editor to be fSourceCode
1573         object_method(fEditor, gensym("settext"), fDSPfactory->get_sourcecode(), gensym("utf-8"));
1574         object_attr_setchar(fEditor, gensym("scratch"), 1);
1575         char name[256];
1576         snprintf(name, 256, "DSP code : %s", fDSPfactory->get_name().c_str());
1577         object_attr_setsym(fEditor, gensym("title"), gensym(name));
1578     }
1579 }
1580 
1581 // Display the Faust module's parameters along with their standard values
display_dsp_params()1582 void faustgen::display_dsp_params()
1583 {
1584     fDSPUI->displayControls();
1585     post("JSON : %s", fDSPfactory->get_json());
1586 }
1587 
display_svg()1588 void faustgen::display_svg()
1589 {
1590     fDSPfactory->display_svg();
1591 }
1592 
display_documentation()1593 void faustgen::display_documentation()
1594 {
1595     fDSPfactory->display_documentation();
1596 }
1597 
display_libraries()1598 void faustgen::display_libraries()
1599 {
1600     fDSPfactory->display_libraries();
1601 }
1602 
hilight_on()1603 void faustgen::hilight_on()
1604 {
1605     t_jrgba color;
1606     jrgba_set(&color, 1.0, 0.0, 0.0, 1.0);
1607     t_object* box;
1608     object_obex_lookup((t_object*)&m_ob, gensym("#B"), &box);
1609     jbox_set_color(box, &color);
1610 }
1611 
hilight_off()1612 void faustgen::hilight_off()
1613 {
1614     t_object* box;
1615     object_obex_lookup((t_object*)&m_ob, gensym("#B"), &box);
1616     jbox_set_color(box, &gDefaultColor);
1617 }
1618 
hilight_error(const string & error)1619 void faustgen::hilight_error(const string& error)
1620 {
1621     object_error_obtrusive((t_object*)&m_ob, (char*)error.c_str());
1622 }
1623 
init_controllers()1624 void faustgen::init_controllers()
1625 {
1626     // Initialize User Interface (here connnection with controls)
1627     if (!fDSPUI) {
1628         fDSPUI = new mspUI();
1629         fDSP->buildUserInterface(fDSPUI);
1630     }
1631 
1632     // MIDI handling
1633     if (!fMidiUI) {
1634         fMidiUI = new MidiUI(&fMidiHandler);
1635         fDSP->buildUserInterface(fMidiUI);
1636     }
1637 
1638     // State handling
1639     if (!fSavedUI) {
1640         fSavedUI = new SaveLabelUI();
1641     }
1642 
1643     // Soundfile handling
1644     if (fDSPfactory->fSoundUI) {
1645         fDSP->buildUserInterface(fDSPfactory->fSoundUI);
1646     }
1647 }
1648 
create_dsp(bool init)1649 void faustgen::create_dsp(bool init)
1650 {
1651     fDSPfactory->lock_audio();
1652     fDSPfactory->lock_ui();
1653     {
1654         fDSP = fDSPfactory->create_dsp_aux();
1655         assert(fDSP);
1656 
1657         // Init all controllers (UI, MIDI, Soundfile)
1658         init_controllers();
1659 
1660         // Initialize at the system's sampling rate
1661         fDSP->init(sys_getsr());
1662 
1663         // Setup MAX audio IO
1664         bool dspstate = false;
1665 
1666         if ((m_siginlets != fDSP->getNumInputs()) || (m_sigoutlets != fDSP->getNumOutputs())) {
1667             // Number of ins/outs have changed... possibly stop IO
1668             dspstate = sys_getdspobjdspstate((t_object*)&m_ob);
1669             if (dspstate) {
1670                 dsp_status("stop");
1671             }
1672         }
1673 
1674         setupIO(&faustgen::perform, &faustgen::init, fDSP->getNumInputs(), fDSP->getNumOutputs(), init);
1675 
1676         // Setup m_midi_outlet MIDI output handler
1677         fMidiHandler.m_midi_outlet = m_midi_outlet;
1678 
1679         // Load old controller state
1680         fDSP->buildUserInterface(fSavedUI);
1681 
1682         // Possibly restart IO
1683         if (dspstate) {
1684             dsp_status("start");
1685         }
1686 
1687         // Send JSON to JS script
1688         create_jsui();
1689     }
1690     fDSPfactory->unlock_ui();
1691     fDSPfactory->unlock_audio();
1692 }
1693 
set_dirty()1694 void faustgen::set_dirty()
1695 {
1696     t_object* mypatcher;
1697     object_obex_lookup(&m_ob, gensym("#P"), &mypatcher);
1698     jpatcher_set_dirty(mypatcher, 1);
1699 }
1700 
check_dac()1701 t_pxobject* faustgen::check_dac()
1702 {
1703     t_object *patcher, *box, *obj;
1704     object_obex_lookup(this, gensym("#P"), &patcher);
1705 
1706     for (box = jpatcher_get_firstobject(patcher); box; box = jbox_get_nextobject(box)) {
1707         obj = jbox_get_object(box);
1708         if (obj) {
1709             if ((object_classname(obj) == gensym("dac~"))
1710                 || (object_classname(obj) == gensym("ezdac~"))
1711                 || (object_classname(obj) == gensym("ezadc~"))
1712                 || (object_classname(obj) == gensym("adc~"))) {
1713                 return (t_pxobject*)box;
1714             }
1715         }
1716     }
1717 
1718     return NULL;
1719 }
1720 
create_jsui()1721 void faustgen::create_jsui()
1722 {
1723     t_object *patcher, *box, *obj;
1724     object_obex_lookup(this, gensym("#P"), &patcher);
1725 
1726     for (box = jpatcher_get_firstobject(patcher); box; box = jbox_get_nextobject(box)) {
1727         obj = jbox_get_object(box);
1728         // Notify JSON
1729         if (obj && strcmp(object_classname(obj)->s_name, "js") == 0) {
1730             t_atom json;
1731             atom_setsym(&json, gensym(fDSPfactory->get_json()));
1732             object_method_typed(obj, gensym("anything"), 1, &json, 0);
1733         }
1734     }
1735 
1736     // Keep all outputs to be notified in update_outputs
1737     fOutputTable.clear();
1738     for (box = jpatcher_get_firstobject(patcher); box; box = jbox_get_nextobject(box)) {
1739         obj = jbox_get_object(box);
1740         t_symbol* scriptingname = jbox_get_varname(obj); // scripting name
1741         // Keep control outputs
1742         if (scriptingname && fDSPUI->isOutputValue(scriptingname->s_name)) {
1743             fOutputTable[scriptingname->s_name].push_back(obj);
1744         }
1745     }
1746 }
1747 
update_outputs()1748 void faustgen::update_outputs()
1749 {
1750     for (auto& it1 : fOutputTable) {
1751         bool new_val = false;
1752         FAUSTFLOAT value = fDSPUI->getOutputValue(it1.first, new_val);
1753         if (new_val) {
1754             t_atom at_value;
1755             atom_setfloat(&at_value, value);
1756             for (auto& it2 : it1.second) {
1757                 object_method_typed(it2, gensym("float"), 1, &at_value, 0);
1758             }
1759         }
1760     }
1761 }
1762 
dsp_status(const char * mess)1763 void faustgen::dsp_status(const char* mess)
1764 {
1765     t_pxobject* dac = NULL;
1766 
1767     if ((dac = check_dac())) {
1768         t_atom msg[1];
1769         atom_setsym(msg, gensym(mess));
1770         object_method_typed(dac, gensym("message"), 1, msg, 0);
1771     } else { // Global
1772         object_method(gensym("dsp")->s_thing, gensym(mess));
1773     }
1774 }
1775 
mute(long inlet,long mute)1776 void faustgen::mute(long inlet, long mute)
1777 {
1778     fMute = mute;
1779 }
1780 
ext_main(void * r)1781 extern "C" void ext_main(void* r)
1782 {
1783 #ifdef WIN32
1784     static bool done = false;
1785     if (done) return;
1786     done = true;
1787 #endif
1788 
1789     // Creates an instance of Faustgen
1790     t_class * mclass = faustgen::makeMaxClass("faustgen~");
1791     post("faustgen~ v%s (sample = 64 bits code = %s)", FAUSTGEN_VERSION, getCodeSize());
1792     post("LLVM powered Faust embedded compiler v%s", getCLibFaustVersion());
1793     post("Copyright (c) 2012-2021 Grame");
1794 
1795     // Start 'libfaust' in multi-thread safe mode
1796     startMTDSPFactories();
1797 
1798     // Process all messages coming to the object using a custom method
1799     REGISTER_METHOD_GIMME(faustgen, anything);
1800 
1801     // Process the "compileoptions" message, to add optional compilation possibilities
1802     REGISTER_METHOD_GIMME(faustgen, compileoptions);
1803 
1804     // Process the "midievent" message
1805     REGISTER_METHOD_GIMME(faustgen, midievent);
1806 
1807     // Process the "polyphony" message
1808     REGISTER_METHOD_GIMME(faustgen, polyphony);
1809 
1810     // Process the "init" message
1811     REGISTER_METHOD_GIMME(faustgen, init);
1812 
1813     // Process the "dump" message
1814     REGISTER_METHOD_GIMME(faustgen, dump);
1815 
1816     // Process the "osc" message
1817     REGISTER_METHOD_GIMME(faustgen, osc);
1818 
1819     // Register inside Max the necessary methods
1820     REGISTER_METHOD_DEFSYM(faustgen, read);
1821     REGISTER_METHOD_DEFSYM(faustgen, write);
1822     REGISTER_METHOD_DEFSYM(faustgen, librarypath);
1823     REGISTER_METHOD_LONG(faustgen, mute);
1824     REGISTER_METHOD_CANT(faustgen, dblclick);
1825     REGISTER_METHOD_ASSIST(faustgen, assist);
1826     REGISTER_METHOD_EDCLOSE(faustgen, edclose);
1827     REGISTER_METHOD_JSAVE(faustgen, appendtodictionary);
1828 }
1829 
1830