/*
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;
}
}