/* 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 . */ #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 #include #include #include #include #include #include #include #include #include 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 }, };