diff options
-rw-r--r-- | matriseq.c | 166 |
1 files changed, 113 insertions, 53 deletions
@@ -33,8 +33,12 @@ #define MATRISEQ_URI "http://drobilla.net/plugins/matriseq" -static const uint32_t STEP_TYPE = 8; -static const uint32_t RING_SIZE = 4096; +#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, @@ -62,16 +66,19 @@ typedef struct { // USB stuff NaubWorld* naub; - ZixThread thread; ZixRing* ring; + ZixThread thread; bool exit; // State - uint32_t time_frames; - uint8_t grid[8][8]; - uint32_t step; double rate; float bpm; + 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. @@ -120,13 +127,17 @@ instantiate(const LV2_Descriptor* descriptor, 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; + // Initialise USB stuff + self->naub = NULL; + self->ring = zix_ring_new(RING_SIZE); - zix_ring_mlock(self->ring); - } + // Initialise state + self->rate = rate; + self->bpm = 140.0f; + self->beats_per_bar = 4; + self->page_y = 1; // Start at note 36 (kick) + + zix_ring_mlock(self->ring); return (LV2_Handle)self; } @@ -148,11 +159,19 @@ connect_port(LV2_Handle instance, } } +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 (self->grid[control.y][control.x]) { + 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); @@ -161,8 +180,27 @@ set_button(Matriseq* self, NaubControlID control, bool active) } static void -naub_event(void* instance, - const NaubEvent* event) +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... @@ -170,50 +208,75 @@ naub_event(void* instance, } 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 (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; + } - 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); + // 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* -thread_run(void* instance) +pad_thread(void* instance) { Matriseq* self = (Matriseq*)instance; uint32_t step = self->step; + // Initialise pad + set_page_indicators(self); + 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 - for (int y = 0; y < 8; ++y) { - const NaubControlID control = { 0, 0, step, y }; - set_button(self, control, false); + if (step >= begin && step < end) { + for (int y = 0; y < 8; ++y) { + const NaubControlID control = { 0, 0, step % GRID_W, 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); + if (new_step >= begin && new_step < end) { + for (int y = 0; y < 8; ++y) { + const NaubControlID control = { 0, 0, new_step % GRID_W, y }; + set_button(self, control, true); + } } // Send bulk update to device @@ -229,15 +292,16 @@ static void activate(LV2_Handle instance) { Matriseq* self = (Matriseq*)instance; - self->naub = naub_world_new(self, naub_event); + self->naub = naub_world_new(self, pad_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"); + 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"); } } } @@ -246,10 +310,9 @@ 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) + + 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; @@ -259,10 +322,9 @@ run(LV2_Handle instance, uint32_t n_frames) 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; + const uint32_t step = (uint32_t)(time_s / s_per_step) % STEP_TYPE; if (step != self->step) { // Update step and frame time self->step = step; @@ -273,14 +335,12 @@ run(LV2_Handle instance, uint32_t n_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 }; + for (uint32_t y = 0; y < SEQ_H; ++y) { + if (self->seq[y][step]) { + const uint8_t ev[] = { 0x90, NOTE_MIN + y, 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); |