M meson.build => meson.build +6 -2
@@ 49,7 49,9 @@ dsp_srcs = [
join_paths('src', 'orbit_quantum.c'),
join_paths('src', 'orbit_subspace.c'),
join_paths('src', 'orbit_monitor.c'),
- join_paths('src', 'orbit_timecapsule.c')
+ join_paths('src', 'orbit_timecapsule.c'),
+ join_paths('src', 'orbit_midi_sync.c'),
+ join_paths('src', 'orbit_midi_clock.c')
]
c_args = [
@@ 103,7 105,9 @@ if build_tests
'http://open-music-kontrollers.ch/lv2/orbit#quantum',
'http://open-music-kontrollers.ch/lv2/orbit#subspace',
'http://open-music-kontrollers.ch/lv2/orbit#monitor',
- 'http://open-music-kontrollers.ch/lv2/orbit#timecapsule'])
+ 'http://open-music-kontrollers.ch/lv2/orbit#timecapsule',
+ 'http://open-music-kontrollers.ch/lv2/orbit#midiSync',
+ 'http://open-music-kontrollers.ch/lv2/orbit#midiClock'])
endif
if reuse.found() and not meson.is_cross_build()
M meson_options.txt => meson_options.txt +1 -1
@@ 9,4 9,4 @@ option('lv2libdir',
type : 'string',
value : 'lib/lv2')
-option('version', type : 'string', value : '0.1.705')
+option('version', type : 'string', value : '0.1.707')
M orbit.lv2/orbit.h => orbit.lv2/orbit.h +4 -0
@@ 35,6 35,8 @@
#define ORBIT_MONITOR_URI ORBIT_URI"#monitor"
#define ORBIT_TIMECAPSULE_URI ORBIT_URI"#timecapsule"
#define ORBIT_QUANTUM_URI ORBIT_URI"#quantum"
+#define ORBIT_MIDI_SYNC_URI ORBIT_URI"#midiSync"
+#define ORBIT_MIDI_CLOCK_URI ORBIT_URI"#midiClock"
extern const LV2_Descriptor orbit_looper;
extern const LV2_Descriptor orbit_click;
@@ 44,5 46,7 @@ extern const LV2_Descriptor orbit_subspace;
extern const LV2_Descriptor orbit_monitor;
extern const LV2_Descriptor orbit_timecapsule;
extern const LV2_Descriptor orbit_quantum;
+extern const LV2_Descriptor orbit_midi_sync;
+extern const LV2_Descriptor orbit_midi_clock;
#endif // _ORBIT_LV2_H
M src/orbit.c => src/orbit.c +4 -0
@@ 26,6 26,10 @@ lv2_descriptor(uint32_t index)
return &orbit_quantum;
case 7:
return &orbit_monitor;
+ case 8:
+ return &orbit_midi_sync;
+ case 9:
+ return &orbit_midi_clock;
default:
return NULL;
}
A src/orbit_midi_clock.c => src/orbit_midi_clock.c +308 -0
@@ 0,0 1,308 @@
+/*
+ * SPDX-FileCopyrightText: Hanspeter Portner <dev@open-music-kontrollers.ch>
+ * SPDX-License-Identifier: Artistic-2.0
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <orbit.lv2/orbit.h>
+#include <timely.lv2/timely.h>
+#include <props.lv2/props.h>
+
+#define MAX_NPROPS 1
+
+typedef struct _plugstate_t plugstate_t;
+typedef struct _plughandle_t plughandle_t;
+
+struct _plugstate_t {
+ int32_t song;
+};
+
+struct _plughandle_t {
+ struct {
+ LV2_URID midi_event;
+ } urid;
+
+ LV2_URID_Map *map;
+ LV2_Atom_Forge forge;
+
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+ timely_t timely;
+
+ const LV2_Atom_Sequence *event_in;
+ LV2_Atom_Sequence *event_out;
+
+ plugstate_t state;
+ plugstate_t stash;
+
+ PROPS_T(props, MAX_NPROPS);
+
+ bool rolling;
+ LV2_Atom_Forge_Ref ref;
+};
+
+static const props_def_t defs [MAX_NPROPS] = {
+ {
+ .property = ORBIT_URI"#song",
+ .offset = offsetof(plugstate_t, song),
+ .type = LV2_ATOM__Int,
+ }
+};
+
+static void
+_midi(plughandle_t *handle, int64_t frames, const uint8_t *msg, size_t msg_len)
+{
+ LV2_Atom_Forge_Ref ref = handle->ref;
+
+ if(ref)
+ {
+ ref = lv2_atom_forge_frame_time(&handle->forge, frames);
+ }
+ if(ref)
+ {
+ ref = lv2_atom_forge_atom(&handle->forge, msg_len, handle->urid.midi_event);
+ }
+ if(ref)
+ {
+ ref = lv2_atom_forge_raw(&handle->forge, msg, msg_len);
+ }
+ if(ref)
+ {
+ lv2_atom_forge_pad(&handle->forge, msg_len);
+ }
+
+ handle->ref = ref;
+}
+
+static void
+_cb(timely_t *timely, int64_t frames, LV2_URID type, void *data)
+{
+ plughandle_t *handle = data;
+
+ if(type == TIMELY_URI_SPEED(timely))
+ {
+ handle->rolling = TIMELY_SPEED(timely) > 0.f ? true : false;
+
+ if(handle->rolling)
+ {
+ const uint64_t beats = TIMELY_BAR(timely) * TIMELY_BEATS_PER_BAR(timely)
+ + TIMELY_BAR_BEAT(timely);
+ const uint16_t midi_beats = beats / 6; // 1 MIDI Beat spans 6 MIDI Clocks
+
+ const uint8_t song_sel [] = {
+ LV2_MIDI_MSG_SONG_SELECT,
+ handle->state.song
+ };
+
+ _midi(handle, frames, song_sel, sizeof(song_sel));
+
+ const uint8_t song_pos [] = {
+ LV2_MIDI_MSG_SONG_POS,
+ midi_beats & 0x7f,
+ midi_beats >> 7
+ };
+
+ _midi(handle, frames, song_pos, sizeof(song_pos));
+
+ const uint8_t msg [] = {
+ LV2_MIDI_MSG_CONTINUE
+ };
+
+ _midi(handle, frames, msg, sizeof(msg));
+ }
+ else
+ {
+ const uint8_t msg [] = {
+ LV2_MIDI_MSG_STOP
+ };
+
+ _midi(handle, frames, msg, sizeof(msg));
+ }
+ }
+ else if(type == TIMELY_URI_BAR_BEAT(timely))
+ {
+ if(handle->rolling)
+ {
+ const uint8_t msg [] = {
+ LV2_MIDI_MSG_CLOCK
+ };
+
+ _midi(handle, frames, msg, sizeof(msg));
+ }
+ }
+}
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path UNUSED, const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ {
+ return NULL;
+ }
+ mlock(handle, sizeof(plughandle_t));
+
+ for(unsigned i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ {
+ handle->map = features[i]->data;
+ }
+ else if(!strcmp(features[i]->URI, LV2_LOG__log))
+ {
+ handle->log= features[i]->data;
+ }
+ }
+
+ if(!handle->map)
+ {
+ fprintf(stderr,
+ "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ if(handle->log)
+ {
+ lv2_log_logger_init(&handle->logger, handle->map, handle->log);
+ }
+
+ handle->urid.midi_event = handle->map->map(handle->map->handle, LV2_MIDI__MidiEvent);
+
+ timely_mask_t mask = TIMELY_MASK_BAR_BEAT_WHOLE
+ | TIMELY_MASK_SPEED;
+ timely_init(&handle->timely, handle->map, rate, mask, _cb, handle);
+ timely_set_multiplier(&handle->timely, 24.f);
+ lv2_atom_forge_init(&handle->forge, handle->map);
+
+ if(!props_init(&handle->props, descriptor->URI,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ fprintf(stderr, "failed to initialize property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->event_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->event_out = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+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;
+ lv2_atom_forge_set_buffer(&handle->forge, (uint8_t *)handle->event_out, capacity);
+ handle->ref = lv2_atom_forge_sequence_head(&handle->forge, &frame, 0);
+
+ props_idle(&handle->props, &handle->forge, 0, &handle->ref);
+
+ 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))
+ {
+ 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);
+ }
+ else
+ {
+ lv2_atom_sequence_clear(handle->event_out);
+
+ if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "forge buffer overflow\n");
+ }
+ }
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ munlock(handle, sizeof(plughandle_t));
+ free(handle);
+}
+
+static LV2_State_Status
+_state_save(LV2_Handle instance, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = instance;
+
+ return props_save(&handle->props, store, state, flags, features);
+}
+
+static LV2_State_Status
+_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = instance;
+
+ return props_restore(&handle->props, retrieve, state, flags, features);
+}
+
+static const LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+static const void*
+extension_data(const char* uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ {
+ return &state_iface;
+ }
+
+ return NULL;
+}
+
+const LV2_Descriptor orbit_midi_clock = {
+ .URI = ORBIT_MIDI_CLOCK_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
A src/orbit_midi_sync.c => src/orbit_midi_sync.c +260 -0
@@ 0,0 1,260 @@
+/*
+ * SPDX-FileCopyrightText: Hanspeter Portner <dev@open-music-kontrollers.ch>
+ * SPDX-License-Identifier: Artistic-2.0
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <orbit.lv2/orbit.h>
+#include <timely.lv2/timely.h>
+#include <props.lv2/props.h>
+
+#define MAX_NPROPS 1
+
+typedef struct _plugstate_t plugstate_t;
+typedef struct _plughandle_t plughandle_t;
+
+struct _plugstate_t {
+ //FIXME
+ int32_t mode;
+};
+
+struct _plughandle_t {
+ struct {
+ LV2_URID midi_event;
+ } urid;
+
+ LV2_URID_Map *map;
+ LV2_Atom_Forge forge;
+
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+ timely_t timely;
+
+ const LV2_Atom_Sequence *event_in;
+ LV2_Atom_Sequence *event_out;
+
+ plugstate_t state;
+ plugstate_t stash;
+
+ PROPS_T(props, MAX_NPROPS);
+
+ bool rolling;
+ 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,
+ }
+};
+
+static void
+_cb(timely_t *timely, int64_t frames UNUSED, LV2_URID type, void *data)
+{
+ plughandle_t *handle = data;
+
+ if(type == TIMELY_URI_SPEED(timely))
+ {
+ handle->rolling = TIMELY_SPEED(timely) > 0.f ? true : false;
+
+ if(handle->rolling)
+ {
+ //FIXME
+ }
+ else
+ {
+ //FIXME
+ }
+ }
+ else if(type == TIMELY_URI_BAR_BEAT(timely))
+ {
+ if(handle->rolling)
+ {
+ //FIXME
+ }
+ }
+ else if(type == TIMELY_URI_BAR(timely))
+ {
+ if(handle->rolling)
+ {
+ //FIXME
+ }
+ }
+}
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path UNUSED, const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ {
+ return NULL;
+ }
+ mlock(handle, sizeof(plughandle_t));
+
+ for(unsigned i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ {
+ handle->map = features[i]->data;
+ }
+ else if(!strcmp(features[i]->URI, LV2_LOG__log))
+ {
+ handle->log= features[i]->data;
+ }
+ }
+
+ if(!handle->map)
+ {
+ fprintf(stderr,
+ "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ if(handle->log)
+ {
+ lv2_log_logger_init(&handle->logger, handle->map, handle->log);
+ }
+
+ 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,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ fprintf(stderr, "failed to initialize property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->event_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->event_out = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+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;
+ lv2_atom_forge_set_buffer(&handle->forge, (uint8_t *)handle->event_out, capacity);
+ handle->ref = lv2_atom_forge_sequence_head(&handle->forge, &frame, 0);
+
+ props_idle(&handle->props, &handle->forge, 0, &handle->ref);
+
+ 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))
+ {
+ 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);
+ }
+ else
+ {
+ lv2_atom_sequence_clear(handle->event_out);
+
+ if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "forge buffer overflow\n");
+ }
+ }
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ munlock(handle, sizeof(plughandle_t));
+ free(handle);
+}
+
+static LV2_State_Status
+_state_save(LV2_Handle instance, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = instance;
+
+ return props_save(&handle->props, store, state, flags, features);
+}
+
+static LV2_State_Status
+_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = instance;
+
+ return props_restore(&handle->props, retrieve, state, flags, features);
+}
+
+static const LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+static const void*
+extension_data(const char* uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ {
+ return &state_iface;
+ }
+
+ return NULL;
+}
+
+const LV2_Descriptor orbit_midi_sync = {
+ .URI = ORBIT_MIDI_SYNC_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
M ttl/manifest.ttl.in => ttl/manifest.ttl.in +16 -0
@@ 70,3 70,19 @@ orbit:quantum
lv2:microVersion @MICRO_VERSION@ ;
lv2:binary <orbit@MODULE_SUFFIX@> ;
rdfs:seeAlso <orbit.ttl> .
+
+# Orbit MIDI Sync
+orbit:midiSync
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <orbit@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <orbit.ttl> .
+
+# Orbit MIDI Clock
+orbit:midiClock
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <orbit@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <orbit.ttl> .
M ttl/orbit.ttl => ttl/orbit.ttl +91 -0
@@ 805,3 805,94 @@ orbit:quantum
state:state [
orbit:quantum_mode 2 ;
] .
+
+orbit:midiSync
+ a lv2:Plugin ,
+ lv2:ConverterPlugin ;
+ doap:name "Orbit MIDI Sync" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:orbit ;
+ lv2:requiredFeature urid:map, state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ;
+ lv2:extensionData state:interface ;
+
+ lv2:port [
+ # sink event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "event_in" ;
+ lv2:name "Event Input" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # source event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ,
+ time:Position ,
+ patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "event_out" ;
+ lv2:name "Event Output" ;
+ lv2:designation lv2:control ;
+ ] ;
+
+ patch:writable
+ orbit:quantum_mode ; # FIXME
+
+ state:state [
+ orbit:quantum_mode 2 ; # FIXME
+ ] .
+
+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 ;
+ doap:name "Orbit MIDI Clock" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:orbit ;
+ lv2:requiredFeature urid:map, state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ;
+ lv2:extensionData state:interface ;
+
+ lv2:port [
+ # sink event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "event_in" ;
+ lv2:name "Event Input" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # source event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ,
+ patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "event_out" ;
+ lv2:name "Event Output" ;
+ lv2:designation lv2:control ;
+ ] ;
+
+ patch:writable
+ orbit:song ;
+
+ state:state [
+ orbit:song 0 ;
+ ] .