~hp/orbit.lv2

8ab331aff3f7d5499e963dbcb4d74aa7658c1d20 — Hanspeter Portner 3 months ago e05d46f
Work on orbit:midiSync plugin
2 files changed, 245 insertions(+), 52 deletions(-)

M src/orbit_midi_sync.c
M ttl/orbit.ttl
M src/orbit_midi_sync.c => src/orbit_midi_sync.c +220 -42
@@ 11,19 11,29 @@
#include <timely.lv2/timely.h>
#include <props.lv2/props.h>

#define MAX_NPROPS 1
#define MAX_NPROPS 6

typedef struct _plugstate_t plugstate_t;
typedef struct _plughandle_t plughandle_t;

struct _plugstate_t {
	//FIXME
	int32_t mode;
	// ro
	int32_t song;
	int32_t pos;
	int32_t rolling;
	int32_t beats_per_minute;
	// rw
	int32_t beat_unit;
	int32_t beats_per_bar;
};

struct _plughandle_t {
	struct {
		LV2_URID midi_event;
		LV2_URID song;
		LV2_URID pos;
		LV2_URID rolling;
		LV2_URID beats_per_minute;
	} urid;

	LV2_URID_Map *map;


@@ 32,8 42,6 @@ struct _plughandle_t {
	LV2_Log_Log *log;
	LV2_Log_Logger logger;

	timely_t timely;

	const LV2_Atom_Sequence *event_in;
	LV2_Atom_Sequence *event_out;



@@ 41,55 49,214 @@ struct _plughandle_t {
	plugstate_t stash;

	PROPS_T(props, MAX_NPROPS);
	
	bool rolling;

	int64_t clocks;
	int64_t nsamples;
	int64_t last_clock;
	double rate;
	LV2_Atom_Forge_Ref ref;
};

static const props_def_t defs [MAX_NPROPS] = {
	{
		.property = ORBIT_URI"#quantum_mode",
		.offset = offsetof(plugstate_t, mode),
		.type = LV2_ATOM__Int,
		.property = ORBIT_URI"#song",
		.offset = offsetof(plugstate_t, song),
		.type = LV2_ATOM__Int
	},
	{
		.property = ORBIT_URI"#sync_position",
		.offset = offsetof(plugstate_t, pos),
		.type = LV2_ATOM__Int
	},
	{
		.property = ORBIT_URI"#pacemaker_beats_per_minute",
		.offset = offsetof(plugstate_t, beats_per_minute),
		.type = LV2_ATOM__Int
	},
	{
		.property = ORBIT_URI"#pacemaker_rolling",
		.offset = offsetof(plugstate_t, rolling),
		.type = LV2_ATOM__Bool
	},
	{
		.property = ORBIT_URI"#pacemaker_beat_unit",
		.offset = offsetof(plugstate_t, beat_unit),
		.type = LV2_ATOM__Int
	},
	{
		.property = ORBIT_URI"#pacemaker_beats_per_bar",
		.offset = offsetof(plugstate_t, beats_per_bar),
		.type = LV2_ATOM__Int
	}
};

static void
_cb(timely_t *timely, int64_t frames UNUSED, LV2_URID type, void *data)
_set_bpm(plughandle_t *handle, LV2_Atom_Forge *forge,
	int64_t frames, int32_t bpm)
{
	plughandle_t *handle = data;
	if(bpm == handle->state.beats_per_minute)
	{
		return;
	}

	handle->state.beats_per_minute = bpm;

	props_set(&handle->props, forge, frames, handle->urid.beats_per_minute, &handle->ref);
}

	if(type == TIMELY_URI_SPEED(timely))
static void
_set_song(plughandle_t *handle, LV2_Atom_Forge *forge,
	int64_t frames, int32_t song)
{
	if(song == handle->state.song)
	{
		handle->rolling = TIMELY_SPEED(timely) > 0.f ? true : false;
		return;
	}

		if(handle->rolling)
		{
			//FIXME
		}
		else
		{
			//FIXME
		}
	handle->state.song = song;

	props_set(&handle->props, forge, frames, handle->urid.song, &handle->ref);
}

static void
_set_pos_raw(plughandle_t *handle, LV2_Atom_Forge *forge,
	int64_t frames, int32_t pos)
{
	if(pos == handle->state.pos)
	{
		return;
	}
	else if(type == TIMELY_URI_BAR_BEAT(timely))

	handle->state.pos = pos;

	props_set(&handle->props, forge, frames, handle->urid.pos, &handle->ref);
}

static void
_set_pos(plughandle_t *handle, LV2_Atom_Forge *forge,
	int64_t frames, int32_t pos)
{
	_set_pos_raw(handle, forge, frames, pos);

	// update clocks based on song position
	handle->clocks = handle->state.pos * 6;
}

static void
_set_rolling(plughandle_t *handle, LV2_Atom_Forge *forge,
	int64_t frames, int32_t rolling)
{
	if(rolling == handle->state.rolling)
	{
		if(handle->rolling)
		{
			//FIXME
		}
		return;
	}
	else if(type == TIMELY_URI_BAR(timely))

	handle->state.rolling = rolling;

	props_set(&handle->props, forge, frames, handle->urid.rolling, &handle->ref);
}

static void
_handle_midi_song_select(plughandle_t *handle, LV2_Atom_Forge *forge,
	int64_t frames, const uint8_t *m)
{
	const int32_t song = m[1];

	_set_song(handle, forge, frames, song);
}

static void
_handle_midi_song_pos(plughandle_t *handle, LV2_Atom_Forge *forge,
	int64_t frames, const uint8_t *m)
{
	const int32_t lsb = m[1];
	const int32_t msb = m[2];
	const int32_t pos = (msb << 7) | lsb;

	_set_pos(handle, forge, frames, pos);
}

static void
_handle_midi_start(plughandle_t *handle, LV2_Atom_Forge *forge,
	int64_t frames)
{
	_set_pos(handle, forge, frames, 0);
	_set_rolling(handle, forge, frames, 1);
}

static void
_handle_midi_continue(plughandle_t *handle, LV2_Atom_Forge *forge,
	int64_t frames)
{
	// keep position
	_set_rolling(handle, forge, frames, 1);
}

static void
_handle_midi_stop(plughandle_t *handle, LV2_Atom_Forge *forge,
	int64_t frames)
{
	_set_rolling(handle, forge, frames, 0);
}

static void
_handle_midi_clock(plughandle_t *handle, LV2_Atom_Forge *forge,
	int64_t frames)
{
	// advance clock
	handle->clocks += 1;

	const int32_t pos = handle->clocks / 6;
	_set_pos_raw(handle, forge, frames, pos);

	const int64_t this_clock = handle->nsamples + frames;
	const int64_t nsamples = this_clock - handle->last_clock;
	handle->last_clock = this_clock;

	const double minutes = nsamples / handle->rate / 60.0;
	const int32_t bpm = 1.0 / 24.0 / minutes; // FIXME use beat_unit
	// FIXME filter this value

	_set_bpm(handle, forge, frames, bpm);
}

static void
_handle_midi(plughandle_t *handle, LV2_Atom_Forge *forge,
	int64_t frames, const uint8_t *m)
{
	const uint8_t comm = m[0] & 0xf0;

	switch(comm)
	{
		if(handle->rolling)
		case LV2_MIDI_MSG_SONG_SELECT:
		{
			//FIXME
		}
			_handle_midi_song_select(handle, forge, frames, m);
		} break;
		case LV2_MIDI_MSG_SONG_POS:
		{
			_handle_midi_song_pos(handle, forge, frames, m);
		} break;
		case LV2_MIDI_MSG_START:
		{
			_handle_midi_start(handle, forge, frames);
		} break;
		case LV2_MIDI_MSG_CONTINUE:
		{
			_handle_midi_continue(handle, forge, frames);
		} break;
		case LV2_MIDI_MSG_STOP:
		{
			_handle_midi_stop(handle, forge, frames);
		} break;
		case LV2_MIDI_MSG_CLOCK:
		{
			_handle_midi_clock(handle, forge, frames);
		} break;
	}
}

static LV2_Handle
instantiate(const LV2_Descriptor* descriptor, double rate,
instantiate(const LV2_Descriptor* descriptor, double rate UNUSED,
	const char *bundle_path UNUSED, const LV2_Feature *const *features)
{
	plughandle_t *handle = calloc(1, sizeof(plughandle_t));


@@ 99,6 266,8 @@ instantiate(const LV2_Descriptor* descriptor, double rate,
	}
	mlock(handle, sizeof(plughandle_t));

	handle->rate = rate;

	for(unsigned i=0; features[i]; i++)
	{
		if(!strcmp(features[i]->URI, LV2_URID__map))


@@ 126,10 295,6 @@ instantiate(const LV2_Descriptor* descriptor, double rate,

	handle->urid.midi_event = handle->map->map(handle->map->handle, LV2_MIDI__MidiEvent);

	timely_mask_t mask = TIMELY_MASK_BAR_BEAT_WHOLE
		| TIMELY_MASK_BAR_WHOLE
		| TIMELY_MASK_SPEED;
	timely_init(&handle->timely, handle->map, rate, mask, _cb, handle);
	lv2_atom_forge_init(&handle->forge, handle->map);

	if(!props_init(&handle->props, descriptor->URI,


@@ 141,6 306,15 @@ instantiate(const LV2_Descriptor* descriptor, double rate,
		return NULL;
	}

	handle->urid.song = props_map(&handle->props,
		ORBIT_URI"#song");
	handle->urid.pos = props_map(&handle->props,
		ORBIT_URI"#sync_position");
	handle->urid.beats_per_minute = props_map(&handle->props,
		ORBIT_URI"#pacemaker_beats_per_minute");
	handle->urid.rolling = props_map(&handle->props,
		ORBIT_URI"#pacemaker_rolling");

	return handle;
}



@@ 166,7 340,6 @@ static void
run(LV2_Handle instance, uint32_t nsamples)
{
	plughandle_t *handle = instance;
	uint32_t last_t = 0;

	const uint32_t capacity = handle->event_out->atom.size;
	LV2_Atom_Forge_Frame frame;


@@ 178,16 351,19 @@ run(LV2_Handle instance, uint32_t nsamples)
	LV2_ATOM_SEQUENCE_FOREACH(handle->event_in, ev)
	{
		const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
		if(!timely_advance(&handle->timely, obj, last_t, ev->time.frames))

		if(obj->atom.type == handle->urid.midi_event)
		{
			const uint8_t *m = LV2_ATOM_BODY_CONST(&obj->atom);

			_handle_midi(handle, &handle->forge, ev->time.frames, m);
		}
		else
		{
			props_advance(&handle->props, &handle->forge, ev->time.frames, obj, &handle->ref);
		}

		last_t = ev->time.frames;
	}

	timely_advance(&handle->timely, NULL, last_t, nsamples);

	if(handle->ref)
	{
		lv2_atom_forge_pop(&handle->forge, &frame);


@@ 201,6 377,8 @@ run(LV2_Handle instance, uint32_t nsamples)
			lv2_log_trace(&handle->logger, "forge buffer overflow\n");
		}
	}

	handle->nsamples += nsamples;
}

static void

M ttl/orbit.ttl => ttl/orbit.ttl +25 -10
@@ 806,6 806,21 @@ orbit:quantum
		orbit:quantum_mode  2 ;
	] .

orbit:song
	a lv2:Parameter ;
	rdfs:label "Song" ;
	rdfs:comment "select song" ;
	rdfs:range atom:Int ;
	lv2:minimum 0 ;
	lv2:maximum 127 .
orbit:sync_position
	a lv2:Parameter ;
	rdfs:label "Position" ;
	rdfs:comment "Position" ;
	rdfs:range atom:Int ;
	lv2:minimum 0 ;
	lv2:maximum 16383 .

orbit:midiSync
	a lv2:Plugin ,
		lv2:ConverterPlugin ;


@@ 842,20 857,20 @@ orbit:midiSync
	] ;

	patch:writable
		orbit:quantum_mode ; # FIXME
		orbit:pacemaker_beat_unit ,
		orbit:pacemaker_beats_per_bar ;

	patch:readable
		orbit:song ,
		orbit:sync_position ,
		orbit:pacemaker_beats_per_minute ,
		orbit:pacemaker_rolling ;

	state:state [
		orbit:quantum_mode  2 ; # FIXME
		orbit:pacemaker_beat_unit 4 ;
		orbit:pacemaker_beats_per_bar 4 ;
	] .

orbit:song
	a lv2:Parameter ;
	rdfs:label "Song" ;
	rdfs:comment "select song" ;
	rdfs:range atom:Int ;
	lv2:minimum 0 ;
	lv2:maximum 127 .

orbit:midiClock
	a lv2:Plugin ,
		lv2:ConverterPlugin ;