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