26#define MDEBUG(...) Ase::debug ("memory", __VA_ARGS__)
36MainAppImpl::MainAppImpl()
41static int arg_unauth_port = 0;
47 main_loop->exec_callback (fun);
68RtJobQueue::operator+= (
const RtCall &call)
72 calljob->loftptr = std::move (loftptr);
73 const bool was_empty = main_rt_jobs_.push (calljob);
81 return !main_rt_jobs_.empty();
87 RtCallJob *calljob = main_rt_jobs_.pop_reversed();
90 calljob = calljob->next;
91 loftptr->call.invoke();
97print_usage (
bool help)
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");
127 printout (
"Options set via $ASE_DEBUG:\n");
128 printout (
" :no-logfile: Disable logging to ~/.cache/anklang/ instead of stderr\n");
133parse_option_arg (
const char *option,
char **argv,
unsigned *ith,
const char **argp)
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] ==
'=')
141 else if ((*argp)[0] == 0) {
143 *argp = argv[*ith] ? argv[*ith] :
"";
144 argv[*ith] =
nullptr;
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");
160 const int OFLAGS = O_CREAT | O_EXCL | O_WRONLY | O_NOCTTY | O_NOFOLLOW | O_CLOEXEC;
161 const int OMODE = 0640;
163 *logfd =
open (fname.
c_str(), OFLAGS, OMODE);
165 const String oldname = fname +
".old";
168 *logfd =
open (fname.
c_str(), OFLAGS, OMODE);
178static constexpr int jsipc_logflags = 1 | 2 | 4 | 8 | 16;
179static constexpr int jsbin_logflags = 1 | 256;
181static StringS check_test_names;
184parse_args (
int *argcp,
char **argv,
MainAppImpl &config)
195 const uint argc = *argcp;
196 for (
uint i = 1; i < argc; i++)
198 const char *
optarg =
nullptr;
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)
207 else if (strcmp (
"--rand64", argv[i]) == 0)
210 constexpr int N = 8192;
214 for (
size_t i = 0; i < N; i++)
215 buffer[i] = prng.next();
216 fwrite (buffer,
sizeof (buffer[0]), N, stdout);
220 else if (strcmp (
"--check", argv[i]) == 0)
222 config.mode = MainApp::CHECK_INTEGRITY_TESTS;
224 printerr (
"CHECK_INTEGRITY_TESTS…\n");
225 default_ui_mode =
"none";
227 else if (strcmp (
"--list-tests", argv[i]) == 0)
229 for (
const auto &t : Test::list_tests())
230 printout (
"%s\n", t.ident);
233 else if (strcmp (
"--test", argv[i]) == 0 || strncmp (
"--test=", argv[i], 7) == 0)
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;
241 default_ui_mode =
"none";
243 else if (argv[i] ==
String (
"--blake3") && i + 1 <
size_t (argc))
246 String hash = blake3_hash_file (argv[i]);
248 printerr (
"%s: failed to read: %s\n", argv[i], strerror (errno));
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());
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))
267 config.midi_override = argv[i];
269 else if (strcmp (
"-P", argv[i]) == 0 && i + 1 <
size_t (argc))
272 config.pcm_override = argv[i];
274 else if (strcmp (
"-h", argv[i]) == 0 ||
275 strcmp (
"--help", argv[i]) == 0)
280 else if (strcmp (
"--version", argv[i]) == 0)
285 else if (argv[i] ==
String (
"-o") && i + 1 <
size_t (argc))
288 config.outputfile = argv[i];
290 else if (argv[i] ==
String (
"--play-autostart"))
292 config.play_autostart =
true;
293 default_ui_mode =
"none";
295 else if (parse_option_arg (
"--unauth-dev", argv, &i, &optarg))
298 default_ui_mode =
"wait";
300 else if (argv[i] ==
String (
"-t") && i + 1 <
size_t (argc))
302 config.play_autostart =
true;
305 default_ui_mode =
"none";
307 else if (parse_option_arg (
"--ui", argv, &i, &optarg))
311 else if (argv[i] ==
String (
"--") && !sep)
313 else if (argv[i][0] ==
'-' && !sep)
314 fatal_error (
"invalid command line argument: %s", argv[i]);
319 if (arg_ui_mode.
empty())
320 arg_ui_mode = default_ui_mode;
324 for (
uint i = 1; i < argc; i++)
338 const char *
const c52 =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz";
347 for (
size_t i = 0; i < 23; ++i)
348 auth += c52[csprng.
random() % 52];
355 if (check_test_names.
empty())
374main_loop_autostop_mt()
378 seen_autostop =
true;
388 case LoopState::PREPARE:
return seen_autostop;
389 case LoopState::CHECK:
return seen_autostop;
390 case LoopState::DISPATCH:
391 log (
"Main: stopping playback (auto)");
403 sigset_t signal_mask;
409 Ase::warning (
"Ase: pthread_sigmask for SIGPIPE failed: %s\n", strerror (errno));
418 if (!loft_needs_preallocation_mt)
420 loft_needs_preallocation_mt =
true;
421 Ase::main_loop_wakeup();
425static size_t last_loft_preallocation = 0;
428preallocate_loft (
size_t preallocation)
431 last_loft_preallocation = preallocation;
434 .watermark = last_loft_preallocation / 2,
435 .flags = Loft::PREFAULT_PAGES,
437 loft_set_config (loftcfg);
438 loft_set_notifier (notify_loft_lowmem);
439 loft_grow_preallocate();
446 const bool keep_alive = lstate.phase == LoopState::DISPATCH;
450 loft_needs_preallocation_mt =
false;
451 last_loft_preallocation *= 2;
452 const size_t newalloc = loft_grow_preallocate (last_loft_preallocation);
454 loft_get_config (config);
455 config.
watermark = last_loft_preallocation / 2;
456 loft_set_config (config);
458 MDEBUG (
"Loft preallocation in main thread: %f MB", newalloc / (1024. * 1024));
463prefault_pages (
size_t stacksize,
size_t heapsize)
465 const size_t pagesize =
sysconf (_SC_PAGESIZE);
466 char *heap = (
char*) malloc (heapsize);
468 for (
size_t i = 0; i < heapsize; i += pagesize)
471 char *stack = (
char*) alloca (stacksize);
473 for (
size_t i = 0; i < stacksize; i += pagesize)
480main (
int argc,
char *argv[])
483 using namespace AnsiColors;
486 TaskRegistry::setup_ase (
"AnklangMainProc");
488 mallopt (M_MMAP_MAX, 0);
490 mallopt (M_TRIM_THRESHOLD, -1);
492 prefault_pages ((1024 + 768) * 1024, 64 * 1024 * 1024);
494 preallocate_loft (64 * 1024 * 1024);
498 if (setpgid (0, 0) < 0)
499 log (
"Main: setpgid failed: %s", ::strerror (errno));
502 if (!setlocale (LC_ALL,
""))
503 fatal_error (
"setlocale: locale not supported by libc: %s", ::strerror (errno));
506 parse_args (&argc, argv, main_app);
515 Preference::load_preferences (
true);
517 const auto B1 =
color (BOLD);
518 const auto B0 =
color (BOLD_OFF);
522 if (App.list_drivers)
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)
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");
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)
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");
559 AudioEngine &audio_engine = make_audio_engine (main_loop_wakeup, 48000, SpeakerArrangement::STEREO);
560 main_app.engine = &audio_engine;
561 audio_engine.start_threads ();
563 main_loop->exec_dispatcher ([&audio_engine] (
const LoopState &state) ->
bool {
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();
581 for (
const auto &filename : App.args)
583 preload_project = ProjectImpl::create (
Path::basename (filename));
584 Error error = Error::NO_MEMORY;
586 error = preload_project->load_project (filename);
589 warning (
"%s: failed to load project: %s", filename,
ase_error_blurb (error));
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;
598 wss->http_alias (
"/Builtin/Controller",
anklang_runpath (RPath::INSTALLDIR,
"/Controller"));
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 =
"";
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); });
609 String redirecthtml = webui_create_auth_redirect (
"anklang", wss->listen_port(), auth_token, arg_ui_mode);
611 fatal_error (
"%s: failed to create html redirect file in $HOME", redirecthtml);
612 webui_url =
"file://" + redirecthtml;
613 wss->see_other (webui_url);
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); });
618 fatal_error (
"Main: failed to run WebUI: %s: %s", ereason.what, ::strerror (ereason.error));
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);
626 atquit_terminate (-1, pgid);
629 USignalSource::install_sigaction (sigid);
633 main_loop->exec_usignal (SIGUSR2, [wss] (
int8 sig) {
634 log (
"Main: got signal %d: reset WebSocket", sig);
638 USignalSource::install_sigaction (SIGUSR2);
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)
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);
660 main_loop->exec_dispatcher (handle_autostop);
663 if (App.mode == MainApp::CHECK_INTEGRITY_TESTS)
664 main_loop->exec_now (run_tests_and_quit);
667 const int exitcode = main_loop->run();
669 log (
"Main: event loop quit: code=%d", exitcode);
673 main_app.web_socket_server =
nullptr;
677 audio_engine.set_project (
nullptr);
678 audio_engine.stop_threads();
679 main_loop->iterate_pending();
680 main_app.engine =
nullptr;
682 log (
"Main: exiting: %d", exitcode);
689extern "C" __attribute__ ((__noinline__))
void
692 log (
"foo: %s+%d", s, 0x11111111);
695extern "C" __attribute__ ((__noinline__))
void
698 log (
"foo: %s+%d", s, 0x11111111);
705 bool seen_engine_job =
false, seen_deleter =
false;
714 seen_engine_job =
true;
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" };
727 main_loop->iterate (
false);
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.
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.
bool mkdirs(const String &dirpath, uint mode)
Create the directories in dirpath with mode, check errno on false returns.
int run(void)
Run all registered tests.
The Anklang C++ API namespace.
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.
bool ase_fatal_warnings
Global boolean to cause the program to abort on warnings.
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 assertion_failed_fatal
Global flag to force aborting on assertion warnings.
LogFlags
Flags to configure logging behaviour.
String program_alias()
Retrieve the program name as used for logging or debug messages.
const char * ase_version()
Provide a string containing the package version.
std::string executable_name()
Retrieve the name part of executable_path().
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.
LogFlags log_setup(int *) __attribute__((__weak__))
Configurable handler to open log files.
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).