Update Version To 3.0.1
[zynaddsubfx-code.git] / src / main.cpp
blob42521ff3ec21fd6adbab5b874a8931929a3f8eb4
1 /*
2 ZynAddSubFX - a software synthesizer
4 main.cpp - Main file of the synthesizer
5 Copyright (C) 2002-2005 Nasca Octavian Paul
6 Copyright (C) 2012-2016 Mark McCurry
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
15 #include <iostream>
16 #include <fstream>
17 #include <map>
18 #include <cmath>
19 #include <cctype>
20 #include <ctime>
21 #include <algorithm>
22 #include <signal.h>
24 #include <err.h>
25 #include <unistd.h>
26 #include <pthread.h>
28 #include <getopt.h>
30 #include <rtosc/rtosc.h>
31 #include <rtosc/ports.h>
32 #include <rtosc/thread-link.h>
33 #include "Params/PADnoteParameters.h"
35 #include "DSP/FFTwrapper.h"
36 #include "Misc/PresetExtractor.h"
37 #include "Misc/Master.h"
38 #include "Misc/Part.h"
39 #include "Misc/Util.h"
40 #include "zyn-version.h"
42 //Nio System
43 #include "Nio/Nio.h"
44 #include "Nio/InMgr.h"
46 //GUI System
47 #include "UI/Connection.h"
48 GUI::ui_handle_t gui;
50 //Glue Layer
51 #include "Misc/MiddleWare.h"
52 MiddleWare *middleware;
54 using namespace std;
56 Master *master;
57 int swaplr = 0; //1 for left-right swapping
59 extern int Pexitprogram; //if the UI set this to 1, the program will exit
61 #if LASH
62 #include "Misc/LASHClient.h"
63 LASHClient *lash = NULL;
64 #endif
66 #if USE_NSM
67 #include "UI/NSM.H"
69 NSM_Client *nsm = 0;
70 #endif
72 char *instance_name = 0;
74 void exitprogram(const Config &config);
77 //cleanup on signaled exit
78 void sigterm_exit(int /*sig*/)
80 if(Pexitprogram)
81 exit(1);
82 Pexitprogram = 1;
86 * Program initialisation
88 void initprogram(SYNTH_T synth, Config* config, int prefered_port)
90 middleware = new MiddleWare(std::move(synth), config, prefered_port);
91 master = middleware->spawnMaster();
92 master->swaplr = swaplr;
94 signal(SIGINT, sigterm_exit);
95 signal(SIGTERM, sigterm_exit);
96 Nio::init(master->synth, config->cfg.oss_devs, master);
100 * Program exit
102 void exitprogram(const Config& config)
104 Nio::stop();
105 config.save();
106 middleware->removeAutoSave();
108 GUI::destroyUi(gui);
109 delete middleware;
110 #if LASH
111 if(lash)
112 delete lash;
113 #endif
114 #if USE_NSM
115 if(nsm)
116 delete nsm;
117 #endif
119 FFT_cleanup();
122 //Windows MIDI OH WHAT A HACK...
123 #ifdef WIN32
124 #include <windows.h>
125 #include <mmsystem.h>
126 extern InMgr *in;
127 HMIDIIN winmidiinhandle = 0;
129 void CALLBACK WinMidiInProc(HMIDIIN hMidiIn,UINT wMsg,DWORD dwInstance,
130 DWORD dwParam1,DWORD dwParam2)
132 int midicommand=0;
133 if (wMsg==MIM_DATA) {
134 int cmd,par1,par2;
135 cmd=dwParam1&0xff;
136 if (cmd==0xfe) return;
137 par1=(dwParam1>>8)&0xff;
138 par2=dwParam1>>16;
139 int cmdchan=cmd&0x0f;
140 int cmdtype=(cmd>>4)&0x0f;
142 int tmp=0;
143 MidiEvent ev;
144 switch (cmdtype) {
145 case(0x8)://noteon
146 ev.type = 1;
147 ev.num = par1;
148 ev.channel = cmdchan;
149 ev.value = 0;
150 in->putEvent(ev);
151 break;
152 case(0x9)://noteoff
153 ev.type = 1;
154 ev.num = par1;
155 ev.channel = cmdchan;
156 ev.value = par2&0xff;
157 in->putEvent(ev);
158 break;
159 case(0xb)://controller
160 ev.type = 2;
161 ev.num = par1;
162 ev.channel = cmdchan;
163 ev.value = par2&0xff;
164 in->putEvent(ev);
165 break;
166 case(0xe)://pitch wheel
167 //tmp=(par1+par2*(long int) 128)-8192;
168 //winmaster->SetController(cmdchan,C_pitchwheel,tmp);
169 break;
170 default:
171 break;
177 void InitWinMidi(int midi)
179 (void)midi;
180 for(int i=0; i<10; ++i) {
181 long int res=midiInOpen(&winmidiinhandle,i,(DWORD_PTR)(void*)WinMidiInProc,0,CALLBACK_FUNCTION);
182 if(res == MMSYSERR_NOERROR) {
183 res=midiInStart(winmidiinhandle);
184 printf("[INFO] Starting Windows MIDI At %d with code %d(noerror=%d)\n", i, res, MMSYSERR_NOERROR);
185 if(res == 0)
186 return;
187 } else
188 printf("[INFO] No Windows MIDI Device At id %d\n", i);
192 //void StopWinMidi()
194 // midiInStop(winmidiinhandle);
195 // midiInClose(winmidiinhandle);
196 //};
197 #else
198 void InitWinMidi(int) {}
199 #endif
202 int main(int argc, char *argv[])
204 SYNTH_T synth;
205 Config config;
206 config.init();
207 int noui = 0;
208 cerr
209 << "\nZynAddSubFX - Copyright (c) 2002-2013 Nasca Octavian Paul and others"
210 << endl;
211 cerr
212 << " Copyright (c) 2009-2016 Mark McCurry [active maintainer]"
213 << endl;
214 cerr << "Compiled: " << __DATE__ << " " << __TIME__ << endl;
215 cerr << "This program is free software (GNU GPL v2 or later) and \n";
216 cerr << "it comes with ABSOLUTELY NO WARRANTY.\n" << endl;
217 if(argc == 1)
218 cerr << "Try 'zynaddsubfx --help' for command-line options." << endl;
220 /* Get the settings from the Config*/
221 synth.samplerate = config.cfg.SampleRate;
222 synth.buffersize = config.cfg.SoundBufferSize;
223 synth.oscilsize = config.cfg.OscilSize;
224 swaplr = config.cfg.SwapStereo;
226 Nio::preferedSampleRate(synth.samplerate);
228 synth.alias(); //build aliases
230 sprng(time(NULL));
232 /* Parse command-line options */
233 struct option opts[] = {
235 "load", 2, NULL, 'l'
238 "load-instrument", 2, NULL, 'L'
241 "midi-learn", 2, NULL, 'M'
244 "sample-rate", 2, NULL, 'r'
247 "buffer-size", 2, NULL, 'b'
250 "oscil-size", 2, NULL, 'o'
253 "swap", 2, NULL, 'S'
256 "no-gui", 0, NULL, 'U'
259 "dummy", 2, NULL, 'Y'
262 "help", 2, NULL, 'h'
265 "version", 2, NULL, 'v'
268 "named", 1, NULL, 'N'
271 "auto-connect", 0, NULL, 'a'
274 "auto-save", 0, NULL, 'A'
277 "pid-in-client-name", 0, NULL, 'p'
280 "prefered-port", 1, NULL, 'P',
283 "output", 1, NULL, 'O'
286 "input", 1, NULL, 'I'
289 "exec-after-init", 1, NULL, 'e'
292 "dump-oscdoc", 2, NULL, 'd'
295 "dump-json-schema", 2, NULL, 'D'
298 0, 0, 0, 0
301 opterr = 0;
302 int option_index = 0, opt, exitwithhelp = 0, exitwithversion = 0;
303 int prefered_port = -1;
304 int auto_save_interval = 60;
305 int wmidi = -1;
307 string loadfile, loadinstrument, execAfterInit, loadmidilearn;
309 while(1) {
310 int tmp = 0;
312 /**\todo check this process for a small memory leak*/
313 opt = getopt_long(argc,
314 argv,
315 "l:L:M:r:b:o:I:O:N:e:P:A:D:hvapSDUYZ",
316 opts,
317 &option_index);
318 char *optarguments = optarg;
320 #define GETOP(x) if(optarguments) \
321 x = optarguments
322 #define GETOPNUM(x) if(optarguments) \
323 x = atoi(optarguments)
326 if(opt == -1)
327 break;
329 switch(opt) {
330 case 'h':
331 exitwithhelp = 1;
332 break;
333 case 'v':
334 exitwithversion = 1;
335 break;
336 case 'Y': /* this command a dummy command (has NO effect)
337 and is used because I need for NSIS installer
338 (NSIS sometimes forces a command line for a
339 program, even if I don't need that; eg. when
340 I want to add a icon to a shortcut.
342 break;
343 case 'U':
344 noui = 1;
345 break;
346 case 'l':
347 GETOP(loadfile);
348 break;
349 case 'L':
350 GETOP(loadinstrument);
351 break;
352 case 'M':
353 GETOP(loadmidilearn);
354 break;
355 case 'r':
356 GETOPNUM(synth.samplerate);
357 if(synth.samplerate < 4000) {
358 cerr << "ERROR:Incorrect sample rate: " << optarguments
359 << endl;
360 exit(1);
362 break;
363 case 'b':
364 GETOPNUM(synth.buffersize);
365 if(synth.buffersize < 2) {
366 cerr << "ERROR:Incorrect buffer size: " << optarguments
367 << endl;
368 exit(1);
370 break;
371 case 'o':
372 if(optarguments)
373 synth.oscilsize = tmp = atoi(optarguments);
374 if(synth.oscilsize < MAX_AD_HARMONICS * 2)
375 synth.oscilsize = MAX_AD_HARMONICS * 2;
376 synth.oscilsize =
377 (int) powf(2,
378 ceil(logf(synth.oscilsize - 1.0f) / logf(2.0f)));
379 if(tmp != synth.oscilsize)
380 cerr
382 "synth.oscilsize is wrong (must be 2^n) or too small. Adjusting to "
383 << synth.oscilsize << "." << endl;
384 break;
385 case 'S':
386 swaplr = 1;
387 break;
388 case 'N':
389 Nio::setPostfix(optarguments);
390 break;
391 case 'I':
392 if(optarguments)
393 Nio::setDefaultSource(optarguments);
394 break;
395 case 'O':
396 if(optarguments)
397 Nio::setDefaultSink(optarguments);
398 break;
399 case 'a':
400 Nio::autoConnect = true;
401 break;
402 case 'p':
403 Nio::pidInClientName = true;
404 break;
405 case 'P':
406 if(optarguments)
407 prefered_port = atoi(optarguments);
408 break;
409 case 'A':
410 if(optarguments)
411 auto_save_interval = atoi(optarguments);
412 break;
413 case 'e':
414 GETOP(execAfterInit);
415 break;
416 case 'd':
417 if(optarguments)
419 rtosc::OscDocFormatter s;
420 ofstream outfile(optarguments);
421 s.prog_name = "ZynAddSubFX";
422 s.p = &Master::ports;
423 s.uri = "http://example.com/fake/";
424 s.doc_origin = "http://example.com/fake/url.xml";
425 s.author_first = "Mark";
426 s.author_last = "McCurry";
427 outfile << s;
429 break;
430 case 'D':
431 if(optarguments)
433 ofstream outfile(optarguments);
434 void dump_json(std::ostream &o,
435 const rtosc::Ports &p);
436 dump_json(outfile, Master::ports);
438 break;
439 case 'Z':
440 if(optarguments)
441 wmidi = atoi(optarguments);
442 break;
443 case '?':
444 cerr << "ERROR:Bad option or parameter.\n" << endl;
445 exitwithhelp = 1;
446 break;
450 synth.alias();
452 if(exitwithversion) {
453 cout << "Version: " << version << endl;
454 return 0;
456 if(exitwithhelp != 0) {
457 cout << "Usage: zynaddsubfx [OPTION]\n\n"
458 << " -h , --help \t\t\t\t Display command-line help and exit\n"
459 << " -v , --version \t\t\t Display version and exit\n"
460 << " -l file, --load=FILE\t\t\t Loads a .xmz file\n"
461 << " -L file, --load-instrument=FILE\t Loads a .xiz file\n"
462 << " -M file, --midi-learn=FILE\t\t Loads a .xlz file\n"
463 << " -r SR, --sample-rate=SR\t\t Set the sample rate SR\n"
465 " -b BS, --buffer-size=SR\t\t Set the buffer size (granularity)\n"
466 << " -o OS, --oscil-size=OS\t\t Set the ADsynth oscil. size\n"
467 << " -S , --swap\t\t\t\t Swap Left <--> Right\n"
469 " -U , --no-gui\t\t\t\t Run ZynAddSubFX without user interface\n"
470 << " -N , --named\t\t\t\t Postfix IO Name when possible\n"
471 << " -a , --auto-connect\t\t\t AutoConnect when using JACK\n"
472 << " -A , --auto-save=INTERVAL\t\t Automatically save at interval (disabled with 0 interval)\n"
473 << " -p , --pid-in-client-name\t\t Append PID to (JACK) "
474 "client name\n"
475 << " -P , --preferred-port\t\t\t Preferred OSC Port\n"
476 << " -O , --output\t\t\t\t Set Output Engine\n"
477 << " -I , --input\t\t\t\t Set Input Engine\n"
478 << " -e , --exec-after-init\t\t Run post-initialization script\n"
479 << " -d , --dump-oscdoc=FILE\t\t Dump oscdoc xml to file\n"
480 << endl;
482 return 0;
485 cerr.precision(1);
486 cerr << std::fixed;
487 cerr << "\nSample Rate = \t\t" << synth.samplerate << endl;
488 cerr << "Sound Buffer Size = \t" << synth.buffersize << " samples" << endl;
489 cerr << "Internal latency = \t" << synth.dt() * 1000.0f << " ms" << endl;
490 cerr << "ADsynth Oscil.Size = \t" << synth.oscilsize << " samples" << endl;
492 initprogram(std::move(synth), &config, prefered_port);
494 bool altered_master = false;
495 if(!loadfile.empty()) {
496 altered_master = true;
497 const char *filename = loadfile.c_str();
498 int tmp = master->loadXML(filename);
499 if(tmp < 0) {
500 cerr << "ERROR: Could not load master file " << loadfile
501 << "." << endl;
502 exit(1);
504 else {
505 strncpy(master->last_xmz, filename, XMZ_PATH_MAX);
506 master->last_xmz[XMZ_PATH_MAX-1] = 0;
507 master->applyparameters();
508 cout << "Master file loaded." << endl;
512 if(!loadinstrument.empty()) {
513 altered_master = true;
514 int loadtopart = 0;
515 int tmp = master->part[loadtopart]->loadXMLinstrument(
516 loadinstrument.c_str());
517 if(tmp < 0) {
518 cerr << "ERROR: Could not load instrument file "
519 << loadinstrument << '.' << endl;
520 exit(1);
522 else {
523 master->part[loadtopart]->applyparameters();
524 master->part[loadtopart]->initialize_rt();
525 cout << "Instrument file loaded." << endl;
529 if(!loadmidilearn.empty()) {
530 char msg[1024];
531 rtosc_message(msg, sizeof(msg), "/load_xlz",
532 "s", loadmidilearn.c_str());
533 middleware->transmitMsg(msg);
536 if(altered_master)
537 middleware->updateResources(master);
539 //Run the Nio system
540 printf("[INFO] Nio::start()\n");
541 bool ioGood = Nio::start();
543 printf("[INFO] exec-after-init\n");
544 if(!execAfterInit.empty()) {
545 cout << "Executing user supplied command: " << execAfterInit << endl;
546 if(system(execAfterInit.c_str()) == -1)
547 cerr << "Command Failed..." << endl;
550 InitWinMidi(wmidi);
553 gui = NULL;
555 //Capture Startup Responses
556 printf("[INFO] startup OSC\n");
557 typedef std::vector<const char *> wait_t;
558 wait_t msg_waitlist;
559 middleware->setUiCallback([](void*v,const char*msg) {
560 wait_t &wait = *(wait_t*)v;
561 size_t len = rtosc_message_length(msg, -1);
562 char *copy = new char[len];
563 memcpy(copy, msg, len);
564 wait.push_back(copy);
565 }, &msg_waitlist);
567 printf("[INFO] UI calbacks\n");
568 if(!noui)
569 gui = GUI::createUi(middleware->spawnUiApi(), &Pexitprogram);
570 middleware->setUiCallback(GUI::raiseUi, gui);
571 middleware->setIdleCallback([](void*){GUI::tickUi(gui);}, NULL);
573 //Replay Startup Responses
574 printf("[INFO] OSC replay\n");
575 for(auto msg:msg_waitlist) {
576 GUI::raiseUi(gui, msg);
577 delete [] msg;
580 if(!noui)
582 GUI::raiseUi(gui, "/show", "i", config.cfg.UserInterfaceMode);
583 if(!ioGood)
584 GUI::raiseUi(gui, "/alert", "s",
585 "Default IO did not initialize.\nDefaulting to NULL backend.");
588 printf("[INFO] auto_save setup\n");
589 if(auto_save_interval > 0 && false) {
590 int old_save = middleware->checkAutoSave();
591 if(old_save > 0)
592 GUI::raiseUi(gui, "/alert-reload", "i", old_save);
593 middleware->enableAutoSave(auto_save_interval);
595 printf("[INFO] NSM Stuff\n");
597 //TODO move this stuff into Cmake
598 #if USE_NSM && defined(WIN32)
599 #undef USE_NSM
600 #define USE_NSM 0
601 #endif
603 #if LASH && defined(WIN32)
604 #undef LASH
605 #define LASH 0
606 #endif
608 #if USE_NSM
609 char *nsm_url = getenv("NSM_URL");
611 if(nsm_url) {
612 nsm = new NSM_Client(middleware);
614 if(!nsm->init(nsm_url))
615 nsm->announce("ZynAddSubFX", ":switch:", argv[0]);
616 else {
617 delete nsm;
618 nsm = NULL;
621 #endif
623 printf("[INFO] LASH Stuff\n");
624 #if USE_NSM
625 if(!nsm)
626 #endif
628 #if LASH
629 lash = new LASHClient(&argc, &argv);
630 GUI::raiseUi(gui, "/session-type", "s", "LASH");
631 #endif
634 #ifdef ZEST_GUI
635 if(!noui) {
636 printf("[INFO] Launching Zyn-Fusion...\n");
637 const char *addr = middleware->getServerAddress();
638 if(fork() == 0) {
639 execlp("zyn-fusion", "zyn-fusion", addr, "--builtin", "--no-hotload", 0);
640 execlp("./zyn-fusion", "zyn-fusion", addr, "--builtin", "--no-hotload", 0);
642 err(1,"Failed to launch Zyn-Fusion");
645 #endif
647 printf("[INFO] Main Loop...\n");
648 while(Pexitprogram == 0) {
649 #ifndef WIN32
650 #if USE_NSM
651 if(nsm) {
652 nsm->check();
653 goto done;
655 #endif
656 #if LASH
658 string filename;
659 switch(lash->checkevents(filename)) {
660 case LASHClient::Save:
661 GUI::raiseUi(gui, "/save-master", "s", filename.c_str());
662 lash->confirmevent(LASHClient::Save);
663 break;
664 case LASHClient::Restore:
665 GUI::raiseUi(gui, "/load-master", "s", filename.c_str());
666 lash->confirmevent(LASHClient::Restore);
667 break;
668 case LASHClient::Quit:
669 Pexitprogram = 1;
670 default:
671 break;
674 #endif //LASH
676 #if USE_NSM
677 done:
678 #endif
679 GUI::tickUi(gui);
680 #endif
681 middleware->tick();
682 #ifdef WIN32
683 Sleep(1);
684 #endif
687 exitprogram(config);
688 return 0;