Forward input (but not sequencer or app-generated) MIDI messages to Python via on_idle.
[calfbox.git] / app.c
blobdd274d5f9abe89b7d6e7cc4213ea13f357ccf9c9
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 "instr.h"
23 #include "io.h"
24 #include "layer.h"
25 #include "menu.h"
26 #include "menuitem.h"
27 #include "meter.h"
28 #include "midi.h"
29 #include "module.h"
30 #include "scene.h"
31 #include "seq.h"
32 #include "song.h"
33 #include "track.h"
34 #include "ui.h"
35 #include "wavebank.h"
37 #include <assert.h>
38 #include <glib.h>
39 #include <glob.h>
40 #include <getopt.h>
41 #include <math.h>
42 #include <ncurses.h>
43 #include <stdio.h>
44 #include <stdint.h>
45 #include <string.h>
46 #include <unistd.h>
48 static gboolean app_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
50 if (!cmd->command)
52 g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "NULL command");
53 return FALSE;
55 if (cmd->command[0] != '/')
57 g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Invalid global command path '%s'", cmd->command);
58 return FALSE;
60 const char *obj = &cmd->command[1];
61 const char *pos = strchr(obj, '/');
62 if (pos)
64 if (!strncmp(obj, "master/", 7))
65 return cbox_execute_sub(&app.rt->master->cmd_target, fb, cmd, pos, error);
66 else
67 if (!strncmp(obj, "config/", 7))
68 return cbox_execute_sub(&app.config_cmd_target, fb, cmd, pos, error);
69 else
70 if (!strncmp(obj, "scene/", 6))
71 return cbox_execute_sub(&app.rt->scene->cmd_target, fb, cmd, pos, error);
72 else
73 if (!strncmp(obj, "rt/", 3))
74 return cbox_execute_sub(&app.rt->cmd_target, fb, cmd, pos, error);
75 else
76 if (!strncmp(obj, "io/", 3))
77 return cbox_execute_sub(&app.io.cmd_target, fb, cmd, pos, error);
78 else
79 if (!strncmp(obj, "song/", 5))
80 return cbox_execute_sub(&app.rt->master->song->cmd_target, fb, cmd, pos, error);
81 else
82 if (!strncmp(obj, "waves/", 6))
83 return cbox_execute_sub(&cbox_waves_cmd_target, fb, cmd, pos, error);
84 else
85 if (!strncmp(obj, "doc/", 4))
86 return cbox_execute_sub(cbox_document_get_cmd_target(app.document), fb, cmd, pos, error);
87 else
89 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);
90 return FALSE;
93 else
94 if (!strcmp(obj, "on_idle") && !strcmp(cmd->arg_types, ""))
96 return cbox_app_on_idle(fb, error);
98 else
99 if (!strcmp(obj, "send_event") && (!strcmp(cmd->arg_types, "iii") || !strcmp(cmd->arg_types, "ii") || !strcmp(cmd->arg_types, "i")))
101 int mcmd = CBOX_ARG_I(cmd, 0);
102 int arg1 = 0, arg2 = 0;
103 if (cmd->arg_types[1] == 'i')
105 arg1 = CBOX_ARG_I(cmd, 1);
106 if (cmd->arg_types[2] == 'i')
107 arg2 = CBOX_ARG_I(cmd, 2);
109 struct cbox_midi_buffer buf;
110 cbox_midi_buffer_init(&buf);
111 cbox_midi_buffer_write_inline(&buf, 0, mcmd, arg1, arg2);
112 cbox_rt_send_events(app.rt, &buf);
113 return TRUE;
115 else
116 if (!strcmp(obj, "play_note") && !strcmp(cmd->arg_types, "iii"))
118 int channel = CBOX_ARG_I(cmd, 0);
119 int note = CBOX_ARG_I(cmd, 1);
120 int velocity = CBOX_ARG_I(cmd, 2);
121 struct cbox_midi_buffer buf;
122 cbox_midi_buffer_init(&buf);
123 cbox_midi_buffer_write_inline(&buf, 0, 0x90 + ((channel - 1) & 15), note & 127, velocity & 127);
124 cbox_midi_buffer_write_inline(&buf, 1, 0x80 + ((channel - 1) & 15), note & 127, velocity & 127);
125 cbox_rt_send_events(app.rt, &buf);
126 return TRUE;
128 else
129 if (!strcmp(obj, "update_playback") && !strcmp(cmd->arg_types, ""))
131 cbox_rt_update_song_playback(app.rt);
132 return TRUE;
134 else
135 if (!strcmp(obj, "get_pattern") && !strcmp(cmd->arg_types, ""))
137 if (!cbox_check_fb_channel(fb, cmd->command, error))
138 return FALSE;
140 if (app.rt->master->song && app.rt->master->song->tracks)
142 struct cbox_track *track = app.rt->master->song->tracks->data;
143 if (track)
145 struct cbox_track_item *item = track->items->data;
146 struct cbox_midi_pattern *pattern = item->pattern;
147 int length = 0;
148 struct cbox_blob *blob = cbox_midi_pattern_to_blob(pattern, &length);
149 gboolean res = cbox_execute_on(fb, NULL, "/pattern", "bi", error, blob, length);
150 cbox_blob_destroy(blob);
151 if (!res)
152 return FALSE;
155 return TRUE;
157 else
158 if (!strcmp(obj, "new_meter") && !strcmp(cmd->arg_types, ""))
160 if (!cbox_check_fb_channel(fb, cmd->command, error))
161 return FALSE;
163 struct cbox_meter *meter = cbox_meter_new(app.document, cbox_rt_get_sample_rate(app.rt));
165 return cbox_execute_on(fb, NULL, "/uuid", "o", error, meter);
167 else
168 if (!strcmp(obj, "new_recorder") && !strcmp(cmd->arg_types, "s"))
170 if (!cbox_check_fb_channel(fb, cmd->command, error))
171 return FALSE;
173 struct cbox_recorder *rec = cbox_recorder_new_stream(app.rt, CBOX_ARG_S(cmd, 0));
175 return cbox_execute_on(fb, NULL, "/uuid", "o", error, rec);
177 else
178 if (!strcmp(obj, "new_scene") && !strcmp(cmd->arg_types, "ii"))
180 if (!cbox_check_fb_channel(fb, cmd->command, error))
181 return FALSE;
183 struct cbox_rt *rt = cbox_rt_new(app.document);
184 cbox_rt_set_offline(rt, CBOX_ARG_I(cmd, 0), CBOX_ARG_I(cmd, 1));
185 struct cbox_scene *rec = cbox_scene_new(app.document, rt, TRUE);
187 return cbox_execute_on(fb, NULL, "/uuid", "o", error, rec);
189 else
190 if (!strcmp(obj, "print_s") && !strcmp(cmd->arg_types, "s"))
192 g_message("Print: %s", CBOX_ARG_S(cmd, 0));
193 return TRUE;
195 else
196 if (!strcmp(obj, "print_i") && !strcmp(cmd->arg_types, "i"))
198 g_message("Print: %d", CBOX_ARG_I(cmd, 0));
199 return TRUE;
201 else
202 if (!strcmp(obj, "print_f") && !strcmp(cmd->arg_types, "f"))
204 g_message("Print: %f", CBOX_ARG_F(cmd, 0));
205 return TRUE;
207 else
209 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);
210 return FALSE;
214 struct config_foreach_data
216 const char *prefix;
217 const char *command;
218 struct cbox_command_target *fb;
219 GError **error;
220 gboolean success;
223 void api_config_cb(void *user_data, const char *key)
225 struct config_foreach_data *cfd = user_data;
226 if (!cfd->success)
227 return;
228 if (cfd->prefix && strncmp(cfd->prefix, key, strlen(cfd->prefix)))
229 return;
231 if (!cbox_execute_on(cfd->fb, NULL, cfd->command, "s", cfd->error, key))
233 cfd->success = FALSE;
234 return;
238 static gboolean config_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
240 if (!strcmp(cmd->command, "/sections") && (!strcmp(cmd->arg_types, "") || !strcmp(cmd->arg_types, "s")))
242 if (!cbox_check_fb_channel(fb, cmd->command, error))
243 return FALSE;
245 struct config_foreach_data cfd = {cmd->arg_types[0] == 's' ? CBOX_ARG_S(cmd, 0) : NULL, "/section", fb, error, TRUE};
246 cbox_config_foreach_section(api_config_cb, &cfd);
247 return cfd.success;
249 else if (!strcmp(cmd->command, "/keys") && (!strcmp(cmd->arg_types, "s") || !strcmp(cmd->arg_types, "ss")))
251 if (!cbox_check_fb_channel(fb, cmd->command, error))
252 return FALSE;
254 struct config_foreach_data cfd = {cmd->arg_types[1] == 's' ? CBOX_ARG_S(cmd, 1) : NULL, "/key", fb, error, TRUE};
255 cbox_config_foreach_key(api_config_cb, CBOX_ARG_S(cmd, 0), &cfd);
256 return cfd.success;
258 else if (!strcmp(cmd->command, "/get") && !strcmp(cmd->arg_types, "ss"))
260 if (!cbox_check_fb_channel(fb, cmd->command, error))
261 return FALSE;
263 const char *value = cbox_config_get_string(CBOX_ARG_S(cmd, 0), CBOX_ARG_S(cmd, 1));
264 if (!value)
265 return TRUE;
266 return cbox_execute_on(fb, NULL, "/value", "s", error, value);
268 else if (!strcmp(cmd->command, "/set") && !strcmp(cmd->arg_types, "sss"))
270 cbox_config_set_string(CBOX_ARG_S(cmd, 0), CBOX_ARG_S(cmd, 1), CBOX_ARG_S(cmd, 2));
271 return TRUE;
273 else if (!strcmp(cmd->command, "/delete") && !strcmp(cmd->arg_types, "ss"))
275 cbox_config_remove_key(CBOX_ARG_S(cmd, 0), CBOX_ARG_S(cmd, 1));
276 return TRUE;
278 else if (!strcmp(cmd->command, "/delete_section") && !strcmp(cmd->arg_types, "s"))
280 cbox_config_remove_section(CBOX_ARG_S(cmd, 0));
281 return TRUE;
283 else if (!strcmp(cmd->command, "/save") && !strcmp(cmd->arg_types, ""))
285 return cbox_config_save(NULL, error);
287 else if (!strcmp(cmd->command, "/save") && !strcmp(cmd->arg_types, "s"))
289 return cbox_config_save(CBOX_ARG_S(cmd, 0), error);
291 else
293 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);
294 return FALSE;
298 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
300 gboolean cbox_app_on_idle(struct cbox_command_target *fb, GError **error)
302 if (app.rt->io)
304 GError *error2 = NULL;
305 if (cbox_io_get_disconnect_status(&app.io, &error2))
306 cbox_io_poll_ports(&app.io, fb);
307 else
309 if (error2)
310 g_error_free(error2);
311 int auto_reconnect = cbox_config_get_int("io", "auto_reconnect", 0);
312 if (auto_reconnect > 0)
314 sleep(auto_reconnect);
315 GError *error2 = NULL;
316 if (!cbox_io_cycle(&app.io, fb, &error2))
318 gboolean suppress = FALSE;
319 if (fb)
320 suppress = cbox_execute_on(fb, NULL, "/io/cycle_failed", "s", NULL, error2->message);
321 if (!suppress)
322 g_warning("Cannot cycle the I/O: %s", (error2 && error2->message) ? error2->message : "Unknown error");
323 g_error_free(error2);
325 else
327 if (fb)
328 cbox_execute_on(fb, NULL, "/io/cycled", "", NULL);
333 if (app.rt)
335 // Process results of asynchronous commands
336 cbox_rt_handle_cmd_queue(app.rt);
338 const struct cbox_midi_buffer *midi_in = cbox_rt_get_input_midi_data(app.rt);
339 // If no feedback, the input events are lost - probably better than if
340 // they filled up the input buffer needlessly.
341 if (fb && midi_in)
343 for (int i = 0; i < midi_in->count; i++)
345 const struct cbox_midi_event *event = cbox_midi_buffer_get_event(midi_in, i);
346 const uint8_t *data = cbox_midi_event_get_data(event);
347 // XXXKF doesn't handle SysEx properly yet, only 3-byte values
348 if (event->size <= 3)
350 if (!cbox_execute_on(fb, NULL, "/io/midi/simple_event", "iii" + (3 - event->size), error, data[0], data[1], data[2]))
351 return FALSE;
353 else
354 g_warning("Propagating 'long' events not implemented yet.");
358 return TRUE;
361 struct cbox_app app =
363 .rt = NULL,
364 .current_scene_name = NULL,
365 .cmd_target =
367 .process_cmd = app_process_cmd,
368 .user_data = &app
370 .config_cmd_target =
372 .process_cmd = config_process_cmd,
373 .user_data = &app