Merge pull request #1 from atsampson/master
[calfbox.git] / app.c
blob6da545ac373f9a420b25c69ffc6dd46e58bfa4f1
1 /*
2 Calf Box, an open source musical instrument.
3 Copyright (C) 2010-2013 Krzysztof Foltman
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "app.h"
20 #include "blob.h"
21 #include "config-api.h"
22 #include "engine.h"
23 #include "instr.h"
24 #include "io.h"
25 #include "layer.h"
26 #include "menu.h"
27 #include "menuitem.h"
28 #include "meter.h"
29 #include "midi.h"
30 #include "module.h"
31 #include "scene.h"
32 #include "seq.h"
33 #include "song.h"
34 #include "track.h"
35 #include "ui.h"
36 #include "wavebank.h"
38 #include <assert.h>
39 #include <glib.h>
40 #include <glob.h>
41 #include <getopt.h>
42 #include <math.h>
43 #include <ncurses.h>
44 #include <stdio.h>
45 #include <stdint.h>
46 #include <string.h>
47 #include <unistd.h>
49 static gboolean lookup_midi_merger(const char *output, struct cbox_midi_merger **pmerger, GError **error)
51 if (*output)
53 struct cbox_uuid uuid;
54 if (!cbox_uuid_fromstring(&uuid, output, error))
55 return FALSE;
57 *pmerger = cbox_rt_get_midi_output(app.rt, &uuid);
58 if (!*pmerger)
60 g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Unknown MIDI output UUID: '%s'", output);
61 return FALSE;
64 else
66 if (!app.engine->scene_count)
68 g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Scene not set");
69 return FALSE;
71 *pmerger = &app.engine->scenes[0]->scene_input_merger;
73 return TRUE;
76 static gboolean app_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
78 if (!cmd->command)
80 g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "NULL command");
81 return FALSE;
83 if (cmd->command[0] != '/')
85 g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Invalid global command path '%s'", cmd->command);
86 return FALSE;
88 const char *obj = &cmd->command[1];
89 const char *pos = strchr(obj, '/');
90 if (pos)
92 if (!strncmp(obj, "master/", 7))
93 return cbox_execute_sub(&app.engine->master->cmd_target, fb, cmd, pos, error);
94 else
95 if (!strncmp(obj, "config/", 7))
96 return cbox_execute_sub(&app.config_cmd_target, fb, cmd, pos, error);
97 else
98 if (!strncmp(obj, "scene/", 6))
99 return cbox_execute_sub(&app.engine->scenes[0]->cmd_target, fb, cmd, pos, error);
100 else
101 if (!strncmp(obj, "engine/", 7))
102 return cbox_execute_sub(&app.engine->cmd_target, fb, cmd, pos, error);
103 else
104 if (!strncmp(obj, "rt/", 3))
105 return cbox_execute_sub(&app.rt->cmd_target, fb, cmd, pos, error);
106 else
107 if (!strncmp(obj, "io/", 3))
108 return cbox_execute_sub(&app.io.cmd_target, fb, cmd, pos, error);
109 else
110 if (!strncmp(obj, "song/", 5) && app.engine->master->song)
111 return cbox_execute_sub(&app.engine->master->song->cmd_target, fb, cmd, pos, error);
112 else
113 if (!strncmp(obj, "waves/", 6))
114 return cbox_execute_sub(&cbox_waves_cmd_target, fb, cmd, pos, error);
115 else
116 if (!strncmp(obj, "doc/", 4))
117 return cbox_execute_sub(cbox_document_get_cmd_target(app.document), fb, cmd, pos, error);
118 else
120 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);
121 return FALSE;
124 else
125 if (!strcmp(obj, "on_idle") && !strcmp(cmd->arg_types, ""))
127 return cbox_app_on_idle(fb, error);
129 else
130 if (!strcmp(obj, "send_event_to") && (!strcmp(cmd->arg_types, "siii") || !strcmp(cmd->arg_types, "sii") || !strcmp(cmd->arg_types, "si")))
132 const char *output = CBOX_ARG_S(cmd, 0);
133 struct cbox_midi_merger *merger = NULL;
134 if (!lookup_midi_merger(output, &merger, error))
135 return FALSE;
136 int mcmd = CBOX_ARG_I(cmd, 1);
137 int arg1 = 0, arg2 = 0;
138 if (cmd->arg_types[2] == 'i')
140 arg1 = CBOX_ARG_I(cmd, 2);
141 if (cmd->arg_types[3] == 'i')
142 arg2 = CBOX_ARG_I(cmd, 3);
144 struct cbox_midi_buffer buf;
145 cbox_midi_buffer_init(&buf);
146 cbox_midi_buffer_write_inline(&buf, 0, mcmd, arg1, arg2);
147 cbox_engine_send_events_to(app.engine, merger, &buf);
148 return TRUE;
150 else
151 if (!strcmp(obj, "send_sysex_to") && !strcmp(cmd->arg_types, "sb"))
153 const char *output = CBOX_ARG_S(cmd, 0);
154 struct cbox_midi_merger *merger = NULL;
155 if (!lookup_midi_merger(output, &merger, error))
156 return FALSE;
157 const struct cbox_blob *blob = CBOX_ARG_B(cmd, 1);
158 struct cbox_midi_buffer buf;
159 cbox_midi_buffer_init(&buf);
160 cbox_midi_buffer_write_event(&buf, 0, blob->data, blob->size);
161 cbox_engine_send_events_to(app.engine, merger, &buf);
162 return TRUE;
164 else
165 if (!strcmp(obj, "update_playback") && !strcmp(cmd->arg_types, ""))
167 cbox_engine_update_song_playback(app.engine);
168 return TRUE;
170 else
171 if (!strcmp(obj, "get_pattern") && !strcmp(cmd->arg_types, ""))
173 if (!cbox_check_fb_channel(fb, cmd->command, error))
174 return FALSE;
176 if (app.engine->master->song && app.engine->master->song->tracks)
178 struct cbox_track *track = app.engine->master->song->tracks->data;
179 if (track)
181 struct cbox_track_item *item = track->items->data;
182 struct cbox_midi_pattern *pattern = item->pattern;
183 int length = 0;
184 struct cbox_blob *blob = cbox_midi_pattern_to_blob(pattern, &length);
185 gboolean res = cbox_execute_on(fb, NULL, "/pattern", "bi", error, blob, length);
186 cbox_blob_destroy(blob);
187 if (!res)
188 return FALSE;
191 return TRUE;
193 else
194 if (!strcmp(obj, "new_meter") && !strcmp(cmd->arg_types, ""))
196 if (!cbox_check_fb_channel(fb, cmd->command, error))
197 return FALSE;
199 struct cbox_meter *meter = cbox_meter_new(app.document, app.rt->io_env.srate);
201 return cbox_execute_on(fb, NULL, "/uuid", "o", error, meter);
203 else
204 if (!strcmp(obj, "new_engine") && !strcmp(cmd->arg_types, "ii"))
206 if (!cbox_check_fb_channel(fb, cmd->command, error))
207 return FALSE;
209 struct cbox_engine *e = cbox_engine_new(app.document, NULL);
210 e->io_env.srate = CBOX_ARG_I(cmd, 0);
211 e->io_env.buffer_size = CBOX_ARG_I(cmd, 1);
213 return e ? cbox_execute_on(fb, NULL, "/uuid", "o", error, e) : FALSE;
215 else
216 if (!strcmp(obj, "print_s") && !strcmp(cmd->arg_types, "s"))
218 g_message("Print: %s", CBOX_ARG_S(cmd, 0));
219 return TRUE;
221 else
222 if (!strcmp(obj, "print_i") && !strcmp(cmd->arg_types, "i"))
224 g_message("Print: %d", CBOX_ARG_I(cmd, 0));
225 return TRUE;
227 else
228 if (!strcmp(obj, "print_f") && !strcmp(cmd->arg_types, "f"))
230 g_message("Print: %f", CBOX_ARG_F(cmd, 0));
231 return TRUE;
233 else
235 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);
236 return FALSE;
240 struct config_foreach_data
242 const char *prefix;
243 const char *command;
244 struct cbox_command_target *fb;
245 GError **error;
246 gboolean success;
249 void api_config_cb(void *user_data, const char *key)
251 struct config_foreach_data *cfd = user_data;
252 if (!cfd->success)
253 return;
254 if (cfd->prefix && strncmp(cfd->prefix, key, strlen(cfd->prefix)))
255 return;
257 if (!cbox_execute_on(cfd->fb, NULL, cfd->command, "s", cfd->error, key))
259 cfd->success = FALSE;
260 return;
264 static gboolean config_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
266 if (!strcmp(cmd->command, "/sections") && (!strcmp(cmd->arg_types, "") || !strcmp(cmd->arg_types, "s")))
268 if (!cbox_check_fb_channel(fb, cmd->command, error))
269 return FALSE;
271 struct config_foreach_data cfd = {cmd->arg_types[0] == 's' ? CBOX_ARG_S(cmd, 0) : NULL, "/section", fb, error, TRUE};
272 cbox_config_foreach_section(api_config_cb, &cfd);
273 return cfd.success;
275 else if (!strcmp(cmd->command, "/keys") && (!strcmp(cmd->arg_types, "s") || !strcmp(cmd->arg_types, "ss")))
277 if (!cbox_check_fb_channel(fb, cmd->command, error))
278 return FALSE;
280 struct config_foreach_data cfd = {cmd->arg_types[1] == 's' ? CBOX_ARG_S(cmd, 1) : NULL, "/key", fb, error, TRUE};
281 cbox_config_foreach_key(api_config_cb, CBOX_ARG_S(cmd, 0), &cfd);
282 return cfd.success;
284 else if (!strcmp(cmd->command, "/get") && !strcmp(cmd->arg_types, "ss"))
286 if (!cbox_check_fb_channel(fb, cmd->command, error))
287 return FALSE;
289 const char *value = cbox_config_get_string(CBOX_ARG_S(cmd, 0), CBOX_ARG_S(cmd, 1));
290 if (!value)
291 return TRUE;
292 return cbox_execute_on(fb, NULL, "/value", "s", error, value);
294 else if (!strcmp(cmd->command, "/set") && !strcmp(cmd->arg_types, "sss"))
296 cbox_config_set_string(CBOX_ARG_S(cmd, 0), CBOX_ARG_S(cmd, 1), CBOX_ARG_S(cmd, 2));
297 return TRUE;
299 else if (!strcmp(cmd->command, "/delete") && !strcmp(cmd->arg_types, "ss"))
301 cbox_config_remove_key(CBOX_ARG_S(cmd, 0), CBOX_ARG_S(cmd, 1));
302 return TRUE;
304 else if (!strcmp(cmd->command, "/delete_section") && !strcmp(cmd->arg_types, "s"))
306 cbox_config_remove_section(CBOX_ARG_S(cmd, 0));
307 return TRUE;
309 else if (!strcmp(cmd->command, "/save") && !strcmp(cmd->arg_types, ""))
311 return cbox_config_save(NULL, error);
313 else if (!strcmp(cmd->command, "/save") && !strcmp(cmd->arg_types, "s"))
315 return cbox_config_save(CBOX_ARG_S(cmd, 0), error);
317 else
319 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);
320 return FALSE;
324 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
326 gboolean cbox_app_on_idle(struct cbox_command_target *fb, GError **error)
328 if (app.rt->io)
330 GError *error2 = NULL;
331 if (cbox_io_get_disconnect_status(&app.io, &error2))
332 cbox_io_poll_ports(&app.io, fb);
333 else
335 if (error2)
336 g_error_free(error2);
337 int auto_reconnect = cbox_config_get_int("io", "auto_reconnect", 0);
338 if (auto_reconnect > 0)
340 sleep(auto_reconnect);
341 GError *error2 = NULL;
342 if (!cbox_io_cycle(&app.io, fb, &error2))
344 gboolean suppress = FALSE;
345 if (fb)
346 suppress = cbox_execute_on(fb, NULL, "/io/cycle_failed", "s", NULL, error2->message);
347 if (!suppress)
348 g_warning("Cannot cycle the I/O: %s", (error2 && error2->message) ? error2->message : "Unknown error");
349 g_error_free(error2);
351 else
353 if (fb)
354 cbox_execute_on(fb, NULL, "/io/cycled", "", NULL);
359 if (app.rt)
361 // Process results of asynchronous commands
362 cbox_rt_handle_cmd_queue(app.rt);
364 if (!cbox_midi_appsink_send_to(&app.engine->appsink, fb, error))
365 return FALSE;
367 return TRUE;
370 struct cbox_app app =
372 .rt = NULL,
373 .current_scene_name = NULL,
374 .cmd_target =
376 .process_cmd = app_process_cmd,
377 .user_data = &app
379 .config_cmd_target =
381 .process_cmd = config_process_cmd,
382 .user_data = &app