Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

main.cpp 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. /*
  2. ZynAddSubFX - a software synthesizer
  3. main.cpp - Main file of the synthesizer
  4. Copyright (C) 2002-2005 Nasca Octavian Paul
  5. Copyright (C) 2012-2014 Mark McCurry
  6. This program is free software; you can redistribute it and/or
  7. modify it under the terms of the GNU General Public License
  8. as published by the Free Software Foundation; either version 2
  9. of the License, or (at your option) any later version.
  10. */
  11. #include <iostream>
  12. #include <fstream>
  13. #include <map>
  14. #include <cmath>
  15. #include <cctype>
  16. #include <algorithm>
  17. #include <signal.h>
  18. #include <unistd.h>
  19. #include <pthread.h>
  20. #include <getopt.h>
  21. #include <lo/lo.h>
  22. #include <rtosc/ports.h>
  23. #include <rtosc/thread-link.h>
  24. #include "Params/PADnoteParameters.h"
  25. #include "DSP/FFTwrapper.h"
  26. #include "Misc/PresetExtractor.h"
  27. #include "Misc/Master.h"
  28. #include "Misc/Part.h"
  29. #include "Misc/Util.h"
  30. //Nio System
  31. #include "Nio/Nio.h"
  32. //GUI System
  33. #include "UI/Connection.h"
  34. GUI::ui_handle_t gui;
  35. //Glue Layer
  36. #include "Misc/MiddleWare.h"
  37. MiddleWare *middleware;
  38. using namespace std;
  39. Master *master;
  40. int swaplr = 0; //1 for left-right swapping
  41. int Pexitprogram = 0; //if the UI set this to 1, the program will exit
  42. #if LASH
  43. #include "Misc/LASHClient.h"
  44. LASHClient *lash = NULL;
  45. #endif
  46. #if USE_NSM
  47. #include "UI/NSM.H"
  48. NSM_Client *nsm = 0;
  49. #endif
  50. char *instance_name = 0;
  51. void exitprogram(const Config &config);
  52. extern pthread_t main_thread;
  53. //cleanup on signaled exit
  54. void sigterm_exit(int /*sig*/)
  55. {
  56. if(Pexitprogram)
  57. exit(1);
  58. Pexitprogram = 1;
  59. }
  60. /*
  61. * Program initialisation
  62. */
  63. void initprogram(SYNTH_T synth, Config* config, int prefered_port)
  64. {
  65. middleware = new MiddleWare(std::move(synth), config, prefered_port);
  66. master = middleware->spawnMaster();
  67. master->swaplr = swaplr;
  68. signal(SIGINT, sigterm_exit);
  69. signal(SIGTERM, sigterm_exit);
  70. Nio::init(master->synth, config->cfg.oss_devs, master);
  71. }
  72. /*
  73. * Program exit
  74. */
  75. void exitprogram(const Config& config)
  76. {
  77. Nio::stop();
  78. config.save();
  79. middleware->removeAutoSave();
  80. GUI::destroyUi(gui);
  81. delete middleware;
  82. #if LASH
  83. if(lash)
  84. delete lash;
  85. #endif
  86. #if USE_NSM
  87. if(nsm)
  88. delete nsm;
  89. #endif
  90. FFT_cleanup();
  91. }
  92. int main(int argc, char *argv[])
  93. {
  94. main_thread = pthread_self();
  95. SYNTH_T synth;
  96. Config config;
  97. config.init();
  98. int noui = 0;
  99. cerr
  100. << "\nZynAddSubFX - Copyright (c) 2002-2013 Nasca Octavian Paul and others"
  101. << endl;
  102. cerr
  103. << " Copyright (c) 2009-2016 Mark McCurry [active maintainer]"
  104. << endl;
  105. cerr << "Compiled: " << __DATE__ << " " << __TIME__ << endl;
  106. cerr << "This program is free software (GNU GPL v2 or later) and \n";
  107. cerr << "it comes with ABSOLUTELY NO WARRANTY.\n" << endl;
  108. if(argc == 1)
  109. cerr << "Try 'zynaddsubfx --help' for command-line options." << endl;
  110. /* Get the settings from the Config*/
  111. synth.samplerate = config.cfg.SampleRate;
  112. synth.buffersize = config.cfg.SoundBufferSize;
  113. synth.oscilsize = config.cfg.OscilSize;
  114. swaplr = config.cfg.SwapStereo;
  115. Nio::preferedSampleRate(synth.samplerate);
  116. synth.alias(); //build aliases
  117. sprng(time(NULL));
  118. /* Parse command-line options */
  119. struct option opts[] = {
  120. {
  121. "load", 2, NULL, 'l'
  122. },
  123. {
  124. "load-instrument", 2, NULL, 'L'
  125. },
  126. {
  127. "sample-rate", 2, NULL, 'r'
  128. },
  129. {
  130. "buffer-size", 2, NULL, 'b'
  131. },
  132. {
  133. "oscil-size", 2, NULL, 'o'
  134. },
  135. {
  136. "swap", 2, NULL, 'S'
  137. },
  138. {
  139. "no-gui", 0, NULL, 'U'
  140. },
  141. {
  142. "dummy", 2, NULL, 'Y'
  143. },
  144. {
  145. "help", 2, NULL, 'h'
  146. },
  147. {
  148. "version", 2, NULL, 'v'
  149. },
  150. {
  151. "named", 1, NULL, 'N'
  152. },
  153. {
  154. "auto-connect", 0, NULL, 'a'
  155. },
  156. {
  157. "auto-save", 0, NULL, 'A'
  158. },
  159. {
  160. "pid-in-client-name", 0, NULL, 'p'
  161. },
  162. {
  163. "prefered-port", 1, NULL, 'P',
  164. },
  165. {
  166. "output", 1, NULL, 'O'
  167. },
  168. {
  169. "input", 1, NULL, 'I'
  170. },
  171. {
  172. "exec-after-init", 1, NULL, 'e'
  173. },
  174. {
  175. "dump-oscdoc", 2, NULL, 'd'
  176. },
  177. {
  178. "dump-json-schema", 2, NULL, 'D'
  179. },
  180. {
  181. "ui-title", 1, NULL, 'u'
  182. },
  183. {
  184. 0, 0, 0, 0
  185. }
  186. };
  187. opterr = 0;
  188. int option_index = 0, opt, exitwithhelp = 0, exitwithversion = 0;
  189. int prefered_port = -1;
  190. int auto_save_interval = 60;
  191. string loadfile, loadinstrument, execAfterInit, ui_title;
  192. while(1) {
  193. int tmp = 0;
  194. /**\todo check this process for a small memory leak*/
  195. opt = getopt_long(argc,
  196. argv,
  197. "l:L:r:b:o:I:O:N:e:P:A:u:D:hvapSDUY",
  198. opts,
  199. &option_index);
  200. char *optarguments = optarg;
  201. #define GETOP(x) if(optarguments) \
  202. x = optarguments
  203. #define GETOPNUM(x) if(optarguments) \
  204. x = atoi(optarguments)
  205. if(opt == -1)
  206. break;
  207. switch(opt) {
  208. case 'h':
  209. exitwithhelp = 1;
  210. break;
  211. case 'v':
  212. exitwithversion = 1;
  213. break;
  214. case 'Y': /* this command a dummy command (has NO effect)
  215. and is used because I need for NSIS installer
  216. (NSIS sometimes forces a command line for a
  217. program, even if I don't need that; eg. when
  218. I want to add a icon to a shortcut.
  219. */
  220. break;
  221. case 'U':
  222. noui = 1;
  223. break;
  224. case 'l':
  225. GETOP(loadfile);
  226. break;
  227. case 'L':
  228. GETOP(loadinstrument);
  229. break;
  230. case 'r':
  231. GETOPNUM(synth.samplerate);
  232. if(synth.samplerate < 4000) {
  233. cerr << "ERROR:Incorrect sample rate: " << optarguments
  234. << endl;
  235. exit(1);
  236. }
  237. break;
  238. case 'b':
  239. GETOPNUM(synth.buffersize);
  240. if(synth.buffersize < 2) {
  241. cerr << "ERROR:Incorrect buffer size: " << optarguments
  242. << endl;
  243. exit(1);
  244. }
  245. break;
  246. case 'o':
  247. if(optarguments)
  248. synth.oscilsize = tmp = atoi(optarguments);
  249. if(synth.oscilsize < MAX_AD_HARMONICS * 2)
  250. synth.oscilsize = MAX_AD_HARMONICS * 2;
  251. synth.oscilsize =
  252. (int) powf(2,
  253. ceil(logf(synth.oscilsize - 1.0f) / logf(2.0f)));
  254. if(tmp != synth.oscilsize)
  255. cerr
  256. <<
  257. "synth.oscilsize is wrong (must be 2^n) or too small. Adjusting to "
  258. << synth.oscilsize << "." << endl;
  259. break;
  260. case 'S':
  261. swaplr = 1;
  262. break;
  263. case 'N':
  264. Nio::setPostfix(optarguments);
  265. break;
  266. case 'I':
  267. if(optarguments)
  268. Nio::setDefaultSource(optarguments);
  269. break;
  270. case 'O':
  271. if(optarguments)
  272. Nio::setDefaultSink(optarguments);
  273. break;
  274. case 'a':
  275. Nio::autoConnect = true;
  276. break;
  277. case 'p':
  278. Nio::pidInClientName = true;
  279. break;
  280. case 'P':
  281. if(optarguments)
  282. prefered_port = atoi(optarguments);
  283. break;
  284. case 'A':
  285. if(optarguments)
  286. auto_save_interval = atoi(optarguments);
  287. break;
  288. case 'e':
  289. GETOP(execAfterInit);
  290. break;
  291. case 'd':
  292. if(optarguments)
  293. {
  294. rtosc::OscDocFormatter s;
  295. ofstream outfile(optarguments);
  296. s.prog_name = "ZynAddSubFX";
  297. s.p = &Master::ports;
  298. s.uri = "http://example.com/fake/";
  299. s.doc_origin = "http://example.com/fake/url.xml";
  300. s.author_first = "Mark";
  301. s.author_last = "McCurry";
  302. outfile << s;
  303. }
  304. break;
  305. case 'D':
  306. if(optarguments)
  307. {
  308. ofstream outfile(optarguments);
  309. void dump_json(std::ostream &o,
  310. const rtosc::Ports &p);
  311. dump_json(outfile, Master::ports);
  312. }
  313. break;
  314. case 'u':
  315. if(optarguments)
  316. ui_title = optarguments;
  317. break;
  318. case '?':
  319. cerr << "ERROR:Bad option or parameter.\n" << endl;
  320. exitwithhelp = 1;
  321. break;
  322. }
  323. }
  324. synth.alias();
  325. if(exitwithversion) {
  326. cout << "Version: " << VERSION << endl;
  327. return 0;
  328. }
  329. if(exitwithhelp != 0) {
  330. cout << "Usage: zynaddsubfx [OPTION]\n\n"
  331. << " -h , --help \t\t\t\t Display command-line help and exit\n"
  332. << " -v , --version \t\t\t Display version and exit\n"
  333. << " -l file, --load=FILE\t\t\t Loads a .xmz file\n"
  334. << " -L file, --load-instrument=FILE\t Loads a .xiz file\n"
  335. << " -r SR, --sample-rate=SR\t\t Set the sample rate SR\n"
  336. <<
  337. " -b BS, --buffer-size=SR\t\t Set the buffer size (granularity)\n"
  338. << " -o OS, --oscil-size=OS\t\t Set the ADsynth oscil. size\n"
  339. << " -S , --swap\t\t\t\t Swap Left <--> Right\n"
  340. <<
  341. " -U , --no-gui\t\t\t\t Run ZynAddSubFX without user interface\n"
  342. << " -N , --named\t\t\t\t Postfix IO Name when possible\n"
  343. << " -a , --auto-connect\t\t\t AutoConnect when using JACK\n"
  344. << " -A , --auto-save=INTERVAL\t\t Automatically save at interval (disabled for negative intervals)\n"
  345. << " -p , --pid-in-client-name\t\t Append PID to (JACK) "
  346. "client name\n"
  347. << " -P , --preferred-port\t\t\t Preferred OSC Port\n"
  348. << " -O , --output\t\t\t\t Set Output Engine\n"
  349. << " -I , --input\t\t\t\t Set Input Engine\n"
  350. << " -e , --exec-after-init\t\t Run post-initialization script\n"
  351. << " -d , --dump-oscdoc=FILE\t\t Dump oscdoc xml to file\n"
  352. << " -u , --ui-title=TITLE\t\t Extend UI Window Titles\n"
  353. << endl;
  354. return 0;
  355. }
  356. cerr.precision(1);
  357. cerr << std::fixed;
  358. cerr << "\nSample Rate = \t\t" << synth.samplerate << endl;
  359. cerr << "Sound Buffer Size = \t" << synth.buffersize << " samples" << endl;
  360. cerr << "Internal latency = \t" << synth.dt() * 1000.0f << " ms" << endl;
  361. cerr << "ADsynth Oscil.Size = \t" << synth.oscilsize << " samples" << endl;
  362. initprogram(std::move(synth), &config, prefered_port);
  363. bool altered_master = false;
  364. if(!loadfile.empty()) {
  365. altered_master = true;
  366. const char *filename = loadfile.c_str();
  367. int tmp = master->loadXML(filename);
  368. if(tmp < 0) {
  369. cerr << "ERROR: Could not load master file " << loadfile
  370. << "." << endl;
  371. exit(1);
  372. }
  373. else {
  374. strncpy(master->last_xmz, filename, XMZ_PATH_MAX);
  375. master->last_xmz[XMZ_PATH_MAX-1] = 0;
  376. master->applyparameters();
  377. cout << "Master file loaded." << endl;
  378. }
  379. }
  380. if(!loadinstrument.empty()) {
  381. altered_master = true;
  382. int loadtopart = 0;
  383. int tmp = master->part[loadtopart]->loadXMLinstrument(
  384. loadinstrument.c_str());
  385. if(tmp < 0) {
  386. cerr << "ERROR: Could not load instrument file "
  387. << loadinstrument << '.' << endl;
  388. exit(1);
  389. }
  390. else {
  391. master->part[loadtopart]->applyparameters();
  392. master->part[loadtopart]->initialize_rt();
  393. cout << "Instrument file loaded." << endl;
  394. }
  395. }
  396. if(altered_master)
  397. middleware->updateResources(master);
  398. //Run the Nio system
  399. bool ioGood = Nio::start();
  400. if(!execAfterInit.empty()) {
  401. cout << "Executing user supplied command: " << execAfterInit << endl;
  402. if(system(execAfterInit.c_str()) == -1)
  403. cerr << "Command Failed..." << endl;
  404. }
  405. gui = NULL;
  406. //Capture Startup Responses
  407. typedef std::vector<const char *> wait_t;
  408. wait_t msg_waitlist;
  409. middleware->setUiCallback([](void*v,const char*msg) {
  410. wait_t &wait = *(wait_t*)v;
  411. size_t len = rtosc_message_length(msg, -1);
  412. char *copy = new char[len];
  413. memcpy(copy, msg, len);
  414. wait.push_back(copy);
  415. }, &msg_waitlist);
  416. if(!noui)
  417. gui = GUI::createUi(middleware->spawnUiApi(), &Pexitprogram);
  418. middleware->setUiCallback(GUI::raiseUi, gui);
  419. middleware->setIdleCallback([](void*){GUI::tickUi(gui);}, NULL);
  420. //Replay Startup Responses
  421. for(auto msg:msg_waitlist) {
  422. GUI::raiseUi(gui, msg);
  423. delete [] msg;
  424. }
  425. //set titles
  426. if(!ui_title.empty())
  427. GUI::raiseUi(gui, "/ui/title", "s", ui_title.c_str());
  428. if(!noui)
  429. {
  430. GUI::raiseUi(gui, "/show", "i", config.cfg.UserInterfaceMode);
  431. if(!ioGood)
  432. GUI::raiseUi(gui, "/alert", "s",
  433. "Default IO did not initialize.\nDefaulting to NULL backend.");
  434. }
  435. if(auto_save_interval >= 0) {
  436. int old_save = middleware->checkAutoSave();
  437. if(old_save > 0)
  438. GUI::raiseUi(gui, "/alert-reload", "i", old_save);
  439. middleware->enableAutoSave(auto_save_interval);
  440. }
  441. #if USE_NSM
  442. char *nsm_url = getenv("NSM_URL");
  443. if(nsm_url) {
  444. nsm = new NSM_Client;
  445. if(!nsm->init(nsm_url))
  446. nsm->announce("ZynAddSubFX", ":switch:", argv[0]);
  447. else {
  448. delete nsm;
  449. nsm = NULL;
  450. }
  451. }
  452. #endif
  453. #if USE_NSM
  454. if(!nsm)
  455. #endif
  456. {
  457. #if LASH
  458. lash = new LASHClient(&argc, &argv);
  459. GUI::raiseUi(gui, "/session-type", "s", "LASH");
  460. #endif
  461. }
  462. while(Pexitprogram == 0) {
  463. #if USE_NSM
  464. if(nsm) {
  465. nsm->check();
  466. goto done;
  467. }
  468. #endif
  469. #if LASH
  470. {
  471. string filename;
  472. switch(lash->checkevents(filename)) {
  473. case LASHClient::Save:
  474. GUI::raiseUi(gui, "/save-master", "s", filename.c_str());
  475. lash->confirmevent(LASHClient::Save);
  476. break;
  477. case LASHClient::Restore:
  478. GUI::raiseUi(gui, "/load-master", "s", filename.c_str());
  479. lash->confirmevent(LASHClient::Restore);
  480. break;
  481. case LASHClient::Quit:
  482. Pexitprogram = 1;
  483. default:
  484. break;
  485. }
  486. }
  487. #endif //LASH
  488. #if USE_NSM
  489. done:
  490. #endif
  491. GUI::tickUi(gui);
  492. middleware->tick();
  493. }
  494. exitprogram(config);
  495. return 0;
  496. }