diff options
author | David Robillard <d@drobilla.net> | 2012-07-07 06:41:46 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2012-07-07 06:41:46 +0000 |
commit | 277cd0f1959f75db24ecf48be1030aa1af03cd6d (patch) | |
tree | 5d384ed3a63f1de3f2278cbdfebba46f3e4a95b0 /matriseq.c | |
download | matriseq.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.c | 341 |
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; + } +} |