From a390e8201cbc0e5b71684f85f56b05d43da399e9 Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Thu, 1 Jun 2023 13:46:06 -0700 Subject: modules/rkt: deprecate seq_module= in favor of scenes= This drops the seq_module= setting in favor of a scenes= setting in the same style of compose::layers; a nested settings instance composed of more nested settings instances, one per scene. A nice side-effect of this change is it no longer uses til_module_setup_randomize() at all, which was being used to mix up the seq_module's settings in a pre-nested-settings world. A new Rocket sync track is introduced named "$context_path:scene" for selecting which scene to render. For now all scenes get created @ context create time, and persist for the entire rkt context lifetime. In the future the context lifetimes might become explicitly controllable with separate Rocket tracks used as booleans. This becomes relevant once modules can make use of existing contexts located within the stream at their respective context paths. Something necessary for integrated transitions between scenes using stuff like fade modules which haven't been added yet. With this change you can already enumerate a set of scenes in the rkt settings string, each 100% explicitly configured, and have Rocket track data select which scene to render on the timeline, and manipulate the taps at their scene-specific context-path-derived track names. In addition to the need for modules picking up existing contexts on the stream, rkt probably needs a way to interactively add/remove/modify scenes then spit out the serialized settings string for the current state of the world. As these aren't functionalities provided by GNU Rocket, and it's unclear how receptive upstream GNU Rocket/glrocket maintainers would be to such additions, rkt will likely first add another listener for a strictly scenes-editing client to connect alongside the GNU Rocket stuff. Just something that shows the current scenes table, and provides a way to edit/add/remove rows there, with the changes realized in rkt real-time. Then the Rocket Editor will just continue using the rkt:scene track to numerically index into this scenes table, without the Rocket Editor having any visibility or awareness of what's going on in that table. Probably ok as an initial stab at making demos with this stack. --- src/modules/rkt/rkt.c | 252 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 197 insertions(+), 55 deletions(-) (limited to 'src/modules/rkt') diff --git a/src/modules/rkt/rkt.c b/src/modules/rkt/rkt.c index 1022119..42f51e7 100644 --- a/src/modules/rkt/rkt.c +++ b/src/modules/rkt/rkt.c @@ -24,27 +24,37 @@ * GNU Rocket (https://github.com/rocket/rocket) */ +typedef struct rkt_scene_t { + const til_module_t *module; + til_module_context_t *module_ctxt; +} rkt_scene_t; + typedef struct rkt_context_t { til_module_context_t til_module_context; - const til_module_t *seq_module; - til_module_context_t *seq_module_ctxt; - struct sync_device *sync_device; + const struct sync_track *scene_track; double rows_per_ms; double rocket_row; unsigned last_ticks; unsigned paused:1; + rkt_scene_t scenes[]; } rkt_context_t; +typedef struct rkt_setup_scene_t { + char *module_name; + til_setup_t *setup; +} rkt_setup_scene_t; + typedef struct rkt_setup_t { til_setup_t til_setup; - const char *seq_module_name; const char *base; double rows_per_ms; unsigned connect:1; const char *host; unsigned short port; + size_t n_scenes; + rkt_setup_scene_t scenes[]; } rkt_setup_t; @@ -71,15 +81,10 @@ static const struct sync_track * sync_get_trackf(struct sync_device *device, con static til_module_context_t * rkt_create_context(const til_module_t *module, til_stream_t *stream, unsigned seed, unsigned ticks, unsigned n_cpus, til_setup_t *setup) { - rkt_setup_t *s = (rkt_setup_t *)setup; - const til_module_t *seq_module; - rkt_context_t *ctxt; + rkt_setup_t *s = (rkt_setup_t *)setup; + rkt_context_t *ctxt; - seq_module = til_lookup_module(s->seq_module_name); - if (!seq_module) - return NULL; - - ctxt = til_module_context_new(module, sizeof(rkt_context_t), stream, seed, ticks, n_cpus, setup); + ctxt = til_module_context_new(module, sizeof(rkt_context_t) + s->n_scenes * sizeof(*ctxt->scenes), stream, seed, ticks, n_cpus, setup); if (!ctxt) return NULL; @@ -93,14 +98,18 @@ static til_module_context_t * rkt_create_context(const til_module_t *module, til return til_module_context_free(&ctxt->til_module_context); } - ctxt->seq_module = seq_module; + ctxt->scene_track = sync_get_trackf(ctxt->sync_device, "%s:scene", setup->path); + if (!ctxt->scene_track) + return til_module_context_free(&ctxt->til_module_context); - { + for (size_t i = 0; i < s->n_scenes; i++) { til_setup_t *module_setup = NULL; - (void) til_module_setup_randomize(ctxt->seq_module, rand_r(&seed), &module_setup, NULL); + ctxt->scenes[i].module = til_lookup_module(s->scenes[i].module_name); + if (!ctxt->scenes[i].module) /* this isn't really expected since setup already does this */ + return til_module_context_free(&ctxt->til_module_context); - (void) til_module_create_context(ctxt->seq_module, stream, rand_r(&seed), ticks, 0, module_setup, &ctxt->seq_module_ctxt); + (void) til_module_create_context(ctxt->scenes[i].module, stream, rand_r(&seed), ticks, 0, s->scenes[i].setup, &ctxt->scenes[i].module_ctxt); til_setup_free(module_setup); } @@ -117,7 +126,10 @@ static void rkt_destroy_context(til_module_context_t *context) if (ctxt->sync_device) sync_destroy_device(ctxt->sync_device); - til_module_context_free(ctxt->seq_module_ctxt); + + for (size_t i = 0; i < ((rkt_setup_t *)context->setup)->n_scenes; i++) + til_module_context_free(ctxt->scenes[i].module_ctxt); + free(context); } @@ -215,9 +227,9 @@ static const til_stream_hooks_t rkt_stream_hooks = { static int rkt_pipe_update(void *context, til_stream_pipe_t *pipe, const void *owner, const void *owner_foo, const til_tap_t *driving_tap) { - rkt_pipe_t *rkt_pipe = (rkt_pipe_t *)owner_foo; + rkt_pipe_t *rkt_pipe = (rkt_pipe_t *)owner_foo; rkt_context_t *ctxt = context; - double val; + double val; /* just ignore pipes we don't own (they're not types we can drive w/rocket) */ if (owner != ctxt) @@ -271,45 +283,137 @@ static void rkt_render_fragment(til_module_context_t *context, til_stream_t *str /* this drives our per-rocket-track updates, with the tracks registered as owner_foo on the pipes, respectively */ til_stream_for_each_pipe(stream, rkt_pipe_update, ctxt); - til_module_render(ctxt->seq_module_ctxt, stream, ticks, fragment_ptr); + { + unsigned scene; + + scene = (unsigned)sync_get_val(ctxt->scene_track, ctxt->rocket_row); + if (scene < ((rkt_setup_t *)context->setup)->n_scenes) + til_module_render(ctxt->scenes[scene].module_ctxt, stream, ticks, fragment_ptr); + else { + txt_t *msg = txt_newf("%s: NO SCENE @ %u", context->setup->path, scene); + + /* TODO: creating/destroying this every frame is dumb, but + * as this is a diagnostic it's not so important. + * + * Once this module deals with disconnects and transparently reconnects, it'll need + * to show some connection status information as well... when that gets added this will + * likely get reworked to become part of that status text. + */ + til_fb_fragment_clear(*fragment_ptr); + txt_render_fragment(msg, *fragment_ptr, 0xffffffff, + 0, 0, + (txt_align_t){ + .horiz = TXT_HALIGN_LEFT, + .vert = TXT_VALIGN_TOP, + }); + txt_free(msg); + } + } +} + + +static void rkt_setup_free(til_setup_t *setup) +{ + rkt_setup_t *s = (rkt_setup_t *)setup; + + if (s) { + for (size_t i = 0; i < s->n_scenes; i++) { + free(s->scenes[i].module_name); + til_setup_free(s->scenes[i].setup); + } + free((void *)s->base); + free((void *)s->host); + free(setup); + } } static int rkt_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 *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. + const til_settings_t *scenes_settings; + const char *connect_values[] = { + "off", + "on", + NULL + }; + const char *scenes; + const char *base; + const char *bpm; + const char *rpb; + const char *connect; + const char *host; + const char *port; + int r; + + /* This is largely taken from compose::layers, but might just go away when I add tables to rocket, + * or maybe they can coexist. */ r = til_settings_get_and_describe_value(settings, &(til_setting_spec_t){ - .name = "Module to sequence", - .key = "seq_module", - .preferred = "compose", + .name = "Comma-separated list of modules for scenes to sequence", + .key = "scenes", + .preferred = "compose,compose,compose,compose", .annotations = NULL, + .as_nested_settings = 1, }, - &seq_module, + &scenes, res_setting, res_desc); if (r) return r; + assert(res_setting && *res_setting && (*res_setting)->value_as_nested_settings); + scenes_settings = (*res_setting)->value_as_nested_settings; + { + til_setting_t *scene_setting; + const char *scene_module; + + for (size_t i = 0; til_settings_get_value_by_idx(scenes_settings, i, &scene_setting); i++) { + if (!scene_setting->value_as_nested_settings) { + r = til_setting_desc_new( scenes_settings, + &(til_setting_spec_t){ + .as_nested_settings = 1, + }, res_desc); + if (r < 0) + return r; + + *res_setting = scene_setting; + + return 1; + } + } + + for (size_t i = 0; til_settings_get_value_by_idx(scenes_settings, i, &scene_setting); i++) { + til_setting_t *scene_module_setting; + const char *scene_module_name = til_settings_get_value_by_idx(scene_setting->value_as_nested_settings, 0, &scene_module_setting); + const til_module_t *scene_module = til_lookup_module(scene_module_name); + + if (!scene_module || !scene_module_setting) + return -EINVAL; + + if (!scene_module_setting->desc) { + r = til_setting_desc_new( scene_setting->value_as_nested_settings, + &(til_setting_spec_t){ + .name = "Scene module name", + .preferred = "none", + .as_label = 1, + }, res_desc); + if (r < 0) + return r; + + *res_setting = scene_module_setting; + + return 1; + } + + if (scene_module->setup) { + r = scene_module->setup(scene_setting->value_as_nested_settings, res_setting, res_desc, NULL); + if (r) + return r; + } + } + } + r = til_settings_get_and_describe_value(settings, &(til_setting_spec_t){ .name = "Rocket \"base\" label", @@ -395,29 +499,67 @@ static int rkt_setup(const til_settings_t *settings, til_setting_t **res_setting } if (res_setup) { - const til_module_t *til_seq_module; + size_t n_scenes = til_settings_get_count(scenes_settings); + til_setting_t *scene_setting; rkt_setup_t *setup; unsigned ibpm, irpb; - if (!strcmp(seq_module, "rkt")) - return -EINVAL; + setup = til_setup_new(settings, sizeof(*setup) + n_scenes * sizeof(*setup->scenes), rkt_setup_free); + if (!setup) + return -ENOMEM; - til_seq_module = til_lookup_module(seq_module); - if (!til_seq_module) - return -ENOENT; + setup->n_scenes = n_scenes; + + for (size_t i = 0; til_settings_get_value_by_idx(scenes_settings, i, &scene_setting); i++) { + const char *scene_module_name = til_settings_get_value_by_idx(scene_setting->value_as_nested_settings, 0, NULL); + const til_module_t *scene_module = til_lookup_module(scene_module_name); + + if (!scene_module || !strcmp(scene_module_name, "rkt")) { + til_setup_free(&setup->til_setup); + + return -EINVAL; + } + + /* XXX If it's appropriate stow the resolved til_module_t* or the name is still unclear, since + * the module names will soon be able to address existing contexts in the stream at their path. + * So for now I'm just going to continue stowing the name, even though the lookup above prevents + * any sort of context address being used... + */ + setup->scenes[i].module_name = strdup(scene_module_name); + if (!setup->scenes[i].module_name) { + til_setup_free(&setup->til_setup); + + return -ENOMEM; + } + + r = til_module_setup_finalize(scene_module, scene_setting->value_as_nested_settings, &setup->scenes[i].setup); + if (r < 0) { + til_setup_free(&setup->til_setup); + + return r; + } + } + + setup->base = strdup(base); + if (!setup->base) { + til_setup_free(&setup->til_setup); - /* TODO: we're going to need a custom setup_free to cleanup host+base etc. */ - setup = til_setup_new(settings, sizeof(*setup), NULL); - if (!setup) return -ENOMEM; + } - 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 */ + + setup->host = strdup(host); + if (!setup->host) { + til_setup_free(&setup->til_setup); + + return -ENOMEM; + } + 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)); -- cgit v1.2.3