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.

745 lines
20KB

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