Anklang 0.3.0-460-gc4ef46ba
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
main.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "main.hh"
3#include "api.hh"
4#include "path.hh"
5#include "utils.hh"
6#include "jsonapi.hh"
7#include "driver.hh"
8#include "engine.hh"
9#include "project.hh"
10#include "loft.hh"
11#include "compress.hh"
12#include "webui.hh"
13#include "internal.hh"
14#include "testing.hh"
15
16#include <limits.h>
17#include <stdlib.h>
18#include <unistd.h>
19#include <signal.h>
20#include <malloc.h>
21#include <unistd.h>
22#include <fcntl.h>
23
24#undef B0 // undo pollution from termios.h
25
26#define MDEBUG(...) Ase::debug ("memory", __VA_ARGS__)
27
28namespace Ase {
29
31 MainAppImpl ();
32};
33MainAppImpl main_app;
34const MainApp &App = main_app;
35
36MainAppImpl::MainAppImpl()
37{}
38
39MainLoopP main_loop;
40static String arg_ui_mode;
41static int arg_unauth_port = 0;
42
43// == JobQueue ==
44static void
45call_main_loop (const std::function<void()> &fun)
46{
47 main_loop->exec_callback (fun);
48}
49JobQueue main_jobs (call_main_loop);
50
51// == RtCall::Callable ==
52struct RtCallJob {
53 explicit RtCallJob (const RtCall &ucall) : call (ucall) {}
54 LoftPtr<RtCallJob> loftptr;
55 std::atomic<RtCallJob*> next = nullptr;
56 RtCall call;
57};
58static inline std::atomic<RtCallJob*>&
59atomic_next_ptrref (RtCallJob *j)
60{
61 return j->next;
62}
63
64static AtomicIntrusiveStack<RtCallJob> main_rt_jobs_;
66
67void
68RtJobQueue::operator+= (const RtCall &call)
69{
70 LoftPtr<RtCallJob> loftptr = loft_make_unique<RtCallJob> (call);
71 RtCallJob *const calljob = &*loftptr;
72 calljob->loftptr = std::move (loftptr); // keeps itself alive
73 const bool was_empty = main_rt_jobs_.push (calljob);
74 if (was_empty)
75 main_loop_wakeup();
76}
77
78static bool
79main_rt_jobs_pending()
80{
81 return !main_rt_jobs_.empty();
82}
83
84static void
85main_rt_jobs_process()
86{
87 RtCallJob *calljob = main_rt_jobs_.pop_reversed();
88 while (calljob) {
89 LoftPtr<RtCallJob> loftptr = std::move (calljob->loftptr); // assume ownership
90 calljob = calljob->next;
91 loftptr->call.invoke();
92 }
93}
94
95// == MainConfig and arguments ==
96static void
97print_usage (bool help)
98{
99 if (!help)
100 {
101 printout ("%s %s\n", executable_name(), ase_version());
102 printout ("Build: %s\n", ase_build_id());
103 return;
104 }
105 printout ("Usage: %s [OPTIONS] [project.anklang]\n", executable_name());
106 printout (" --check Run integrity tests\n");
107 printout (" --disable-randomization Test mode for deterministic tests\n");
108 printout (" --fatal-warnings Abort on warnings and failing assertions\n");
109 printout (" --help Print program usage and options\n");
110 printout (" --jsbin Print Javascript IPC & binary messages\n");
111 printout (" --jsipc Print Javascript IPC messages\n");
112 printout (" --jsonts Print TypeScript bindings\n");
113 printout (" --list-drivers Print PCM and MIDI drivers\n");
114 printout (" --list-tests List all test names\n");
115 printout (" --norc Prevent loading of any rc files\n");
116 printout (" --play-autostart Automatically start playback of `project.anklang`\n");
117 printout (" --rand64 Produce 64bit random numbers on stdout\n");
118 printout (" --test[=test] Run specific tests\n");
119 printout (" --unauth-dev=NUM Open an unauthenticated websocket port for testing\n");
120 printout (" --ui <none|chromium|google-chrome|htmlgui>\n");
121 printout (" Open GUI in web browser [htmlgui]\n");
122 printout (" --version Print program version\n");
123 printout (" -M mididriver Force use of <mididriver>\n");
124 printout (" -P pcmdriver Force use of <pcmdriver>\n");
125 printout (" -o wavfile Capture output to OPUS/FLAC/WAV file\n");
126 printout (" -t <time> Automatically play and stop after <time> has passed\n"); // -t <time>[{,|;}tailtime]
127 printout ("Options set via $ASE_DEBUG:\n");
128 printout (" :no-logfile: Disable logging to ~/.cache/anklang/ instead of stderr\n");
129}
130
132static bool
133parse_option_arg (const char *option, char **argv, unsigned *ith, const char **argp)
134{
135 const size_t l = strlen (option);
136 if (strncmp (option, argv[*ith], l) == 0) {
137 *argp = argv[*ith] + l;
138 argv[*ith] = nullptr;
139 if ((*argp)[0] == '=')
140 *argp += 1;
141 else if ((*argp)[0] == 0) {
142 *ith += 1;
143 *argp = argv[*ith] ? argv[*ith] : "";
144 argv[*ith] = nullptr;
145 }
146 return true;
147 }
148 return false;
149}
150
152log_setup (int *logfd)
153{
154 int flags = LOG_STDERR;
155 const char *asedebug = getenv ("ASE_DEBUG");
156 if (!asedebug || !strstr (asedebug, "no-log2file")) {
157 const String logdir = Path::join (Path::xdg_dir ("CACHE"), "anklang");
158 if (Path::mkdirs (logdir)) {
159 const String fname = string_format ("%s/%s-%08x.log", logdir, program_alias(), gethostid());
160 const int OFLAGS = O_CREAT | O_EXCL | O_WRONLY | O_NOCTTY | O_NOFOLLOW | O_CLOEXEC; // O_TRUNC
161 const int OMODE = 0640;
162 errno = EBUSY;
163 *logfd = open (fname.c_str(), OFLAGS, OMODE);
164 if (*logfd < 0 && errno == EEXIST) {
165 const String oldname = fname + ".old";
166 if (rename (fname.c_str(), oldname.c_str()) < 0)
167 perror (string_format ("%s: failed to rename \"%s\"", program_alias(), oldname.c_str()).c_str());
168 *logfd = open (fname.c_str(), OFLAGS, OMODE);
169 if (*logfd < 0)
170 perror (string_format ("%s: failed to open log file \"%s\"", program_alias(), fname.c_str()).c_str());
171 }
172 }
173 }
174 return LogFlags (flags);
175}
176
177// 1:ERROR 2:FAILED+REJECT 4:IO 8:MESSAGE 16:GET 256:BINARY
178static constexpr int jsipc_logflags = 1 | 2 | 4 | 8 | 16;
179static constexpr int jsbin_logflags = 1 | 256;
180
181static StringS check_test_names;
182
183static void
184parse_args (int *argcp, char **argv, MainAppImpl &config)
185{
186 if (0) // allow jsipc logging via ASE_DEBUG ?
187 {
188 config.jsonapi_logflags |= debug_key_enabled ("jsbin") ? jsbin_logflags : 0;
189 config.jsonapi_logflags |= debug_key_enabled ("jsipc") ? jsipc_logflags : 0;
190 }
191
192 config.norc = false;
193 bool sep = false; // -- separator
194 std::string default_ui_mode = "htmlgui";
195 const uint argc = *argcp;
196 for (uint i = 1; i < argc; i++)
197 {
198 const char *optarg = nullptr;
199 if (sep)
200 config.args.push_back (argv[i]);
201 else if (strcmp (argv[i], "--fatal-warnings") == 0 || strcmp (argv[i], "--g-fatal-warnings") == 0)
203 else if (strcmp ("--disable-randomization", argv[i]) == 0)
204 config.allow_randomization = false;
205 else if (strcmp ("--norc", argv[i]) == 0)
206 config.norc = true;
207 else if (strcmp ("--rand64", argv[i]) == 0)
208 {
209 FastRng prng;
210 constexpr int N = 8192;
211 uint64_t buffer[N];
212 while (1)
213 {
214 for (size_t i = 0; i < N; i++)
215 buffer[i] = prng.next();
216 fwrite (buffer, sizeof (buffer[0]), N, stdout);
217 }
218 exit (0);
219 }
220 else if (strcmp ("--check", argv[i]) == 0)
221 {
222 config.mode = MainApp::CHECK_INTEGRITY_TESTS;
224 printerr ("CHECK_INTEGRITY_TESTS…\n");
225 default_ui_mode = "none";
226 }
227 else if (strcmp ("--list-tests", argv[i]) == 0)
228 {
229 for (const auto &t : Test::list_tests())
230 printout ("%s\n", t.ident);
231 exit (0);
232 }
233 else if (strcmp ("--test", argv[i]) == 0 || strncmp ("--test=", argv[i], 7) == 0)
234 {
235 const char *eq = strchr (argv[i], '=');
236 const char *arg = eq ? eq + 1 : i+1 < argc ? argv[++i] : nullptr;
237 config.mode = MainApp::CHECK_INTEGRITY_TESTS;
239 if (arg)
240 check_test_names.push_back (arg);
241 default_ui_mode = "none";
242 }
243 else if (argv[i] == String ("--blake3") && i + 1 < size_t (argc))
244 {
245 argv[i++] = nullptr;
246 String hash = blake3_hash_file (argv[i]);
247 if (hash.empty())
248 printerr ("%s: failed to read: %s\n", argv[i], strerror (errno));
249 else
250 printout ("%s\n", string_to_hex (hash));
251 exit (hash == "");
252 }
253 else if (strcmp ("--jsonts", argv[i]) == 0) {
254 if (getenv ("ASE_JSONTS") == nullptr)
255 fatal_error ("%s: environment must contain ASE_JSONTS for --jsonts", argv[0]);
256 printout ("%s\n", Jsonipc::g_binding_printer->finish());
257 exit (0);
258 } else if (strcmp ("--jsipc", argv[i]) == 0)
259 config.jsonapi_logflags |= jsipc_logflags;
260 else if (strcmp ("--jsbin", argv[i]) == 0)
261 config.jsonapi_logflags |= jsbin_logflags;
262 else if (strcmp ("--list-drivers", argv[i]) == 0)
263 config.list_drivers = true;
264 else if (strcmp ("-M", argv[i]) == 0 && i + 1 < size_t (argc))
265 {
266 argv[i++] = nullptr;
267 config.midi_override = argv[i];
268 }
269 else if (strcmp ("-P", argv[i]) == 0 && i + 1 < size_t (argc))
270 {
271 argv[i++] = nullptr;
272 config.pcm_override = argv[i];
273 }
274 else if (strcmp ("-h", argv[i]) == 0 ||
275 strcmp ("--help", argv[i]) == 0)
276 {
277 print_usage (true);
278 exit (0);
279 }
280 else if (strcmp ("--version", argv[i]) == 0)
281 {
282 print_usage (false);
283 exit (0);
284 }
285 else if (argv[i] == String ("-o") && i + 1 < size_t (argc))
286 {
287 argv[i++] = nullptr;
288 config.outputfile = argv[i];
289 }
290 else if (argv[i] == String ("--play-autostart"))
291 {
292 config.play_autostart = true;
293 default_ui_mode = "none";
294 }
295 else if (parse_option_arg ("--unauth-dev", argv, &i, &optarg))
296 {
297 arg_unauth_port = string_to_int (optarg);
298 default_ui_mode = "wait";
299 }
300 else if (argv[i] == String ("-t") && i + 1 < size_t (argc))
301 {
302 config.play_autostart = true;
303 argv[i++] = nullptr;
304 config.play_autostop = string_to_seconds (argv[i]);
305 default_ui_mode = "none";
306 }
307 else if (parse_option_arg ("--ui", argv, &i, &optarg))
308 {
309 arg_ui_mode = optarg;
310 }
311 else if (argv[i] == String ("--") && !sep)
312 sep = true;
313 else if (argv[i][0] == '-' && !sep)
314 fatal_error ("invalid command line argument: %s", argv[i]);
315 else
316 config.args.push_back (argv[i]);
317 argv[i] = nullptr;
318 }
319 if (arg_ui_mode.empty())
320 arg_ui_mode = default_ui_mode;
321 if (*argcp > 1)
322 {
323 uint e = 1;
324 for (uint i = 1; i < argc; i++)
325 if (argv[i])
326 {
327 argv[e++] = argv[i];
328 if (i >= e)
329 argv[i] = nullptr;
330 }
331 *argcp = e;
332 }
333}
334
335static String
336make_auth_string()
337{
338 const char *const c52 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz";
339 /* We use WebScoket subprotocol randomization as authentication, so:
340 * a) Authentication happens *before* message interpretation, so an
341 * unauthenticated sender cannot cause crahses via e.g. rapidjson exceptions.
342 * b) To serve as working authentication measure, the subprotocol random string
343 * must be cryptographically-secure.
344 */
345 KeccakCryptoRng csprng;
346 String auth = "sessC";
347 for (size_t i = 0; i < 23; ++i)
348 auth += c52[csprng.random() % 52]; // each step adds 5.7 bits
349 return auth;
350}
351
352static void
353run_tests_and_quit ()
354{
355 if (check_test_names.empty())
356 Test::run();
357 else
358 Test::run (check_test_names);
359 main_loop->quit (0);
360}
361
362void
363main_loop_wakeup ()
364{
365 MainLoopP loop = main_loop;
366 if (loop)
367 loop->wakeup();
368}
369
370static std::atomic<bool> seen_autostop = false;
371
372// Lock and obstruction-free autostop trigger.
373void
374main_loop_autostop_mt()
375{
376 if (!seen_autostop)
377 {
378 seen_autostop = true;
379 main_loop_wakeup();
380 }
381}
382
383static bool
384handle_autostop (const LoopState &state)
385{
386 switch (state.phase)
387 {
388 case LoopState::PREPARE: return seen_autostop;
389 case LoopState::CHECK: return seen_autostop;
390 case LoopState::DISPATCH:
391 log ("Main: stopping playback (auto)");
392 main_loop->quit (0);
393 return true; // keep alive
394 default: ;
395 }
396 return false;
397}
398
399static void
400init_sigpipe()
401{
402 // don't die if we write() data to a process and that process dies (i.e. jackd)
403 sigset_t signal_mask;
404 sigemptyset (&signal_mask);
405 sigaddset (&signal_mask, SIGPIPE);
406
407 int rc = pthread_sigmask (SIG_BLOCK, &signal_mask, NULL);
408 if (rc != 0)
409 Ase::warning ("Ase: pthread_sigmask for SIGPIPE failed: %s\n", strerror (errno));
410}
411
412static std::atomic<bool> loft_needs_preallocation_mt = false;
413
414// handle watermark underrun notifications
415static void
416notify_loft_lowmem ()
417{
418 if (!loft_needs_preallocation_mt)
419 {
420 loft_needs_preallocation_mt = true;
421 Ase::main_loop_wakeup();
422 }
423}
424
425static size_t last_loft_preallocation = 0;
426
427static void
428preallocate_loft (size_t preallocation)
429{
430 using namespace Ase;
431 last_loft_preallocation = preallocation;
432 LoftConfig loftcfg = {
433 .preallocate = last_loft_preallocation,
434 .watermark = last_loft_preallocation / 2,
435 .flags = Loft::PREFAULT_PAGES,
436 };
437 loft_set_config (loftcfg);
438 loft_set_notifier (notify_loft_lowmem);
439 loft_grow_preallocate();
440}
441
442static bool
443dispatch_loft_lowmem (const Ase::LoopState &lstate)
444{
445 using namespace Ase;
446 const bool keep_alive = lstate.phase == LoopState::DISPATCH;
447 // generally, dispatch logic may only run in LoopState::DISPATCH, but this handler
448 // makes a rare exception, because we try to get ahead of concurrently runnint RT-threads...
449 return_unless (loft_needs_preallocation_mt, keep_alive);
450 loft_needs_preallocation_mt = false;
451 last_loft_preallocation *= 2;
452 const size_t newalloc = loft_grow_preallocate (last_loft_preallocation);
453 LoftConfig config;
454 loft_get_config (config);
455 config.watermark = last_loft_preallocation / 2;
456 loft_set_config (config);
457 if (newalloc > 0)
458 MDEBUG ("Loft preallocation in main thread: %f MB", newalloc / (1024. * 1024));
459 return keep_alive;
460}
461
462static void
463prefault_pages (size_t stacksize, size_t heapsize)
464{
465 const size_t pagesize = sysconf (_SC_PAGESIZE);
466 char *heap = (char*) malloc (heapsize);
467 if (heap)
468 for (size_t i = 0; i < heapsize; i += pagesize)
469 heap[i] = 1;
470 free (heap);
471 char *stack = (char*) alloca (stacksize);
472 if (stack)
473 for (size_t i = 0; i < stacksize; i += pagesize)
474 stack[i] = 1;
475}
476
477} // Ase
478
479int
480main (int argc, char *argv[])
481{
482 using namespace Ase;
483 using namespace AnsiColors;
484
485 // setup thread identifier
486 TaskRegistry::setup_ase ("AnklangMainProc");
487 // use malloc to serve allocations via sbrk only (avoid mmap)
488 mallopt (M_MMAP_MAX, 0);
489 // avoid releasing sbrk memory back to the system (reduce page faults)
490 mallopt (M_TRIM_THRESHOLD, -1);
491 // reserve large sbrk area and reduce page faults for heap and stack
492 prefault_pages ((1024 + 768) * 1024, 64 * 1024 * 1024);
493 // preallocate memory for lock-free allocator
494 preallocate_loft (64 * 1024 * 1024);
495
496 // SIGPIPE init: needs to be done before any child thread is created
497 init_sigpipe();
498 if (setpgid (0, 0) < 0)
499 log ("Main: setpgid failed: %s", ::strerror (errno));
500
501 // apply user locale
502 if (!setlocale (LC_ALL, ""))
503 fatal_error ("setlocale: locale not supported by libc: %s", ::strerror (errno));
504
505 // parse args and config
506 parse_args (&argc, argv, main_app);
507
508 // prepare main event loop (needed before parse_args)
509 main_loop = MainLoop::create();
510 // handle loft preallocation needs
511 main_loop->exec_dispatcher (dispatch_loft_lowmem, EventLoop::PRIORITY_CEILING);
512
513 // load preferences unless --norc was given
514 if (!App.norc)
515 Preference::load_preferences (true);
516
517 const auto B1 = color (BOLD);
518 const auto B0 = color (BOLD_OFF);
519
520 // load drivers and dump device list
522 if (App.list_drivers)
523 {
524 Ase::Driver::EntryVec entries;
525 printout ("%s", _("Available PCM drivers:\n"));
526 entries = Ase::PcmDriver::list_drivers();
527 std::sort (entries.begin(), entries.end(), [] (auto &a, auto &b) { return a.priority < b.priority; });
528 for (const auto &entry : entries)
529 {
530 printout (" %-30s (%s, %08x)\n\t%s\n%s%s%s%s", entry.devid + ":",
531 entry.readonly ? "Input" : entry.writeonly ? "Output" : "Duplex",
532 entry.priority, entry.device_name,
533 entry.capabilities.empty() ? "" : "\t" + entry.capabilities + "\n",
534 entry.device_info.empty() ? "" : "\t" + entry.device_info + "\n",
535 entry.hints.empty() ? "" : "\t(" + entry.hints + ")\n",
536 entry.notice.empty() ? "" : "\t" + entry.notice + "\n");
537 if (debug_key_enabled ("driver"))
538 printerr (" %08x: %s\n", entry.priority, Driver::priority_string (entry.priority));
539 }
540 printout ("%s", _("Available MIDI drivers:\n"));
541 entries = Ase::MidiDriver::list_drivers();
542 std::sort (entries.begin(), entries.end(), [] (auto &a, auto &b) { return a.priority < b.priority; });
543 for (const auto &entry : entries)
544 {
545 printout (" %-30s (%s, %08x)\n\t%s\n%s%s%s%s", entry.devid + ":",
546 entry.readonly ? "Input" : entry.writeonly ? "Output" : "Duplex",
547 entry.priority, entry.device_name,
548 entry.capabilities.empty() ? "" : "\t" + entry.capabilities + "\n",
549 entry.device_info.empty() ? "" : "\t" + entry.device_info + "\n",
550 entry.hints.empty() ? "" : "\t(" + entry.hints + ")\n",
551 entry.notice.empty() ? "" : "\t" + entry.notice + "\n");
552 if (debug_key_enabled ("driver"))
553 printerr (" %08x: %s\n", entry.priority, Driver::priority_string (entry.priority));
554 }
555 return 0;
556 }
557
558 // start audio engine
559 AudioEngine &audio_engine = make_audio_engine (main_loop_wakeup, 48000, SpeakerArrangement::STEREO);
560 main_app.engine = &audio_engine;
561 audio_engine.start_threads ();
562 /*const uint loopdispatcherid =*/
563 main_loop->exec_dispatcher ([&audio_engine] (const LoopState &state) -> bool {
564 switch (state.phase)
565 {
566 case LoopState::PREPARE:
567 return main_rt_jobs_pending() || audio_engine.ipc_pending();
568 case LoopState::CHECK:
569 return main_rt_jobs_pending() || audio_engine.ipc_pending();
570 case LoopState::DISPATCH:
571 audio_engine.ipc_dispatch();
572 main_rt_jobs_process();
573 return true;
574 default:
575 return false;
576 }
577 });
578
579 // load projects
580 ProjectImplP preload_project;
581 for (const auto &filename : App.args)
582 {
583 preload_project = ProjectImpl::create (Path::basename (filename));
584 Error error = Error::NO_MEMORY;
585 if (preload_project)
586 error = preload_project->load_project (filename);
587 log ("Main: load project: %s: %s", filename, ase_error_blurb (error));
588 if (!!error)
589 warning ("%s: failed to load project: %s", filename, ase_error_blurb (error));
590 }
591
592 // open Jsonapi socket
593 const String auth_token = arg_unauth_port > 0 ? "" : make_auth_string();
594 auto wss = WebSocketServer::create (jsonapi_make_connection, App.jsonapi_logflags, auth_token);
595 main_app.web_socket_server = &*wss;
596 wss->http_dir (anklang_runpath (RPath::INSTALLDIR, "/ui/"));
597 // wss->http_alias ("/User/Controller", anklang_home_dir ("/Controller"));
598 wss->http_alias ("/Builtin/Controller", anklang_runpath (RPath::INSTALLDIR, "/Controller"));
599 // wss->http_alias ("/User/Scripts", anklang_home_dir ("/Scripts"));
600 wss->http_alias ("/Builtin/Scripts", anklang_runpath (RPath::INSTALLDIR,"/Scripts"));
601 const int xport = arg_unauth_port > 0 ? arg_unauth_port : 0;
602 const String subprotocol = ""; // make_auth_string()
603 jsonapi_set_subprotocol (subprotocol);
604 if (App.mode == MainApp::SYNTHENGINE && arg_ui_mode != "none") {
605 const char *host = "127.0.0.1";
606 wss->listen (host, xport, [] () { main_loop->quit (-1); });
607 std::string webui_url = wss->url();
608 if (!xport) {
609 String redirecthtml = webui_create_auth_redirect ("anklang", wss->listen_port(), auth_token, arg_ui_mode);
610 if (errno)
611 fatal_error ("%s: failed to create html redirect file in $HOME", redirecthtml);
612 webui_url = "file://" + redirecthtml;
613 wss->see_other (webui_url);
614 }
615 log ("Main: WebUI address: %s", webui_url);
616 auto ereason = webui_start_browser (arg_ui_mode, main_loop, webui_url, [] () { main_loop->quit (0); });
617 if (ereason.error)
618 fatal_error ("Main: failed to run WebUI: %s: %s", ereason.what, ::strerror (ereason.error));
619 }
620
621 // run atquit handler on SIGHUP SIGINT
622 for (int sigid : { SIGHUP, SIGINT, SIGQUIT, SIGABRT, SIGTERM, SIGSYS }) {
623 main_loop->exec_usignal (sigid, [] (int8 sig) {
624 log ("Main: got signal %d: aborting", sig);
625 const pid_t pgid = getpgrp();
626 atquit_terminate (-1, pgid);
627 return false;
628 });
629 USignalSource::install_sigaction (sigid);
630 }
631
632 // catch SIGUSR2 to close sockets
633 main_loop->exec_usignal (SIGUSR2, [wss] (int8 sig) {
634 log ("Main: got signal %d: reset WebSocket", sig);
635 wss->reset();
636 return true;
637 });
638 USignalSource::install_sigaction (SIGUSR2);
639
640 // start output capturing
641 if (App.outputfile)
642 {
644 log ("Main: Start caputure: %s", App.outputfile);
645 App.engine->queue_capture_start (*callbacks, App.outputfile, true);
646 auto job = [callbacks] () {
647 for (const auto &callback : *callbacks)
648 callback();
649 };
650 App.engine->async_jobs += job;
651 }
652
653 // start auto play
654 if (App.play_autostart && preload_project)
655 main_loop->exec_idle ([preload_project] () {
656 log ("Main: starting playback (auto)");
657 preload_project->start_playback (App.play_autostop);
658 });
659 // handle automatic shutdown
660 main_loop->exec_dispatcher (handle_autostop);
661
662 // run test suite
663 if (App.mode == MainApp::CHECK_INTEGRITY_TESTS)
664 main_loop->exec_now (run_tests_and_quit);
665
666 // run main event loop and catch SIGUSR2
667 const int exitcode = main_loop->run();
668 assert_return (main_loop, -1); // ptr must be kept around
669 log ("Main: event loop quit: code=%d", exitcode);
670
671 // cleanup
672 wss->shutdown(); // close socket, allow no more calls
673 main_app.web_socket_server = nullptr;
674 wss = nullptr;
675
676 // halt audio engine, join its threads, dispatch cleanups
677 audio_engine.set_project (nullptr);
678 audio_engine.stop_threads();
679 main_loop->iterate_pending();
680 main_app.engine = nullptr;
681
682 log ("Main: exiting: %d", exitcode);
683 return exitcode;
684}
685
686namespace { // Anon
687using namespace Ase;
688
689extern "C" __attribute__ ((__noinline__)) void
690tlog1 (const char *s)
691{
692 log ("foo: %s+%d", s, 0x11111111);
693}
694
695extern "C" __attribute__ ((__noinline__)) void
696tlog2 (const char *s)
697{
698 log ("foo: %s+%d", s, 0x11111111);
699}
700
701TEST_INTEGRITY (job_queue_tests);
702static void
703job_queue_tests()
704{
705 bool seen_engine_job = false, seen_deleter = false;
706 // enqueue job with deleter into engine
707 AudioEngine *e = App.engine;
708 std::shared_ptr<void> vp = { nullptr, [e,&seen_deleter] (void*) {
709 printerr (" job_queue_tests: Run Deleter (in_engine=%d)\n", e->thread_id == std::this_thread::get_id());
710 seen_deleter = true;
711 } };
712 e->async_jobs += [e,vp,&seen_engine_job] () {
713 printerr (" job_queue_tests: Run Handler (in_engine=%d)\n", e->thread_id == std::this_thread::get_id());
714 seen_engine_job = true;
715 };
716 vp.reset(); // required to allow deleter execution further down
717 // enqueue jobs into main loop
718 main_rt_jobs += RtCall ([]() { printerr (" job_queue_tests: Hello %s!\n", "void()"); });
719 main_rt_jobs += RtCall ((void(*)(const char*)) [] (const char *a) { printerr (" job_queue_tests: Hello %s!\n", a); }, "RtJobQueue");
720 struct Test1 { const char *a_; void print() { printerr (" job_queue_tests: Hello %s!\n", a_); } };
721 static Test1 test1 { "MemFn" };
722 main_rt_jobs += RtCall (test1, &Test1::print);
723 // lame busy looping to give the engine a chance at the job queue
724 uint64 start_usecs = timestamp_realtime();
725 do {
726 usleep (1500); // give the audio engine some time
727 main_loop->iterate (false);
728 } while (timestamp_realtime() < start_usecs + 1871 * 1000 && !seen_deleter);
729 assert_return (seen_engine_job == true);
730 assert_return (seen_deleter == true);
731}
732
733} // Anon
#define EBUSY
T c_str(T... args)
JobQueue async_jobs
Executed asynchronously, may modify AudioProcessor objects.
Definition engine.hh:65
static String priority_string(uint priority)
Return string which represents the given priority mask.
Definition driver.cc:31
static const int16 PRIORITY_CEILING
Internal upper limit, don't use.
Definition loop.hh:88
uint64_t random()
static MainLoopP create()
Create a MainLoop shared pointer handle.
Definition loop.cc:369
Marsaglia multiply-with-carry generator, period ca 2^255.
Definition randomhash.hh:37
T empty(T... args)
errno
exit
free
fwrite
T get_id(T... args)
getenv
gethostid
optarg
getpgrp
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
Definition internal.hh:29
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
Definition internal.hh:71
#define _(...)
Retrieve the translation of a C or C++ string.
Definition internal.hh:18
#define TEST_INTEGRITY(FUNC)
Register func as an integrity test.
Definition internal.hh:77
log
std::string color(Colors acolor, Colors c1, Colors c2, Colors c3, Colors c4, Colors c5, Colors c6)
Return ANSI code for the specified color if stdout & stderr should be colorized, see colorize_tty().
Definition platform.cc:190
String basename(const String &path)
Strips all directory components from path and returns the resulting file name.
Definition path.cc:68
bool mkdirs(const String &dirpath, uint mode)
Create the directories in dirpath with mode, check errno on false returns.
Definition path.cc:197
int run(void)
Run all registered tests.
Definition testing.cc:264
The Anklang C++ API namespace.
Definition api.hh:9
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
bool debug_key_enabled(const char *conditional)
Check if conditional is enabled by $ASE_DEBUG.
Definition utils.cc:38
bool ase_fatal_warnings
Global boolean to cause the program to abort on warnings.
Definition utils.cc:20
uint64_t uint64
A 64-bit unsigned integer, use PRI*64 in format strings.
Definition cxxaux.hh:25
String string_to_hex(const String &input)
Convert bytes in string input to hexadecimal numbers.
Definition strings.cc:1171
int8_t int8
An 8-bit signed integer.
Definition cxxaux.hh:26
JobQueue main_jobs(call_main_loop)
Execute a job callback in the Ase main loop.
Definition main.hh:39
Error
Enum representing Error states.
Definition api.hh:22
const char * ase_error_blurb(Error error)
Describe Error condition.
Definition server.cc:227
std::string anklang_runpath(RPath rpath, const String &segment)
Retrieve various resource paths at runtime.
Definition platform.cc:58
double string_to_seconds(const String &string, double fallback)
Parse string into seconds.
Definition strings.cc:773
int64 string_to_int(const String &string, size_t *consumed, uint base)
Parse a string into a 64bit integer, optionally specifying the expected number base.
Definition strings.cc:578
bool assertion_failed_fatal
Global flag to force aborting on assertion warnings.
Definition cxxaux.cc:106
LogFlags
Flags to configure logging behaviour.
Definition logging.hh:26
String program_alias()
Retrieve the program name as used for logging or debug messages.
Definition platform.cc:849
const char * ase_version()
Provide a string containing the package version.
Definition platform.cc:835
std::string executable_name()
Retrieve the name part of executable_path().
Definition platform.cc:742
uint32_t uint
Provide 'uint' as convenience type.
Definition cxxaux.hh:18
void load_registered_drivers()
Load all registered drivers.
Definition driver.cc:76
RtJobQueue main_rt_jobs
Queue a callback for the main_loop without invoking malloc(), addition is obstruction free.
Definition main.cc:65
LogFlags log_setup(int *) __attribute__((__weak__))
Configurable handler to open log files.
Definition main.cc:152
size_t preallocate
Amount of preallocated available memory.
Definition loft.hh:45
uint64 timestamp_realtime()
Return the current time as uint64 in µseconds.
Definition platform.cc:578
size_t watermark
Watermark to trigger async preallocation.
Definition loft.hh:46
const char * ase_build_id()
Provide a string containing the ASE library build id.
Definition platform.cc:841
Configuration for Loft allocations.
Definition loft.hh:44
open
perror
pthread_sigmask
T push_back(T... args)
rename
sigaddset
sigemptyset
T sort(T... args)
typedef uint64_t
strchr
strlen
strstr
Wrap simple callback pointers, without using malloc (obstruction free).
Definition callback.hh:91
Add a simple callback to the main event loop, without using malloc (obstruction free).
Definition main.hh:42
typedef pid_t
sysconf