From 1976f033a30f9cffd92c2abd1fcceb7d78baa162 Mon Sep 17 00:00:00 2001 From: Nick Lanham Date: Mon, 6 Feb 2012 22:54:18 +0100 Subject: [PATCH] initial commit, working basics. - loads a hard-coded hydrogen kit - repsonds to midi - no individual control - no gui --- Makefile | 19 ++++ drmr.c | 228 ++++++++++++++++++++++++++++++++++++++++++++++++ drmr.h | 70 +++++++++++++++ drmr.ttl | 37 ++++++++ drmr_hydrogen.c | 208 +++++++++++++++++++++++++++++++++++++++++++ drmr_hydrogen.h | 6 ++ manifest.ttl | 5 ++ 7 files changed, 573 insertions(+) create mode 100644 Makefile create mode 100644 drmr.c create mode 100644 drmr.h create mode 100644 drmr.ttl create mode 100644 drmr_hydrogen.c create mode 100644 drmr_hydrogen.h create mode 100644 manifest.ttl diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..13a5e26 --- /dev/null +++ b/Makefile @@ -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 \ No newline at end of file diff --git a/drmr.c b/drmr.c new file mode 100644 index 0000000..5d47736 --- /dev/null +++ b/drmr.c @@ -0,0 +1,228 @@ +/* + LV2 DrMr plugin + Copyright 2012 Nick Lanham + + 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 +#include +#include + +#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;posleft[pos] = cs->data[cs->offset++]; + drmr->right[pos] = cs->data[cs->offset++]; + } + first_active = 0; + } else { + for (pos=0;posleft[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;posleft[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; + } +} diff --git a/drmr.h b/drmr.h new file mode 100644 index 0000000..d7dc9b7 --- /dev/null +++ b/drmr.h @@ -0,0 +1,70 @@ +/* + LV2 DrMr plugin + Copyright 2012 Nick Lanham + + 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 + +// 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 diff --git a/drmr.ttl b/drmr.ttl new file mode 100644 index 0000000..5d71655 --- /dev/null +++ b/drmr.ttl @@ -0,0 +1,37 @@ +@prefix lv2: . +@prefix ev: . +@prefix foaf: . +@prefix doap: . +@prefix rdf: . +@prefix rdfs: . + + + a lv2:Plugin; + lv2:binary ; + doap:name "DrMr Sampler"; + doap:maintainer [ + foaf:name "Nick Lanham" ; + foaf:homepage ; + foaf:mbox + ] ; + doap:license ; + lv2:port [ + a ev:EventPort, lv2:InputPort; + lv2:index 0; + ev:supportsEvent ; + 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"; + ]. \ No newline at end of file diff --git a/drmr_hydrogen.c b/drmr_hydrogen.c new file mode 100644 index 0000000..9c7503c --- /dev/null +++ b/drmr_hydrogen.c @@ -0,0 +1,208 @@ +/* + LV2 DrMr plugin + Copyright 2012 Nick Lanham + + 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 +#include +#include + +#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;icur_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; + +} diff --git a/drmr_hydrogen.h b/drmr_hydrogen.h new file mode 100644 index 0000000..1d50c98 --- /dev/null +++ b/drmr_hydrogen.h @@ -0,0 +1,6 @@ +#ifndef DRMR_HYDRO_H +#define DRMR_HYDRO_H + +int load_hydrogen_kit(DrMr* drmr, char* path); + +#endif // DRMR_HYDRO_H diff --git a/manifest.ttl b/manifest.ttl new file mode 100644 index 0000000..f68faa1 --- /dev/null +++ b/manifest.ttl @@ -0,0 +1,5 @@ +@prefix lv2: . +@prefix rdfs: . + + a lv2:Plugin; + rdfs:seeAlso . \ No newline at end of file