/*
* melisma - extensible and scriptable audio engine
* Copyright (C) 2026 Ludvig Gunne Lindström
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "melisma.h"
#include "graph.h"
#include "util.h"
#include "event.h"
#include "port.h"
#include "mem.h"
#include "io.h"
jack_client_t *client;
lua_State *state;
static const char *program = NULL;
static const char *path = NULL;
static const char *client_name = "melisma";
static char **script_argv = NULL;
static void usage(FILE *f)
{
fprintf(f,"Usage: %s [OPTION...] [FILE] [ARG...]\n",program);
fprintf(f,"Options:\n");
fprintf(f,"\t-h Show this help message.\n");
fprintf(f,"\t-c NAME Set Jack client name to NAME.\n");
fprintf(f,"\t-C PATH Change working directory to PATH.\n");
}
static void change_cwd(const char *path)
{
if (chdir(path) < 0)
{
fprintf(stderr,"%s: %s: %s\n",
program,path,strerror(errno));
exit(EXIT_FAILURE);
}
}
static void parse_args(int argc, char **argv)
{
program = argv[0];
char c;
while ((c = getopt(argc, argv, "hc:C:")) != -1)
{
switch (c)
{
case 'h':
usage(stdout);
exit(EXIT_SUCCESS);
case 'c':
client_name = optarg;
break;
case 'C':
change_cwd(optarg);
break;
default:
usage(stderr);
exit(EXIT_FAILURE);
}
}
if (optind < argc && strcmp(argv[optind],"-") != 0)
{
path = argv[optind];
}
script_argv = &argv[optind + 1];
}
static int l_sample_rate(lua_State *s)
{
lua_pushinteger(s,ml_sample_rate());
return 1;
}
static int load_internal_module(lua_State *s)
{
lua_newtable(s);
int tbl = lua_absindex(s,lua_gettop(s));
lua_pushcfunction(s,l_graph_layout);
lua_setfield(s,tbl,"layout");
lua_pushcfunction(s,l_sample_rate);
lua_setfield(s,tbl,"samplerate");
lua_newtable(s);
int argv_tbl = lua_gettop(s);
int i = 1;
for (; *script_argv; script_argv++)
{
lua_pushstring(s,*script_argv);
lua_rawseti(s,argv_tbl,i++);
}
lua_settop(s,argv_tbl);
lua_setfield(s,tbl,"argv");
lua_settop(s,tbl);
return 1;
}
static void register_internal_module(lua_State *s)
{
int top = lua_gettop(s);
lua_getglobal(s,"package");
int package = lua_gettop(s);
lua_getfield(s,package,"preload");
int preload = lua_gettop(s);
lua_pushcfunction(s,load_internal_module);
lua_setfield(s,preload,"melisma.internal");
lua_settop(s,top);
}
static char *read_stdin(void)
{
size_t len = 0;
size_t cap = 0;
char *buf = NULL;
for (;;)
{
char c = fgetc(stdin);
if (len == cap)
{
cap = cap ? 2 * cap : 1024;
buf = srealloc(buf, cap);
}
buf[len++] = c == EOF ? 0 : c;
if (c == EOF)
{
break;
}
}
return buf;
}
int main(int argc, char **argv)
{
int exitc = EXIT_SUCCESS;
parse_args(argc,argv);
client = jack_client_open(client_name,0,NULL);
if (client == NULL)
{
fprintf(stderr,"Failed to open Jack client '%s'\n",client_name);
exit(EXIT_FAILURE);
}
state = luaL_newstate();
luaL_openlibs(state);
register_internal_module(state);
init_graph(state);
init_events(state);
init_ports(state);
char *text = NULL;
if (path == NULL)
{
text = read_stdin();
if (luaL_loadstring(state,text) != LUA_OK)
{
const char *err = lua_tostring(state,-1);
fprintf(stderr,"%s\n",err);
exitc = EXIT_FAILURE;
goto cleanup;
}
}
else
{
if (luaL_loadfile(state,path) != LUA_OK)
{
const char *err = lua_tostring(state,-1);
fprintf(stderr,"%s\n",err);
exitc = EXIT_FAILURE;
goto cleanup;
}
}
if (lua_pcall_with_backtrace(state,0,LUA_MULTRET) != LUA_OK)
{
const char *err = lua_tostring(state,-1);
fprintf(stderr,"%s\n",err);
exitc = EXIT_FAILURE;
goto cleanup;
}
const char *err = build_graph();
if (err != NULL)
{
fprintf(stderr,"Error: %s\n",err);
exitc = EXIT_FAILURE;
goto cleanup;
}
jack_activate(client);
ml_add_io_handler(STDIN_FILENO,POLLIN,NULL,NULL);
io_loop();
cleanup:
free(text);
lua_close(state);
jack_client_close(client);
exit(exitc);
}
unsigned long ml_sample_rate(void)
{
return jack_get_sample_rate(client);
}