From e2da2f9ba634209cfe95507d83bf84088d4950df Mon Sep 17 00:00:00 2001 From: ale <ale@incal.net> Date: Fri, 8 Mar 2019 22:53:20 +0000 Subject: [PATCH] Working version --- Makefile.am | 5 ++- engine/adsr.c | 19 ++++++++ engine/config.h | 26 ----------- engine/drumkit.c | 24 +++++++++- engine/sampler.c | 12 ++++- engine/verify-voice.c | 101 ++++++++++++++++++++++++++++++++++++++++++ engine/voice.c | 30 +++++++------ engine/voice.h | 4 +- plugin/hydrumkit.ttl | 2 +- plugin/plugin.c | 3 +- plugin/ui.c | 26 ++++------- plugin/uris.h | 16 ------- 12 files changed, 188 insertions(+), 80 deletions(-) delete mode 100644 engine/config.h create mode 100644 engine/verify-voice.c diff --git a/Makefile.am b/Makefile.am index 30ae5f7..f951194 100644 --- a/Makefile.am +++ b/Makefile.am @@ -10,11 +10,14 @@ libengine_la_SOURCES = \ engine/sampler.c engine/sampler.h libengine_la_LIBADD = $(SNDFILE_LIBS) $(SAMPLERATE_LIBS) -noinst_PROGRAMS = load-drumkit +noinst_PROGRAMS = load-drumkit verify-voice load_drumkit_SOURCES = engine/load-drumkit.c load_drumkit_LDADD = libengine.la +verify_voice_SOURCES = engine/verify-voice.c +verify_voice_LDADD = libengine.la + if LV2 lv2plugin_DATA = plugin/hydrumkit.ttl plugin/manifest.ttl diff --git a/engine/adsr.c b/engine/adsr.c index 4eb400a..078ff60 100644 --- a/engine/adsr.c +++ b/engine/adsr.c @@ -11,6 +11,24 @@ static float linear_interpolation_fraction(float a, float b, int num, int den) { } void adsr_trigger(struct adsr *adsr, int attack, int decay, float sustain, int release) { + // Normalize values. + if (attack < 0) + attack = 0; + if (decay < 0) + decay = 0; + if (sustain < 0) + sustain = 0; + if (release < 256) + release = 256; + if (attack > 100000) + attack = 100000; + if (decay > 100000) + decay = 100000; + if (sustain > 1.0) + sustain = 1.0; + if (release > 100256) + release = 100256; + adsr->attack = attack; adsr->decay = decay; adsr->sustain = sustain; @@ -52,6 +70,7 @@ float adsr_get_value(struct adsr *adsr, float step) { case ADSR_SUSTAIN: value = adsr->sustain; + //adsr->ticks += step; break; case ADSR_RELEASE: diff --git a/engine/config.h b/engine/config.h deleted file mode 100644 index a9623c7..0000000 --- a/engine/config.h +++ /dev/null @@ -1,26 +0,0 @@ -/* 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" diff --git a/engine/drumkit.c b/engine/drumkit.c index 98ee1dc..97a4fdc 100644 --- a/engine/drumkit.c +++ b/engine/drumkit.c @@ -19,7 +19,7 @@ static void instrument_set_defaults(struct instrument *ins) { ins->envelope_attack = 0; ins->envelope_decay = 0; ins->envelope_sustain = 1; - ins->envelope_release = 1000; + ins->envelope_release = 10000; ins->mute_group = -1; ins->note = 0; } @@ -377,9 +377,19 @@ struct drumkit *drumkit_new(const char *filename, int target_samplerate) { return kit; } +static void fix_bad_xml(xmlNodePtr cur, xmlNsPtr ns) { + for (; cur; cur = cur->next) { + if (cur->type == XML_ELEMENT_NODE) { + xmlSetNs(cur, ns); + } + fix_bad_xml(cur->children, ns); + } +} + int drumkit_load(struct drumkit *kit, const char *filename, int target_samplerate) { char *basedir = NULL; xmlDocPtr doc = NULL; + xmlNodePtr root_element = NULL; xmlXPathContextPtr xpathCtx = NULL; xmlXPathObjectPtr xpathObj = NULL; int n, r = 1; @@ -394,13 +404,23 @@ int drumkit_load(struct drumkit *kit, const char *filename, int target_samplerat goto cleanup; } + // Check for malformed XML (missing namespace). We can convince + // libxml2 to parse it anyway by setting the proper namespace on all + // elements. + root_element = xmlDocGetRootElement(doc); + if (!strcmp(root_element->name, "drumkit_info") && root_element->ns == NULL) { + xmlNsPtr ns; + fprintf(stderr, "spotted old-style (bad) XML\n"); + ns = xmlNewNs(root_element, (xmlChar *)"http://www.hydrogen-music.org/drumkit", (xmlChar *)""); + fix_bad_xml(root_element, ns); + } + xpathCtx = xmlXPathNewContext(doc); if (xpathCtx == NULL) { fprintf(stderr, "Error: unable to create new XPath context\n"); r = 0; goto cleanup; } - xmlXPathRegisterNs(xpathCtx, (xmlChar *)"dk", (xmlChar *)"http://www.hydrogen-music.org/drumkit"); diff --git a/engine/sampler.c b/engine/sampler.c index 51d8c54..a11273f 100644 --- a/engine/sampler.c +++ b/engine/sampler.c @@ -46,6 +46,13 @@ void sampler_noteon(struct sampler *sampler, int note, int velocity) { return; } + // Kill other notes in the same mute_group. + if (ins->mute_group >= 0) { + for (i = 0; i < sampler->num_voices; i++) { + voice_noteoff_if_mute_group(&sampler->voices[i], ins->mute_group); + } + } + // Find a free voice. struct voice *voice = NULL; for (i = 0; i < sampler->num_voices; i++) { @@ -58,7 +65,7 @@ void sampler_noteon(struct sampler *sampler, int note, int velocity) { printf("all voices are busy\n"); return; } - + // Humanize: modify preamble. voice_noteon(voice, ins, note, velocity, 0); } @@ -67,7 +74,7 @@ void sampler_noteoff(struct sampler *sampler, int note) { int i; for (i = 0; i < sampler->num_voices; i++) { - voice_noteoff(&sampler->voices[i], note); + voice_noteoff_if_note(&sampler->voices[i], note); } } @@ -78,6 +85,7 @@ void sampler_output(struct sampler *sampler, float *out_l, float *out_r, int num for (i = 0; i < num_frames; i++) { out_l[i] = out_r[i] = 0; } + // Accumulate output from all the voices. for (i = 0; i < sampler->num_voices; i++) { voice_process(&sampler->voices[i], out_l, out_r, num_frames); diff --git a/engine/verify-voice.c b/engine/verify-voice.c new file mode 100644 index 0000000..1a5d6d1 --- /dev/null +++ b/engine/verify-voice.c @@ -0,0 +1,101 @@ +#include <stdio.h> +#include <libxml/parser.h> +#include <sndfile.h> + +#include "drumkit.h" +#include "voice.h" + +#define SR 96000 + +void write_sample(const char *filename, float *data_l, float *data_r, int num_frames) { + SF_INFO info = {0}; + SNDFILE *sndfile; + float *buf; + int i; + + buf = (float *)malloc(2 * sizeof(float) * num_frames); + for (i = 0; i < num_frames; i++) { + buf[i*2] = data_l[i]; + buf[i*2+1] = data_r[i]; + } + + info.samplerate = SR; + info.channels = 2; + info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; + + sndfile = sf_open(filename, SFM_WRITE, &info); + sf_writef_float(sndfile, buf, num_frames); + sf_close(sndfile); + free(buf); +} + +int main(int argc, char **argv) { + int status = 0; + struct drumkit kit = {0}; + struct voice voice; + struct instrument *ins; + struct layer *layer; + float *out_l, *out_r; + int total_samples; + int lead_samples = 100; + int note_samples = 100000; + int note = 36; + int velocity = 100; + int i; + + if (argc != 2) { + return 2; + } + + /* + * this initialize the library and check potential ABI mismatches + * between the version it was compiled for and the actual shared + * library used. + */ + LIBXML_TEST_VERSION + + if (!drumkit_load(&kit, argv[1], SR)) { + status = 1; + goto cleanup; + } + + ins = drumkit_find_instrument_by_note(&kit, note); + if (!ins) { + printf("no instrument for note %d\n", note); + status = 1; + goto cleanup; + } + + voice_init(&voice, SR); + total_samples = lead_samples + note_samples; + out_l = (float *)malloc(sizeof(float) * total_samples); + out_r = (float *)malloc(sizeof(float) * total_samples); + for (i = 0; i < total_samples; i++) { + out_l[i] = out_r[i] = 0; + } + + // Some silence first. + voice_process(&voice, out_l, out_r, lead_samples); + // Then a note (kick). + voice_noteon(&voice, ins, note, velocity, 0); + voice_process(&voice, out_l + lead_samples, out_r + lead_samples, note_samples); + + // Dump the layer used by the voice. + layer = instrument_find_layer_by_velocity(ins, velocity); + write_sample("layer.wav", layer->sample.data_l, layer->sample.data_r, layer->sample.info.frames); + + // Dump the output. + write_sample("out.wav", out_l, out_r, total_samples); + + cleanup: + free(out_l); + free(out_r); + drumkit_free(&kit); + + /* + * Cleanup function for the XML library. + */ + xmlCleanupParser(); + + return status; +} diff --git a/engine/voice.c b/engine/voice.c index 57af849..561604d 100644 --- a/engine/voice.c +++ b/engine/voice.c @@ -5,7 +5,8 @@ #include "voice.h" static float velocity_to_gain(int velocity) { - return (float)velocity / 127.0; + float f = (float)velocity / 128.0; + return f * f; } void voice_init(struct voice *voice, int samplerate) { @@ -27,6 +28,7 @@ int voice_noteon(struct voice *voice, struct instrument *ins, int note, int velo voice->pan_l = ins->pan_l; voice->pan_r = ins->pan_r; voice->gain = ins->volume * ins->gain * layer->gain * velocity_to_gain(velocity); + voice->mute_group = ins->mute_group; adsr_trigger(&voice->adsr, (int)ins->envelope_attack, (int)ins->envelope_decay, @@ -45,11 +47,17 @@ int voice_noteon(struct voice *voice, struct instrument *ins, int note, int velo return 1; } -int voice_noteoff(struct voice *voice, int note) { +int voice_noteoff_if_note(struct voice *voice, int note) { if ((voice->playing || voice->preamble_countdown) && voice->note == note) { adsr_release(&voice->adsr); - voice->playing = 0; - voice->ticks = 0; + return 1; + } + return 0; +} + +int voice_noteoff_if_mute_group(struct voice *voice, int mute_group) { + if ((voice->playing || voice->preamble_countdown) && voice->mute_group == mute_group) { + adsr_release(&voice->adsr); return 1; } return 0; @@ -58,26 +66,22 @@ int voice_noteoff(struct voice *voice, int note) { void voice_process(struct voice *voice, float *out_l, float *out_r, int num_frames) { // Accumulate the sample to the output buffer. while (num_frames--) { - if (voice->in_preamble) { voice->preamble_countdown--; if (voice->preamble_countdown <= 0) { voice->in_preamble = 0; voice->playing = 1; + voice->ticks = 0; } } if (voice->playing) { float gain = voice->gain * adsr_get_value(&voice->adsr, 1); - // Stop playing when the envelope is done. - if (voice->adsr.state == ADSR_IDLE) { - voice->playing = 0; - continue; - } - - // Stop playing when the sample is done. - if (voice->ticks > voice->cur_sample->info.frames) { + // Stop playing when the envelope is done, or the sample is + // done, whichever comes first. + if ((voice->adsr.state == ADSR_IDLE) + || (voice->ticks >= voice->cur_sample->info.frames)) { voice->playing = 0; continue; } diff --git a/engine/voice.h b/engine/voice.h index f5b450b..aedd0ac 100644 --- a/engine/voice.h +++ b/engine/voice.h @@ -11,6 +11,7 @@ struct voice { int in_preamble; int preamble_countdown; int note; + int mute_group; int ticks; float pan_l, pan_r, gain; @@ -20,7 +21,8 @@ struct voice { void voice_init(struct voice *, int); int voice_noteon(struct voice *, struct instrument *, int, int, int); -int voice_noteoff(struct voice *, int); +int voice_noteoff_if_note(struct voice *, int); +int voice_noteoff_if_mute_group(struct voice *, int); void voice_process(struct voice *, float *, float *, int); static inline int voice_is_playing(struct voice *voice) { diff --git a/plugin/hydrumkit.ttl b/plugin/hydrumkit.ttl index 4af9e3e..06b5c13 100644 --- a/plugin/hydrumkit.ttl +++ b/plugin/hydrumkit.ttl @@ -19,7 +19,7 @@ <http://lv2.incal.net/plugins/hydrumkit> a lv2:Plugin ; - doap:name "Exampler" ; + doap:name "HyDrumKit" ; doap:license <http://opensource.org/licenses/isc> ; lv2:project <http://lv2plug.in/ns/lv2> ; lv2:requiredFeature state:loadDefaultState , diff --git a/plugin/plugin.c b/plugin/plugin.c index df89bbc..83ce61d 100644 --- a/plugin/plugin.c +++ b/plugin/plugin.c @@ -184,11 +184,12 @@ static void handle_event(struct sampler_plugin *plugin, LV2_Atom_Event *ev) { case LV2_MIDI_MSG_NOTE_ON: note = (int)msg[1]; velocity = (int)msg[2]; + sampler_noteoff(plugin->sampler, note); sampler_noteon(plugin->sampler, note, velocity); break; case LV2_MIDI_MSG_NOTE_OFF: note = (int)msg[1]; - sampler_noteoff(plugin->sampler, note); + //sampler_noteoff(plugin->sampler, note); break; default: // Make -Wall happy. break; diff --git a/plugin/ui.c b/plugin/ui.c index 393592c..510f349 100644 --- a/plugin/ui.c +++ b/plugin/ui.c @@ -1,19 +1,3 @@ -/* - LV2 Sampler Example Plugin UI - Copyright 2011-2016 David Robillard <d@drobilla.net> - - 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 "uris.h" @@ -92,6 +76,7 @@ static LV2UI_Handle instantiate(const LV2UI_Descriptor *descriptor, struct plugin_ui *ui = (struct plugin_ui *)calloc(1, sizeof(struct plugin_ui)); const char *missing; + char *home; LV2_Atom *msg; LV2_Atom_Forge_Frame frame; GtkFileFilter *filter; @@ -128,8 +113,15 @@ static LV2UI_Handle instantiate(const LV2UI_Descriptor *descriptor, ui->file_button = gtk_file_chooser_button_new("Load Drumkit", GTK_FILE_CHOOSER_ACTION_OPEN); gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(ui->file_button), filter); + gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(ui->file_button), "/usr/share/hydrogen/data/drumkits", NULL); - gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(ui->file_button), "~/.hydrogen/data/drumkits", NULL); + home = getenv("HOME"); + if (home) { + char *homebuf = (char *)malloc(strlen(home) + 48); + sprintf(homebuf, "%s/.hydrogen/data/drumkits", home); + gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(ui->file_button), homebuf, NULL); + free(homebuf); + } gtk_container_set_border_width(GTK_CONTAINER(ui->box), 4); gtk_box_pack_start(GTK_BOX(ui->box), ui->button_box, TRUE, TRUE, 0); diff --git a/plugin/uris.h b/plugin/uris.h index b49a3c9..66a6a98 100644 --- a/plugin/uris.h +++ b/plugin/uris.h @@ -1,19 +1,3 @@ -/* - LV2 Sampler Example Plugin - Copyright 2011-2016 David Robillard <d@drobilla.net> - - 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 __uris_H #define __uris_H -- GitLab