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