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.

562 lines
15KB

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