drmr2/drmr_hydrogen.c
Nick Lanham f6a8f9a514 Number of fixes: don't hold mutex while loading, fix ui crash when kit index out of range, don't load negative kit indexes, respond to midi base note port events.
Most important here is that the mutex change means the run loop with not block for a significant period ever, which previously was causing the jack thread to get zombified.
2012-02-15 22:56:08 +01:00

561 lines
16 KiB
C

/* drmr_hydrogen.c
* LV2 DrMr plugin
* Copyright 2012 Nick Lanham <nick@afternight.org>
*
* Public License v3. source code is available at
* <http://github.com/nicklan/drmr>
* 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 <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <math.h>
#include "samplerate.h"
#include "drmr.h"
#include "drmr_hydrogen.h"
#include "expat.h"
/* Below is a list of the locations that DrMr will
* search for drumkit files. It will scan each sub-directory
* in these locations (non-recursive) for a drumkit.xml
* file, and if found, parse it and add it to the list
* of available kits.
*
* Strings that start with a ~ will be expanded to the HOME
* environment variable. NB: only a ~ at the start of a string
* will be expanded, ones in the middle will be left in place.
*/
static char* default_drumkit_locations[] = {
"/usr/share/hydrogen/data/drumkits/",
"/usr/local/share/hydrogen/data/drumkits/",
"/usr/share/drmr/drumkits/",
"~/.hydrogen/data/drumkits/",
"~/.drmr/drumkits/",
NULL
};
// Quality of conversion for libsamplerate.
// See http://www.mega-nerd.com/SRC/api_misc.html#Converters
// for info about availble qualities
#define RATE_CONV_QUALITY SRC_SINC_MEDIUM_QUALITY
#define MAX_CHAR_DATA 512
struct instrument_layer {
char* filename;
float min;
float max;
float gain;
struct instrument_layer *next;
};
struct instrument_info {
int id;
char* filename;
char* name;
float gain;
struct instrument_layer *layers;
struct instrument_info *next;
// maybe pan/vol/etc..
};
struct kit_info {
char* name;
char* desc;
int inst_count;
// linked list of intruments, null terminated
struct instrument_info* instruments;
};
struct hp_info {
char scan_only;
char in_info;
char in_instrument_list;
char in_instrument;
char in_layer;
char counted_cur_inst;
int cur_off;
char cur_buf[MAX_CHAR_DATA];
struct instrument_info* cur_instrument;
struct instrument_layer* cur_layer;
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) {
if (!strcmp(name,"layer") && !info->scan_only) {
info->in_layer = 1;
info->cur_layer = malloc(sizeof(struct instrument_layer));
memset(info->cur_layer,0,sizeof(struct instrument_layer));
}
}
if (info->in_instrument_list) {
if (!strcmp(name,"instrument")) {
info->in_instrument = 1;
if (!info->scan_only) {
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;
if (info->cur_off == MAX_CHAR_DATA) info->cur_off--;
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->scan_only && info->in_info && !info->in_instrument_list && !strcmp(name,"info"))
info->kit_info->desc = strdup(info->cur_buf);
if (info->in_layer && !info->scan_only) {
if (!strcmp(name,"filename"))
info->cur_layer->filename = strdup(info->cur_buf);
if (!strcmp(name,"min"))
info->cur_layer->min = atof(info->cur_buf);
if (!strcmp(name,"max"))
info->cur_layer->max = atof(info->cur_buf);
if (!strcmp(name,"gain"))
info->cur_layer->gain = atof(info->cur_buf);
}
if (info->in_instrument) {
if (info->scan_only) {
if (!strcmp(name,"filename") && !info->counted_cur_inst) {
info->kit_info->inst_count++;
info->counted_cur_inst = 1;
}
}
else if (!info->in_layer) {
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->scan_only &&
info->in_layer &&
!strcmp(name,"layer") &&
info->cur_layer->filename) {
struct instrument_layer *cur_l = info->cur_instrument->layers;
if (cur_l) {
while(cur_l->next) cur_l = cur_l->next;
cur_l->next = info->cur_layer;
} else
info->cur_instrument->layers = info->cur_layer;
info->cur_layer = NULL;
info->in_layer = 0;
}
if (info->scan_only && info->in_instrument && !strcmp(name,"instrument")) {
info->counted_cur_inst = 0;
info->in_instrument = 0;
}
if (!info->scan_only && info->in_instrument &&
(info->cur_instrument && (info->cur_instrument->filename || info->cur_instrument->layers)) &&
!strcmp(name,"instrument")) {
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;
if (!info->in_info) return;
for(i = 0;i<len;i++) {
if (info->cur_off < MAX_CHAR_DATA) {
info->cur_buf[info->cur_off] = data[i];
info->cur_off++;
}
}
}
struct kit_list {
scanned_kit* skit;
struct kit_list* next;
};
// see note above at default_drumkit_locations
// for how this function works
static char* expand_path(char* path, char* buf) {
char *home_dir;
int n;
if (*path != '~') return path;
home_dir = getenv("HOME");
if (!home_dir) {
fprintf(stderr,"Home dir not set, can't expand ~ paths\n");
return 0;
}
n = snprintf(buf,BUFSIZ,"%s%s",home_dir,path+1);
if (n >= BUFSIZ) {
fprintf(stderr,"Path too long for buffer, can't expand: %s\n",path);
return 0;
}
return buf;
}
kits* scan_kits() {
DIR* dp;
FILE* file;
XML_Parser parser;
int done;
struct hp_info info;
struct kit_info kit_info;
struct dirent *ep;
int cp = 0;
char* cur_path = default_drumkit_locations[cp++];
kits* ret = malloc(sizeof(kits));
struct kit_list* scanned_kits = NULL;
char buf[BUFSIZ], path_buf[BUFSIZ];
ret->num_kits = 0;
while (cur_path) {
cur_path = expand_path(cur_path,path_buf);
if (!cur_path) {
cur_path = default_drumkit_locations[cp++];
continue;
}
dp = opendir (cur_path);
if (dp != NULL) {
while ((ep = readdir (dp))) {
if (ep->d_name[0]=='.') continue;
if (snprintf(buf,BUFSIZ,"%s/%s/drumkit.xml",cur_path,ep->d_name) >= BUFSIZ) {
fprintf(stderr,"Warning: Skipping scan of %s as path name is too long\n",cur_path);
continue;
}
file = fopen(buf,"r");
if (!file) continue; // couldn't open file
parser = XML_ParserCreate(NULL);
memset(&info,0,sizeof(struct hp_info));
memset(&kit_info,0,sizeof(struct kit_info));
info.kit_info = &kit_info;
info.scan_only = 1;
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));
break;
}
} while (!done);
XML_ParserFree(parser);
if (info.kit_info->name) {
scanned_kit* kit = malloc(sizeof(scanned_kit));
struct kit_list* node = malloc(sizeof(struct kit_list));
memset(kit,0,sizeof(scanned_kit));
memset(node,0,sizeof(struct kit_list));
kit->name = info.kit_info->name;
kit->desc = info.kit_info->desc;
kit->samples = info.kit_info->inst_count;
snprintf(buf,BUFSIZ,"%s/%s/",cur_path,ep->d_name);
kit->path = strdup(buf);
node->skit = kit;
struct kit_list * cur_k = scanned_kits;
if (cur_k) {
while(cur_k->next) cur_k = cur_k->next;
cur_k->next = node;
} else
scanned_kits = node;
}
}
(void) closedir (dp);
}
else if (errno != ENOENT)
fprintf(stderr,"Couldn't open %s: %s\n",cur_path,strerror(errno));
cur_path = default_drumkit_locations[cp++];
}
// valid kits are in scanned_kits at this point
cp = 0;
struct kit_list * cur_k = scanned_kits;
while(cur_k) {
//printf("found kit: %s\nat:%s\n\n",cur_k->skit->name,cur_k->skit->path);
cur_k = cur_k->next;
cp++;
}
printf("found %i kits\n",cp);
ret->num_kits = cp;
ret->kits = malloc(cp*sizeof(scanned_kit));
cur_k = scanned_kits;
cp = 0;
while(cur_k) {
ret->kits[cp].name = cur_k->skit->name;
ret->kits[cp].desc = cur_k->skit->desc;
ret->kits[cp].path = cur_k->skit->path;
ret->kits[cp].samples = cur_k->skit->samples;
cp++;
free(cur_k->skit);
cur_k = cur_k->next;
// free each node as we go along
free(scanned_kits);
scanned_kits = cur_k;
}
return ret;
}
void free_samples(drmr_sample* samples, int num_samples) {
int i,j;
for (i=0;i<num_samples;i++) {
if (samples[i].layer_count == 0) {
if (samples[i].info) free(samples[i].info);
if (samples[i].data) free(samples[i].data);
} else {
for (j = 0;j < samples[i].layer_count;j++) {
if (samples[i].layers[j].info) free(samples[i].layers[j].info);
if (samples[i].layers[j].data) free(samples[i].layers[j].data);
}
free(samples[i].layers);
}
}
free(samples);
}
void free_kits(kits* kits) {
int i;
for (i = 0;i < kits->num_kits;i++) {
free(kits->kits[i].name);
free(kits->kits[i].desc);
free(kits->kits[i].path);
}
free(kits->kits);
free(kits);
}
int load_sample(char* path, drmr_layer* layer, double target_rate) {
SNDFILE* sndf;
long size;
//printf("Loading: %s\n",path);
layer->info = malloc(sizeof(SF_INFO));
memset(layer->info,0,sizeof(SF_INFO));
sndf = sf_open(path,SFM_READ,layer->info);
if (!sndf) {
fprintf(stderr,"Failed to open sound file: %s - %s\n",path,sf_strerror(sndf));
free(layer->info);
return 1;
}
if (layer->info->channels > 2) {
fprintf(stderr, "File has too many channels. Can only handle mono/stereo samples\n");
free(layer->info);
return 1;
}
size = layer->info->frames * layer->info->channels;
layer->limit = size;
layer->data = malloc(size*sizeof(float));
if (!layer->data) {
fprintf(stderr,"Failed to allocate sample memory for %s\n",path);
free(layer->info);
return 1;
}
sf_read_float(sndf,layer->data,size);
sf_close(sndf);
// convert rate if needed
if (layer->info->samplerate != target_rate) {
SRC_DATA src_data;
int stat;
double ratio = (target_rate/layer->info->samplerate);
long out_frames = (long)ceil(layer->info->frames * ratio);
long out_size = out_frames*layer->info->channels;
float *data_out = malloc(sizeof(float)*out_size);
src_data.data_in = layer->data;
src_data.input_frames = layer->info->frames;
src_data.data_out = data_out;
src_data.output_frames = out_frames;
src_data.src_ratio = ratio;
stat = src_simple(&src_data,RATE_CONV_QUALITY,layer->info->channels);
if (stat) {
fprintf(stderr,"Failed to convert rate for %s: %s. Using original rate\n",
path,src_strerror(stat));
free(data_out);
return 0;
}
if (src_data.input_frames_used != layer->info->frames)
fprintf(stderr,"Didn't consume all input frames. used: %li had: %li gened: %li\n",
src_data.input_frames_used, layer->info->frames,src_data.output_frames_gen);
free(layer->data);
layer->data = data_out;
layer->limit = src_data.output_frames_gen*layer->info->channels;
layer->info->samplerate = target_rate;
layer->info->frames = src_data.output_frames_gen;
}
return 0;
}
drmr_sample* load_hydrogen_kit(char *path, double rate, int *num_samples) {
FILE* file;
char buf[BUFSIZ];
XML_Parser parser;
int done;
struct hp_info info;
struct kit_info kit_info;
drmr_sample *samples;
struct instrument_info * cur_i;
int i = 0, num_inst = 0;
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 NULL;
}
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 NULL;
}
} while (!done);
XML_ParserFree(parser);
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) {
if (cur_i->filename) { // top level filename, just make one dummy layer
drmr_layer *layer = malloc(sizeof(drmr_layer));
layer->min = 0;
layer->max = 1;
snprintf(buf,BUFSIZ,"%s/%s",path,cur_i->filename);
if (load_sample(buf,layer,rate)) {
fprintf(stderr,"Could not load sample: %s\n",buf);
// set limit to zero, will never try and play
layer->info = NULL;
layer->limit = 0;
layer->data = NULL;
}
samples[i].layer_count = 0;
samples[i].layers = NULL;
samples[i].offset = 0;
samples[i].info = layer->info;
samples[i].limit = layer->limit;
samples[i].data = layer->data;
free(layer);
} else {
int layer_count = 0;
int j;
struct instrument_layer *cur_l = cur_i->layers;
while(cur_l) {
layer_count++;
cur_l = cur_l->next;
}
samples[i].layer_count = layer_count;
samples[i].layers = malloc(sizeof(drmr_layer)*layer_count);
cur_l = cur_i->layers;
j = 0;
while(cur_l) {
snprintf(buf,BUFSIZ,"%s/%s",path,cur_l->filename);
if (load_sample(buf,samples[i].layers+j,rate)) {
fprintf(stderr,"Could not load sample: %s\n",buf);
// set limit to zero, will never try and play
samples[i].layers[j].info = NULL;
samples[i].layers[j].limit = 0;
samples[i].layers[j].data = NULL;
}
samples[i].layers[j].min = cur_l->min;
samples[i].layers[j].max = cur_l->max;
j++;
cur_l = cur_l->next;
}
}
samples[i].active = 0;
i++;
cur_i = cur_i->next;
}
*num_samples = num_inst;
return samples;
}