1 /*
2     main.cpp
3 
4     Copyright 2009-2011, Alan Calvert
5     Copyright 2014-2021, Will Godfrey & others
6 
7     This file is part of yoshimi, which is free software: you can
8     redistribute it and/or modify it under the terms of the GNU General
9     Public License as published by the Free Software Foundation, either
10     version 2 of the License, or (at your option) any later version.
11 
12     yoshimi is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU General Public License for more details.
16 
17     You should have received a copy of the GNU General Public License
18     along with yoshimi.  If not, see <http://www.gnu.org/licenses/>.
19 
20 */
21 
22 #include <iostream>
23 #include <string>
24 #include <list>
25 #include <termios.h>
26 #include <pthread.h>
27 #include <atomic>
28 
29 #include <readline/readline.h>
30 #include <readline/history.h>
31 
32 #include "Misc/CmdOptions.h"
33 #include "Misc/SynthEngine.h"
34 #include "MusicIO/MusicClient.h"
35 #include "CLI/CmdInterface.h"
36 #include "Misc/FileMgrFuncs.h"
37 
38 #ifdef GUI_FLTK
39     #include "MasterUI.h"
40     #include "UI/MiscGui.h"
41     #include <FL/Fl.H>
42     #include <FL/Fl_Window.H>
43     #include <FL/Fl_PNG_Image.H>
44     #include "Misc/Splash.h"
45 #endif
46 
47 extern map<SynthEngine *, MusicClient *> synthInstances;
48 extern SynthEngine *firstSynth;
49 
50 // used by automated test launched via CLI
51 std::atomic <bool> waitForTest{false};
52 
53 
54 void mainRegisterAudioPort(SynthEngine *s, int portnum);
55 int mainCreateNewInstance(unsigned int forceId);
56 Config *firstRuntime = NULL;
57 static std::list<std::string> globalAllArgs;
58 bool isSingleMaster = false;
59 bool bShowGui = true;
60 bool bShowCmdLine = true;
61 bool showSplash = false;
62 bool splashSet = true;
63 bool configuring = false;
64 #ifdef GUI_FLTK
65 time_t old_father_time, here_and_now;
66 #endif
67 
68 //Andrew Deryabin: signal handling moved to main from Config Runtime
69 //It's only suitable for single instance app support
70 static struct sigaction yoshimiSigAction;
71 
72 
newBlock()73 void newBlock()
74 {
75     for (int i = 1; i < 32; ++i)
76     {
77         if ((firstRuntime->activeInstance >> i) & 1)
78         {
79             while (configuring)
80                 usleep(1000);
81             // in case there is still an instance starting from elsewhere
82             configuring = true;
83             mainCreateNewInstance(i);
84             configuring = false;
85         }
86     }
87 }
88 
89 
newInstance()90 void newInstance()
91 {
92     while (configuring)
93         usleep(1000);
94     // in case there is still an instance starting from elsewhere
95     configuring = true;
96     startInstance = 0x81;
97 }
98 
99 
yoshimiSigHandler(int sig)100 void yoshimiSigHandler(int sig)
101 {
102     switch (sig)
103     {
104         case SIGINT:
105         case SIGHUP:
106         case SIGTERM:
107         case SIGQUIT:
108             firstRuntime->setInterruptActive();
109             break;
110 
111         case SIGUSR1:
112             firstRuntime->setLadi1Active();
113             sigaction(SIGUSR1, &yoshimiSigAction, NULL);
114             break;
115         case SIGUSR2: // start next instance
116             if (isSingleMaster)
117                 newInstance();
118             sigaction(SIGUSR2, &yoshimiSigAction, NULL);
119             break;
120         default:
121             break;
122     }
123 }
124 
mainThread(void * arg)125 static void *mainThread(void *arg)
126 {
127     sem_post((sem_t *)arg);
128     map<SynthEngine *, MusicClient *>::iterator it;
129 
130 #ifdef GUI_FLTK
131     const int textHeight = 15;
132     const int textY = 10;
133     const unsigned char lred = 0xd7;
134     const unsigned char lgreen = 0xf7;
135     const unsigned char lblue = 0xff;
136     int winH=10, winW=50;
137     int LbX=2, LbY=2, LbW=2, LbH=2;
138     int timeout =3;
139     std::string startup = YOSHIMI_VERSION;
140 
141     if (bShowGui)
142     {
143         Fl::lock();
144 
145         if (showSplash)
146         {
147             startup = "V " + startup;
148             winH = splashHeight;
149             winW = splashWidth;
150             LbX = 0;
151             LbY = winH - textY - textHeight;
152             LbW = winW;
153             LbH = textHeight;
154             timeout = 5;
155         }
156         else
157         {
158             startup = "Yoshimi V " + startup + " is starting";
159             winH = 36;
160             winW = 300;
161             LbX = 2;
162             LbY = 2;
163             LbW = winW - 4;
164             LbH = winH -4;
165             timeout = 3;
166         }
167     }//bShowGui
168 
169     Fl_PNG_Image pix("splash_screen_png", splashPngData, splashPngLength);
170     Fl_Window winSplash(winW, winH, "yoshimi splash screen");
171     Fl_Box box(0, 0, winW,winH);
172     Fl_Box boxLb(LbX, LbY, LbW, LbH, startup.c_str());
173 
174     if (bShowGui)
175     {
176         if (showSplash)
177         {
178             box.image(pix);
179             boxLb.box(FL_NO_BOX);
180             boxLb.align(FL_ALIGN_CENTER);
181             boxLb.labelsize(textHeight);
182             boxLb.labeltype(FL_NORMAL_LABEL);
183             boxLb.labelcolor(fl_rgb_color(lred, lgreen, lblue));
184             boxLb.labelfont(FL_HELVETICA | FL_BOLD);
185         }
186         else
187         {
188             boxLb.box(FL_EMBOSSED_FRAME);
189             boxLb.labelsize(16);
190             boxLb.labelfont(FL_BOLD);
191             boxLb.labelcolor(YOSHI_COLOUR);
192         }
193         winSplash.border(false);
194         if (splashSet && bShowGui)
195         {
196             winSplash.position((Fl::w() - winSplash.w()) / 2, (Fl::h() - winSplash.h()) / 2);
197         }
198         else
199             splashSet = false;
200         do
201         {
202             winSplash.show();
203             usleep(33333);
204         }
205         while (firstSynth == NULL); // just wait
206     }
207     else /* not bShowGui */
208 #endif /*GUI_FLTK*/
209     {
210         while (firstSynth == NULL); // just wait
211     }
212 
213     if (firstRuntime->autoInstance)
214         newBlock();
215     while (firstRuntime->runSynth)
216     {
217         firstRuntime->signalCheck();
218 
219         for (it = synthInstances.begin(); it != synthInstances.end(); ++it)
220         {
221             SynthEngine *_synth = it->first;
222             MusicClient *_client = it->second;
223             if (!_synth->getRuntime().runSynth && _synth->getUniqueId() > 0)
224             {
225                 if (_synth->getRuntime().configChanged)
226                     _synth->getRuntime().restoreConfig(_synth);
227                 _synth->getRuntime().saveConfig();
228                 unsigned int instanceID =  _synth->getUniqueId();
229                 if (_client)
230                 {
231                     _client->Close();
232                     delete _client;
233                 }
234 
235                 if (_synth)
236                 {
237                     int instancebit = (1 << instanceID);
238                     if (_synth->getRuntime().activeInstance & instancebit)
239                         _synth->getRuntime().activeInstance -= instancebit;
240                     _synth->saveBanks();
241                     _synth->getRuntime().flushLog();
242                     delete _synth;
243                 }
244 
245                 synthInstances.erase(it);
246                 std::cout << "\nStopped " << instanceID << "\n";
247                 break;
248             }
249 #ifdef GUI_FLTK
250             if (bShowGui)
251             {
252                 MasterUI *guiMaster = _synth->getGuiMaster(false);
253                 if (guiMaster)
254                 {
255                     if (guiMaster->masterwindow)
256                     {
257                         guiMaster->checkBuffer();
258                         Fl::check();
259                     }
260                 }
261                 else
262                     GuiThreadMsg::processGuiMessages();
263             }
264 #endif
265         }
266 
267         // where all the action is ...
268         if (startInstance > 0x80)
269         {
270             int testInstance = startInstance &= 0x7f;
271             configuring = true;
272             mainCreateNewInstance(testInstance);
273             configuring = false;
274             startInstance = testInstance; // to prevent repeats!
275         }
276         else
277         {
278 #ifdef GUI_FLTK
279             if (bShowGui)
280             {
281                 if (splashSet)
282                 {
283                     if (showSplash)
284                     {
285                         winSplash.show(); // keeps it in front;
286                         usleep(1000);
287                     }
288                     if (time(&here_and_now) < 0) // no time?
289                         here_and_now = old_father_time + timeout;
290                     if ((here_and_now - old_father_time) >= timeout)
291                     {
292                         splashSet = false;
293                         winSplash.hide();
294                     }
295                 }
296                 Fl::wait(0.033333);
297             }
298             else
299 #endif
300                 usleep(33333);
301         }
302     }
303 
304     if (firstRuntime->configChanged && (bShowGui | bShowCmdLine)) // don't want this if no cli or gui
305         firstSynth->getRuntime().restoreConfig(firstSynth);
306 
307     firstRuntime->saveConfig(true);
308     firstSynth->saveHistory();
309     firstSynth->saveBanks();
310     return NULL;
311 }
312 
mainCreateNewInstance(unsigned int forceId)313 int mainCreateNewInstance(unsigned int forceId)
314 {
315     MusicClient *musicClient = NULL;
316     unsigned int instanceID;
317     SynthEngine *synth = new SynthEngine(globalAllArgs, LV2PluginTypeNone, forceId);
318     if (!synth->getRuntime().isRuntimeSetupCompleted())
319         goto bail_out;
320     instanceID = synth->getUniqueId();
321     if (!synth)
322     { // this has to be a direct call - no synth for log!
323         std::cerr << "Failed to allocate SynthEngine" << std::endl;
324         goto bail_out;
325     }
326 
327     if (!(musicClient = MusicClient::newMusicClient(synth)))
328     {
329         synth->getRuntime().Log("Failed to instantiate MusicClient",_SYS_::LogError);
330         goto bail_out;
331     }
332 
333     if (!synth->Init(musicClient->getSamplerate(), musicClient->getBuffersize()))
334     {
335         synth->getRuntime().Log("SynthEngine init failed",_SYS_::LogError);
336         goto bail_out;
337     }
338 
339     if (!musicClient->Start())
340     {
341         synth->getRuntime().Log("Failed to start MusicIO",_SYS_::LogError);
342         goto bail_out;
343     }
344 #ifdef GUI_FLTK
345     if (synth->getRuntime().showGui)
346     {
347         synth->setWindowTitle(musicClient->midiClientName());
348         if (firstSynth != NULL) //FLTK is not ready yet - send this message later for first synth
349             GuiThreadMsg::sendMessage(synth, GuiThreadMsg::NewSynthEngine, 0);
350     }
351     else
352         synth->getRuntime().toConsole = false;
353 #else
354     synth->getRuntime().toConsole = false;
355 #endif
356     synth->getRuntime().StartupReport(musicClient->midiClientName());
357 
358     if (instanceID == 0)
359         std::cout << "\nYay! We're up and running :-)\n";
360     else
361     {
362         std::cout << "\nStarted "<< instanceID << "\n";
363         // following copied here for other instances
364         synth->installBanks();
365     }
366 
367     synthInstances.insert(std::make_pair(synth, musicClient));
368     //register jack ports for enabled parts
369     for (int npart = 0; npart < NUM_MIDI_PARTS; ++npart)
370     {
371         if (synth->partonoffRead(npart))
372             mainRegisterAudioPort(synth, npart);
373     }
374     synth->getRuntime().activeInstance |= (1 << instanceID);
375     return instanceID;
376 
377 bail_out:
378     synth->getRuntime().runSynth = false;
379     synth->getRuntime().Log("Bail: Yoshimi stages a strategic retreat :-(",_SYS_::LogError);
380     if (musicClient)
381     {
382         musicClient->Close();
383         delete musicClient;
384     }
385     if (synth)
386     {
387         synth->getRuntime().flushLog();
388         delete synth;
389     }
390 
391     return -1;
392 }
393 
commandThread(void *)394 void *commandThread(void *) // silence warning (was *arg = NULL)
395 {
396     CmdInterface commandInit;
397     commandInit.cmdIfaceCommandLoop();
398     return 0;
399 }
400 
runCommand(std::string command)401 std::string runCommand(std::string command)
402 {
403     string returnLine = "";
404     file::cmd2string(command, returnLine);
405     //std::cout << returnLine << std::endl;
406     return returnLine;
407 }
408 
main(int argc,char * argv[])409 int main(int argc, char *argv[])
410 {
411     /*
412      * The following is a way to quickly identify and read key config startup values
413      * before the synth engine has started, or any of the normal functions have been
414      * identified. The base config file is quite small and is always uncompressed
415      * (regardless of settings) as it is useful to be able to read and/or manually
416      * change settings under fault conditions.
417      */
418     std::string Home = getenv("HOME");
419     std::string Config = file::loadText(Home + "/.config/yoshimi/yoshimi.config");
420     if (Config.empty())
421     {
422         std::cout << "Not there" << std::endl;
423 #ifdef GUI_FLTK
424         showSplash = true;
425 #endif
426     }
427     else
428     {
429         int count = 0;
430         int found = 0;
431         while (!Config.empty() && count < 16 && found < 3)
432         { // no need to count beyond 16 lines!
433             std::string line = func::nextLine(Config);
434             ++ count;
435             if (line.find("enable_splash") != std::string::npos)
436             {
437                 ++ found;
438                 if (line.find("yes") != std::string::npos)
439                     showSplash = true;
440             }
441             if (line.find("enable_single_master") != std::string::npos)
442             {
443                 ++ found;
444                 if (line.find("yes") != std::string::npos)
445                     isSingleMaster = true;
446             }
447         }
448     }
449 
450     if (isSingleMaster)
451     {
452 
453         std::string firstText = runCommand("pgrep -o -x yoshimi");
454         int firstpid = std::stoi(firstText);
455         int firstTime = std::stoi(runCommand("ps -o etimes= -p " + firstText));
456         int secondTime = std::stoi(runCommand("ps -o etimes= -p " + std::to_string(getpid())));
457         if ((firstTime - secondTime) > 0)
458         {
459                 kill(firstpid, SIGUSR2); // send message to 1st instance
460                 return 0; // exit quietly
461         }
462     }
463 
464     std::list<std::string> allArgs;
465 
466     int gui;
467     int cmd;
468     CmdOptions(argc, argv, allArgs, gui, cmd);
469     globalAllArgs = allArgs;
470     bool mainThreadStarted = false;
471 
472 #ifdef GUI_FLTK
473     time(&old_father_time);
474     here_and_now = old_father_time;
475 #endif
476 
477     struct termios  oldTerm;
478     tcgetattr(0, &oldTerm);
479 
480     std::cout << "Yoshimi " << YOSHIMI_VERSION << " is starting" << std::endl; // guaranteed start message
481 
482     bool bExitSuccess = false;
483     map<SynthEngine *, MusicClient *>::iterator it;
484 
485     pthread_t threadMainLoop;
486     pthread_attr_t pthreadAttr;
487     sem_t semMain;
488 
489     if (mainCreateNewInstance(0) == -1)
490     {
491         goto bail_out;
492     }
493 
494     it = synthInstances.begin();
495     firstRuntime = &it->first->getRuntime();
496     firstSynth = it->first;
497     bShowGui = firstRuntime->showGui;
498     bShowCmdLine = firstRuntime->showCli;
499 
500     if (firstRuntime->oldConfig)
501     {
502 
503         std::cout << "\nExisting config older than " << MIN_CONFIG_MAJOR << "." << MIN_CONFIG_MINOR << "\nCheck settings, save and restart.\n"<< std::endl;
504     }
505     if (sem_init(&semMain, 0, 0) == 0)
506     {
507         if (pthread_attr_init(&pthreadAttr) == 0)
508         {
509             if (pthread_create(&threadMainLoop, &pthreadAttr, mainThread, (void *)&semMain) == 0)
510                 mainThreadStarted = true;
511             pthread_attr_destroy(&pthreadAttr);
512         }
513     }
514 
515     if (!mainThreadStarted)
516     {
517         std::cout << "Yoshimi can't start main loop!" << std::endl;
518         goto bail_out;
519     }
520     sem_wait(&semMain);
521     sem_destroy(&semMain);
522 
523     memset(&yoshimiSigAction, 0, sizeof(yoshimiSigAction));
524     yoshimiSigAction.sa_handler = yoshimiSigHandler;
525     if (sigaction(SIGUSR1, &yoshimiSigAction, NULL))
526         firstRuntime->Log("Setting SIGUSR1 handler failed",_SYS_::LogError);
527     if (sigaction(SIGUSR2, &yoshimiSigAction, NULL))
528         firstRuntime->Log("Setting SIGUSR2 handler failed",_SYS_::LogError);
529     if (sigaction(SIGINT, &yoshimiSigAction, NULL))
530         firstRuntime->Log("Setting SIGINT handler failed",_SYS_::LogError);
531     if (sigaction(SIGHUP, &yoshimiSigAction, NULL))
532         firstRuntime->Log("Setting SIGHUP handler failed",_SYS_::LogError);
533     if (sigaction(SIGTERM, &yoshimiSigAction, NULL))
534         firstRuntime->Log("Setting SIGTERM handler failed",_SYS_::LogError);
535     if (sigaction(SIGQUIT, &yoshimiSigAction, NULL))
536         firstRuntime->Log("Setting SIGQUIT handler failed",_SYS_::LogError);
537     // following moved here for faster first synth startup
538     firstSynth->loadHistory();
539     firstSynth->installBanks();
540 #ifdef GUI_FLTK
541     if (bShowGui)
542         GuiThreadMsg::sendMessage(firstSynth, GuiThreadMsg::NewSynthEngine, 0);
543 #endif
544 
545     //create command line processing thread
546     pthread_t threadCmd;
547 
548     if (bShowCmdLine)
549     {
550         if (pthread_attr_init(&pthreadAttr) == 0)
551         {
552             if (pthread_create(&threadCmd, &pthreadAttr, commandThread, (void *)firstSynth) == 0)
553             {
554                 ;
555             }
556             pthread_attr_destroy(&pthreadAttr);
557         }
558     }
559 
560 //firstRuntime->Log("test normal msg");
561 //firstRuntime->Log("test not serious",_SYS_::LogNotSerious);
562 //firstRuntime->Log("test error msg",_SYS_::LogError);
563 //firstRuntime->Log("test not serious error",_SYS_::LogNotSerious | _SYS_::LogError);
564 
565     void *res;
566     pthread_join(threadMainLoop, &res);
567     if (res == (void *)1)
568     {
569         goto bail_out;
570     }
571     if (waitForTest && threadCmd)
572     {   // CLI about to launch TestRunner for automated test
573         MusicClient* music = synthInstances[firstSynth];
574         if (music) music->Close(); // causes esp. JackClient to detach
575 
576         pthread_join(threadCmd, &res);
577         if (res == (void *)1)
578         {
579             goto bail_out;
580         }
581     }
582     bExitSuccess = true;
583 
584 bail_out:
585     // firstSynth is freed in the for loop below, so save this for later
586     int exitType = firstSynth->getRuntime().exitType;
587     for (it = synthInstances.begin(); it != synthInstances.end(); ++it)
588     {
589         SynthEngine *_synth = it->first;
590         MusicClient *_client = it->second;
591         _synth->getRuntime().runSynth = false;
592         if (!bExitSuccess)
593         {
594             _synth->getRuntime().Log("Bail: Yoshimi stages a strategic retreat :-(",_SYS_::LogError);
595         }
596 
597         if (_client)
598         {
599             _client->Close();
600             delete _client;
601         }
602 
603         if (_synth)
604         {
605             _synth->getRuntime().flushLog();
606             delete _synth;
607         }
608     }
609     if (bShowCmdLine)
610         tcsetattr(0, TCSANOW, &oldTerm);
611     if (bExitSuccess)
612     {
613         if (exitType == FORCED_EXIT)
614             std::cout << "\nExit was forced :(" << std::endl;
615         else
616             std::cout << "\nGoodbye - Play again soon?"<< std::endl;
617         exit(exitType);
618     }
619     else
620         exit(EXIT_FAILURE);
621 }
622 
mainRegisterAudioPort(SynthEngine * s,int portnum)623 void mainRegisterAudioPort(SynthEngine *s, int portnum)
624 {
625     if (s && (portnum < NUM_MIDI_PARTS) && (portnum >= 0))
626     {
627         map<SynthEngine *, MusicClient *>::iterator it = synthInstances.find(s);
628         if (it != synthInstances.end())
629         {
630             it->second->registerAudioPort(portnum);
631         }
632     }
633 }
634