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