diff options
Diffstat (limited to 'src/modules/rocket')
| -rw-r--r-- | src/modules/rocket/rocket.c | 351 | 
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 */  }; | 
