diff options
author | David Robillard <d@drobilla.net> | 2015-11-09 06:58:14 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2015-11-09 06:58:14 +0000 |
commit | 92d4404abbe1373386e7fa7bd42a3acd1e4eeba3 (patch) | |
tree | 2e87d6ecccfeaef07f39b57e21421d54dfc5e873 /matriseq.c | |
parent | d75f8b95fca74098d8b939e7236acde7c6b3a9ae (diff) | |
download | matriseq.lv2-92d4404abbe1373386e7fa7bd42a3acd1e4eeba3.tar.gz matriseq.lv2-92d4404abbe1373386e7fa7bd42a3acd1e4eeba3.tar.bz2 matriseq.lv2-92d4404abbe1373386e7fa7bd42a3acd1e4eeba3.zip |
Rewrite Matriseq for the Launchpad Pro
git-svn-id: http://svn.drobilla.net/lad/trunk/plugins/matriseq@5817 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'matriseq.c')
-rw-r--r-- | matriseq.c | 483 |
1 files changed, 287 insertions, 196 deletions
@@ -1,6 +1,6 @@ /* - This file is part of Matriseq. - Copyright 2007-2012 David Robillard <http://drobilla.net/> + Matriseq, a simple step sequencer for the Launchpad Pro. + Copyright 2007-2015 David Robillard <http://drobilla.net/> 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 @@ -21,12 +21,9 @@ #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/log/logger.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" @@ -37,15 +34,69 @@ #define GRID_H 8 #define GRID_W 8 #define SEQ_H (8 * 8) +#define SEQ_W 16 #define NOTE_MIN 28 #define STEP_TYPE 16 #define RING_SIZE 4096 +static const uint8_t msg_set_col[] = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x10, 0xC }; +static const uint8_t msg_set_row[] = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x10, 0xD }; +static const uint8_t msg_show_text[] = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x10, 0x14 }; +static const uint8_t msg_light_led[] = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x10, 0xA }; + typedef enum { - MATRISEQ_IN = 0, - MATRISEQ_OUT = 1 + BLACK = 0, + DARK_GREY = 1, + LIGHT_GREY = 2, + WHITE = 3, + RED = 5, + YELLOW = 13, + DARK_GREEN = 15, + GREEN = 21 +} Color; + +typedef enum { + MATRISEQ_CONTROL_IN = 0, + MATRISEQ_CONTROL_OUT = 1, + MATRISEQ_PLAYBACK = 2 } PortIndex; +typedef enum { + BOTTOM_1 = 1, + BOTTOM_2 = 2, + BOTTOM_3 = 3, + BOTTOM_4 = 4, + BOTTOM_5 = 5, + BOTTOM_6 = 6, + BOTTOM_7 = 7, + BOTTOM_8 = 8, + LEFT_1 = 10, + LEFT_2 = 20, + LEFT_3 = 30, + LEFT_4 = 40, + LEFT_5 = 50, + LEFT_6 = 60, + LEFT_7 = 70, + LEFT_8 = 80, + RIGHT_1 = 19, + RIGHT_2 = 29, + RIGHT_3 = 39, + RIGHT_4 = 49, + RIGHT_5 = 59, + RIGHT_6 = 69, + RIGHT_7 = 79, + RIGHT_8 = 89, + TOP_1 = 91, + TOP_2 = 92, + TOP_3 = 93, + TOP_4 = 94, + TOP_5 = 95, + TOP_6 = 96, + TOP_7 = 97, + TOP_8 = 98, + TOP_9 = 99 +} ButtonID; + // URIDs used by this plugin typedef struct { LV2_URID atom_Blank; @@ -60,23 +111,19 @@ typedef struct { typedef struct { // Port buffers - LV2_Atom_Sequence* in; - LV2_Atom_Sequence* out; + LV2_Atom_Sequence* control_in; + LV2_Atom_Sequence* control_out; + LV2_Atom_Sequence* playback; // Features LV2_URID_Map* map; LV2_Log_Log* log; - // LV2 stuff - LV2_Atom_Forge forge; + LV2_Log_Logger logger; + LV2_Atom_Forge control_forge; + LV2_Atom_Forge playback_forge; MatriseqURIs uris; - // USB stuff - NaubWorld* naub; - ZixRing* ring; - ZixThread thread; - bool exit; - // State double rate; float bpm; @@ -86,24 +133,12 @@ typedef struct { uint32_t step; uint8_t page_x; uint8_t page_y; - uint32_t seq[SEQ_H][STEP_TYPE]; + uint32_t seq[SEQ_H][SEQ_W]; + uint32_t last_inquiry; + bool refresh; + bool attached; } 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, @@ -124,7 +159,7 @@ instantiate(const LV2_Descriptor* descriptor, } } if (!self->map) { - print(self, self->uris.log_Error, "Missing feature urid:map.\n"); + lv2_log_error(&self->logger, "Missing feature urid:map\n"); free(self); return NULL; } @@ -139,21 +174,18 @@ instantiate(const LV2_Descriptor* descriptor, 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); + lv2_atom_forge_init(&self->control_forge, self->map); + lv2_atom_forge_init(&self->playback_forge, self->map); + lv2_log_logger_init(&self->logger, self->map, self->log); // Initialise state self->rate = rate; self->bpm = 140.0f; - self->speed = 0.0f; + //self->speed = 0.0f; + self->speed = 1.0f; self->beats_per_bar = 4; self->page_y = 1; // Start at note 36 (kick) - zix_ring_mlock(self->ring); - return (LV2_Handle)self; } @@ -165,165 +197,198 @@ connect_port(LV2_Handle instance, Matriseq* self = (Matriseq*)instance; switch ((PortIndex)port) { - case MATRISEQ_IN: - self->in = (LV2_Atom_Sequence*)data; + case MATRISEQ_CONTROL_IN: + self->control_in = (LV2_Atom_Sequence*)data; break; - case MATRISEQ_OUT: - self->out = (LV2_Atom_Sequence*)data; + case MATRISEQ_CONTROL_OUT: + self->control_out = (LV2_Atom_Sequence*)data; + break; + case MATRISEQ_PLAYBACK: + self->playback = (LV2_Atom_Sequence*)data; break; } } static uint32_t* -get_cell(Matriseq* self, NaubControlID control) +get_cell(Matriseq* self, uint8_t pad_x, uint8_t pad_y) { - 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]; + const uint32_t seq_x = (self->page_x * GRID_W) + pad_x; + const uint32_t seq_y = (self->page_y * GRID_H) + pad_y; + return &self->seq[seq_y][seq_x]; } static void -set_button(Matriseq* self, NaubControlID control, bool active) +on_button(Matriseq* self, uint8_t num, uint8_t value) { - 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); + if (!value) { + return; // Ignore button release } - 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); + if (num == TOP_1 && self->page_y < 7) { // Page up + ++self->page_y; + self->refresh = true; + } else if (num == TOP_2 && self->page_y > 0) { // Page down + --self->page_y; + self->refresh = true; + } else if (num == TOP_3 && self->page_x > 0) { // Page left + --self->page_x; + self->refresh = true; + } else if (num == TOP_4 && self->page_x < 1) { // Page right + ++self->page_x; + self->refresh = true; + } else if (num % 10 == 9) { // Right button column + self->page_y = num / 10 - 1; + self->refresh = true; + } else if (num <= BOTTOM_8) { // Bottom button row + if ((num - 1) * GRID_W < SEQ_W) { + self->page_x = num - 1; + self->refresh = true; + } } } static void -set_page_indicators(Matriseq* self) +write_control(Matriseq* self, uint32_t t, const uint8_t* msg, uint32_t n) { - 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)); + lv2_atom_forge_frame_time(&self->control_forge, t); + lv2_atom_forge_atom(&self->control_forge, n, self->uris.midi_MidiEvent); + lv2_atom_forge_write(&self->control_forge, msg, n); } +/** Pad on the central 8x8 grid pressed. */ static void -show_page(Matriseq* self) +on_pad(Matriseq* self, uint32_t t, uint8_t x, uint8_t y, uint8_t vel) { - 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); - } + if (vel > 0) { + // Flip sequence cell + uint32_t* cell = get_cell(self, x, y); + *cell = !*cell; + + // Illuminate pad + const uint8_t msg[] = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x10, 0xA, + (y + 1) * 10 + x + 1, + *cell ? GREEN : BLACK, + 0xF7 }; + + write_control(self, t, msg, sizeof(msg)); } } -static void -pad_event(void* instance, const NaubEvent* event) +static uint8_t +step_button_color(Matriseq* self, uint8_t step, uint8_t col) { - 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; - } + const uint32_t page_begin = self->page_x * 8; + const uint32_t page_end = (self->page_x + 1) * 8; - // 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); - } + if (step >= page_begin && step < page_end && step % 8 == col) { + return GREEN; + } else if (self->page_x == col) { + return DARK_GREY; } - - naub_flush(self->naub); + return BLACK; } -static void* -pad_thread(void* instance) +/** Display scrolling text banner. */ +static void +pad_show_text(Matriseq* self, uint32_t t, const char* text) { - 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 size_t len = strlen(text); + uint8_t msg[sizeof(msg_show_text) + 3 + len + 1]; + memcpy(msg, msg_show_text, sizeof(msg_show_text)); + + uint8_t* ptr = msg + sizeof(msg_show_text); + *ptr++ = DARK_GREY; // Text color + *ptr++ = 0; // Loop + *ptr++ = 7; // Speed (1-7) + memcpy((char*)ptr, text, len); + ptr += len; + *ptr++ = 0xF7; + write_control(self, t, msg, sizeof(msg)); +} - const uint32_t begin = self->page_x * GRID_W; - const uint32_t end = (self->page_x + 1) * GRID_W; +/** Update step indicators when the page has not changed. */ +static void +pad_update_step(Matriseq* self, uint32_t t, uint32_t old_step, uint32_t new_step) +{ + // Turn off old step indicator + const uint8_t off_msg[] = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x10, 0xA, + old_step % 8 + 1, + step_button_color(self, new_step, old_step % 8), + 0xF7 }; + write_control(self, t, off_msg, sizeof(off_msg)); + + // Turn on new step indicator + const uint8_t on_msg[] = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x10, 0xA, + new_step % 8 + 1, + step_button_color(self, new_step, new_step % 8), + 0xF7 }; + write_control(self, t, on_msg, sizeof(on_msg)); +} - // De-highlight old active row - if (step >= begin && step < end) { - set_column(self, step, false); - } +/** Refresh page and step indicators. */ +static void +pad_refresh_position(Matriseq* self, uint32_t t) +{ + // Set bottom horizontal tick/page indicators + uint8_t xmsg[sizeof(msg_set_row) + 11]; + memcpy(xmsg, msg_set_row, sizeof(msg_set_row)); + + uint8_t* ptr = xmsg + sizeof(msg_set_row); + *ptr++ = 0; // Row number 0 (bottom) + *ptr++ = 0; // Non-existent bottom left button color + for (int i = 0; i < 8; ++i) { + *ptr++ = step_button_color(self, self->step, i); + } + *ptr++ = 0xF7; + write_control(self, t, xmsg, sizeof(xmsg)); + + // Set right vertical page indicators + uint8_t ymsg[sizeof(msg_set_col) + 11]; + memcpy(ymsg, msg_set_col, sizeof(msg_set_col)); + + ptr = ymsg + sizeof(msg_set_col); + *ptr++ = 9; // Column number 9 (right) + *ptr++ = 0; // Non-existent bottom right button color + for (int i = 0; i < 8; ++i) { + *ptr++ = self->page_y == i ? DARK_GREY : BLACK; + } + *ptr++ = 0xF7; + write_control(self, t, ymsg, sizeof(ymsg)); +} - // Highlight new active row - if (new_step >= begin && new_step < end) { - set_column(self, new_step, true); - } +static void +pad_clear(Matriseq* self, uint32_t t) +{ + const uint8_t msg[] = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x10, 0xE, 0, 0xF7 }; + write_control(self, t, msg, sizeof(msg)); +} - // Send bulk update to device - naub_flush(self->naub); +static void +pad_refresh_grid(Matriseq* self, uint32_t t) +{ + uint8_t msg[136]; + memcpy(msg, msg_light_led, sizeof(msg_light_led)); + uint8_t* ptr = msg + sizeof(msg_light_led); + for (uint32_t y = 0; y < 8; ++y) { + for (uint32_t x = 0; x < 8; ++x) { + const uint32_t gx = (self->page_x * GRID_W) + x; + const uint32_t gy = (self->page_y * GRID_H) + y; - step = new_step; + *ptr++ = (y + 1) * 10 + x + 1; + *ptr++ = self->seq[gy][gx] ? GREEN : BLACK; } } - return NULL; + + *ptr++ = 0xF7; + write_control(self, t, msg, sizeof(msg)); } static void -activate(LV2_Handle instance) +send_device_inquiry(Matriseq* self, uint32_t t) { - 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"); - } - } + const uint8_t msg[] = { 0xF0, 0x7E, 0x7F, 0x06, 0x01, 0xF7 }; + write_control(self, t, msg, sizeof(msg)); + self->last_inquiry = self->time_frames; } static void @@ -335,16 +400,25 @@ run(LV2_Handle instance, uint32_t n_frames) 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); + // Set output buffers for forges + lv2_atom_forge_set_buffer( + &self->control_forge, (uint8_t*)self->control_out, self->control_out->atom.size); + lv2_atom_forge_set_buffer( + &self->playback_forge, (uint8_t*)self->playback, self->playback->atom.size); + + // Initialise outputs to empty sequence + LV2_Atom_Forge_Frame control_frame; + LV2_Atom_Forge_Frame playback_frame; + lv2_atom_forge_sequence_head(&self->control_forge, &control_frame, 0); + lv2_atom_forge_sequence_head(&self->playback_forge, &playback_frame, 0); + + if (!self->attached && self->time_frames - self->last_inquiry > self->rate) { + // Haven't heard from Launchpad, send device inquiry once a second + send_device_inquiry(self, 0); + } // Work forwards in time frame by frame, handling events as we go - const LV2_Atom_Sequence* in = self->in; + const LV2_Atom_Sequence* in = self->control_in; const 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) && @@ -371,6 +445,27 @@ run(LV2_Handle instance, uint32_t n_frames) self->speed = ((LV2_Atom_Float*)speed)->body; } } + } else if (ev->body.type == uris->midi_MidiEvent) { + const uint8_t* const msg = (const uint8_t*)(ev + 1); + switch (lv2_midi_message_type(msg)) { + case LV2_MIDI_MSG_NOTE_ON: + on_pad(self, t, msg[1] % 10 - 1, msg[1] / 10 - 1, msg[2]); + break; + case LV2_MIDI_MSG_CONTROLLER: + on_button(self, msg[1], msg[2]); + break; + case LV2_MIDI_MSG_SYSTEM_EXCLUSIVE: + if (msg[1] == 0x7E) { + // Launchpad responded to device inquiry, initialise + pad_clear(self, t); + pad_refresh_position(self, t); + pad_refresh_grid(self, t); + pad_show_text(self, t, "Matriseq"); + self->attached = true; + } + default: + break; + } } ev = lv2_atom_sequence_next(ev); @@ -380,21 +475,30 @@ run(LV2_Handle instance, uint32_t n_frames) const uint32_t step = (uint32_t)(time_s / s_per_step) % STEP_TYPE; if (step != self->step) { // Update step + const uint32_t last_step = self->step; + pad_update_step(self, t, self->step, 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 offs for enabled notes last step + for (uint32_t y = 0; y < SEQ_H; ++y) { + if (self->seq[y][last_step]) { + const uint8_t off[] = { 0x80, NOTE_MIN + y, 0x40 }; + lv2_atom_forge_frame_time(&self->playback_forge, t); + lv2_atom_forge_atom(&self->playback_forge, 3, self->uris.midi_MidiEvent); + lv2_atom_forge_write(&self->playback_forge, off, 3); + } + } // 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); + lv2_atom_forge_frame_time(&self->playback_forge, t); + lv2_atom_forge_atom(&self->playback_forge, 3, self->uris.midi_MidiEvent); + lv2_atom_forge_write(&self->playback_forge, on, 3); } } } @@ -403,25 +507,18 @@ run(LV2_Handle instance, uint32_t n_frames) ++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; + if (self->refresh) { + pad_refresh_position(self, n_frames - 1); + pad_refresh_grid(self, n_frames - 1); + self->refresh = false; + } } static void cleanup(LV2_Handle instance) { - Matriseq* self = (Matriseq*)instance; - zix_ring_free(self->ring); - free(self); + free(instance); } static const void* @@ -434,21 +531,15 @@ static const LV2_Descriptor descriptor = { MATRISEQ_URI, instantiate, connect_port, - activate, + NULL, run, - deactivate, + NULL, cleanup, extension_data }; -LV2_SYMBOL_EXPORT -const LV2_Descriptor* +LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor(uint32_t index) { - switch (index) { - case 0: - return &descriptor; - default: - return NULL; - } + return (index == 0) ? &descriptor : NULL; } |