Commit 9c420f32 authored by ale's avatar ale

Initial commit

parents
Makefile
Makefile.in
aclocal.m4
ar-lib
autom4te.cache
compile
config.h
*~
config.log
config.guess
config.status
configure
depcomp
.deps
.dirstamp
*.[ao]
install-sh
missing
stamp-*
config.h.in
config.sub
ltmain.sh
*.lo
*.la
.libs
libtool
load-drumkit
AM_CPPFLAGS = -I$(top_srcdir)/engine $(SNDFILE_CFLAGS) $(SAMPLERATE_CFLAGS)
noinst_LTLIBRARIES = libengine.la
libengine_la_SOURCES = \
engine/exponential_tables.h \
engine/adsr.c engine/adsr.h \
engine/voice.h engine/voice.c \
engine/drumkit.c engine/drumkit.h \
engine/sampler.c engine/sampler.h
libengine_la_LIBADD = $(SNDFILE_LIBS) $(SAMPLERATE_LIBS)
noinst_PROGRAMS = load-drumkit
load_drumkit_SOURCES = engine/load-drumkit.c
load_drumkit_LDADD = libengine.la
if LV2
lv2plugin_DATA = plugin/hydrumkit.ttl plugin/manifest.ttl
lv2plugin_LTLIBRARIES = hydrumkit.la hydrumkit_ui.la
hydrumkit_la_SOURCES = \
plugin/plugin.c \
plugin/atom_sink.h \
plugin/uris.h \
plugin/lv2_util.h
hydrumkit_la_LDFLAGS = -module -avoid-version -shared
hydrumkit_la_LIBADD = libengine.la
hydrumkit_ui_la_SOURCES = \
plugin/ui.c \
plugin/uris.h \
plugin/lv2_util.h
hydrumkit_ui_la_CPPFLAGS = $(GTK_CFLAGS)
hydrumkit_ui_la_LDFLAGS = -module -avoid-version -shared
hydrumkit_ui_la_LIBADD = libengine.la $(GTK_LIBS)
endif
AC_PREREQ([2.69])
AC_INIT([hydrumkit], [0.1], [ale@incal.net])
AC_CONFIG_SRCDIR([engine/sampler.c])
AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
# Enable LV2 plugin build.
AC_ARG_ENABLE(lv2,
AS_HELP_STRING([--enable-lv2], [enable LV2 plug-in build (default=yes)]),
[ac_lv2="$enableval"],
[ac_lv2="yes"])
# Set for alternate LV2 installation dir.
AC_ARG_WITH(lv2,
AS_HELP_STRING([--with-lv2=PATH], [use alternate LV2 install path]),
[CPPFLAGS="$CPPFLAGS -I$withval"])
# Set LV2 plugin installation dir.
ac_lv2_plugin_dir="$HOME/.lv2"
AC_ARG_WITH(lv2-plugin-dir,
AS_HELP_STRING([--with-lv2-plugin-dir=PATH], [set install path for LV2 plugins]),
[ac_lv2_plugin_dir="$withval"])
lv2plugindir="$ac_lv2_plugin_dir/hydrumkit.lv2"
AC_SUBST(lv2plugindir)
# Checks for programs.
AC_PROG_CC
AC_PROG_INSTALL
AC_PROG_LN_S
AC_PROG_MAKE_SET
AM_PROG_AR
LT_INIT
# Checks for typedefs, structures, and compiler characteristics.
# Checks for libraries.
AM_PATH_XML2([], [], [
AC_MSG_ERROR([libxml2 is missing])])
CPPFLAGS="$CPPFLAGS $XML_CPPFLAGS"
LIBS="$LIBS $XML_LIBS"
# Check for LV2 headers.
if test "x$ac_lv2" = "xyes"; then
AC_CHECK_HEADERS(lv2.h \
lv2/lv2plug.in/ns/ext/atom/atom.h \
lv2/lv2plug.in/ns/ext/atom/forge.h \
lv2/lv2plug.in/ns/ext/atom/util.h,
[ac_lv2="yes"], [ac_lv2="no"])
if test "x$ac_lv2" = "xyes"; then
AC_DEFINE(CONFIG_LV2, 1, [Define if LV2 plug-in build is enabled.])
ac_all_targets="lv2 $ac_all_targets"
ac_install_targets="install_lv2 $ac_install_targets"
ac_uninstall_targets="uninstall_lv2 $ac_uninstall_targets"
ac_clean_targets="clean_lv2 $ac_clean_targets"
AC_CACHE_CHECK([for lv2_atom_forge_object],
ac_cv_lv2_atom_forge_object, [
AC_TRY_LINK([#include "lv2/lv2plug.in/ns/ext/atom/forge.h"], [
// Checking for lv2_atom_forge_object...
LV2_Atom_Forge *forge;
LV2_Atom_Forge_Frame *frame;
lv2_atom_forge_object(forge, frame, 0, 101);
], ac_cv_lv2_atom_forge_object="yes", ac_cv_lv2_atom_forge_object="no")
])
ac_lv2_atom_forge_object=$ac_cv_lv2_atom_forge_object
if test "x$ac_lv2_atom_forge_object" = "xyes"; then
AC_DEFINE(CONFIG_LV2_ATOM_FORGE_OBJECT, 1, [Define if lv2_atom_forge_object is available.])
fi
AC_CACHE_CHECK([for lv2_atom_forge_key],
ac_cv_lv2_atom_forge_key, [
AC_TRY_LINK([#include "lv2/lv2plug.in/ns/ext/atom/forge.h"], [
// Checking for lv2_atom_forge_key...
LV2_Atom_Forge *forge;
lv2_atom_forge_key(forge, 102);
], ac_cv_lv2_atom_forge_key="yes", ac_cv_lv2_atom_forge_key="no")
])
ac_lv2_atom_forge_key=$ac_cv_lv2_atom_forge_key
if test "x$ac_lv2_atom_forge_key" = "xyes"; then
AC_DEFINE(CONFIG_LV2_ATOM_FORGE_KEY, 1, [Define if lv2_atom_forge_key is available.])
fi
AC_CHECK_HEADERS(lv2/lv2plug.in/ns/lv2core/lv2_util.h)
else
AC_MSG_WARN([*** LV2 header files not found.])
AC_MSG_WARN([*** LV2 plug-in build will be disabled.])
fi
fi
AM_CONDITIONAL([LV2], [test x$ac_lv2 = xyes])
# Checks for libraries via pkg-config.
PKG_CHECK_MODULES([SNDFILE], [sndfile])
PKG_CHECK_MODULES([SAMPLERATE], [samplerate])
PKG_CHECK_MODULES([GTK], [gtk+-3.0])
AC_CONFIG_FILES([
Makefile
])
AC_OUTPUT
#include "config.h"
#include <stdio.h>
#include "adsr.h"
#include "exponential_tables.h"
static float linear_interpolation_fraction(float a, float b, int num, int den) {
float val = (float)num / (float)den;
return a * (1 - val) + b * val;
}
void adsr_trigger(struct adsr *adsr, int attack, int decay, float sustain, int release) {
adsr->attack = attack;
adsr->decay = decay;
adsr->sustain = sustain;
adsr->release = release;
adsr->state = ADSR_ATTACK;
adsr->ticks = 0;
adsr->value = adsr->release_value = 0;
}
float adsr_get_value(struct adsr *adsr, float step) {
float value;
switch (adsr->state) {
case ADSR_ATTACK:
if (adsr->attack == 0) {
value = 1;
} else {
value = convex_exponent(linear_interpolation_fraction(0, 1, adsr->ticks, adsr->attack));
}
adsr->ticks += step;
if (adsr->ticks > adsr->attack) {
adsr->state = ADSR_DECAY;
adsr->ticks = 0;
}
break;
case ADSR_DECAY:
if (adsr->decay == 0) {
value = adsr->sustain;
} else {
value = concave_exponent(linear_interpolation_fraction(1, 0, adsr->ticks, adsr->decay)) * (1 - adsr->sustain) + adsr->sustain;
}
adsr->ticks += step;
if (adsr->ticks > adsr->decay) {
adsr->state = ADSR_SUSTAIN;
adsr->ticks = 0;
}
break;
case ADSR_SUSTAIN:
value = adsr->sustain;
break;
case ADSR_RELEASE:
value = concave_exponent(linear_interpolation_fraction(1, 0, adsr->ticks, adsr->release)) * adsr->release_value;
adsr->ticks += step;
if (adsr->ticks > adsr->release) {
adsr->state = ADSR_IDLE;
adsr->ticks = 0;
}
break;
default:
value = 0;
}
adsr->value = value;
return value;
}
void adsr_release(struct adsr *adsr) {
if (adsr->state == ADSR_IDLE || adsr->state == ADSR_RELEASE)
return;
adsr->release_value = adsr->value;
adsr->state = ADSR_RELEASE;
adsr->ticks = 0;
}
#ifndef __adsr_H
#define __adsr_H 1
#define ADSR_IDLE 0
#define ADSR_ATTACK 1
#define ADSR_DECAY 2
#define ADSR_SUSTAIN 3
#define ADSR_RELEASE 4
struct adsr {
int attack, decay, release;
float sustain;
int state;
int ticks;
float value;
float release_value;
};
void adsr_trigger(struct adsr *, int, int, float, int);
float adsr_get_value(struct adsr *, float);
void adsr_release(struct adsr *);
#endif
/* config.h. Generated from config.h.in by configure. */
/* config.h.in. Generated from configure.ac by autoheader. */
/* Name of package */
#define PACKAGE "hydrumkit"
/* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT "ale@incal.net"
/* Define to the full name of this package. */
#define PACKAGE_NAME "hydrumkit"
/* Define to the full name and version of this package. */
#define PACKAGE_STRING "hydrumkit 0.1"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "hydrumkit"
/* Define to the home page for this package. */
#define PACKAGE_URL ""
/* Define to the version of this package. */
#define PACKAGE_VERSION "0.1"
/* Version number of package */
#define VERSION "0.1"
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <samplerate.h>
#include "drumkit.h"
static void instrument_set_defaults(struct instrument *ins) {
ins->pan_l = ins->pan_r = 1;
ins->gain = ins->volume = 1;
ins->envelope_attack = 0;
ins->envelope_decay = 0;
ins->envelope_sustain = 1;
ins->envelope_release = 1000;
ins->mute_group = -1;
ins->note = 0;
}
static char *sf_format_name(int format) {
static char fmtbuf[32];
const char *major = "weird", *subtype = NULL;
switch (format & SF_FORMAT_TYPEMASK) {
case SF_FORMAT_WAV: major = "wav"; break;
case SF_FORMAT_AIFF: major = "aiff"; break;
case SF_FORMAT_FLAC: major = "flac"; break;
}
switch (format & SF_FORMAT_SUBMASK) {
case SF_FORMAT_PCM_S8: subtype = "s8"; break;
case SF_FORMAT_PCM_U8: subtype = "u8"; break;
case SF_FORMAT_PCM_16: subtype = "s16"; break;
case SF_FORMAT_PCM_24: subtype = "s24"; break;
case SF_FORMAT_PCM_32: subtype = "s32"; break;
case SF_FORMAT_FLOAT: subtype = "float"; break;
case SF_FORMAT_DOUBLE: subtype = "double"; break;
case SF_FORMAT_VORBIS: subtype = "vorbis"; break;
}
strcpy(fmtbuf, major);
if (subtype) {
strcat(fmtbuf, "/");
strcat(fmtbuf, subtype);
}
return fmtbuf;
}
static void print_instrument(struct instrument *ins) {
int i;
printf("instrument %s: pan=%g/%g, gain=%g\n", ins->name, ins->pan_l,
ins->pan_r, ins->gain);
for (i = 0; i < ins->num_layers; i++) {
printf(" + [%f,%f] @%dHz %s/%s\n", ins->layers[i].min, ins->layers[i].max,
ins->layers[i].sample.info.samplerate,
(ins->layers[i].sample.info.channels > 1) ? "s" : "m",
sf_format_name(ins->layers[i].sample.info.format));
}
}
static const char *xpath_value_as_string(xmlNodePtr node, const char *xpathExpr,
xmlXPathContextPtr xpathCtx) {
xmlXPathObjectPtr res;
const char *content;
res = xmlXPathNodeEval(node, (const xmlChar *)xpathExpr, xpathCtx);
if (res == NULL || res->nodesetval == NULL) {
return NULL;
}
if (res->nodesetval->nodeNr < 1) {
return NULL;
}
content = (const char *)res->nodesetval->nodeTab[0]->content;
xmlXPathFreeObject(res);
return content;
}
static void xpath_get_float(float *out, xmlNodePtr node, const char *xpathExpr,
xmlXPathContextPtr xpathCtx) {
const char *s;
s = xpath_value_as_string(node, xpathExpr, xpathCtx);
if (s == NULL) {
return;
}
*out = strtof(s, NULL);
}
static void xpath_get_int(int *out, xmlNodePtr node, const char *xpathExpr,
xmlXPathContextPtr xpathCtx) {
const char *s;
s = xpath_value_as_string(node, xpathExpr, xpathCtx);
if (s == NULL) {
return;
}
*out = atoi(s);
}
static void xpath_get_string(char **out, xmlNodePtr node,
const char *xpathExpr,
xmlXPathContextPtr xpathCtx) {
*out = strdup(xpath_value_as_string(node, xpathExpr, xpathCtx));
}
static int xpath_count(xmlNodePtr node, const char *xpathExpr, xmlXPathContextPtr xpathCtx) {
xmlXPathObjectPtr res;
int n;
res = xmlXPathNodeEval(node, (const xmlChar *)xpathExpr, xpathCtx);
if (res == NULL || res->nodesetval == NULL) {
return 0;
}
n = res->nodesetval->nodeNr;
xmlXPathFreeObject(res);
return n;
}
// Our version of dirname that does not modify the source. Always
// returns newly allocated memory. Assumes that the input does not end
// with a slash.
static char *path_dirname(const char *filename) {
char *buf = strdup(filename), *p;
if ((p = strrchr(buf, '/')) != NULL) {
*p = '\0';
} else {
strcpy(buf, ".");
}
return buf;
}
static char *path_join(const char *base, const char *filename) {
char *buf = (char *)malloc(strlen(base) + strlen(filename) + 2);
sprintf(buf, "%s/%s", base, filename);
return buf;
}
static int load_sample_data(struct sample *sample, SNDFILE *sndfile, int target_samplerate) {
SF_INFO *info = &sample->info;
float *tmp, *p, *wl, *wr;
int i;
tmp = (float *)malloc(sizeof(float) * info->frames * info->channels);
sf_seek(sndfile, 0, SEEK_SET);
sf_readf_float(sndfile, tmp, info->frames);
// Resample the input if necessary, and rewrite the SF_INFO to
// reflect the updated information.
if (target_samplerate != info->samplerate) {
SRC_DATA src_data = {0};
double ratio;
int max_out_frames;
float *resampled;
ratio = (double)target_samplerate / (double)info->samplerate;
max_out_frames = (int)(ratio * info->frames) + 32;
resampled = (float *)malloc(sizeof(float) * max_out_frames * info->channels);
src_data.data_in = tmp;
src_data.data_out = resampled;
src_data.input_frames = info->frames;
src_data.output_frames = max_out_frames;
src_data.src_ratio = ratio;
src_simple(&src_data, SRC_SINC_MEDIUM_QUALITY, info->channels);
free(tmp);
tmp = resampled;
info->samplerate = target_samplerate;
info->frames = src_data.output_frames_gen;
}
sample->data_l = (float *)malloc(sizeof(float) * info->frames);
sample->data_r = (float *)malloc(sizeof(float) * info->frames);
// Split the file data into separate channels, duplicating if mono.
for (i = 0, p = tmp, wl = sample->data_l, wr = sample->data_r;
i < info->frames;
i++, p += info->channels, wl++, wr++) {
*wl = *p;
if (info->channels > 1) {
*wr = *(p + 1);
} else {
*wr = *p;
}
}
free(tmp);
return 1;
}
static int load_sample(struct sample *sample, const char *path, int target_samplerate) {
SF_INFO *info = &sample->info;
SNDFILE *sndfile;
int r = 1;
sndfile = sf_open(path, SFM_READ, info);
if (!sndfile) {
printf("failed to open sample %s: %s\n", path, sf_strerror(NULL));
return 0;
}
// Read all the sample data.
if (!load_sample_data(sample, sndfile, target_samplerate)) {
printf("failed to load sample data from %s\n", path);
r = 0;
}
sf_close(sndfile);
return r;
}
static void free_sample(struct sample *sample) {
if (sample->data_l) {
free(sample->data_l);
}
if (sample->data_r) {
free(sample->data_r);
}
}
static int parse_layer(struct layer *layer, xmlNodePtr node,
xmlXPathContextPtr xpathCtx, const char *basedir,
int target_samplerate) {
char *rel_filename = NULL, *filename;
int r;
xpath_get_float(&layer->min, node, "dk:min/text()", xpathCtx);
xpath_get_float(&layer->max, node, "dk:max/text()", xpathCtx);
xpath_get_float(&layer->gain, node, "dk:gain/text()", xpathCtx);
xpath_get_string(&rel_filename, node, "dk:filename/text()", xpathCtx);
if (rel_filename == NULL) {
return 0;
}
filename = path_join(basedir, rel_filename);
r = load_sample(&layer->sample, filename, target_samplerate);
free(filename);
free(rel_filename);
return r;
}
static void free_layer(struct layer *layer) {
free_sample(&layer->sample);
}
static int parse_instrument(struct instrument *ins, xmlNodePtr node,
xmlXPathContextPtr xpathCtx, const char *basedir,
int target_samplerate) {
const char *instrument_id;
xmlXPathObjectPtr layers = NULL;
int i, num_components;
instrument_id = xpath_value_as_string(node, "dk:id/text()", xpathCtx);
if (instrument_id == NULL) {
return 0;
}
xpath_get_string(&ins->name, node, "dk:name/text()", xpathCtx);
xpath_get_float(&ins->pan_l, node, "dk:pan_L/text()", xpathCtx);
xpath_get_float(&ins->pan_r, node, "dk:pan_R/text()", xpathCtx);
xpath_get_float(&ins->gain, node, "dk:gain/text()", xpathCtx);
xpath_get_float(&ins->volume, node, "dk:volume/text()", xpathCtx);
xpath_get_float(&ins->envelope_attack, node, "dk:Attack/text()", xpathCtx);
xpath_get_float(&ins->envelope_decay, node, "dk:Decay/text()", xpathCtx);
xpath_get_float(&ins->envelope_sustain, node, "dk:Sustain/text()", xpathCtx);
xpath_get_float(&ins->envelope_release, node, "dk:Release/text()", xpathCtx);
xpath_get_int(&ins->mute_group, node, "dk:muteGroup/text()", xpathCtx);
xpath_get_int(&ins->note, node, "dk:midiOutNote/text()", xpathCtx);
// Sanity check to spot multi-component instruments.
num_components = xpath_count(node, ".//dk:instrumentComponent", xpathCtx);
if (num_components > 1) {
fprintf(stderr, "found instrument with multiple (%d) components!\n", num_components);
}
layers = xmlXPathNodeEval(node, (xmlChar *)".//dk:layer", xpathCtx);
if (layers == NULL || layers->nodesetval == NULL) {
printf("error: instrument has no layers?\n");
goto fail;
}
ins->num_layers = layers->nodesetval->nodeNr;
ins->layers = (struct layer *)calloc(ins->num_layers, sizeof(struct layer));
for (i = 0; i < ins->num_layers; i++) {
if (!parse_layer(&ins->layers[i], layers->nodesetval->nodeTab[i],
xpathCtx, basedir, target_samplerate)) {
printf("error parsing instrument layer %d\n", i);
goto fail;
}
}
xmlXPathFreeObject(layers);
return 1;
fail:
xmlXPathFreeObject(layers);
return 0;
}
static void free_instrument(struct instrument *ins) {
int i;
for (i = 0; i < ins->num_layers; i++) {
free_layer(&ins->layers[i]);
}
free(ins->layers);
free(ins->name);
}
struct layer *instrument_find_layer_by_velocity(struct instrument *ins, float velocity) {
int i;
for (i = 0; i < ins->num_layers; i++) {
if (ins->layers[i].max > velocity) {
return &ins->layers[i];
}
}
return &ins->layers[ins->num_layers - 1];
}
static int parse_instruments(struct instrument **instruments,