Sampled Instrument Player with static and monolithic design. All instruments are built-in.
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

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