initial commit, working basics.

- loads a hard-coded hydrogen kit
- repsonds to midi
- no individual control
- no gui
This commit is contained in:
Nick Lanham 2012-02-06 22:54:18 +01:00
commit 1976f033a3
7 changed files with 573 additions and 0 deletions

19
Makefile Normal file
View File

@ -0,0 +1,19 @@
BUNDLE = lv2pftci-drmr.lv2
INSTALL_DIR = /home/nick/.lv2
CC=gcc
$(BUNDLE): manifest.ttl drmr.ttl drmr.so
rm -rf $(BUNDLE)
mkdir $(BUNDLE)
cp manifest.ttl drmr.ttl drmr.so $(BUNDLE)
drmr.so: drmr.c drmr_hydrogen.c
$(CC) -g -shared -fPIC -DPIC drmr.c drmr_hydrogen.c `pkg-config --cflags --libs lv2-plugin sndfile` -lexpat -o drmr.so
install: $(BUNDLE)
mkdir -p $(INSTALL_DIR)
rm -rf $(INSTALL_DIR)/$(BUNDLE)
cp -R $(BUNDLE) $(INSTALL_DIR)
clean:
rm -rf $(BUNDLE) drmr.so

228
drmr.c Normal file
View File

@ -0,0 +1,228 @@
/*
LV2 DrMr plugin
Copyright 2012 Nick Lanham <nick@afternight.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "drmr.h"
int load_sample(char* path, drmr_sample* samp) {
SNDFILE* sndf;
int size;
printf("Loading: %s\n",path);
samp->active = 0;
memset(&(samp->info),0,sizeof(SF_INFO));
sndf = sf_open(path,SFM_READ,&(samp->info));
if (!sndf) {
fprintf(stderr,"Failed to open sound file: %s\n",sf_strerror(sndf));
return 1;
}
if (samp->info.channels > 2) {
fprintf(stderr, "File has too many channels. Can only handle mono/stereo samples\n");
return 1;
}
size = samp->info.frames * samp->info.channels;
samp->limit = size;
samp->data = malloc(size*sizeof(float));
if (!samp->data) {
fprintf(stderr,"Failed to allocate sample memory for %s\n",path);
return 1;
}
sf_read_float(sndf,samp->data,size);
sf_close(sndf);
return 0;
}
static LV2_Handle
instantiate(const LV2_Descriptor* descriptor,
double rate,
const char* bundle_path,
const LV2_Feature* const* features) {
int i;
DrMr* drmr = malloc(sizeof(DrMr));
drmr->map = NULL;
drmr->num_samples = 0;
// Map midi uri
while(*features) {
if (!strcmp((*features)->URI, LV2_URI_MAP_URI)) {
drmr->map = (LV2_URI_Map_Feature *)((*features)->data);
drmr->uris.midi_event = drmr->map->uri_to_id
(drmr->map->callback_data,
"http://lv2plug.in/ns/ext/event",
"http://lv2plug.in/ns/ext/midi#MidiEvent");
}
features++;
}
if (!drmr->map) {
fprintf(stderr, "LV2 host does not support urid:map.\n");
free(drmr);
return 0;
}
load_hydrogen_kit(drmr,"/usr/share/hydrogen/data/drumkits/GMkit/");
//load_hydrogen_kit(drmr,"/usr/share/hydrogen/data/drumkits/3355606kit/");
return (LV2_Handle)drmr;
}
static void
connect_port(LV2_Handle instance,
uint32_t port,
void* data) {
DrMr* drmr = (DrMr*)instance;
switch ((DrMrPortIndex)port) {
case DRMR_MIDI:
drmr->midi_port = (LV2_Event_Buffer*)data;
break;
case DRMR_LEFT:
drmr->left = (float*)data;
break;
case DRMR_RIGHT:
drmr->right = (float*)data;
break;
default:
break;
}
}
static void activate(LV2_Handle instance) { }
static void run(LV2_Handle instance, uint32_t n_samples) {
int i;
char first_active, one_active;
DrMr* drmr = (DrMr*)instance;
LV2_Event_Iterator eit;
if (lv2_event_begin(&eit,drmr->midi_port)) { // if we have any events
LV2_Event *cur_ev;
uint8_t* data;
while (lv2_event_is_valid(&eit)) {
cur_ev = lv2_event_get(&eit,&data);
if (cur_ev->type == drmr->uris.midi_event) {
int channel = *data & 15;
switch ((*data) >> 4) {
case 8: // ignore note-offs for now, should probably be a setting
//if (drmr->cur_samp) drmr->cur_samp->active = 0;
break;
case 9: {
uint8_t nn = data[1];
nn-=60; // middle c is our root note (setting?)
if (nn >= 0 && nn < drmr->num_samples) {
drmr->samples[nn].active = 1;
drmr->samples[nn].offset = 0;
}
break;
}
default:
printf("Unhandeled status: %i\n",(*data)>>4);
}
}
lv2_event_increment(&eit);
}
}
first_active = 1;
for (i = 0;i < drmr->num_samples;i++) {
int pos,lim;
drmr_sample* cs = drmr->samples+i;
if (cs->active) {
one_active = 1;
if (cs->info.channels == 1) { // play mono sample
lim = (n_samples < (cs->limit - cs->offset)?n_samples:(cs->limit-cs->offset));
if (first_active) {
for(pos = 0;pos < lim;pos++) {
drmr->left[pos] = cs->data[cs->offset];
drmr->right[pos] = cs->data[cs->offset];
cs->offset++;
}
first_active = 0;
} else {
for(pos = 0;pos < lim;pos++) {
drmr->left[pos] += cs->data[cs->offset];
drmr->right[pos] += cs->data[cs->offset];
cs->offset++;
}
}
} else { // play stereo sample
lim = (cs->limit-cs->offset)/cs->info.channels;
if (lim > n_samples) lim = n_samples;
if (first_active) {
for (pos=0;pos<lim;pos++) {
drmr->left[pos] = cs->data[cs->offset++];
drmr->right[pos] = cs->data[cs->offset++];
}
first_active = 0;
} else {
for (pos=0;pos<lim;pos++) {
drmr->left[pos] += cs->data[cs->offset++];
drmr->right[pos] += cs->data[cs->offset++];
}
}
}
if (cs->offset >= cs->limit) cs->active = 0;
}
}
if (first_active) { // didn't find any samples
int pos;
for(pos = 0;pos<n_samples;pos++) {
drmr->left[pos] = 0.0f;
drmr->right[pos] = 0.0f;
}
}
}
static void deactivate(LV2_Handle instance) {}
static void cleanup(LV2_Handle instance) {
free(instance);
}
const void* extension_data(const char* uri) {
return NULL;
}
static const LV2_Descriptor descriptor = {
DRMR_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;
}
}

70
drmr.h Normal file
View File

@ -0,0 +1,70 @@
/*
LV2 DrMr plugin
Copyright 2012 Nick Lanham <nick@afternight.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef DRMR_H
#define DRMR_H
#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
#include "lv2/lv2plug.in/ns/ext/event/event.h"
#include "lv2/lv2plug.in/ns/ext/event/event-helpers.h"
#include "lv2/lv2plug.in/ns/ext/uri-map/uri-map.h"
#include <sndfile.h>
// libsndfile stuff
typedef struct {
SF_INFO info;
char active;
uint32_t offset;
uint32_t limit;
float* data;
} drmr_sample;
int load_sample(char* path,drmr_sample* smp);
// lv2 stuff
#define DRMR_URI "http://github.com/nicklan/drmr"
typedef enum {
DRMR_MIDI = 0,
DRMR_LEFT,
DRMR_RIGHT,
DRMR_NUM_PORTS
} DrMrPortIndex;
typedef struct {
// Ports
float* left;
float* right;
LV2_Event_Buffer *midi_port;
// URIs
LV2_URI_Map_Feature* map;
struct {
uint32_t midi_event;
} uris;
// Samples
drmr_sample* samples;
uint8_t num_samples;
} DrMr;
#endif // DRMR_H

37
drmr.ttl Normal file
View File

@ -0,0 +1,37 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#>.
@prefix ev: <http://lv2plug.in/ns/ext/event#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix doap: <http://usefulinc.com/ns/doap#>.
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
<http://github.com/nicklan/drmr>
a lv2:Plugin;
lv2:binary <drmr.so>;
doap:name "DrMr Sampler";
doap:maintainer [
foaf:name "Nick Lanham" ;
foaf:homepage <http://github.com/nick/drmr/wiki> ;
foaf:mbox <nick@afternight.org>
] ;
doap:license <http://usefulinc.com/doap/licenses/gpl>;
lv2:port [
a ev:EventPort, lv2:InputPort;
lv2:index 0;
ev:supportsEvent <http://lv2plug.in/ns/ext/midi#MidiEvent>;
lv2:symbol "midi";
lv2:name "MIDI";
],
[
a lv2:AudioPort, lv2:OutputPort;
lv2:index 1;
lv2:symbol "left";
lv2:name "Left";
],
[
a lv2:AudioPort, lv2:OutputPort;
lv2:index 2;
lv2:symbol "right";
lv2:name "Right";
].

208
drmr_hydrogen.c Normal file
View File

@ -0,0 +1,208 @@
/*
LV2 DrMr plugin
Copyright 2012 Nick Lanham <nick@afternight.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Utilities for loading up a hydrogen kit
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "drmr.h"
#include "drmr_hydrogen.h"
#include "expat.h"
#define MAX_CHAR_DATA 512
struct instrument_info {
int id;
char* filename;
char* name;
struct instrument_info* next;
// maybe pan/vol/etc..
};
struct kit_info {
char* name;
char* desc;
// linked list of intruments, null terminated
struct instrument_info* instruments;
};
struct hp_info {
char in_info;
char in_instrument_list;
char in_instrument;
int cur_off;
char cur_buf[MAX_CHAR_DATA];
struct instrument_info* cur_instrument;
struct kit_info* kit_info;
};
static void XMLCALL
startElement(void *userData, const char *name, const char **atts)
{
struct hp_info* info = (struct hp_info*)userData;
info->cur_off = 0;
if (info->in_info) {
if (info->in_instrument_list) {
if (!strcmp(name,"instrument")) {
info->in_instrument = 1;
info->cur_instrument = malloc(sizeof(struct instrument_info));
memset(info->cur_instrument,0,sizeof(struct instrument_info));
}
} else {
if (!strcmp(name,"instrumentList"))
info->in_instrument_list = 1;
}
} else {
if (!strcmp(name,"drumkit_info"))
info->in_info = 1;
}
}
static void XMLCALL
endElement(void *userData, const char *name)
{
struct hp_info* info = (struct hp_info*)userData;
info->cur_buf[info->cur_off]='\0';
if (info->in_info && !info->in_instrument_list && !strcmp(name,"name"))
info->kit_info->name = strdup(info->cur_buf);
if (info->in_instrument) {
if (!strcmp(name,"id"))
info->cur_instrument->id = atoi(info->cur_buf);
if (!strcmp(name,"filename"))
info->cur_instrument->filename = strdup(info->cur_buf);
if (!strcmp(name,"name"))
info->cur_instrument->name = strdup(info->cur_buf);
}
info->cur_off = 0;
if (info->in_instrument && !strcmp(name,"instrument")) {
// ending an instrument, add current struct to end of list
struct instrument_info * cur_i = info->kit_info->instruments;
if (cur_i) {
while(cur_i->next) cur_i = cur_i->next;
cur_i->next = info->cur_instrument;
} else
info->kit_info->instruments = info->cur_instrument;
info->cur_instrument = NULL;
info->in_instrument = 0;
}
if (info->in_instrument_list && !strcmp(name,"instrumentList")) info->in_instrument_list = 0;
if (info->in_info && !strcmp(name,"drumkit_info")) info->in_info = 0;
}
static void XMLCALL
charData(void *userData,
const char* data,
int len) {
int i;
struct hp_info* info = (struct hp_info*)userData;
for(i = 0;i<len;i++) {
if (info->cur_off < MAX_CHAR_DATA) {
info->cur_buf[info->cur_off] = data[i];
info->cur_off++;
} else
fprintf(stderr,"Warning, losing data because too much\n");
}
}
int load_hydrogen_kit(DrMr* drmr, char* path) {
char* fp_buf;
FILE* file;
char buf[BUFSIZ];
XML_Parser parser;
int done;
struct hp_info info;
struct kit_info kit_info;
snprintf(buf,BUFSIZ,"%s/drumkit.xml",path);
printf("trying to load: %s\n",buf);
file = fopen(buf,"r");
if (!file) {
perror("Unable to open file:");
return 1;
}
parser = XML_ParserCreate(NULL);
memset(&info,0,sizeof(struct hp_info));
memset(&kit_info,0,sizeof(struct kit_info));
info.kit_info = &kit_info;
XML_SetUserData(parser, &info);
XML_SetElementHandler(parser, startElement, endElement);
XML_SetCharacterDataHandler(parser, charData);
do {
int len = (int)fread(buf, 1, sizeof(buf), file);
done = len < sizeof(buf);
if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR) {
fprintf(stderr,
"%s at line %lu\n",
XML_ErrorString(XML_GetErrorCode(parser)),
XML_GetCurrentLineNumber(parser));
return 1;
}
} while (!done);
XML_ParserFree(parser);
{
drmr_sample* samples;
struct instrument_info * cur_i;
int i = 0, num_inst = 0;
printf("Read kit: %s\n",kit_info.name);
cur_i = kit_info.instruments;
while(cur_i) { // first count how many samples we have
num_inst ++;
cur_i = cur_i->next;
}
printf("Loading %i instruments\n",num_inst);
samples = malloc(num_inst*sizeof(drmr_sample));
cur_i = kit_info.instruments;
while(cur_i) {
snprintf(buf,BUFSIZ,"%s/%s",path,cur_i->filename);
if (load_sample(buf,samples+i)) {
fprintf(stderr,"Could not load sample: %s\n",buf);
// TODO: Memory leak on previously loaded samples
return 1;
}
i++;
cur_i = cur_i->next;
}
if (num_inst > drmr->num_samples) {
// we have more, so we can safely swap our sample list in before updating num_samples
drmr->samples = samples;
drmr->num_samples = num_inst;
} else {
// previous has more, update count first
drmr->num_samples = num_inst;
drmr->samples = samples;
}
}
return 0;
}

6
drmr_hydrogen.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef DRMR_HYDRO_H
#define DRMR_HYDRO_H
int load_hydrogen_kit(DrMr* drmr, char* path);
#endif // DRMR_HYDRO_H

5
manifest.ttl Normal file
View File

@ -0,0 +1,5 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#>.
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
<http://github.com/nicklan/drmr>
a lv2:Plugin;
rdfs:seeAlso <drmr.ttl>.