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.

690 lines
19KB

  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 <ctime>
  17. #include <algorithm>
  18. #include <signal.h>
  19. #include <err.h>
  20. #include <unistd.h>
  21. #include <pthread.h>
  22. #include <getopt.h>
  23. #include <rtosc/rtosc.h>
  24. #include <rtosc/ports.h>
  25. #include <rtosc/thread-link.h>
  26. #include "Params/PADnoteParameters.h"
  27. #include "DSP/FFTwrapper.h"
  28. #include "Misc/PresetExtractor.h"
  29. #include "Misc/Master.h"
  30. #include "Misc/Part.h"
  31. #include "Misc/Util.h"
  32. #include "zyn-version.h"
  33. //Nio System
  34. #include "Nio/Nio.h"
  35. #include "Nio/InMgr.h"
  36. //GUI System
  37. #include "UI/Connection.h"
  38. GUI::ui_handle_t gui;
  39. //Glue Layer
  40. #include "Misc/MiddleWare.h"
  41. MiddleWare *middleware;
  42. using namespace std;
  43. Master *master;
  44. int swaplr = 0; //1 for left-right swapping
  45. extern int Pexitprogram; //if the UI set this to 1, the program will exit
  46. #if LASH
  47. #include "Misc/LASHClient.h"
  48. LASHClient *lash = NULL;
  49. #endif
  50. #if USE_NSM
  51. #include "UI/NSM.H"
  52. NSM_Client *nsm = 0;
  53. #endif
  54. char *instance_name = 0;
  55. void exitprogram(const CarlaConfig &config);
  56. //cleanup on signaled exit
  57. void sigterm_exit(int /*sig*/)
  58. {
  59. if(Pexitprogram)
  60. exit(1);
  61. Pexitprogram = 1;
  62. }
  63. /*
  64. * Program initialisation
  65. */
  66. void initprogram(SYNTH_T synth, CarlaConfig* config, int prefered_port)
  67. {
  68. middleware = new MiddleWare(std::move(synth), config, prefered_port);
  69. master = middleware->spawnMaster();
  70. master->swaplr = swaplr;
  71. signal(SIGINT, sigterm_exit);
  72. signal(SIGTERM, sigterm_exit);
  73. Nio::init(master->synth, config->cfg.oss_devs, master);
  74. }
  75. /*
  76. * Program exit
  77. */
  78. void exitprogram(const CarlaConfig& config)
  79. {
  80. Nio::stop();
  81. config.save();
  82. middleware->removeAutoSave();
  83. GUI::destroyUi(gui);
  84. delete middleware;
  85. #if LASH
  86. if(lash)
  87. delete lash;
  88. #endif
  89. #if USE_NSM
  90. if(nsm)
  91. delete nsm;
  92. #endif
  93. FFT_cleanup();
  94. }
  95. //Windows MIDI OH WHAT A HACK...
  96. #ifdef WIN32
  97. #include <windows.h>
  98. #include <mmsystem.h>
  99. extern InMgr *in;
  100. HMIDIIN winmidiinhandle = 0;
  101. void CALLBACK WinMidiInProc(HMIDIIN hMidiIn,UINT wMsg,DWORD dwInstance,
  102. DWORD dwParam1,DWORD dwParam2)
  103. {
  104. int midicommand=0;
  105. if (wMsg==MIM_DATA) {
  106. int cmd,par1,par2;
  107. cmd=dwParam1&0xff;
  108. if (cmd==0xfe) return;
  109. par1=(dwParam1>>8)&0xff;
  110. par2=dwParam1>>16;
  111. int cmdchan=cmd&0x0f;
  112. int cmdtype=(cmd>>4)&0x0f;
  113. int tmp=0;
  114. MidiEvent ev;
  115. switch (cmdtype) {
  116. case(0x8)://noteon
  117. ev.type = 1;
  118. ev.num = par1;
  119. ev.channel = cmdchan;
  120. ev.value = 0;
  121. in->putEvent(ev);
  122. break;
  123. case(0x9)://noteoff
  124. ev.type = 1;
  125. ev.num = par1;
  126. ev.channel = cmdchan;
  127. ev.value = par2&0xff;
  128. in->putEvent(ev);
  129. break;
  130. case(0xb)://controller
  131. ev.type = 2;
  132. ev.num = par1;
  133. ev.channel = cmdchan;
  134. ev.value = par2&0xff;
  135. in->putEvent(ev);
  136. break;
  137. case(0xe)://pitch wheel
  138. //tmp=(par1+par2*(long int) 128)-8192;
  139. //winmaster->SetController(cmdchan,C_pitchwheel,tmp);
  140. break;
  141. default:
  142. break;
  143. };
  144. };
  145. };
  146. void InitWinMidi(int midi)
  147. {
  148. (void)midi;
  149. for(int i=0; i<10; ++i) {
  150. long int res=midiInOpen(&winmidiinhandle,i,(DWORD_PTR)(void*)WinMidiInProc,0,CALLBACK_FUNCTION);
  151. if(res == MMSYSERR_NOERROR) {
  152. res=midiInStart(winmidiinhandle);
  153. printf("[INFO] Starting Windows MIDI At %d with code %d(noerror=%d)\n", i, res, MMSYSERR_NOERROR);
  154. if(res == 0)
  155. return;
  156. } else
  157. printf("[INFO] No Windows MIDI Device At id %d\n", i);
  158. }
  159. };
  160. //void StopWinMidi()
  161. //{
  162. // midiInStop(winmidiinhandle);
  163. // midiInClose(winmidiinhandle);
  164. //};
  165. #else
  166. void InitWinMidi(int) {}
  167. #endif
  168. int main(int argc, char *argv[])
  169. {
  170. SYNTH_T synth;
  171. CarlaConfig config;
  172. config.init();
  173. int noui = 0;
  174. cerr
  175. << "\nZynAddSubFX - Copyright (c) 2002-2013 Nasca Octavian Paul and others"
  176. << endl;
  177. cerr
  178. << " Copyright (c) 2009-2016 Mark McCurry [active maintainer]"
  179. << endl;
  180. cerr << "Compiled: " << __DATE__ << " " << __TIME__ << endl;
  181. cerr << "This program is free software (GNU GPL v2 or later) and \n";
  182. cerr << "it comes with ABSOLUTELY NO WARRANTY.\n" << endl;
  183. if(argc == 1)
  184. cerr << "Try 'zynaddsubfx --help' for command-line options." << endl;
  185. /* Get the settings from the CarlaConfig*/
  186. synth.samplerate = config.cfg.SampleRate;
  187. synth.buffersize = config.cfg.SoundBufferSize;
  188. synth.oscilsize = config.cfg.OscilSize;
  189. swaplr = config.cfg.SwapStereo;
  190. Nio::preferedSampleRate(synth.samplerate);
  191. synth.alias(); //build aliases
  192. sprng(time(NULL));
  193. /* Parse command-line options */
  194. struct option opts[] = {
  195. {
  196. "load", 2, NULL, 'l'
  197. },
  198. {
  199. "load-instrument", 2, NULL, 'L'
  200. },
  201. {
  202. "midi-learn", 2, NULL, 'M'
  203. },
  204. {
  205. "sample-rate", 2, NULL, 'r'
  206. },
  207. {
  208. "buffer-size", 2, NULL, 'b'
  209. },
  210. {
  211. "oscil-size", 2, NULL, 'o'
  212. },
  213. {
  214. "swap", 2, NULL, 'S'
  215. },
  216. {
  217. "no-gui", 0, NULL, 'U'
  218. },
  219. {
  220. "dummy", 2, NULL, 'Y'
  221. },
  222. {
  223. "help", 2, NULL, 'h'
  224. },
  225. {
  226. "version", 2, NULL, 'v'
  227. },
  228. {
  229. "named", 1, NULL, 'N'
  230. },
  231. {
  232. "auto-connect", 0, NULL, 'a'
  233. },
  234. {
  235. "auto-save", 0, NULL, 'A'
  236. },
  237. {
  238. "pid-in-client-name", 0, NULL, 'p'
  239. },
  240. {
  241. "prefered-port", 1, NULL, 'P',
  242. },
  243. {
  244. "output", 1, NULL, 'O'
  245. },
  246. {
  247. "input", 1, NULL, 'I'
  248. },
  249. {
  250. "exec-after-init", 1, NULL, 'e'
  251. },
  252. {
  253. "dump-oscdoc", 2, NULL, 'd'
  254. },
  255. {
  256. "dump-json-schema", 2, NULL, 'D'
  257. },
  258. {
  259. 0, 0, 0, 0
  260. }
  261. };
  262. opterr = 0;
  263. int option_index = 0, opt, exitwithhelp = 0, exitwithversion = 0;
  264. int prefered_port = -1;
  265. int auto_save_interval = 60;
  266. int wmidi = -1;
  267. string loadfile, loadinstrument, execAfterInit, loadmidilearn;
  268. while(1) {
  269. int tmp = 0;
  270. /**\todo check this process for a small memory leak*/
  271. opt = getopt_long(argc,
  272. argv,
  273. "l:L:M:r:b:o:I:O:N:e:P:A:D:hvapSDUYZ",
  274. opts,
  275. &option_index);
  276. char *optarguments = optarg;
  277. #define GETOP(x) if(optarguments) \
  278. x = optarguments
  279. #define GETOPNUM(x) if(optarguments) \
  280. x = atoi(optarguments)
  281. if(opt == -1)
  282. break;
  283. switch(opt) {
  284. case 'h':
  285. exitwithhelp = 1;
  286. break;
  287. case 'v':
  288. exitwithversion = 1;
  289. break;
  290. case 'Y': /* this command a dummy command (has NO effect)
  291. and is used because I need for NSIS installer
  292. (NSIS sometimes forces a command line for a
  293. program, even if I don't need that; eg. when
  294. I want to add a icon to a shortcut.
  295. */
  296. break;
  297. case 'U':
  298. noui = 1;
  299. break;
  300. case 'l':
  301. GETOP(loadfile);
  302. break;
  303. case 'L':
  304. GETOP(loadinstrument);
  305. break;
  306. case 'M':
  307. GETOP(loadmidilearn);
  308. break;
  309. case 'r':
  310. GETOPNUM(synth.samplerate);
  311. if(synth.samplerate < 4000) {
  312. cerr << "ERROR:Incorrect sample rate: " << optarguments
  313. << endl;
  314. exit(1);
  315. }
  316. break;
  317. case 'b':
  318. GETOPNUM(synth.buffersize);
  319. if(synth.buffersize < 2) {
  320. cerr << "ERROR:Incorrect buffer size: " << optarguments
  321. << endl;
  322. exit(1);
  323. }
  324. break;
  325. case 'o':
  326. if(optarguments)
  327. synth.oscilsize = tmp = atoi(optarguments);
  328. if(synth.oscilsize < MAX_AD_HARMONICS * 2)
  329. synth.oscilsize = MAX_AD_HARMONICS * 2;
  330. synth.oscilsize =
  331. (int) powf(2,
  332. ceil(logf(synth.oscilsize - 1.0f) / logf(2.0f)));
  333. if(tmp != synth.oscilsize)
  334. cerr
  335. <<
  336. "synth.oscilsize is wrong (must be 2^n) or too small. Adjusting to "
  337. << synth.oscilsize << "." << endl;
  338. break;
  339. case 'S':
  340. swaplr = 1;
  341. break;
  342. case 'N':
  343. Nio::setPostfix(optarguments);
  344. break;
  345. case 'I':
  346. if(optarguments)
  347. Nio::setDefaultSource(optarguments);
  348. break;
  349. case 'O':
  350. if(optarguments)
  351. Nio::setDefaultSink(optarguments);
  352. break;
  353. case 'a':
  354. Nio::autoConnect = true;
  355. break;
  356. case 'p':
  357. Nio::pidInClientName = true;
  358. break;
  359. case 'P':
  360. if(optarguments)
  361. prefered_port = atoi(optarguments);
  362. break;
  363. case 'A':
  364. if(optarguments)
  365. auto_save_interval = atoi(optarguments);
  366. break;
  367. case 'e':
  368. GETOP(execAfterInit);
  369. break;
  370. case 'd':
  371. if(optarguments)
  372. {
  373. rtosc::OscDocFormatter s;
  374. ofstream outfile(optarguments);
  375. s.prog_name = "ZynAddSubFX";
  376. s.p = &Master::ports;
  377. s.uri = "http://example.com/fake/";
  378. s.doc_origin = "http://example.com/fake/url.xml";
  379. s.author_first = "Mark";
  380. s.author_last = "McCurry";
  381. outfile << s;
  382. }
  383. break;
  384. case 'D':
  385. if(optarguments)
  386. {
  387. ofstream outfile(optarguments);
  388. void dump_json(std::ostream &o,
  389. const rtosc::Ports &p);
  390. dump_json(outfile, Master::ports);
  391. }
  392. break;
  393. case 'Z':
  394. if(optarguments)
  395. wmidi = atoi(optarguments);
  396. break;
  397. case '?':
  398. cerr << "ERROR:Bad option or parameter.\n" << endl;
  399. exitwithhelp = 1;
  400. break;
  401. }
  402. }
  403. synth.alias();
  404. if(exitwithversion) {
  405. cout << "Version: " << version << endl;
  406. return 0;
  407. }
  408. if(exitwithhelp != 0) {
  409. cout << "Usage: zynaddsubfx [OPTION]\n\n"
  410. << " -h , --help \t\t\t\t Display command-line help and exit\n"
  411. << " -v , --version \t\t\t Display version and exit\n"
  412. << " -l file, --load=FILE\t\t\t Loads a .xmz file\n"
  413. << " -L file, --load-instrument=FILE\t Loads a .xiz file\n"
  414. << " -M file, --midi-learn=FILE\t\t Loads a .xlz file\n"
  415. << " -r SR, --sample-rate=SR\t\t Set the sample rate SR\n"
  416. <<
  417. " -b BS, --buffer-size=SR\t\t Set the buffer size (granularity)\n"
  418. << " -o OS, --oscil-size=OS\t\t Set the ADsynth oscil. size\n"
  419. << " -S , --swap\t\t\t\t Swap Left <--> Right\n"
  420. <<
  421. " -U , --no-gui\t\t\t\t Run ZynAddSubFX without user interface\n"
  422. << " -N , --named\t\t\t\t Postfix IO Name when possible\n"
  423. << " -a , --auto-connect\t\t\t AutoConnect when using JACK\n"
  424. << " -A , --auto-save=INTERVAL\t\t Automatically save at interval (disabled with 0 interval)\n"
  425. << " -p , --pid-in-client-name\t\t Append PID to (JACK) "
  426. "client name\n"
  427. << " -P , --preferred-port\t\t\t Preferred OSC Port\n"
  428. << " -O , --output\t\t\t\t Set Output Engine\n"
  429. << " -I , --input\t\t\t\t Set Input Engine\n"
  430. << " -e , --exec-after-init\t\t Run post-initialization script\n"
  431. << " -d , --dump-oscdoc=FILE\t\t Dump oscdoc xml to file\n"
  432. << endl;
  433. return 0;
  434. }
  435. cerr.precision(1);
  436. cerr << std::fixed;
  437. cerr << "\nSample Rate = \t\t" << synth.samplerate << endl;
  438. cerr << "Sound Buffer Size = \t" << synth.buffersize << " samples" << endl;
  439. cerr << "Internal latency = \t" << synth.dt() * 1000.0f << " ms" << endl;
  440. cerr << "ADsynth Oscil.Size = \t" << synth.oscilsize << " samples" << endl;
  441. initprogram(std::move(synth), &config, prefered_port);
  442. bool altered_master = false;
  443. if(!loadfile.empty()) {
  444. altered_master = true;
  445. const char *filename = loadfile.c_str();
  446. int tmp = master->loadXML(filename);
  447. if(tmp < 0) {
  448. cerr << "ERROR: Could not load master file " << loadfile
  449. << "." << endl;
  450. exit(1);
  451. }
  452. else {
  453. strncpy(master->last_xmz, filename, XMZ_PATH_MAX);
  454. master->last_xmz[XMZ_PATH_MAX-1] = 0;
  455. master->applyparameters();
  456. cout << "Master file loaded." << endl;
  457. }
  458. }
  459. if(!loadinstrument.empty()) {
  460. altered_master = true;
  461. int loadtopart = 0;
  462. int tmp = master->part[loadtopart]->loadXMLinstrument(
  463. loadinstrument.c_str());
  464. if(tmp < 0) {
  465. cerr << "ERROR: Could not load instrument file "
  466. << loadinstrument << '.' << endl;
  467. exit(1);
  468. }
  469. else {
  470. master->part[loadtopart]->applyparameters();
  471. master->part[loadtopart]->initialize_rt();
  472. cout << "Instrument file loaded." << endl;
  473. }
  474. }
  475. if(!loadmidilearn.empty()) {
  476. char msg[1024];
  477. rtosc_message(msg, sizeof(msg), "/load_xlz",
  478. "s", loadmidilearn.c_str());
  479. middleware->transmitMsg(msg);
  480. }
  481. if(altered_master)
  482. middleware->updateResources(master);
  483. //Run the Nio system
  484. printf("[INFO] Nio::start()\n");
  485. bool ioGood = Nio::start();
  486. printf("[INFO] exec-after-init\n");
  487. if(!execAfterInit.empty()) {
  488. cout << "Executing user supplied command: " << execAfterInit << endl;
  489. if(system(execAfterInit.c_str()) == -1)
  490. cerr << "Command Failed..." << endl;
  491. }
  492. InitWinMidi(wmidi);
  493. gui = NULL;
  494. //Capture Startup Responses
  495. printf("[INFO] startup OSC\n");
  496. typedef std::vector<const char *> wait_t;
  497. wait_t msg_waitlist;
  498. middleware->setUiCallback([](void*v,const char*msg) {
  499. wait_t &wait = *(wait_t*)v;
  500. size_t len = rtosc_message_length(msg, -1);
  501. char *copy = new char[len];
  502. memcpy(copy, msg, len);
  503. wait.push_back(copy);
  504. }, &msg_waitlist);
  505. printf("[INFO] UI calbacks\n");
  506. if(!noui)
  507. gui = GUI::createUi(middleware->spawnUiApi(), &Pexitprogram);
  508. middleware->setUiCallback(GUI::raiseUi, gui);
  509. middleware->setIdleCallback([](void*){GUI::tickUi(gui);}, NULL);
  510. //Replay Startup Responses
  511. printf("[INFO] OSC replay\n");
  512. for(auto msg:msg_waitlist) {
  513. GUI::raiseUi(gui, msg);
  514. delete [] msg;
  515. }
  516. if(!noui)
  517. {
  518. GUI::raiseUi(gui, "/show", "i", config.cfg.UserInterfaceMode);
  519. if(!ioGood)
  520. GUI::raiseUi(gui, "/alert", "s",
  521. "Default IO did not initialize.\nDefaulting to NULL backend.");
  522. }
  523. printf("[INFO] auto_save setup\n");
  524. if(auto_save_interval > 0 && false) {
  525. int old_save = middleware->checkAutoSave();
  526. if(old_save > 0)
  527. GUI::raiseUi(gui, "/alert-reload", "i", old_save);
  528. middleware->enableAutoSave(auto_save_interval);
  529. }
  530. printf("[INFO] NSM Stuff\n");
  531. //TODO move this stuff into Cmake
  532. #if USE_NSM && defined(WIN32)
  533. #undef USE_NSM
  534. #define USE_NSM 0
  535. #endif
  536. #if LASH && defined(WIN32)
  537. #undef LASH
  538. #define LASH 0
  539. #endif
  540. #if USE_NSM
  541. char *nsm_url = getenv("NSM_URL");
  542. if(nsm_url) {
  543. nsm = new NSM_Client(middleware);
  544. if(!nsm->init(nsm_url))
  545. nsm->announce("ZynAddSubFX", ":switch:", argv[0]);
  546. else {
  547. delete nsm;
  548. nsm = NULL;
  549. }
  550. }
  551. #endif
  552. printf("[INFO] LASH Stuff\n");
  553. #if USE_NSM
  554. if(!nsm)
  555. #endif
  556. {
  557. #if LASH
  558. lash = new LASHClient(&argc, &argv);
  559. GUI::raiseUi(gui, "/session-type", "s", "LASH");
  560. #endif
  561. }
  562. #ifdef ZEST_GUI
  563. if(!noui) {
  564. printf("[INFO] Launching Zyn-Fusion...\n");
  565. const char *addr = middleware->getServerAddress();
  566. if(fork() == 0) {
  567. execlp("zyn-fusion", "zyn-fusion", addr, "--builtin", "--no-hotload", 0);
  568. execlp("./zyn-fusion", "zyn-fusion", addr, "--builtin", "--no-hotload", 0);
  569. err(1,"Failed to launch Zyn-Fusion");
  570. }
  571. }
  572. #endif
  573. printf("[INFO] Main Loop...\n");
  574. while(Pexitprogram == 0) {
  575. #ifndef WIN32
  576. #if USE_NSM
  577. if(nsm) {
  578. nsm->check();
  579. goto done;
  580. }
  581. #endif
  582. #if LASH
  583. {
  584. string filename;
  585. switch(lash->checkevents(filename)) {
  586. case LASHClient::Save:
  587. GUI::raiseUi(gui, "/save-master", "s", filename.c_str());
  588. lash->confirmevent(LASHClient::Save);
  589. break;
  590. case LASHClient::Restore:
  591. GUI::raiseUi(gui, "/load-master", "s", filename.c_str());
  592. lash->confirmevent(LASHClient::Restore);
  593. break;
  594. case LASHClient::Quit:
  595. Pexitprogram = 1;
  596. default:
  597. break;
  598. }
  599. }
  600. #endif //LASH
  601. #if USE_NSM
  602. done:
  603. #endif
  604. GUI::tickUi(gui);
  605. #endif
  606. middleware->tick();
  607. #ifdef WIN32
  608. Sleep(1);
  609. #endif
  610. }
  611. exitprogram(config);
  612. return 0;
  613. }