You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
391 lines
13 KiB
391 lines
13 KiB
/*
|
|
Calf Box, an open source musical instrument.
|
|
Copyright (C) 2010-2013 Krzysztof Foltman
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "app.h"
|
|
#include "blob.h"
|
|
#include "config-api.h"
|
|
#include "engine.h"
|
|
#include "instr.h"
|
|
#include "io.h"
|
|
#include "layer.h"
|
|
#include "menu.h"
|
|
#include "menuitem.h"
|
|
#include "meter.h"
|
|
#include "midi.h"
|
|
#include "module.h"
|
|
#include "scene.h"
|
|
#include "seq.h"
|
|
#include "song.h"
|
|
#include "track.h"
|
|
#include "ui.h"
|
|
#include "wavebank.h"
|
|
|
|
#include <assert.h>
|
|
#include <glib.h>
|
|
#include <glob.h>
|
|
#include <getopt.h>
|
|
#include <math.h>
|
|
#include <ncurses.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
static gboolean lookup_midi_merger(const char *output, struct cbox_midi_merger **pmerger, GError **error)
|
|
{
|
|
if (*output)
|
|
{
|
|
struct cbox_uuid uuid;
|
|
if (!cbox_uuid_fromstring(&uuid, output, error))
|
|
return FALSE;
|
|
|
|
*pmerger = cbox_rt_get_midi_output(app.rt, &uuid);
|
|
if (!*pmerger)
|
|
{
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Unknown MIDI output UUID: '%s'", output);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!app.engine->scene_count)
|
|
{
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Scene not set");
|
|
return FALSE;
|
|
}
|
|
*pmerger = &app.engine->scenes[0]->scene_input_merger;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean app_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
|
|
{
|
|
if (!cmd->command)
|
|
{
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "NULL command");
|
|
return FALSE;
|
|
}
|
|
if (cmd->command[0] != '/')
|
|
{
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Invalid global command path '%s'", cmd->command);
|
|
return FALSE;
|
|
}
|
|
const char *obj = &cmd->command[1];
|
|
const char *pos = strchr(obj, '/');
|
|
if (pos)
|
|
{
|
|
if (!strncmp(obj, "master/", 7))
|
|
return cbox_execute_sub(&app.engine->master->cmd_target, fb, cmd, pos, error);
|
|
else
|
|
if (!strncmp(obj, "config/", 7))
|
|
return cbox_execute_sub(&app.config_cmd_target, fb, cmd, pos, error);
|
|
else
|
|
if (!strncmp(obj, "scene/", 6))
|
|
return cbox_execute_sub(&app.engine->scenes[0]->cmd_target, fb, cmd, pos, error);
|
|
else
|
|
if (!strncmp(obj, "engine/", 7))
|
|
return cbox_execute_sub(&app.engine->cmd_target, fb, cmd, pos, error);
|
|
else
|
|
if (!strncmp(obj, "rt/", 3))
|
|
return cbox_execute_sub(&app.rt->cmd_target, fb, cmd, pos, error);
|
|
else
|
|
if (!strncmp(obj, "io/", 3))
|
|
return cbox_execute_sub(&app.io.cmd_target, fb, cmd, pos, error);
|
|
else
|
|
if (!strncmp(obj, "song/", 5) && app.engine->master->song)
|
|
return cbox_execute_sub(&app.engine->master->song->cmd_target, fb, cmd, pos, error);
|
|
else
|
|
if (!strncmp(obj, "waves/", 6))
|
|
return cbox_execute_sub(&cbox_waves_cmd_target, fb, cmd, pos, error);
|
|
else
|
|
if (!strncmp(obj, "doc/", 4))
|
|
return cbox_execute_sub(cbox_document_get_cmd_target(app.document), fb, cmd, pos, error);
|
|
else
|
|
{
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Unknown combination of target path and argument: '%s', '%s'", cmd->command, cmd->arg_types);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
if (!strcmp(obj, "on_idle") && !strcmp(cmd->arg_types, ""))
|
|
{
|
|
return cbox_app_on_idle(fb, error);
|
|
}
|
|
else
|
|
if (!strcmp(obj, "send_event_to") && (!strcmp(cmd->arg_types, "siii") || !strcmp(cmd->arg_types, "sii") || !strcmp(cmd->arg_types, "si")))
|
|
{
|
|
const char *output = CBOX_ARG_S(cmd, 0);
|
|
struct cbox_midi_merger *merger = NULL;
|
|
if (!lookup_midi_merger(output, &merger, error))
|
|
return FALSE;
|
|
int mcmd = CBOX_ARG_I(cmd, 1);
|
|
int arg1 = 0, arg2 = 0;
|
|
if (cmd->arg_types[2] == 'i')
|
|
{
|
|
arg1 = CBOX_ARG_I(cmd, 2);
|
|
if (cmd->arg_types[3] == 'i')
|
|
arg2 = CBOX_ARG_I(cmd, 3);
|
|
}
|
|
struct cbox_midi_buffer buf;
|
|
cbox_midi_buffer_init(&buf);
|
|
cbox_midi_buffer_write_inline(&buf, 0, mcmd, arg1, arg2);
|
|
cbox_engine_send_events_to(app.engine, merger, &buf);
|
|
return TRUE;
|
|
}
|
|
else
|
|
if (!strcmp(obj, "send_sysex_to") && !strcmp(cmd->arg_types, "sb"))
|
|
{
|
|
const char *output = CBOX_ARG_S(cmd, 0);
|
|
struct cbox_midi_merger *merger = NULL;
|
|
if (!lookup_midi_merger(output, &merger, error))
|
|
return FALSE;
|
|
const struct cbox_blob *blob = CBOX_ARG_B(cmd, 1);
|
|
struct cbox_midi_buffer buf;
|
|
cbox_midi_buffer_init(&buf);
|
|
cbox_midi_buffer_write_event(&buf, 0, blob->data, blob->size);
|
|
cbox_engine_send_events_to(app.engine, merger, &buf);
|
|
return TRUE;
|
|
}
|
|
else
|
|
if (!strcmp(obj, "update_playback") && !strcmp(cmd->arg_types, ""))
|
|
{
|
|
cbox_engine_update_song_playback(app.engine);
|
|
return TRUE;
|
|
}
|
|
else
|
|
if (!strcmp(obj, "get_pattern") && !strcmp(cmd->arg_types, ""))
|
|
{
|
|
if (!cbox_check_fb_channel(fb, cmd->command, error))
|
|
return FALSE;
|
|
|
|
if (app.engine->master->song && app.engine->master->song->tracks)
|
|
{
|
|
struct cbox_track *track = app.engine->master->song->tracks->data;
|
|
if (track)
|
|
{
|
|
struct cbox_track_item *item = track->items->data;
|
|
struct cbox_midi_pattern *pattern = item->pattern;
|
|
int length = 0;
|
|
struct cbox_blob *blob = cbox_midi_pattern_to_blob(pattern, &length);
|
|
gboolean res = cbox_execute_on(fb, NULL, "/pattern", "bi", error, blob, length);
|
|
cbox_blob_destroy(blob);
|
|
if (!res)
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
else
|
|
if (!strcmp(obj, "new_meter") && !strcmp(cmd->arg_types, ""))
|
|
{
|
|
if (!cbox_check_fb_channel(fb, cmd->command, error))
|
|
return FALSE;
|
|
|
|
struct cbox_meter *meter = cbox_meter_new(app.document, app.rt->io_env.srate);
|
|
|
|
return cbox_execute_on(fb, NULL, "/uuid", "o", error, meter);
|
|
}
|
|
else
|
|
if (!strcmp(obj, "new_engine") && !strcmp(cmd->arg_types, "ii"))
|
|
{
|
|
if (!cbox_check_fb_channel(fb, cmd->command, error))
|
|
return FALSE;
|
|
|
|
struct cbox_engine *e = cbox_engine_new(app.document, NULL);
|
|
e->io_env.srate = CBOX_ARG_I(cmd, 0);
|
|
e->io_env.buffer_size = CBOX_ARG_I(cmd, 1);
|
|
|
|
return e ? cbox_execute_on(fb, NULL, "/uuid", "o", error, e) : FALSE;
|
|
}
|
|
else
|
|
if (!strcmp(obj, "print_s") && !strcmp(cmd->arg_types, "s"))
|
|
{
|
|
g_message("Print: %s", CBOX_ARG_S(cmd, 0));
|
|
return TRUE;
|
|
}
|
|
else
|
|
if (!strcmp(obj, "print_i") && !strcmp(cmd->arg_types, "i"))
|
|
{
|
|
g_message("Print: %d", CBOX_ARG_I(cmd, 0));
|
|
return TRUE;
|
|
}
|
|
else
|
|
if (!strcmp(obj, "print_f") && !strcmp(cmd->arg_types, "f"))
|
|
{
|
|
g_message("Print: %f", CBOX_ARG_F(cmd, 0));
|
|
return TRUE;
|
|
}
|
|
else
|
|
if (!strcmp(obj, "print_b") && !strcmp(cmd->arg_types, "b"))
|
|
{
|
|
g_message("Print: %s", (char *)CBOX_ARG_B(cmd, 0)->data);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Unknown combination of target path and argument: '%s', '%s'", cmd->command, cmd->arg_types);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
struct config_foreach_data
|
|
{
|
|
const char *prefix;
|
|
const char *command;
|
|
struct cbox_command_target *fb;
|
|
GError **error;
|
|
gboolean success;
|
|
};
|
|
|
|
void api_config_cb(void *user_data, const char *key)
|
|
{
|
|
struct config_foreach_data *cfd = user_data;
|
|
if (!cfd->success)
|
|
return;
|
|
if (cfd->prefix && strncmp(cfd->prefix, key, strlen(cfd->prefix)))
|
|
return;
|
|
|
|
if (!cbox_execute_on(cfd->fb, NULL, cfd->command, "s", cfd->error, key))
|
|
{
|
|
cfd->success = FALSE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
static gboolean config_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
|
|
{
|
|
if (!strcmp(cmd->command, "/sections") && (!strcmp(cmd->arg_types, "") || !strcmp(cmd->arg_types, "s")))
|
|
{
|
|
if (!cbox_check_fb_channel(fb, cmd->command, error))
|
|
return FALSE;
|
|
|
|
struct config_foreach_data cfd = {cmd->arg_types[0] == 's' ? CBOX_ARG_S(cmd, 0) : NULL, "/section", fb, error, TRUE};
|
|
cbox_config_foreach_section(api_config_cb, &cfd);
|
|
return cfd.success;
|
|
}
|
|
else if (!strcmp(cmd->command, "/keys") && (!strcmp(cmd->arg_types, "s") || !strcmp(cmd->arg_types, "ss")))
|
|
{
|
|
if (!cbox_check_fb_channel(fb, cmd->command, error))
|
|
return FALSE;
|
|
|
|
struct config_foreach_data cfd = {cmd->arg_types[1] == 's' ? CBOX_ARG_S(cmd, 1) : NULL, "/key", fb, error, TRUE};
|
|
cbox_config_foreach_key(api_config_cb, CBOX_ARG_S(cmd, 0), &cfd);
|
|
return cfd.success;
|
|
}
|
|
else if (!strcmp(cmd->command, "/get") && !strcmp(cmd->arg_types, "ss"))
|
|
{
|
|
if (!cbox_check_fb_channel(fb, cmd->command, error))
|
|
return FALSE;
|
|
|
|
const char *value = cbox_config_get_string(CBOX_ARG_S(cmd, 0), CBOX_ARG_S(cmd, 1));
|
|
if (!value)
|
|
return TRUE;
|
|
return cbox_execute_on(fb, NULL, "/value", "s", error, value);
|
|
}
|
|
else if (!strcmp(cmd->command, "/set") && !strcmp(cmd->arg_types, "sss"))
|
|
{
|
|
cbox_config_set_string(CBOX_ARG_S(cmd, 0), CBOX_ARG_S(cmd, 1), CBOX_ARG_S(cmd, 2));
|
|
return TRUE;
|
|
}
|
|
else if (!strcmp(cmd->command, "/delete") && !strcmp(cmd->arg_types, "ss"))
|
|
{
|
|
cbox_config_remove_key(CBOX_ARG_S(cmd, 0), CBOX_ARG_S(cmd, 1));
|
|
return TRUE;
|
|
}
|
|
else if (!strcmp(cmd->command, "/delete_section") && !strcmp(cmd->arg_types, "s"))
|
|
{
|
|
cbox_config_remove_section(CBOX_ARG_S(cmd, 0));
|
|
return TRUE;
|
|
}
|
|
else if (!strcmp(cmd->command, "/save") && !strcmp(cmd->arg_types, ""))
|
|
{
|
|
return cbox_config_save(NULL, error);
|
|
}
|
|
else if (!strcmp(cmd->command, "/save") && !strcmp(cmd->arg_types, "s"))
|
|
{
|
|
return cbox_config_save(CBOX_ARG_S(cmd, 0), error);
|
|
}
|
|
else
|
|
{
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Unknown combination of target path and argument: '%s', '%s'", cmd->command, cmd->arg_types);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
gboolean cbox_app_on_idle(struct cbox_command_target *fb, GError **error)
|
|
{
|
|
if (app.rt->io)
|
|
{
|
|
GError *error2 = NULL;
|
|
if (cbox_io_get_disconnect_status(&app.io, &error2))
|
|
cbox_io_poll_ports(&app.io, fb);
|
|
else
|
|
{
|
|
if (error2)
|
|
g_error_free(error2);
|
|
int auto_reconnect = cbox_config_get_int("io", "auto_reconnect", 0);
|
|
if (auto_reconnect > 0)
|
|
{
|
|
sleep(auto_reconnect);
|
|
GError *error2 = NULL;
|
|
if (!cbox_io_cycle(&app.io, fb, &error2))
|
|
{
|
|
gboolean suppress = FALSE;
|
|
if (fb)
|
|
suppress = cbox_execute_on(fb, NULL, "/io/cycle_failed", "s", NULL, error2->message);
|
|
if (!suppress)
|
|
g_warning("Cannot cycle the I/O: %s", (error2 && error2->message) ? error2->message : "Unknown error");
|
|
g_error_free(error2);
|
|
}
|
|
else
|
|
{
|
|
if (fb)
|
|
cbox_execute_on(fb, NULL, "/io/cycled", "", NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (app.rt)
|
|
{
|
|
// Process results of asynchronous commands
|
|
cbox_rt_handle_cmd_queue(app.rt);
|
|
|
|
if (!cbox_midi_appsink_send_to(&app.engine->appsink, fb, error))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
struct cbox_app app =
|
|
{
|
|
.rt = NULL,
|
|
.current_scene_name = NULL,
|
|
.cmd_target =
|
|
{
|
|
.process_cmd = app_process_cmd,
|
|
.user_data = &app
|
|
},
|
|
.config_cmd_target =
|
|
{
|
|
.process_cmd = config_process_cmd,
|
|
.user_data = &app
|
|
},
|
|
};
|
|
|
|
|