23#ifdef ASE_WITH_CPPTRACE
24#include <cpptrace/from_current.hpp>
29#define MDEBUG(...) Ase::debug ("memory", __VA_ARGS__)
39MainAppImpl::MainAppImpl()
44static int arg_unauth_port = 0;
50 main_loop->exec_callback (fun);
71RtJobQueue::operator+= (
const RtCall &call)
75 calljob->loftptr = std::move (loftptr);
76 const bool was_empty = main_rt_jobs_.push (calljob);
84 return !main_rt_jobs_.empty();
90 RtCallJob *calljob = main_rt_jobs_.pop_reversed();
93 calljob = calljob->next;
94 loftptr->call.invoke();
100print_usage (
bool help)
108 printout (
"Usage: %s [OPTIONS] [project.anklang]\n",
executable_name());
109 printout (
" --check Run integrity tests\n");
110 printout (
" --disable-randomization Test mode for deterministic tests\n");
111 printout (
" --fatal-warnings Abort on warnings and failing assertions\n");
112 printout (
" --help Print program usage and options\n");
113 printout (
" --jsbin Print Javascript IPC & binary messages\n");
114 printout (
" --jsipc Print Javascript IPC messages\n");
115 printout (
" --jsonts Print TypeScript bindings\n");
116 printout (
" --list-drivers Print PCM and MIDI drivers\n");
117 printout (
" --list-tests List all test names\n");
118 printout (
" --norc Prevent loading of any rc files\n");
119 printout (
" --play-autostart Automatically start playback of `project.anklang`\n");
120 printout (
" --rand64 Produce 64bit random numbers on stdout\n");
121 printout (
" --test[=test] Run specific tests\n");
122 printout (
" --unauth-dev=NUM Open an unauthenticated websocket port for testing\n");
123 printout (
" --ui <none|chromium|google-chrome|htmlgui>\n");
124 printout (
" Open GUI in web browser [htmlgui]\n");
125 printout (
" --version Print program version\n");
126 printout (
" -M mididriver Force use of <mididriver>\n");
127 printout (
" -P pcmdriver Force use of <pcmdriver>\n");
128 printout (
" -o wavfile Capture output to OPUS/FLAC/WAV file\n");
129 printout (
" -t <time> Automatically play and stop after <time> has passed\n");
130 printout (
"Options set via $ASE_DEBUG:\n");
131 printout (
" :no-logfile: Disable logging to ~/.cache/anklang/ instead of stderr\n");
136parse_option_arg (
const char *option,
char **argv,
unsigned *ith,
const char **argp)
138 const size_t l =
strlen (option);
139 if (strncmp (option, argv[*ith], l) == 0) {
140 *argp = argv[*ith] + l;
141 argv[*ith] =
nullptr;
142 if ((*argp)[0] ==
'=')
144 else if ((*argp)[0] == 0) {
146 *argp = argv[*ith] ? argv[*ith] :
"";
147 argv[*ith] =
nullptr;
155static constexpr int jsipc_logflags = 1 | 2 | 4 | 8 | 16;
156static constexpr int jsbin_logflags = 1 | 256;
158static StringS check_test_names;
161parse_args (
int *argcp,
char **argv,
MainAppImpl &config)
172 const uint argc = *argcp;
173 for (
uint i = 1; i < argc; i++)
175 const char *
optarg =
nullptr;
178 else if (strcmp (argv[i],
"--fatal-warnings") == 0 || strcmp (argv[i],
"--g-fatal-warnings") == 0)
180 else if (strcmp (
"--disable-randomization", argv[i]) == 0)
181 config.allow_randomization =
false;
182 else if (strcmp (
"--norc", argv[i]) == 0)
184 else if (strcmp (
"--rand64", argv[i]) == 0)
187 constexpr int N = 8192;
191 for (
size_t i = 0; i < N; i++)
192 buffer[i] = prng.next();
193 fwrite (buffer,
sizeof (buffer[0]), N, stdout);
197 else if (strcmp (
"--check", argv[i]) == 0)
199 config.mode = MainApp::CHECK_INTEGRITY_TESTS;
201 printerr (
"CHECK_INTEGRITY_TESTS…\n");
202 default_ui_mode =
"none";
204 else if (strcmp (
"--list-tests", argv[i]) == 0)
207 for (
const auto &t : Test::list_tests())
208 ids.push_back (t.ident);
210 for (
const auto &t : ids)
211 printout (
"%s\n", t);
214 else if (strcmp (
"--test", argv[i]) == 0 || strncmp (
"--test=", argv[i], 7) == 0)
216 const char *eq =
strchr (argv[i],
'=');
217 const char *arg = eq ? eq + 1 : i+1 < argc ? argv[++i] :
nullptr;
218 config.mode = MainApp::CHECK_INTEGRITY_TESTS;
222 default_ui_mode =
"none";
224 else if (argv[i] ==
String (
"--blake3") && i + 1 <
size_t (argc))
227 String hash = blake3_hash_file (argv[i]);
229 printerr (
"%s: failed to read: %s\n", argv[i], strerror (errno));
234 else if (strcmp (
"--jsonts", argv[i]) == 0) {
235 if (getenv (
"ASE_JSONTS") ==
nullptr)
236 fatal_error (
"%s: environment must contain ASE_JSONTS for --jsonts", argv[0]);
237 printout (
"%s\n", Jsonipc::g_binding_printer->finish());
239 }
else if (strcmp (
"--jsipc", argv[i]) == 0)
240 config.jsonapi_logflags |= jsipc_logflags;
241 else if (strcmp (
"--jsbin", argv[i]) == 0)
242 config.jsonapi_logflags |= jsbin_logflags;
243 else if (strcmp (
"--list-drivers", argv[i]) == 0)
244 config.list_drivers =
true;
245 else if (strcmp (
"-M", argv[i]) == 0 && i + 1 <
size_t (argc))
248 config.midi_override = argv[i];
250 else if (strcmp (
"-P", argv[i]) == 0 && i + 1 <
size_t (argc))
253 config.pcm_override = argv[i];
255 else if (strcmp (
"-h", argv[i]) == 0 ||
256 strcmp (
"--help", argv[i]) == 0)
261 else if (strcmp (
"--version", argv[i]) == 0)
266 else if (argv[i] ==
String (
"-o") && i + 1 <
size_t (argc))
269 config.outputfile = argv[i];
271 else if (argv[i] ==
String (
"--play-autostart"))
273 config.play_autostart =
true;
274 default_ui_mode =
"none";
276 else if (parse_option_arg (
"--unauth-dev", argv, &i, &optarg))
279 default_ui_mode =
"wait";
281 else if (argv[i] ==
String (
"-t") && i + 1 <
size_t (argc))
283 config.play_autostart =
true;
286 default_ui_mode =
"none";
288 else if (parse_option_arg (
"--ui", argv, &i, &optarg))
292 else if (argv[i] ==
String (
"--") && !sep)
294 else if (argv[i][0] ==
'-' && !sep)
295 fatal_error (
"invalid command line argument: %s", argv[i]);
300 if (arg_ui_mode.
empty())
301 arg_ui_mode = default_ui_mode;
305 for (
uint i = 1; i < argc; i++)
319 const char *
const c52 =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz";
328 for (
size_t i = 0; i < 23; ++i)
329 auth += c52[csprng.
random() % 52];
336 if (check_test_names.
empty())
355main_loop_autostop_mt()
359 seen_autostop =
true;
369 case LoopState::PREPARE:
return seen_autostop;
370 case LoopState::CHECK:
return seen_autostop;
371 case LoopState::DISPATCH:
372 info (
"Main: stopping playback (auto)");
384 sigset_t signal_mask;
390 Ase::warning (
"Ase: pthread_sigmask for SIGPIPE failed: %s\n", strerror (errno));
399 if (!loft_needs_preallocation_mt)
401 loft_needs_preallocation_mt =
true;
402 Ase::main_loop_wakeup();
406static size_t last_loft_preallocation = 0;
409preallocate_loft (
size_t preallocation)
412 last_loft_preallocation = preallocation;
415 .watermark = last_loft_preallocation / 2,
416 .flags = Loft::PREFAULT_PAGES,
418 loft_set_config (loftcfg);
419 loft_set_notifier (notify_loft_lowmem);
420 loft_grow_preallocate();
427 const bool keep_alive = lstate.phase == LoopState::DISPATCH;
431 loft_needs_preallocation_mt =
false;
432 last_loft_preallocation *= 2;
433 const size_t newalloc = loft_grow_preallocate (last_loft_preallocation);
435 loft_get_config (config);
436 config.
watermark = last_loft_preallocation / 2;
437 loft_set_config (config);
439 MDEBUG (
"Loft preallocation in main thread: %f MB", newalloc / (1024. * 1024));
444prefault_pages (
size_t stacksize,
size_t heapsize)
446 const size_t pagesize =
sysconf (_SC_PAGESIZE);
447 char *heap = (
char*) malloc (heapsize);
449 for (
size_t i = 0; i < heapsize; i += pagesize)
452 char *stack = (
char*) alloca (stacksize);
454 for (
size_t i = 0; i < stacksize; i += pagesize)
459main (
int argc,
char *argv[])
462 using namespace AnsiColors;
465 TaskRegistry::setup_ase (
"AnklangMainProc");
467 mallopt (M_MMAP_MAX, 0);
469 mallopt (M_TRIM_THRESHOLD, -1);
471 prefault_pages ((1024 + 768) * 1024, 64 * 1024 * 1024);
473 preallocate_loft (64 * 1024 * 1024);
476 logging_handle_terminate();
480 if (setpgid (0, 0) < 0)
481 diag (
"Main: setpgid failed: %s", ::strerror (errno));
484 if (!setlocale (LC_ALL,
""))
485 fatal_error (
"setlocale: locale not supported by libc: %s", ::strerror (errno));
488 parse_args (&argc, argv, main_app);
489 logging_configure (arg_ui_mode !=
"none");
498 Preference::load_preferences (
true);
500 const auto B1 =
color (BOLD);
501 const auto B0 =
color (BOLD_OFF);
505 if (App.list_drivers)
507 Ase::Driver::EntryVec entries;
508 printout (
"%s",
_(
"Available PCM drivers:\n"));
509 entries = Ase::PcmDriver::list_drivers();
510 std::sort (entries.begin(), entries.end(), [] (
auto &a,
auto &b) { return a.priority < b.priority; });
511 for (
const auto &entry : entries)
513 printout (
" %-30s (%s, %08x)\n\t%s\n%s%s%s%s", entry.devid +
":",
514 entry.readonly ?
"Input" : entry.writeonly ?
"Output" :
"Duplex",
515 entry.priority, entry.device_name,
516 entry.capabilities.
empty() ?
"" :
"\t" + entry.capabilities +
"\n",
517 entry.device_info.
empty() ?
"" :
"\t" + entry.device_info +
"\n",
518 entry.hints.
empty() ?
"" :
"\t(" + entry.hints +
")\n",
519 entry.notice.
empty() ?
"" :
"\t" + entry.notice +
"\n");
523 printout (
"%s",
_(
"Available MIDI drivers:\n"));
524 entries = Ase::MidiDriver::list_drivers();
525 std::sort (entries.begin(), entries.end(), [] (
auto &a,
auto &b) { return a.priority < b.priority; });
526 for (
const auto &entry : entries)
528 printout (
" %-30s (%s, %08x)\n\t%s\n%s%s%s%s", entry.devid +
":",
529 entry.readonly ?
"Input" : entry.writeonly ?
"Output" :
"Duplex",
530 entry.priority, entry.device_name,
531 entry.capabilities.
empty() ?
"" :
"\t" + entry.capabilities +
"\n",
532 entry.device_info.
empty() ?
"" :
"\t" + entry.device_info +
"\n",
533 entry.hints.
empty() ?
"" :
"\t(" + entry.hints +
")\n",
534 entry.notice.
empty() ?
"" :
"\t" + entry.notice +
"\n");
542 AudioEngine &audio_engine = make_audio_engine (main_loop_wakeup, 48000, SpeakerArrangement::STEREO);
543 main_app.engine = &audio_engine;
544 audio_engine.start_threads ();
546 main_loop->exec_dispatcher ([&audio_engine] (
const LoopState &state) ->
bool {
549 case LoopState::PREPARE:
550 return main_rt_jobs_pending() || audio_engine.ipc_pending();
551 case LoopState::CHECK:
552 return main_rt_jobs_pending() || audio_engine.ipc_pending();
553 case LoopState::DISPATCH:
554 audio_engine.ipc_dispatch();
555 main_rt_jobs_process();
564 for (
const auto &filename : App.args)
566 preload_project = ProjectImpl::create (
Path::basename (filename));
567 Error error = Error::NO_MEMORY;
569 error = preload_project->load_project (filename);
570 diag (
"Main: load project: %s: %s", filename,
ase_error_blurb (error));
572 warning (
"%s: failed to load project: %s", filename,
ase_error_blurb (error));
576 const String auth_token = arg_unauth_port > 0 ?
"" : make_auth_string();
577 auto wss = WebSocketServer::create (jsonapi_make_connection, App.jsonapi_logflags, auth_token);
578 main_app.web_socket_server = &*wss;
581 wss->http_alias (
"/Builtin/Controller",
anklang_runpath (RPath::INSTALLDIR,
"/Controller"));
583 wss->http_alias (
"/Builtin/Scripts",
anklang_runpath (RPath::INSTALLDIR,
"/Scripts"));
584 const int xport = arg_unauth_port > 0 ? arg_unauth_port : 0;
585 const String subprotocol =
"";
586 jsonapi_set_subprotocol (subprotocol);
587 if (App.mode == MainApp::SYNTHENGINE && arg_ui_mode !=
"none") {
588 const char *host =
"127.0.0.1";
589 wss->listen (host, xport, [] () { main_loop->quit (-1); });
592 String redirecthtml = webui_create_auth_redirect (
"anklang", wss->listen_port(), auth_token, arg_ui_mode);
594 fatal_error (
"%s: failed to create html redirect file in $HOME", redirecthtml);
595 webui_url =
"file://" + redirecthtml;
596 wss->see_other (webui_url);
598 info (
"Main: WebUI address: %s", webui_url);
599 auto ereason = webui_start_browser (arg_ui_mode, main_loop, webui_url, [] () { main_loop->quit (0); });
601 fatal_error (
"Main: failed to run WebUI: %s: %s", ereason.what, ::strerror (ereason.error));
605 for (
int sigid : { SIGHUP, SIGINT, SIGQUIT, SIGABRT, SIGTERM, SIGSYS }) {
606 main_loop->exec_usignal (sigid, [] (
int8 sig) {
607 info (
"Main: got signal %d: terminate", sig);
609 atquit_terminate (-1, pgid);
612 USignalSource::install_sigaction (sigid);
616 main_loop->exec_usignal (SIGUSR2, [wss] (
int8 sig) {
617 info (
"Main: got signal %d: reset WebSocket", sig);
621 USignalSource::install_sigaction (SIGUSR2);
627 info (
"Main: Start caputure: %s", App.outputfile);
628 App.engine->queue_capture_start (*callbacks, App.outputfile,
true);
629 auto job = [callbacks] () {
630 for (
const auto &callback : *callbacks)
637 if (App.play_autostart && preload_project)
638 main_loop->exec_idle ([preload_project] () {
639 info (
"Main: starting playback (auto)");
640 preload_project->start_playback (App.play_autostop);
643 main_loop->exec_dispatcher (handle_autostop);
646 if (App.mode == MainApp::CHECK_INTEGRITY_TESTS)
647 main_loop->exec_now (run_tests_and_quit);
650 const int exitcode = main_loop->run();
652 diag (
"Main: event loop quit: code=%d", exitcode);
656 main_app.web_socket_server =
nullptr;
660 audio_engine.set_project (
nullptr);
661 audio_engine.stop_threads();
662 main_loop->iterate_pending();
663 main_app.engine =
nullptr;
665 diag (
"Main: exiting: %d", exitcode);
672main (
int argc,
char *argv[])
675#ifdef ASE_WITH_CPPTRACE
676 CPPTRACE_TRY { r = Ase::main (argc, argv); }
684 cpptrace::from_current_exception().print();
687 r = Ase::main (argc, argv);
695extern "C" __attribute__ ((__noinline__))
void
698 debug (
"foo: %s+%d", s, 0x11111111);
701extern "C" __attribute__ ((__noinline__))
void
704 debug (
"foo: %s+%d", s, 0x11111111);
711 bool seen_engine_job =
false, seen_deleter =
false;
720 seen_engine_job =
true;
724 main_rt_jobs +=
RtCall ([]() { printerr (
" job_queue_tests: Hello %s!\n",
"void()"); });
725 main_rt_jobs +=
RtCall ((
void(*)(
const char*)) [] (
const char *a) { printerr (
" job_queue_tests: Hello %s!\n", a); },
"RtJobQueue");
726 struct Test1 {
const char *a_;
void print() { printerr (
" job_queue_tests: Hello %s!\n", a_); } };
727 static Test1 test1 {
"MemFn" };
733 main_loop->iterate (
false);
Lock-free stack with atomic push() and pop_all operations.
Main handle for AudioProcessor administration and audio rendering.
JobQueue async_jobs
Executed asynchronously, may modify AudioProcessor objects.
static String priority_string(uint priority)
Return string which represents the given priority mask.
static const int16 PRIORITY_CEILING
Internal upper limit, don't use.
KeccakCryptoRng - A KeccakF1600 based cryptographic quality pseudo-random number generator.
uint64_t random()
Generate uniformly distributed 64 bit pseudo random number.
static MainLoopP create()
Create a MainLoop shared pointer handle.
Marsaglia multiply-with-carry generator, period ca 2^255.
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
#define _(...)
Retrieve the translation of a C or C++ string.
#define TEST_INTEGRITY(FUNC)
Register func as an integrity test.
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().
String basename(const String &path)
Strips all directory components from path and returns the resulting file name.
int run(void)
Run all registered tests.
The Anklang C++ API namespace.
uint64_t uint64
A 64-bit unsigned integer, use PRI*64 in format strings.
String string_to_hex(const String &input)
Convert bytes in string input to hexadecimal numbers.
int8_t int8
An 8-bit signed integer.
JobQueue main_jobs(call_main_loop)
Execute a job callback in the Ase main loop.
Error
Enum representing Error states.
const char * ase_error_blurb(Error error)
Describe Error condition.
std::string anklang_runpath(RPath rpath, const String &segment)
Retrieve various resource paths at runtime.
double string_to_seconds(const String &string, double fallback)
Parse string into seconds.
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.
bool logging_fatal_warnings
Global flag to cause the program to abort on warnings.
const char * ase_version()
Provide a string containing the package version.
std::string executable_name()
Retrieve the name part of executable_path().
bool debug_key_enabled(const char *conditional) noexcept
Check if conditional is enabled by $ASE_DEBUG.
uint32_t uint
Provide 'uint' as convenience type.
void load_registered_drivers()
Load all registered drivers.
RtJobQueue main_rt_jobs
Queue a callback for the main_loop without invoking malloc(), addition is obstruction free.
size_t preallocate
Amount of preallocated available memory.
uint64 timestamp_realtime()
Return the current time as uint64 in µseconds.
size_t watermark
Watermark to trigger async preallocation.
const char * ase_build_id()
Provide a string containing the ASE library build id.
Configuration for Loft allocations.
Wrap simple callback pointers, without using malloc (obstruction free).
Add a simple callback to the main event loop, without using malloc (obstruction free).