summaryrefslogtreecommitdiff
path: root/src/modules/rocket/rocket.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/rocket/rocket.c')
-rw-r--r--src/modules/rocket/rocket.c351
1 files changed, 321 insertions, 30 deletions
diff --git a/src/modules/rocket/rocket.c b/src/modules/rocket/rocket.c
index 1ad2c51..6add6be 100644
--- a/src/modules/rocket/rocket.c
+++ b/src/modules/rocket/rocket.c
@@ -2,10 +2,16 @@
#include <string.h>
#include <time.h>
+#include "rocket/rocket/lib/device.h"
+#include "rocket/rocket/lib/sync.h"
+#include "rocket/rocket/lib/track.h"
+
#include "til.h"
#include "til_fb.h"
#include "til_module_context.h"
#include "til_settings.h"
+#include "til_stream.h"
+#include "til_tap.h"
#include "til_util.h"
#include "txt/txt.h"
@@ -14,52 +20,75 @@
/* This implements a rudimentary sequencing module varying
* "tapped" variables of other modules on a timeline via
- * GNU Rocket.
+ * GNU Rocket (https://github.com/rocket/rocket)
*/
typedef struct rocket_context_t {
til_module_context_t til_module_context;
- const til_module_t *module;
- til_module_context_t *module_ctxt;
- char *module_settings;
+ const til_module_t *seq_module;
+ til_module_context_t *seq_module_ctxt;
+
+ struct sync_device *sync_device;
+ double rows_per_ms;
+ double rocket_row;
+ unsigned last_ticks;
+ unsigned paused:1;
} rocket_context_t;
typedef struct rocket_setup_t {
til_setup_t til_setup;
- const char *module;
+ const char *seq_module_name;
+ const char *base;
+ double rows_per_ms;
+ unsigned connect:1;
+ const char *host;
+ unsigned short port;
} rocket_setup_t;
-static rocket_setup_t rocket_default_setup = { .module = "rtv" };
+static rocket_setup_t rocket_default_setup = { .seq_module_name = "compose" };
-static til_module_context_t * rocket_create_context(til_stream_t *stream, unsigned seed, unsigned ticks, unsigned n_cpus, char *path, til_setup_t *setup)
+static til_module_context_t * rocket_create_context(const til_module_t *module, til_stream_t *stream, unsigned seed, unsigned ticks, unsigned n_cpus, char *path, til_setup_t *setup)
{
rocket_context_t *ctxt;
- const til_module_t *module;
+ const til_module_t *seq_module;
if (!setup)
setup = &rocket_default_setup.til_setup;
- module = til_lookup_module(((rocket_setup_t *)setup)->module);
- if (!module)
+ seq_module = til_lookup_module(((rocket_setup_t *)setup)->seq_module_name);
+ if (!seq_module)
return NULL;
- ctxt = til_module_context_new(stream, sizeof(rocket_context_t), seed, ticks, n_cpus, path);
+ ctxt = til_module_context_new(module, sizeof(rocket_context_t), stream, seed, ticks, n_cpus, path);
if (!ctxt)
return NULL;
- ctxt->module = module;
+ ctxt->sync_device = sync_create_device(((rocket_setup_t *)setup)->base);
+ if (!ctxt->sync_device)
+ return til_module_context_free(&ctxt->til_module_context);
+
+ if (((rocket_setup_t *)setup)->connect) {
+ /* XXX: it'd be better if we just reconnected periodically instead of hard failing */
+ if (sync_tcp_connect(ctxt->sync_device, ((rocket_setup_t *)setup)->host, ((rocket_setup_t *)setup)->port))
+ return til_module_context_free(&ctxt->til_module_context);
+ }
+
+ ctxt->seq_module = seq_module;
{
til_setup_t *module_setup = NULL;
- (void) til_module_randomize_setup(ctxt->module, rand_r(&seed), &module_setup, NULL);
+ (void) til_module_randomize_setup(ctxt->seq_module, rand_r(&seed), &module_setup, NULL);
- (void) til_module_create_context(ctxt->module, stream, rand_r(&seed), ticks, 0, path, module_setup, &ctxt->module_ctxt);
+ (void) til_module_create_context(ctxt->seq_module, stream, rand_r(&seed), ticks, 0, path, module_setup, &ctxt->seq_module_ctxt);
til_setup_free(module_setup);
}
+ ctxt->rows_per_ms = ((rocket_setup_t *)setup)->rows_per_ms;
+ ctxt->last_ticks = ticks;
+
return &ctxt->til_module_context;
}
@@ -68,57 +97,319 @@ static void rocket_destroy_context(til_module_context_t *context)
{
rocket_context_t *ctxt = (rocket_context_t *)context;
- til_module_context_free(ctxt->module_ctxt);
+ if (ctxt->sync_device)
+ sync_destroy_device(ctxt->sync_device);
+ til_module_context_free(ctxt->seq_module_ctxt);
free(context);
}
+static void rocket_sync_pause(void *context, int flag)
+{
+ rocket_context_t *ctxt = context;
+
+ if (flag)
+ ctxt->paused = 1;
+ else
+ ctxt->paused = 0;
+}
+
+
+static void rocket_sync_set_row(void *context, int row)
+{
+ rocket_context_t *ctxt = context;
+
+ ctxt->rocket_row = row;
+}
+
+
+static int rocket_sync_is_playing(void *context)
+{
+ rocket_context_t *ctxt = context;
+
+ /* returns bool, 1 for is playing */
+ return !ctxt->paused;
+}
+
+
+static struct sync_cb rocket_sync_cb = {
+ rocket_sync_pause,
+ rocket_sync_set_row,
+ rocket_sync_is_playing,
+};
+
+
+typedef struct rocket_pipe_t {
+ /* rocket basically only applies to floats, so we only need a float, its tap, and a sync track */
+ til_tap_t tap;
+
+ union {
+ float f;
+ double d;
+ } var;
+
+ union {
+ float *f;
+ double *d;
+ } ptr;
+
+ const struct sync_track *track;
+ char track_name[];
+} rocket_pipe_t;
+
+
+int rocket_stream_pipe_ctor(void *context, til_stream_t *stream, const void *owner, const void *owner_foo, const char *parent_path, uint32_t parent_hash, const til_tap_t *tap, const void **res_owner, const void **res_owner_foo, const til_tap_t **res_driving_tap)
+{
+ rocket_context_t *ctxt = context;
+ rocket_pipe_t *rocket_pipe;
+ size_t track_name_len;
+
+ assert(stream);
+ assert(tap);
+ assert(res_owner);
+ assert(res_owner_foo);
+ assert(res_driving_tap);
+
+ if (tap->type != TIL_TAP_TYPE_FLOAT &&
+ tap->type != TIL_TAP_TYPE_DOUBLE)
+ return 0; /* not interesting to us */
+
+ /* we take ownership, and create our own tap and rocket track to stow @ owner_foo */
+
+ /* rocket has its own syntax for track names so instead of consttructing a concatenated path
+ * in til_stream_pipe_t and passing it to the ctor, just construct our own in the end of rocket_pipe_t
+ */
+ track_name_len = strlen(parent_path) + 1 + strlen(tap->name) + 1;
+ rocket_pipe = calloc(1, sizeof(rocket_pipe_t) + track_name_len);
+ if (!rocket_pipe)
+ return -ENOMEM;
+
+ snprintf(rocket_pipe->track_name, track_name_len, "%s:%s", parent_path, tap->name);
+ rocket_pipe->tap = til_tap_init(ctxt, tap->type, &rocket_pipe->ptr, 1, &rocket_pipe->var, tap->name);
+ rocket_pipe->track = sync_get_track(ctxt->sync_device, rocket_pipe->track_name);
+
+ *res_owner = ctxt;
+ *res_owner_foo = rocket_pipe;
+ *res_driving_tap = rocket_pipe->track->num_keys ? &rocket_pipe->tap : tap;
+
+ return 1;
+}
+
+
+static const til_stream_hooks_t rocket_stream_hooks = {
+ .pipe_ctor = rocket_stream_pipe_ctor,
+ /* .pipe_dtor unneeded */
+};
+
+
+static int rocket_pipe_update(void *context, til_stream_pipe_t *pipe, const void *owner, const void *owner_foo, const til_tap_t *driving_tap)
+{
+ rocket_pipe_t *rocket_pipe = (rocket_pipe_t *)owner_foo;
+ rocket_context_t *ctxt = context;
+ double val;
+
+ /* just ignore pipes we don't own (they're not types we can drive w/rocket) */
+ if (owner != ctxt)
+ return 0;
+
+ /* when there's no keys in the track, flag as inactive so someone else can drive */
+ if (!rocket_pipe->track->num_keys) {
+ rocket_pipe->tap.inactive = 1;
+
+ return 0;
+ }
+
+ rocket_pipe->tap.inactive = 0;
+ if (driving_tap != &rocket_pipe->tap)
+ til_stream_pipe_set_driving_tap(pipe, &rocket_pipe->tap);
+
+ /* otherwise get the current interpolated value from the rocket track @ owner_foo->track
+ * to update owner_foo->var.[fd], which _should_ be the driving tap.
+ */
+ val = sync_get_val(rocket_pipe->track, ctxt->rocket_row);
+ switch (rocket_pipe->tap.type) {
+ case TIL_TAP_TYPE_FLOAT:
+ rocket_pipe->var.f = val;
+ break;
+ case TIL_TAP_TYPE_DOUBLE:
+ rocket_pipe->var.d = val;
+ break;
+ default:
+ assert(0);
+ }
+
+ return 0;
+}
+
+
static void rocket_render_fragment(til_module_context_t *context, til_stream_t *stream, unsigned ticks, unsigned cpu, til_fb_fragment_t **fragment_ptr)
{
rocket_context_t *ctxt = (rocket_context_t *)context;
- til_module_render(ctxt->module_ctxt, stream, ticks, fragment_ptr);
+ if (!ctxt->paused)
+ ctxt->rocket_row += ((double)(ticks - ctxt->last_ticks)) * ctxt->rows_per_ms;
+
+ ctxt->last_ticks = ticks;
+
+ /* hooks-setting is idempotent and cheap so we just always do it, and technicallly the stream can get changed out on us frame-to-frame */
+ til_stream_set_hooks(stream, &rocket_stream_hooks, ctxt);
+
+ /* ctxt->rocket_row needs to be updated */
+ sync_update(ctxt->sync_device, ctxt->rocket_row, &rocket_sync_cb, ctxt);
+
+ /* this drives our per-rocket-track updates, with the tracks registered as owner_foo on the pipes, respectively */
+ til_stream_for_each_pipe(stream, rocket_pipe_update, ctxt);
+
+ til_module_render(ctxt->seq_module_ctxt, stream, ticks, fragment_ptr);
}
static int rocket_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup)
{
- const char *module;
+ const char *connect_values[] = {
+ "off",
+ "on",
+ NULL
+ };
+ const char *seq_module;
+ const char *base;
+ const char *bpm;
+ const char *rpb;
+ const char *connect;
+ const char *host;
+ const char *port;
int r;
+ /* TODO:
+ * Instead of driving a single module, we could accept a list of module specifiers
+ * including settings for each (requiring the recursive settings support to land).
+ * Then just use a module selector track for switching between the modules... that
+ * might work for getting full-blown demos sequenced via rocket.
+ */
r = til_settings_get_and_describe_value(settings,
&(til_setting_desc_t){
.name = "Module to sequence",
- .key = "module",
- .preferred = "rtv",
+ .key = "seq_module",
+ .preferred = "compose",
+ .annotations = NULL,
+ },
+ &seq_module,
+ res_setting,
+ res_desc);
+ if (r)
+ return r;
+
+ r = til_settings_get_and_describe_value(settings,
+ &(til_setting_desc_t){
+ .name = "Rocket \"base\" label",
+ .key = "base",
+ .preferred = "tiller",
+ .annotations = NULL,
+ },
+ &base,
+ res_setting,
+ res_desc);
+ if (r)
+ return r;
+
+ r = til_settings_get_and_describe_value(settings,
+ &(til_setting_desc_t){
+ .name = "Beats per minute",
+ .key = "bpm",
+ .preferred = "125",
+ .annotations = NULL,
+ },
+ &bpm,
+ res_setting,
+ res_desc);
+ if (r)
+ return r;
+
+ r = til_settings_get_and_describe_value(settings,
+ &(til_setting_desc_t){
+ .name = "Rows per beat",
+ .key = "rpb",
+ .preferred = "8",
.annotations = NULL,
},
- &module,
+ &rpb,
res_setting,
res_desc);
if (r)
return r;
- /* turn layers colon-separated list into a null-terminated array of strings */
+ r = til_settings_get_and_describe_value(settings,
+ &(til_setting_desc_t){
+ .name = "Editor connection toggle",
+ .key = "connect",
+ /* TODO: regex */
+ .preferred = connect_values[1],
+ .values = connect_values,
+ .annotations = NULL,
+ },
+ &connect,
+ res_setting,
+ res_desc);
+ if (r)
+ return r;
+
+ if (!strcasecmp(connect, "on")) {
+ r = til_settings_get_and_describe_value(settings,
+ &(til_setting_desc_t){
+ .name = "Editor host",
+ .key = "host",
+ .preferred = "localhost",
+ /* TODO: regex */
+ .annotations = NULL,
+ },
+ &host,
+ res_setting,
+ res_desc);
+ if (r)
+ return r;
+
+ r = til_settings_get_and_describe_value(settings,
+ &(til_setting_desc_t){
+ .name = "Editor port",
+ .key = "port",
+ .preferred = TIL_SETTINGS_STR(SYNC_DEFAULT_PORT),
+ /* TODO: regex */
+ .annotations = NULL,
+ },
+ &port,
+ res_setting,
+ res_desc);
+ if (r)
+ return r;
+ }
+
if (res_setup) {
- const til_module_t *til_module;
+ const til_module_t *til_seq_module;
rocket_setup_t *setup;
+ unsigned ibpm, irpb;
- if (!strcmp(module, "rocket"))
+ if (!strcmp(seq_module, "rocket"))
return -EINVAL;
- til_module = til_lookup_module(module);
- if (!til_module)
+ til_seq_module = til_lookup_module(seq_module);
+ if (!til_seq_module)
return -ENOENT;
- if (til_module->flags & (TIL_MODULE_HERMETIC | TIL_MODULE_EXPERIMENTAL))
- return -EINVAL;
-
+ /* TODO: we're going to need a custom setup_free to cleanup host+base etc. */
setup = til_setup_new(sizeof(*setup), (void(*)(til_setup_t *))free);
if (!setup)
return -ENOMEM;
- setup->module = til_module->name;
+ setup->seq_module_name = til_seq_module->name;
+ setup->base = strdup(base); /* FIXME errors */
+ if (!strcasecmp(connect, "on")) {
+ setup->connect = 1;
+ setup->host = strdup(host); /* FIXME errors */
+ sscanf(port, "%hu", &setup->port); /* FIXME parse errors */
+ }
+ sscanf(bpm, "%u", &ibpm);
+ sscanf(rpb, "%u", &irpb);
+ setup->rows_per_ms = ((double)(ibpm * irpb)) * (1.0 / (60.0 * 1000.0));
*res_setup = &setup->til_setup;
}
@@ -134,5 +425,5 @@ til_module_t rocket_module = {
.name = "rocket",
.description = "GNU Rocket module sequencer",
.setup = rocket_setup,
- .flags = TIL_MODULE_HERMETIC | TIL_MODULE_EXPERIMENTAL,
+ .flags = TIL_MODULE_HERMETIC | TIL_MODULE_EXPERIMENTAL, /* this needs refinement esp. if rocket gets split into a player and editor */
};
© All Rights Reserved