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).