summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
authorRobin Gareus <robin@gareus.org>2013-10-20 04:31:07 +0200
committerRobin Gareus <robin@gareus.org>2013-10-20 04:31:07 +0200
commitf5c386bbb40e176028022efa7577369eca79b788 (patch)
treea02001d5eba0478d246125d0d2dc9d7e6d156640 /libs
parentf191bdf6a010d05309b30bc3397bababc4ec4a3a (diff)
add reasonablesynth.lv2
A reasonably simple synth to allow new users to 'hear midi'. This is a first step. It still needs proper install and bundling.
Diffstat (limited to 'libs')
-rw-r--r--libs/plugins/reasonablesynth.lv2/lv2.c190
-rw-r--r--libs/plugins/reasonablesynth.lv2/manifest.ttl.in8
-rw-r--r--libs/plugins/reasonablesynth.lv2/reasonablesynth.ttl.in51
-rw-r--r--libs/plugins/reasonablesynth.lv2/rsynth.c490
-rw-r--r--libs/plugins/reasonablesynth.lv2/wscript44
5 files changed, 783 insertions, 0 deletions
diff --git a/libs/plugins/reasonablesynth.lv2/lv2.c b/libs/plugins/reasonablesynth.lv2/lv2.c
new file mode 100644
index 0000000000..85aa968e8f
--- /dev/null
+++ b/libs/plugins/reasonablesynth.lv2/lv2.c
@@ -0,0 +1,190 @@
+/* reasonable simple synth
+ *
+ * Copyright (C) 2013 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+/* LV2 */
+#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
+#include "lv2/lv2plug.in/ns/ext/atom/util.h"
+#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+
+#define RSY_URI "http://gareus.org/oss/lv2/reasonablesynth"
+
+/* the synth interface */
+static void * synth_alloc (void);
+static void synth_init (void *, double rate);
+static void synth_free (void *);
+static void synth_parse_midi (void *, uint8_t *data, size_t size);
+static uint32_t synth_sound (void *, uint32_t written, uint32_t nframes, float **out);
+
+#include "rsynth.c"
+
+typedef enum {
+ RSY_MIDIIN = 0,
+ RSY_OUTL,
+ RSY_OUTR
+} PortIndex;
+
+typedef struct {
+ const LV2_Atom_Sequence* midiin;
+ float* outL;
+ float* outR;
+
+ LV2_URID_Map* map;
+ LV2_URID midi_MidiEvent;
+
+ double SampleRateD;
+ void *synth;
+} RSynth;
+
+/* main LV2 */
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor,
+ double rate,
+ const char* bundle_path,
+ const LV2_Feature* const* features)
+{
+ if (rate < 8000) {
+ fprintf(stderr, "RSynth.lv2 error: unsupported sample-rate (must be > 8k)\n");
+ return NULL;
+ }
+ RSynth* self = (RSynth*)calloc(1, sizeof(RSynth));
+ if(!self) {
+ return NULL;
+ }
+
+ self->SampleRateD = rate;
+
+ int i;
+ for (i=0; features[i]; ++i) {
+ if (!strcmp(features[i]->URI, LV2_URID__map)) {
+ self->map = (LV2_URID_Map*)features[i]->data;
+ }
+ }
+
+ if (!self->map) {
+ fprintf(stderr, "RSynth.lv2 error: Host does not support urid:map\n");
+ free(self);
+ return NULL;
+ }
+
+ self->midi_MidiEvent = self->map->map(self->map->handle, LV2_MIDI__MidiEvent);
+
+ self->synth = synth_alloc();
+ synth_init(self->synth, rate);
+
+ return (LV2_Handle)self;
+}
+
+static void
+connect_port(LV2_Handle handle,
+ uint32_t port,
+ void* data)
+{
+ RSynth* self = (RSynth*)handle;
+
+ switch ((PortIndex)port) {
+ case RSY_MIDIIN:
+ self->midiin = (const LV2_Atom_Sequence*)data;
+ break;
+ case RSY_OUTL:
+ self->outL = (float*)data;
+ break;
+ case RSY_OUTR:
+ self->outR = (float*)data;
+ break;
+ }
+}
+
+static void
+run(LV2_Handle handle, uint32_t n_samples)
+{
+ RSynth* self = (RSynth*)handle;
+ float* audio[2];
+
+ audio[0] = self->outL;
+ audio[1] = self->outR;
+
+ uint32_t written = 0;
+
+ /* Process incoming MIDI events */
+ if (self->midiin) {
+ LV2_Atom_Event* ev = lv2_atom_sequence_begin(&(self->midiin)->body);
+ while(!lv2_atom_sequence_is_end(&(self->midiin)->body, (self->midiin)->atom.size, ev)) {
+ if (ev->body.type == self->midi_MidiEvent) {
+ if (written + BUFFER_SIZE_SAMPLES < ev->time.frames
+ && ev->time.frames < n_samples) {
+ /* first synthesize sound up until the message timestamp */
+ written = synth_sound(self->synth, written, ev->time.frames, audio);
+ }
+ /* send midi message to synth */
+ synth_parse_midi(self->synth, (uint8_t*)(ev+1), ev->body.size);
+ }
+ ev = lv2_atom_sequence_next(ev);
+ }
+ }
+
+ /* synthesize [remaining] sound */
+ synth_sound(self->synth, written, n_samples, audio);
+}
+
+static void
+cleanup(LV2_Handle handle)
+{
+ RSynth* self = (RSynth*)handle;
+ synth_free(self->synth);
+ free(handle);
+}
+
+static const void*
+extension_data(const char* uri)
+{
+ return NULL;
+}
+
+static const LV2_Descriptor descriptor = {
+ RSY_URI,
+ instantiate,
+ connect_port,
+ NULL,
+ run,
+ NULL,
+ cleanup,
+ extension_data
+};
+
+LV2_SYMBOL_EXPORT
+const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch (index) {
+ case 0:
+ return &descriptor;
+ default:
+ return NULL;
+ }
+}
+
+/* vi:set ts=8 sts=2 sw=2: */
diff --git a/libs/plugins/reasonablesynth.lv2/manifest.ttl.in b/libs/plugins/reasonablesynth.lv2/manifest.ttl.in
new file mode 100644
index 0000000000..ad0cff08fa
--- /dev/null
+++ b/libs/plugins/reasonablesynth.lv2/manifest.ttl.in
@@ -0,0 +1,8 @@
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
+
+<http://gareus.org/oss/lv2/reasonablesynth>
+ a lv2:Plugin ;
+ lv2:binary <reasonablesynth@LIB_EXT@> ;
+ rdfs:seeAlso <reasonablesynth.ttl> .
diff --git a/libs/plugins/reasonablesynth.lv2/reasonablesynth.ttl.in b/libs/plugins/reasonablesynth.lv2/reasonablesynth.ttl.in
new file mode 100644
index 0000000000..2b8861f3d6
--- /dev/null
+++ b/libs/plugins/reasonablesynth.lv2/reasonablesynth.ttl.in
@@ -0,0 +1,51 @@
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix pg: <http://lv2plug.in/ns/ext/port-groups#> .
+@prefix units: <http://lv2plug.in/ns/extensions/units#> .
+@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
+
+<http://gareus.org/rgareus#me>
+ a foaf:Person ;
+ foaf:name "Robin Gareus" ;
+ foaf:mbox <mailto:robin@gareus.org> ;
+ foaf:homepage <http://gareus.org/> .
+
+<http://gareus.org/oss/lv2/reasonablesynth>
+ a lv2:Plugin, lv2:InstrumentPlugin, doap:Project;
+ doap:license <http://usefulinc.com/doap/licenses/gpl> ;
+ doap:maintainer <http://gareus.org/rgareus#me> ;
+ doap:name "Reasonable Synth";
+ lv2:optionalFeature lv2:hardRTCapable ;
+ lv2:requiredFeature urid:map ;
+ rdfs:comment """A simple synthesizer with no controls at all but a reasonable sound instead.""" ;
+ lv2:port
+ [
+ a atom:AtomPort ,
+ lv2:InputPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports <http://lv2plug.in/ns/ext/midi#MidiEvent> ;
+ lv2:index 0 ;
+ lv2:symbol "MidiIn" ;
+ lv2:name "MIDI Input" ;
+ ],
+ [
+ a lv2:AudioPort ,
+ lv2:OutputPort ;
+ lv2:index 1 ;
+ lv2:symbol "outL" ;
+ lv2:name "Left output" ;
+ lv2:designation pg:left ;
+ ],
+ [
+ a lv2:AudioPort ,
+ lv2:OutputPort ;
+ lv2:index 2 ;
+ lv2:symbol "outR" ;
+ lv2:name "Right Output" ;
+ lv2:designation pg:right ;
+ ]
+ .
diff --git a/libs/plugins/reasonablesynth.lv2/rsynth.c b/libs/plugins/reasonablesynth.lv2/rsynth.c
new file mode 100644
index 0000000000..b89f0fb45f
--- /dev/null
+++ b/libs/plugins/reasonablesynth.lv2/rsynth.c
@@ -0,0 +1,490 @@
+/* reasonable simple synth
+ *
+ * Copyright (C) 2013 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE // needed for M_PI
+#endif
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <assert.h>
+
+#ifndef BUFFER_SIZE_SAMPLES
+#define BUFFER_SIZE_SAMPLES 64
+#endif
+
+#ifndef MIN
+#define MIN(A, B) ( (A) < (B) ? (A) : (B) )
+#endif
+
+/* internal MIDI event abstraction */
+enum RMIDI_EV_TYPE {
+ INVALID=0,
+ NOTE_ON,
+ NOTE_OFF,
+ PROGRAM_CHANGE,
+ CONTROL_CHANGE,
+};
+
+struct rmidi_event_t {
+ enum RMIDI_EV_TYPE type;
+ uint8_t channel; /**< the MIDI channel number 0-15 */
+ union {
+ struct {
+ uint8_t note;
+ uint8_t velocity;
+ } tone;
+ struct {
+ uint8_t param;
+ uint8_t value;
+ } control;
+ } d;
+};
+
+typedef struct {
+ uint32_t tme[3]; // attack, decay, release times [settings:ms || internal:samples]
+ float vol[2]; // attack, sustain volume [0..1]
+ uint32_t off[3]; // internal use (added attack,decay,release times)
+} ADSRcfg;
+
+typedef struct _RSSynthChannel {
+ uint32_t keycomp;
+ uint32_t adsr_cnt[128];
+ float adsr_amp[128];
+ float phase[128]; // various use, zero'ed on note-on
+ int8_t miditable[128]; // internal, note-on/off velocity
+ ADSRcfg adsr;
+ void (*synthesize) (struct _RSSynthChannel* sc,
+ const uint8_t note, const float vol, const float pc,
+ const size_t n_samples, float* left, float* right);
+} RSSynthChannel;
+
+typedef void (*SynthFunction) (RSSynthChannel* sc,
+ const uint8_t note, const float vol, const float pc,
+ const size_t n_samples, float* left, float* right);
+
+typedef struct {
+ uint32_t boffset;
+ float buf [2][BUFFER_SIZE_SAMPLES];
+ RSSynthChannel sc[16];
+ float freqs[128];
+ float kcgain;
+ float kcfilt;
+ double rate;
+} RSSynthesizer;
+
+
+/* initialize ADSR values
+ *
+ * @param rate sample-rate
+ * @param a attack time in seconds
+ * @param d decay time in seconds
+ * @param r release time in seconds
+ * @param avol attack gain [0..1]
+ * @param svol sustain volume level [0..1]
+ */
+static void init_adsr(ADSRcfg *adsr, const double rate,
+ const uint32_t a, const uint32_t d, const uint32_t r,
+ const float avol, const float svol) {
+
+ adsr->vol[0] = avol;
+ adsr->vol[1] = svol;
+ adsr->tme[0] = a * rate / 1000.0;
+ adsr->tme[1] = d * rate / 1000.0;
+ adsr->tme[2] = r * rate / 1000.0;
+
+ assert(adsr->tme[0] > 32);
+ assert(adsr->tme[1] > 32);
+ assert(adsr->tme[2] > 32);
+ assert(adsr->vol[0] >=0 && adsr->vol[1] <= 1.0);
+ assert(adsr->vol[1] >=0 && adsr->vol[1] <= 1.0);
+
+ adsr->off[0] = adsr->tme[0];
+ adsr->off[1] = adsr->tme[1] + adsr->off[0];
+ adsr->off[2] = adsr->tme[2] + adsr->off[1];
+}
+
+/* calculate per-sample, per-key envelope */
+static inline float adsr_env(RSSynthChannel *sc, const uint8_t note) {
+
+ if (sc->adsr_cnt[note] < sc->adsr.off[0]) {
+ // attack
+ const uint32_t p = ++sc->adsr_cnt[note];
+ if (p == sc->adsr.tme[0]) {
+ sc->adsr_amp[note] = sc->adsr.vol[0];
+ return sc->adsr.vol[0];
+ } else {
+ const float d = sc->adsr.vol[0] - sc->adsr_amp[note];
+ return sc->adsr_amp[note] + (p / (float) sc->adsr.tme[0]) * d;
+ }
+ }
+ else if (sc->adsr_cnt[note] < sc->adsr.off[1]) {
+ // decay
+ const uint32_t p = ++sc->adsr_cnt[note] - sc->adsr.off[0];
+ if (p == sc->adsr.tme[1]) {
+ sc->adsr_amp[note] = sc->adsr.vol[1];
+ return sc->adsr.vol[1];
+ } else {
+ const float d = sc->adsr.vol[1] - sc->adsr_amp[note];
+ return sc->adsr_amp[note] + (p / (float) sc->adsr.tme[1]) * d;
+ }
+ }
+ else if (sc->adsr_cnt[note] == sc->adsr.off[1]) {
+ // sustain
+ return sc->adsr.vol[1];
+ }
+ else if (sc->adsr_cnt[note] < sc->adsr.off[2]) {
+ // release
+ const uint32_t p = ++sc->adsr_cnt[note] - sc->adsr.off[1];
+ if (p == sc->adsr.tme[2]) {
+ sc->adsr_amp[note] = 0;
+ return 0;
+ } else {
+ const float d = 0 - sc->adsr_amp[note];
+ return sc->adsr_amp[note] + (p / (float) sc->adsr.tme[2]) * d;
+ }
+ }
+ else {
+ sc->adsr_cnt[note] = 0;
+ return 0;
+ }
+}
+
+
+/*****************************************************************************/
+/* piano like sound w/slight stereo phase */
+static void synthesize_sineP (RSSynthChannel* sc,
+ const uint8_t note, const float vol, const float fq,
+ const size_t n_samples, float* left, float* right) {
+
+ float phase = sc->phase[note];
+
+ for (size_t i=0; i < n_samples; ++i) {
+ float env = adsr_env(sc, note);
+ if (sc->adsr_cnt[note] == 0) break;
+ const float amp = vol * env;
+
+ left[i] += amp * sinf(2.0 * M_PI * phase);
+ left[i] += .300 * amp * sinf(2.0 * M_PI * phase * 2.0);
+ left[i] += .150 * amp * sinf(2.0 * M_PI * phase * 3.0);
+ left[i] += .080 * amp * sinf(2.0 * M_PI * phase * 4.0);
+ //left[i] -= .007 * amp * sinf(2.0 * M_PI * phase * 5.0);
+ //left[i] += .010 * amp * sinf(2.0 * M_PI * phase * 6.0);
+ //left[i] += .020 * amp * sinf(2.0 * M_PI * phase * 7.0);
+ phase += fq;
+ right[i] += amp * sinf(2.0 * M_PI * phase);
+ right[i] += .300 * amp * sinf(2.0 * M_PI * phase * 2.0);
+ right[i] += .150 * amp * sinf(2.0 * M_PI * phase * 3.0);
+ right[i] -= .080 * amp * sinf(2.0 * M_PI * phase * 4.0);
+ //right[i] += .007 * amp * sinf(2.0 * M_PI * phase * 5.0);
+ //right[i] += .010 * amp * sinf(2.0 * M_PI * phase * 6.0);
+ //right[i] -= .020 * amp * sinf(2.0 * M_PI * phase * 7.0);
+ if (phase > 1.0) phase -= 2.0;
+ }
+ sc->phase[note] = phase;
+}
+
+static const ADSRcfg piano_adsr = {{ 5, 1300, 100}, { 1.0, 0.0}, {0,0,0}};
+
+/*****************************************************************************/
+
+
+/* process note - move through ADSR states, count active keys,.. */
+static void process_key (void *synth,
+ const uint8_t chn, const uint8_t note,
+ const size_t n_samples, float *left, float *right)
+{
+ RSSynthesizer* rs = (RSSynthesizer*)synth;
+ RSSynthChannel* sc = &rs->sc[chn];
+ const int8_t vel = sc->miditable[note];
+ const float vol = /* master_volume */ 0.25 * fabsf(vel) / 127.0;
+ const float phase = sc->phase[note];
+
+ if (phase == -10 && vel > 0) {
+ // new note on
+ assert(sc->adsr_cnt[note] == 0);
+ sc->adsr_amp[note] = 0;
+ sc->adsr_cnt[note] = 0;
+ sc->phase[note] = 0;
+ sc->keycomp++;
+ //printf("[On] Now %d keys active on chn %d\n", sc->keycomp, chn);
+ }
+ else if (phase >= -1.0 && phase <= 1.0 && vel > 0) {
+ // sustain note or re-start note while adsr in progress:
+ if (sc->adsr_cnt[note] > sc->adsr.off[1]) {
+ // x-fade to attack
+ sc->adsr_amp[note] = adsr_env(sc, note);
+ sc->adsr_cnt[note] = 0;
+ }
+ }
+ else if (phase >= -1.0 && phase <= 1.0 && vel < 0) {
+ // note off
+ if (sc->adsr_cnt[note] <= sc->adsr.off[1]) {
+ if (sc->adsr_cnt[note] != sc->adsr.off[1]) {
+ // x-fade to release
+ sc->adsr_amp[note] = adsr_env(sc, note);
+ }
+ sc->adsr_cnt[note] = sc->adsr.off[1] + 1;
+ }
+ }
+ else {
+ /* note-on + off in same cycle */
+ sc->miditable[note] = 0;
+ sc->adsr_cnt[note] = 0;
+ sc->phase[note] = -10;
+ return;
+ }
+
+ // synthesize actual sound
+ sc->synthesize(sc, note, vol, rs->freqs[note], n_samples, left, right);
+
+ if (sc->adsr_cnt[note] == 0) {
+ //printf("Note %d,%d released\n", chn, note);
+ sc->miditable[note] = 0;
+ sc->adsr_amp[note] = 0;
+ sc->phase[note] = -10;
+ sc->keycomp--;
+ //printf("[off] Now %d keys active on chn %d\n", sc->keycomp, chn);
+ }
+}
+
+/* synthesize a BUFFER_SIZE_SAMPLES's of audio-data */
+static void synth_fragment (void *synth, const size_t n_samples, float *left, float *right) {
+ RSSynthesizer* rs = (RSSynthesizer*)synth;
+ memset (left, 0, n_samples * sizeof(float));
+ memset (right, 0, n_samples * sizeof(float));
+ uint8_t keycomp = 0;
+
+ for (int c=0; c < 16; ++c) {
+ for (int k=0; k < 128; ++k) {
+ if (rs->sc[c].miditable[k] == 0) continue;
+ process_key(synth, c, k, n_samples, left, right);
+ }
+ keycomp += rs->sc[c].keycomp;
+ }
+
+#if 1 // key-compression
+ float kctgt = 8.0 / (float)(keycomp + 7.0);
+ if (kctgt < .5) kctgt = .5;
+ if (kctgt > 1.0) kctgt = 1.0;
+ const float _w = rs->kcfilt;
+ for (unsigned int i=0; i < n_samples; ++i) {
+ rs->kcgain += _w * (kctgt - rs->kcgain);
+ left[i] *= rs->kcgain;
+ right[i] *= rs->kcgain;
+ }
+ rs->kcgain += 1e-12;
+#endif
+}
+
+static void synth_reset_channel(RSSynthChannel* sc) {
+ for (int k=0; k < 128; ++k) {
+ sc->adsr_cnt[k] = 0;
+ sc->adsr_amp[k] = 0;
+ sc->phase[k] = -10;
+ sc->miditable[k] = 0;
+ }
+ sc->keycomp = 0;
+}
+
+static void synth_reset(void *synth) {
+ RSSynthesizer* rs = (RSSynthesizer*)synth;
+ for (int c=0; c < 16; ++c) {
+ synth_reset_channel(&(rs->sc[c]));
+ }
+ rs->kcgain = 0;
+}
+
+static void synth_load(RSSynthChannel *sc, const double rate,
+ SynthFunction synthesize,
+ ADSRcfg const * const adsr) {
+ synth_reset_channel(sc);
+ init_adsr(&sc->adsr, rate,
+ adsr->tme[0], adsr->tme[1], adsr->tme[2],
+ adsr->vol[0], adsr->vol[1]);
+ sc->synthesize = synthesize;
+}
+
+
+/**
+ * internal abstraction of MIDI data handling
+ */
+static void synth_process_midi_event(void *synth, struct rmidi_event_t *ev) {
+ RSSynthesizer* rs = (RSSynthesizer*)synth;
+ switch(ev->type) {
+ case NOTE_ON:
+ if (rs->sc[ev->channel].miditable[ev->d.tone.note] <= 0)
+ rs->sc[ev->channel].miditable[ev->d.tone.note] = ev->d.tone.velocity;
+ break;
+ case NOTE_OFF:
+ if (rs->sc[ev->channel].miditable[ev->d.tone.note] > 0)
+ rs->sc[ev->channel].miditable[ev->d.tone.note] *= -1.0;
+ break;
+ case PROGRAM_CHANGE:
+ break;
+ case CONTROL_CHANGE:
+ if (ev->d.control.param == 0x00 || ev->d.control.param == 0x20) {
+ /* 0x00 and 0x20 are used for BANK select */
+ break;
+ } else
+ if (ev->d.control.param == 121) {
+ /* reset all controllers */
+ break;
+ } else
+ if (ev->d.control.param == 120 || ev->d.control.param == 123) {
+ /* Midi panic: 120: all sound off, 123: all notes off*/
+ synth_reset_channel(&(rs->sc[ev->channel]));
+ break;
+ } else
+ if (ev->d.control.param >= 120) {
+ /* params 122-127 are reserved - skip them. */
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/******************************************************************************
+ * PUBLIC API (used by lv2.c)
+ */
+
+/**
+ * align LV2 and internal synth buffers
+ * call synth_fragment as often as needed for the given LV2 buffer size
+ *
+ * @param synth synth-handle
+ * @param written samples written so far (offset in \ref out)
+ * @param nframes total samples to synthesize and write to the \out buffer
+ * @param out pointer to stereo output buffers
+ * @return end of buffer (written + nframes)
+ */
+static uint32_t synth_sound (void *synth, uint32_t written, const uint32_t nframes, float **out) {
+ RSSynthesizer* rs = (RSSynthesizer*)synth;
+
+ while (written < nframes) {
+ uint32_t nremain = nframes - written;
+
+ if (rs->boffset >= BUFFER_SIZE_SAMPLES) {
+ rs->boffset = 0;
+ synth_fragment(rs, BUFFER_SIZE_SAMPLES, rs->buf[0], rs->buf[1]);
+ }
+
+ uint32_t nread = MIN(nremain, (BUFFER_SIZE_SAMPLES - rs->boffset));
+
+ memcpy(&out[0][written], &rs->buf[0][rs->boffset], nread*sizeof(float));
+ memcpy(&out[1][written], &rs->buf[1][rs->boffset], nread*sizeof(float));
+
+ written += nread;
+ rs->boffset += nread;
+ }
+ return written;
+}
+
+/**
+ * parse raw midi-data.
+ *
+ * @param synth synth-handle
+ * @param data 8bit midi message
+ * @param size number of bytes in the midi-message
+ */
+static void synth_parse_midi(void *synth, uint8_t *data, size_t size) {
+ if (size < 2 || size > 3) return;
+ // All messages need to be 3 bytes; except program-changes: 2bytes.
+ if (size == 2 && (data[0] & 0xf0) != 0xC0) return;
+
+ struct rmidi_event_t ev;
+
+ ev.channel = data[0]&0x0f;
+ switch (data[0] & 0xf0) {
+ case 0x80:
+ ev.type=NOTE_OFF;
+ ev.d.tone.note=data[1]&0x7f;
+ ev.d.tone.velocity=data[2]&0x7f;
+ break;
+ case 0x90:
+ ev.type=NOTE_ON;
+ ev.d.tone.note=data[1]&0x7f;
+ ev.d.tone.velocity=data[2]&0x7f;
+ break;
+ case 0xB0:
+ ev.type=CONTROL_CHANGE;
+ ev.d.control.param=data[1]&0x7f;
+ ev.d.control.value=data[2]&0x7f;
+ break;
+ case 0xC0:
+ ev.type=PROGRAM_CHANGE;
+ ev.d.control.value=data[1]&0x7f;
+ break;
+ default:
+ return;
+ }
+ synth_process_midi_event(synth, &ev);
+}
+
+/**
+ * initialize the synth
+ * This should be called after synth_alloc()
+ * as soon as the sample-rate is known
+ *
+ * @param synth synth-handle
+ * @param rate sample-rate
+ */
+static void synth_init(void *synth, double rate) {
+ RSSynthesizer* rs = (RSSynthesizer*)synth;
+ rs->rate = rate;
+ rs->boffset = BUFFER_SIZE_SAMPLES;
+ const float tuning = 440;
+ for (int k=0; k < 128; k++) {
+ rs->freqs[k] = (2.0 * tuning / 32.0f) * powf(2, (k - 9.0) / 12.0) / rate;
+ assert(rs->freqs[k] < M_PI/2); // otherwise spatialization may phase out..
+ }
+ rs->kcfilt = 12.0 / rate;
+ synth_reset(synth);
+
+ for (int c=0; c < 16; c++) {
+ synth_load(&rs->sc[c], rate, &synthesize_sineP, &piano_adsr);
+ }
+}
+
+/**
+ * Allocate data-structure, create a handle for all other synth_* functions.
+ *
+ * This data should be freeded with \ref synth_free when the synth is no
+ * longer needed.
+ *
+ * The synth can only be used after calling \rev synth_init as well.
+ *
+ * @return synth-handle
+ */
+static void * synth_alloc(void) {
+ return calloc(1, sizeof(RSSynthesizer));
+}
+
+/**
+ * release synth data structure
+ * @param synth synth-handle
+ */
+static void synth_free(void *synth) {
+ free(synth);
+}
+/* vi:set ts=8 sts=2 sw=2: */
diff --git a/libs/plugins/reasonablesynth.lv2/wscript b/libs/plugins/reasonablesynth.lv2/wscript
new file mode 100644
index 0000000000..10dd3c21b8
--- /dev/null
+++ b/libs/plugins/reasonablesynth.lv2/wscript
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+import os
+import re
+import shutil
+import waflib.extras.autowaf as autowaf
+
+# Mandatory variables
+top = '.'
+out = 'build'
+
+def options(opt):
+ autowaf.set_options(opt)
+
+def configure(opt):
+ conf.load('compiler_c')
+ autowaf.configure(conf)
+ autowaf.set_c99_mode(conf)
+ autowaf.check_pkg(conf, 'lv2', atleast_version='1.4.1',
+ uselib_store='LV2_1_4_1')
+
+def build(bld):
+ bundle = 'reasonablesynth.lv2'
+ module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN)
+ module_ext = module_pat[module_pat.rfind('.'):]
+
+ # Build RDF files
+ for i in ['manifest.ttl', 'reasonablesynth.ttl']:
+ bld(features = 'subst',
+ source = i + '.in',
+ target = '%s/%s' % (bundle, i),
+ install_path = '${LV2DIR}/%s' % bundle,
+ LIB_EXT = module_ext)
+
+ # Build plugin library
+ obj = bld(features = 'c cshlib',
+ source = 'lv2.c',
+ dep_files = 'rsynth.c',
+ name = 'reasonablesynth',
+ target = '%s/reasonablesynth' % bundle,
+ install_path = '${LV2DIR}/%s' % bundle,
+ use = 'LV2_1_4_1')
+ obj.env.cshlib_PATTERN = module_pat
+
+# vi:set ts=4 sw=4 et: