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.

549 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-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", 2, 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. "ui-title", 1, NULL, 'u'
  179. },
  180. {
  181. 0, 0, 0, 0
  182. }
  183. };
  184. opterr = 0;
  185. int option_index = 0, opt, exitwithhelp = 0, exitwithversion = 0;
  186. int prefered_port = -1;
  187. int auto_save_interval = 60;
  188. string loadfile, loadinstrument, execAfterInit, ui_title;
  189. while(1) {
  190. int tmp = 0;
  191. /**\todo check this process for a small memory leak*/
  192. opt = getopt_long(argc,
  193. argv,
  194. "l:L:r:b:o:I:O:N:e:P:A:u:hvapSDUY",
  195. opts,
  196. &option_index);
  197. char *optarguments = optarg;
  198. #define GETOP(x) if(optarguments) \
  199. x = optarguments
  200. #define GETOPNUM(x) if(optarguments) \
  201. x = atoi(optarguments)
  202. if(opt == -1)
  203. break;
  204. switch(opt) {
  205. case 'h':
  206. exitwithhelp = 1;
  207. break;
  208. case 'v':
  209. exitwithversion = 1;
  210. break;
  211. case 'Y': /* this command a dummy command (has NO effect)
  212. and is used because I need for NSIS installer
  213. (NSIS sometimes forces a command line for a
  214. program, even if I don't need that; eg. when
  215. I want to add a icon to a shortcut.
  216. */
  217. break;
  218. case 'U':
  219. noui = 1;
  220. break;
  221. case 'l':
  222. GETOP(loadfile);
  223. break;
  224. case 'L':
  225. GETOP(loadinstrument);
  226. break;
  227. case 'r':
  228. GETOPNUM(synth.samplerate);
  229. if(synth.samplerate < 4000) {
  230. cerr << "ERROR:Incorrect sample rate: " << optarguments
  231. << endl;
  232. exit(1);
  233. }
  234. break;
  235. case 'b':
  236. GETOPNUM(synth.buffersize);
  237. if(synth.buffersize < 2) {
  238. cerr << "ERROR:Incorrect buffer size: " << optarguments
  239. << endl;
  240. exit(1);
  241. }
  242. break;
  243. case 'o':
  244. if(optarguments)
  245. synth.oscilsize = tmp = atoi(optarguments);
  246. if(synth.oscilsize < MAX_AD_HARMONICS * 2)
  247. synth.oscilsize = MAX_AD_HARMONICS * 2;
  248. synth.oscilsize =
  249. (int) powf(2,
  250. ceil(logf(synth.oscilsize - 1.0f) / logf(2.0f)));
  251. if(tmp != synth.oscilsize)
  252. cerr
  253. <<
  254. "synth.oscilsize is wrong (must be 2^n) or too small. Adjusting to "
  255. << synth.oscilsize << "." << endl;
  256. break;
  257. case 'S':
  258. swaplr = 1;
  259. break;
  260. case 'N':
  261. Nio::setPostfix(optarguments);
  262. break;
  263. case 'I':
  264. if(optarguments)
  265. Nio::setDefaultSource(optarguments);
  266. break;
  267. case 'O':
  268. if(optarguments)
  269. Nio::setDefaultSink(optarguments);
  270. break;
  271. case 'a':
  272. Nio::autoConnect = true;
  273. break;
  274. case 'p':
  275. Nio::pidInClientName = true;
  276. break;
  277. case 'P':
  278. if(optarguments)
  279. prefered_port = atoi(optarguments);
  280. break;
  281. case 'A':
  282. if(optarguments)
  283. auto_save_interval = atoi(optarguments);
  284. break;
  285. case 'e':
  286. GETOP(execAfterInit);
  287. break;
  288. case 'd':
  289. if(optarguments)
  290. {
  291. rtosc::OscDocFormatter s;
  292. ofstream outfile(optarguments);
  293. s.prog_name = "ZynAddSubFX";
  294. s.p = &Master::ports;
  295. s.uri = "http://example.com/fake/";
  296. s.doc_origin = "http://example.com/fake/url.xml";
  297. s.author_first = "Mark";
  298. s.author_last = "McCurry";
  299. outfile << s;
  300. }
  301. break;
  302. case 'u':
  303. if(optarguments)
  304. ui_title = optarguments;
  305. break;
  306. case '?':
  307. cerr << "ERROR:Bad option or parameter.\n" << endl;
  308. exitwithhelp = 1;
  309. break;
  310. }
  311. }
  312. synth.alias();
  313. if(exitwithversion) {
  314. cout << "Version: " << VERSION << endl;
  315. return 0;
  316. }
  317. if(exitwithhelp != 0) {
  318. cout << "Usage: zynaddsubfx [OPTION]\n\n"
  319. << " -h , --help \t\t\t\t Display command-line help and exit\n"
  320. << " -v , --version \t\t\t Display version and exit\n"
  321. << " -l file, --load=FILE\t\t\t Loads a .xmz file\n"
  322. << " -L file, --load-instrument=FILE\t Loads a .xiz file\n"
  323. << " -r SR, --sample-rate=SR\t\t Set the sample rate SR\n"
  324. <<
  325. " -b BS, --buffer-size=SR\t\t Set the buffer size (granularity)\n"
  326. << " -o OS, --oscil-size=OS\t\t Set the ADsynth oscil. size\n"
  327. << " -S , --swap\t\t\t\t Swap Left <--> Right\n"
  328. <<
  329. " -U , --no-gui\t\t\t\t Run ZynAddSubFX without user interface\n"
  330. << " -N , --named\t\t\t\t Postfix IO Name when possible\n"
  331. << " -a , --auto-connect\t\t\t AutoConnect when using JACK\n"
  332. << " -A , --auto-save=INTERVAL\t\t Automatically save at interval (disabled for negative intervals)\n"
  333. << " -p , --pid-in-client-name\t\t Append PID to (JACK) "
  334. "client name\n"
  335. << " -P , --preferred-port\t\t\t Preferred OSC Port\n"
  336. << " -O , --output\t\t\t\t Set Output Engine\n"
  337. << " -I , --input\t\t\t\t Set Input Engine\n"
  338. << " -e , --exec-after-init\t\t Run post-initialization script\n"
  339. << " -d , --dump-oscdoc=FILE\t\t Dump oscdoc xml to file\n"
  340. << " -u , --ui-title=TITLE\t\t Extend UI Window Titles\n"
  341. << endl;
  342. return 0;
  343. }
  344. cerr.precision(1);
  345. cerr << std::fixed;
  346. cerr << "\nSample Rate = \t\t" << synth.samplerate << endl;
  347. cerr << "Sound Buffer Size = \t" << synth.buffersize << " samples" << endl;
  348. cerr << "Internal latency = \t" << synth.dt() * 1000.0f << " ms" << endl;
  349. cerr << "ADsynth Oscil.Size = \t" << synth.oscilsize << " samples" << endl;
  350. initprogram(std::move(synth), &config, prefered_port);
  351. bool altered_master = false;
  352. if(!loadfile.empty()) {
  353. altered_master = true;
  354. const char *filename = loadfile.c_str();
  355. int tmp = master->loadXML(filename);
  356. if(tmp < 0) {
  357. cerr << "ERROR: Could not load master file " << loadfile
  358. << "." << endl;
  359. exit(1);
  360. }
  361. else {
  362. strncpy(master->last_xmz, filename, XMZ_PATH_MAX);
  363. master->last_xmz[XMZ_PATH_MAX-1] = 0;
  364. master->applyparameters();
  365. cout << "Master file loaded." << endl;
  366. }
  367. }
  368. if(!loadinstrument.empty()) {
  369. altered_master = true;
  370. int loadtopart = 0;
  371. int tmp = master->part[loadtopart]->loadXMLinstrument(
  372. loadinstrument.c_str());
  373. if(tmp < 0) {
  374. cerr << "ERROR: Could not load instrument file "
  375. << loadinstrument << '.' << endl;
  376. exit(1);
  377. }
  378. else {
  379. master->part[loadtopart]->applyparameters();
  380. master->part[loadtopart]->initialize_rt();
  381. cout << "Instrument file loaded." << endl;
  382. }
  383. }
  384. if(altered_master)
  385. middleware->updateResources(master);
  386. //Run the Nio system
  387. bool ioGood = Nio::start();
  388. if(!execAfterInit.empty()) {
  389. cout << "Executing user supplied command: " << execAfterInit << endl;
  390. if(system(execAfterInit.c_str()) == -1)
  391. cerr << "Command Failed..." << endl;
  392. }
  393. gui = NULL;
  394. //Capture Startup Responses
  395. typedef std::vector<const char *> wait_t;
  396. wait_t msg_waitlist;
  397. middleware->setUiCallback([](void*v,const char*msg) {
  398. wait_t &wait = *(wait_t*)v;
  399. size_t len = rtosc_message_length(msg, -1);
  400. char *copy = new char[len];
  401. memcpy(copy, msg, len);
  402. wait.push_back(copy);
  403. }, &msg_waitlist);
  404. if(!noui)
  405. gui = GUI::createUi(middleware->spawnUiApi(), &Pexitprogram);
  406. middleware->setUiCallback(GUI::raiseUi, gui);
  407. middleware->setIdleCallback([](void*){GUI::tickUi(gui);}, NULL);
  408. //Replay Startup Responses
  409. for(auto msg:msg_waitlist) {
  410. GUI::raiseUi(gui, msg);
  411. delete [] msg;
  412. }
  413. //set titles
  414. if(!ui_title.empty())
  415. GUI::raiseUi(gui, "/ui/title", "s", ui_title.c_str());
  416. if(!noui)
  417. {
  418. GUI::raiseUi(gui, "/show", "i", config.cfg.UserInterfaceMode);
  419. if(!ioGood)
  420. GUI::raiseUi(gui, "/alert", "s",
  421. "Default IO did not initialize.\nDefaulting to NULL backend.");
  422. }
  423. if(auto_save_interval >= 0) {
  424. int old_save = middleware->checkAutoSave();
  425. if(old_save > 0)
  426. GUI::raiseUi(gui, "/alert-reload", "i", old_save);
  427. middleware->enableAutoSave(auto_save_interval);
  428. }
  429. #if USE_NSM
  430. char *nsm_url = getenv("NSM_URL");
  431. if(nsm_url) {
  432. nsm = new NSM_Client;
  433. if(!nsm->init(nsm_url))
  434. nsm->announce("ZynAddSubFX", ":switch:", argv[0]);
  435. else {
  436. delete nsm;
  437. nsm = NULL;
  438. }
  439. }
  440. #endif
  441. #if USE_NSM
  442. if(!nsm)
  443. #endif
  444. {
  445. #if LASH
  446. lash = new LASHClient(&argc, &argv);
  447. GUI::raiseUi(gui, "/session-type", "s", "LASH");
  448. #endif
  449. }
  450. while(Pexitprogram == 0) {
  451. #if USE_NSM
  452. if(nsm) {
  453. nsm->check();
  454. goto done;
  455. }
  456. #endif
  457. #if LASH
  458. {
  459. string filename;
  460. switch(lash->checkevents(filename)) {
  461. case LASHClient::Save:
  462. GUI::raiseUi(gui, "/save-master", "s", filename.c_str());
  463. lash->confirmevent(LASHClient::Save);
  464. break;
  465. case LASHClient::Restore:
  466. GUI::raiseUi(gui, "/load-master", "s", filename.c_str());
  467. lash->confirmevent(LASHClient::Restore);
  468. break;
  469. case LASHClient::Quit:
  470. Pexitprogram = 1;
  471. default:
  472. break;
  473. }
  474. }
  475. #endif //LASH
  476. #if USE_NSM
  477. done:
  478. #endif
  479. GUI::tickUi(gui);
  480. middleware->tick();
  481. }
  482. exitprogram(config);
  483. return 0;
  484. }