/* This file is part of Matriseq. Copyright 2007-2012 David Robillard Matriseq is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. Matriseq is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. You should have received a copy of the GNU Affero General Public License along with Matriseq. If not, see . */ #include #include #include #include #include #include #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/time/time.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" #define GRID_H 8 #define GRID_W 8 #define SEQ_H (8 * 8) #define NOTE_MIN 28 #define STEP_TYPE 16 #define RING_SIZE 4096 typedef enum { MATRISEQ_IN = 0, MATRISEQ_OUT = 1 } PortIndex; // URIDs used by this plugin typedef struct { LV2_URID atom_Blank; LV2_URID atom_Float; LV2_URID log_Error; LV2_URID midi_MidiEvent; LV2_URID time_Position; LV2_URID time_barBeat; LV2_URID time_beatsPerMinute; LV2_URID time_speed; } 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; ZixRing* ring; ZixThread thread; bool exit; // State double rate; float bpm; float speed; uint32_t beats_per_bar; uint32_t time_frames; uint32_t step; uint8_t page_x; uint8_t page_y; uint32_t seq[SEQ_H][STEP_TYPE]; } 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.atom_Blank = map->map(map->handle, LV2_ATOM__Blank); self->uris.atom_Float = map->map(map->handle, LV2_ATOM__Float); self->uris.log_Error = map->map(map->handle, LV2_LOG__Error); self->uris.midi_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent); self->uris.time_Position = map->map(map->handle, LV2_TIME__Position); self->uris.time_barBeat = map->map(map->handle, LV2_TIME__barBeat); self->uris.time_beatsPerMinute = map->map(map->handle, LV2_TIME__beatsPerMinute); self->uris.time_speed = map->map(map->handle, LV2_TIME__speed); lv2_atom_forge_init(&self->forge, self->map); // Initialise USB stuff self->naub = NULL; self->ring = zix_ring_new(RING_SIZE); // Initialise state self->rate = rate; self->bpm = 140.0f; self->speed = 0.0f; self->beats_per_bar = 4; self->page_y = 1; // Start at note 36 (kick) 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 uint32_t* get_cell(Matriseq* self, NaubControlID control) { const uint32_t x = (self->page_x * GRID_W) + control.x; const uint32_t y = (self->page_y * GRID_H) + (7 - control.y); return &self->seq[y][x]; } static void set_button(Matriseq* self, NaubControlID control, bool active) { int32_t value = 0; if (*get_cell(self, control)) { 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 set_column(Matriseq* self, uint32_t step, bool active) { for (int y = 0; y < 8; ++y) { const NaubControlID control = { 0, 0, step % GRID_W, y }; set_button(self, control, active); } } static void set_page_indicators(Matriseq* self) { const NaubControlID page_x_but = { 0, 1, self->page_x, 0 }; const NaubControlID page_y_but = { 0, 2, 0, 7 - self->page_y }; naub_set_control(self->naub, page_x_but, naub_rgb(0, 1, 0)); naub_set_control(self->naub, page_y_but, naub_rgb(0, 1, 0)); } static void show_page(Matriseq* self) { for (uint32_t y = 0; y < 8; ++y) { for (uint32_t x = 0; x < 8; ++x) { const NaubControlID control = { 0, 0, x, y }; set_button(self, control, x == self->step); } } } static void pad_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 == 1 && event->button.pressed) { const NaubControlID old_page_x_but = { 0, 1, self->page_x, 0 }; const NaubControlID old_page_y_but = { 0, 2, 0, 7 - self->page_y }; if (control.x == 0 && self->page_y < 7) { ++self->page_y; } else if (control.x == 1 && self->page_y > 0) { --self->page_y; } else if (control.x == 2 && self->page_x > 0) { --self->page_x; } else if (control.x == 3 && self->page_x < 1) { ++self->page_x; } else { return; } // Turn off old page indicator buttons naub_set_control(self->naub, old_page_x_but, naub_rgb(0, 0, 0)); naub_set_control(self->naub, old_page_y_but, naub_rgb(0, 0, 0)); // Turn on new page indicator buttons set_page_indicators(self); // Update grid display show_page(self); } else if (control.group == 0) { if (event->button.pressed) { naub_set_control(self->naub, control, naub_rgb(1, 0, 0)); } else { uint32_t* cell = get_cell(self, control); *cell = *cell ? 0 : 1; set_button(self, control, self->step == control.y); } } naub_flush(self->naub); } static void* pad_thread(void* instance) { Matriseq* self = (Matriseq*)instance; uint32_t step = self->step; // Initialise pad set_page_indicators(self); set_column(self, step, true); naub_flush(self->naub); 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)); const uint32_t begin = self->page_x * GRID_W; const uint32_t end = (self->page_x + 1) * GRID_W; // De-highlight old active row if (step >= begin && step < end) { set_column(self, step, false); } // Highlight new active row if (new_step >= begin && new_step < end) { set_column(self, new_step, 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, pad_event); if (self->naub) { if (!naub_world_open( self->naub, NAUB_VENDOR_NOVATION, NAUB_PRODUCT_LAUNCHPAD)) { if (zix_thread_create(&self->thread, 1024, pad_thread, self)) { print(self, self->uris.log_Error, "Failed to create thread\n"); return; } } else { print(self, self->uris.log_Error, "Failed to open controller\n"); } } } static void run(LV2_Handle instance, uint32_t n_frames) { Matriseq* self = (Matriseq*)instance; const MatriseqURIs* uris = &self->uris; const float s_per_beat = 60.0f / self->bpm; const float s_per_step = s_per_beat * self->beats_per_bar / STEP_TYPE; // 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); // Work forwards in time frame by frame, handling events as we go const LV2_Atom_Sequence* in = self->in; LV2_Atom_Event* ev = lv2_atom_sequence_begin(&in->body); for (uint32_t t = 0; t < n_frames; ++t) { while (!lv2_atom_sequence_is_end(&in->body, in->atom.size, ev) && ev->time.frames == t) { if (ev->body.type == uris->atom_Blank) { const LV2_Atom_Object* obj = (LV2_Atom_Object*)&ev->body; if (obj->body.otype == uris->time_Position) { // Update transport position and speed LV2_Atom *beat = NULL, *bpm = NULL, *speed = NULL; lv2_atom_object_get(obj, uris->time_barBeat, &beat, uris->time_beatsPerMinute, &bpm, uris->time_speed, &speed, NULL); if (bpm && bpm->type == uris->atom_Float) { self->bpm = ((LV2_Atom_Float*)bpm)->body; } if (beat && beat->type == uris->atom_Float) { self->time_frames = (((LV2_Atom_Float*)beat)->body * s_per_beat * self->rate); } if (speed && speed->type == uris->atom_Float) { self->speed = ((LV2_Atom_Float*)speed)->body; } } } ev = lv2_atom_sequence_next(ev); } const double time_s = self->time_frames / self->rate; const uint32_t step = (uint32_t)(time_s / s_per_step) % STEP_TYPE; if (step != self->step) { // Update step self->step = step; if (step == 0) { self->time_frames = 0; } // Notify USB thread of new step zix_ring_write(self->ring, &self->step, sizeof(self->step)); // Send note ons for enabled notes this step for (uint32_t y = 0; y < SEQ_H; ++y) { if (self->seq[y][step]) { const uint8_t on[] = { 0x90, NOTE_MIN + y, 0x40 }; lv2_atom_forge_frame_time(&self->forge, t); lv2_atom_forge_atom(&self->forge, 3, self->uris.midi_MidiEvent); lv2_atom_forge_write(&self->forge, on, 3); } } } if (self->speed) { ++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); } static 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; } }