summaryrefslogtreecommitdiffstats
path: root/matriseq.c
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2012-07-07 06:41:46 +0000
committerDavid Robillard <d@drobilla.net>2012-07-07 06:41:46 +0000
commit277cd0f1959f75db24ecf48be1030aa1af03cd6d (patch)
tree5d384ed3a63f1de3f2278cbdfebba46f3e4a95b0 /matriseq.c
downloadmatriseq.lv2-277cd0f1959f75db24ecf48be1030aa1af03cd6d.tar.gz
matriseq.lv2-277cd0f1959f75db24ecf48be1030aa1af03cd6d.tar.bz2
matriseq.lv2-277cd0f1959f75db24ecf48be1030aa1af03cd6d.zip
Add matriseq, a step sequencer for the Novation Launchpad.
git-svn-id: http://svn.drobilla.net/lad/trunk/plugins/matriseq@4512 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'matriseq.c')
-rw-r--r--matriseq.c341
1 files changed, 341 insertions, 0 deletions
diff --git a/matriseq.c b/matriseq.c
new file mode 100644
index 0000000..bb9c1fe
--- /dev/null
+++ b/matriseq.c
@@ -0,0 +1,341 @@
+/*
+ Copyright 2012 David Robillard <d@drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "naub/naub.h"
+#include "zix/thread.h"
+#include "zix/ring.h"
+
+#include "lv2/lv2plug.in/ns/ext/atom/forge.h"
+#include "lv2/lv2plug.in/ns/ext/log/log.h"
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
+#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
+
+#define MATRISEQ_URI "http://drobilla.net/plugins/matriseq"
+
+static const uint32_t STEP_TYPE = 8;
+static const uint32_t RING_SIZE = 4096;
+
+typedef enum {
+ MATRISEQ_IN = 0,
+ MATRISEQ_OUT = 1
+} PortIndex;
+
+// URIDs used by this plugin
+typedef struct {
+ LV2_URID log_Error;
+ LV2_URID midi_MidiEvent;
+} MatriseqURIs;
+
+typedef struct {
+ // Port buffers
+ LV2_Atom_Sequence* in;
+ LV2_Atom_Sequence* out;
+
+ // Features
+ LV2_URID_Map* map;
+ LV2_Log_Log* log;
+
+ // LV2 stuff
+ LV2_Atom_Forge forge;
+ MatriseqURIs uris;
+
+ // USB stuff
+ NaubWorld* naub;
+ ZixThread thread;
+ ZixRing* ring;
+ bool exit;
+
+ // State
+ uint32_t time_frames;
+ uint8_t grid[8][8];
+ uint32_t step;
+ double rate;
+ float bpm;
+} Matriseq;
+
+// Log a message to the host if available, or stderr otherwise.
+LV2_LOG_FUNC(3, 4)
+static void
+print(Matriseq* self, LV2_URID type, const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ if (self->log) {
+ self->log->vprintf(self->log->handle, type, fmt, args);
+ } else {
+ vfprintf(stderr, fmt, args);
+ }
+ va_end(args);
+}
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor,
+ double rate,
+ const char* bundle_path,
+ const LV2_Feature* const* features)
+{
+ Matriseq* self = (Matriseq*)calloc(1, sizeof(Matriseq));
+ if (!self) {
+ return NULL;
+ }
+
+ // Get features
+ for (int i = 0; features[i]; ++i) {
+ if (!strcmp(features[i]->URI, LV2_URID__map)) {
+ self->map = (LV2_URID_Map*)features[i]->data;
+ } else if (!strcmp(features[i]->URI, LV2_LOG__log)) {
+ self->log = (LV2_Log_Log*)features[i]->data;
+ }
+ }
+ if (!self->map) {
+ print(self, self->uris.log_Error, "Missing feature urid:map.\n");
+ free(self);
+ return NULL;
+ }
+
+ // Initialise LV2 stuff
+ LV2_URID_Map* map = self->map;
+ self->uris.log_Error = map->map(map->handle, LV2_LOG__Error);
+ self->uris.midi_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent);
+ lv2_atom_forge_init(&self->forge, self->map);
+
+ if (self) {
+ self->ring = zix_ring_new(RING_SIZE);
+ self->rate = rate;
+ self->bpm = 140.0f;
+
+ zix_ring_mlock(self->ring);
+ }
+
+ return (LV2_Handle)self;
+}
+
+static void
+connect_port(LV2_Handle instance,
+ uint32_t port,
+ void* data)
+{
+ Matriseq* self = (Matriseq*)instance;
+
+ switch ((PortIndex)port) {
+ case MATRISEQ_IN:
+ self->in = (LV2_Atom_Sequence*)data;
+ break;
+ case MATRISEQ_OUT:
+ self->out = (LV2_Atom_Sequence*)data;
+ break;
+ }
+}
+
+static void
+set_button(Matriseq* self, NaubControlID control, bool active)
+{
+ int32_t value = 0;
+ if (self->grid[control.y][control.x]) {
+ value = active ? naub_rgb(1, 0, 0) : naub_rgb(1, 1, 0);
+ } else {
+ value = active ? naub_rgb(0, 0.4, 0) : naub_rgb(0, 0, 0);
+ }
+ naub_set_control(self->naub, control, value);
+}
+
+static void
+naub_event(void* instance,
+ const NaubEvent* event)
+{
+ Matriseq* self = (Matriseq*)instance;
+ if (event->type != NAUB_EVENT_BUTTON) { // Odd...
+ return;
+ }
+
+ const NaubControlID control = event->button.control;
+ if (control.group != 0) {
+ return;
+ }
+
+ /*
+ fprintf(stderr, "PRESS %d:%d.%d.%d\n",
+ control.device,
+ control.group,
+ control.x,
+ control.y);
+ */
+
+ if (event->button.pressed) {
+ naub_set_control(self->naub, control, naub_rgb(1, 0, 0));
+ } else {
+ uint8_t* cell = &self->grid[control.y][control.x];
+ *cell = *cell ? 0 : 1;
+ set_button(self, control, self->step == control.y);
+ }
+
+ naub_flush(self->naub);
+}
+
+static void*
+thread_run(void* instance)
+{
+ Matriseq* self = (Matriseq*)instance;
+ uint32_t step = self->step;
+
+ while (!naub_handle_events_timeout(self->naub, 10) && !self->exit) {
+ uint32_t new_step;
+ if (zix_ring_read_space(self->ring) >= sizeof(new_step)) {
+ zix_ring_read(self->ring, &new_step, sizeof(new_step));
+
+ // De-highlight old active row
+ for (int y = 0; y < 8; ++y) {
+ const NaubControlID control = { 0, 0, step, y };
+ set_button(self, control, false);
+ }
+
+ // Highlight new active row
+ for (int y = 0; y < 8; ++y) {
+ const NaubControlID control = { 0, 0, new_step, y };
+ set_button(self, control, true);
+ }
+
+ // Send bulk update to device
+ naub_flush(self->naub);
+
+ step = new_step;
+ }
+ }
+ return NULL;
+}
+
+static void
+activate(LV2_Handle instance)
+{
+ Matriseq* self = (Matriseq*)instance;
+ self->naub = naub_world_new(self, naub_event);
+ if (self->naub) {
+ if (!naub_world_open(
+ self->naub, NAUB_VENDOR_NOVATION, NAUB_PRODUCT_LAUNCHPAD)) {
+ fprintf(stderr, "Opened Launchpad\n");
+ if (zix_thread_create(&self->thread, 1024, thread_run, self)) {
+ fprintf(stderr, "Failed to create thread\n");
+ return;
+ }
+ }
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t n_frames)
+{
+ Matriseq* self = (Matriseq*)instance;
+ const float bps = self->bpm / 60.0f; // Beats per second
+ const float spb = 1.0 / bps; // Seconds per beat
+ //const float spt = spb / STEP_TYPE;
+ const float spt = spb / 2; // Seconds per tick (step)
+
+ // Prepare for writing to out port
+ const uint32_t out_capacity = self->out->atom.size;
+ lv2_atom_forge_set_buffer(&self->forge, (uint8_t*)self->out, out_capacity);
+
+ // Initialise output port to empty sequence
+ LV2_Atom_Forge_Frame out_frame;
+ lv2_atom_forge_sequence_head(&self->forge, &out_frame, 0);
+
+ //fprintf(stderr, "BPS: %f SPB: %f\n", bps, spb);
+ for (uint32_t i = 0; i < n_frames; ++i) {
+ const double time_s = self->time_frames / self->rate;
+ const uint32_t step = (uint32_t)(time_s / spt) % STEP_TYPE;
+ if (step != self->step) {
+ // Update step and frame time
+ self->step = step;
+ if (step == 0) {
+ self->time_frames = 0;
+ } else {
+ ++self->time_frames;
+ }
+
+ // Notify USB thread of new step
+ //fprintf(stderr, "LV2 STEP %d @ %f\n", self->step, time_s);
+ zix_ring_write(self->ring, &self->step, sizeof(self->step));
+
+ // Send note ons for enabled notes this step
+ for (uint32_t y = 0; y < 8; ++y) {
+ if (self->grid[y][step]) {
+ const uint8_t note = (7 - y) + 36;
+ const uint8_t ev[] = { 0x90, note, 0x40 };
+ lv2_atom_forge_frame_time(&self->forge, i);
+ lv2_atom_forge_atom(&self->forge, 3, self->uris.midi_MidiEvent);
+ lv2_atom_forge_write(&self->forge, ev, 3);
+ }
+ }
+ } else {
+ ++self->time_frames;
+ }
+ }
+}
+
+static void
+deactivate(LV2_Handle instance)
+{
+ Matriseq* self = (Matriseq*)instance;
+ self->exit = true;
+ void* thread_ret = NULL;
+ zix_thread_join(self->thread, &thread_ret);
+ naub_world_free(self->naub);
+ self->naub = NULL;
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ Matriseq* self = (Matriseq*)instance;
+ zix_ring_free(self->ring);
+ free(self);
+}
+
+const void*
+extension_data(const char* uri)
+{
+ return NULL;
+}
+
+static const LV2_Descriptor descriptor = {
+ MATRISEQ_URI,
+ instantiate,
+ connect_port,
+ activate,
+ run,
+ deactivate,
+ cleanup,
+ extension_data
+};
+
+LV2_SYMBOL_EXPORT
+const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch (index) {
+ case 0:
+ return &descriptor;
+ default:
+ return NULL;
+ }
+}