diff --git a/Makefile.am b/Makefile.am
index f9511944369e14090875d6bb6773b9cccd5c8bf3..60d7d17f2fb4745e230be9e80f04b4bae6bcac76 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5,7 +5,8 @@ noinst_LTLIBRARIES = libengine.la
 libengine_la_SOURCES = \
 	engine/exponential_tables.h \
 	engine/adsr.c engine/adsr.h \
-	engine/voice.h engine/voice.c \
+	engine/voice.c engine/voice.h \
+	engine/humanize.c engine/humanize.h \
 	engine/drumkit.c engine/drumkit.h \
 	engine/sampler.c engine/sampler.h
 libengine_la_LIBADD = $(SNDFILE_LIBS) $(SAMPLERATE_LIBS)
diff --git a/engine/drumkit.c b/engine/drumkit.c
index ca1d0715bc922c2343499b4c3b1a36c3ecc325db..98678b61ac125781d4c4f56f36f99afdd3d7285a 100644
--- a/engine/drumkit.c
+++ b/engine/drumkit.c
@@ -386,6 +386,7 @@ static int parse_instruments(struct instrument **instruments,
 
   for (i = 0; i < n; i++) {
     instrument_set_defaults(&(*instruments)[i]);
+    (*instruments)[i].index = i;
     if (!parse_instrument(&(*instruments)[i], nodes->nodeTab[i], xpathCtx, basedir, target_samplerate)) {
       return 0;
     }
diff --git a/engine/drumkit.h b/engine/drumkit.h
index b74d25a2de7a01fb0add1398ee07a644078032a6..b349f5aad2851136a0d4011ea28d93937db9b289 100644
--- a/engine/drumkit.h
+++ b/engine/drumkit.h
@@ -16,6 +16,7 @@ struct layer {
 };
 
 struct instrument {
+  int index;
   char *name;
   int note;
   float envelope_attack, envelope_decay,
diff --git a/engine/humanize.c b/engine/humanize.c
new file mode 100644
index 0000000000000000000000000000000000000000..15c5290561acb7022493ad4ee760d09accd1b6d8
--- /dev/null
+++ b/engine/humanize.c
@@ -0,0 +1,81 @@
+
+#include <stdlib.h>
+#include <memory.h>
+#include <math.h>
+
+#include "humanize.h"
+
+static inline int ms_to_samples(float ms, int samplerate) {
+  return (int)(ms / 1000.0 * samplerate);
+}
+
+static float gauss(void) {
+  float x = (float)random() / RAND_MAX,
+         y = (float)random() / RAND_MAX,
+         z = sqrtf(-2 * logf(x)) * cosf(2 * M_PI * y);
+  return z;
+}
+
+static float random_normal_distribution(float stddev) {
+  return gauss() * stddev;
+}
+
+// Initialize a humanizer (which tracks a single instrument).
+void humanizer_init(struct humanizer *hum, struct humanizer_settings *settings) {
+  hum->settings = settings;
+  hum->latency_offset = 0;
+  hum->latency_last_pos = 0;
+}
+
+// Return the latency (number of samples) for the next event.
+int humanize_latency_get_next_offset(struct humanizer *hum, int pos) {
+  int latency_max, latency_laid_back;
+  float duration, offset_ms;
+
+  latency_max = ms_to_samples(hum->settings->latency_max_ms, hum->settings->samplerate);
+  latency_laid_back = ms_to_samples(hum->settings->latency_laid_back_ms, hum->settings->samplerate);
+
+  duration = (float)(pos - hum->latency_last_pos) / hum->settings->samplerate;
+  hum->latency_offset *= pow(1.0 - hum->settings->latency_regain, duration);
+  hum->latency_last_pos = pos;
+
+  offset_ms = random_normal_distribution(hum->settings->latency_stddev_ms);
+  hum->latency_offset += ms_to_samples(offset_ms, hum->settings->samplerate);
+  if (hum->latency_offset < -latency_max)
+    hum->latency_offset = -latency_max;
+  if (hum->latency_offset > latency_max)
+    hum->latency_offset = latency_max;
+
+  return latency_max + latency_laid_back + hum->latency_offset;
+}
+
+int humanize_velocity(struct humanizer *hum, int velocity) {
+  float v;
+  int vo;
+
+  v = velocity / 127.0;
+  v *= 1 + random_normal_distribution(hum->settings->velocity_stddev);
+  vo = v * 127;
+  if (vo < 0)
+    vo = 0;
+  if (vo > 127)
+    vo = 127;
+  return vo;
+}
+
+// Initialize humanizer_settings with default (disabled) values.
+void humanizer_settings_init(struct humanizer_settings *settings, int samplerate) {
+  settings->samplerate = samplerate;
+  settings->latency_regain = 1;
+  settings->latency_laid_back_ms = 0;
+  settings->latency_max_ms = 0;
+  settings->latency_stddev_ms = 0;
+  settings->velocity_stddev = 0;
+}
+
+// Returns the maximum latency introduced by the humanizer, so that
+// plugins can compensate for it. This aligns the humanizer 0 time
+// with latency_max_ms.
+int humanize_get_latency(struct humanizer_settings *settings) {
+  return ms_to_samples(settings->latency_max_ms, settings->samplerate);
+}
diff --git a/engine/humanize.h b/engine/humanize.h
new file mode 100644
index 0000000000000000000000000000000000000000..9353c2f06c5324a16c6d663ec6a257e9e63a55b1
--- /dev/null
+++ b/engine/humanize.h
@@ -0,0 +1,29 @@
+#ifndef __humanize_H
+#define __humanize_H 1
+
+
+struct humanizer_settings {
+  float samplerate;
+
+  float latency_regain;
+  float latency_laid_back_ms;
+  float latency_max_ms;
+  float latency_stddev_ms;
+
+  float velocity_stddev;
+};
+
+struct humanizer {
+  struct humanizer_settings *settings;
+
+  float latency_offset;
+  int latency_last_pos;
+};
+
+void humanizer_init(struct humanizer *, struct humanizer_settings *);
+void humanizer_settings_init(struct humanizer_settings *, int);
+int humanize_latency_get_next_offset(struct humanizer *, int);
+int humanize_velocity(struct humanizer *, int);
+int humanize_get_latency(struct humanizer_settings *);
+
+#endif
diff --git a/engine/sampler.c b/engine/sampler.c
index d8dccdc6c93ae852492467ac3e20b039ad565691..7206b81e4ac2ecead2f8b2f11b9dbb338b8c6b51 100644
--- a/engine/sampler.c
+++ b/engine/sampler.c
@@ -4,15 +4,21 @@
 
 #include "sampler.h"
 
-struct sampler *sampler_new(int num_voices, int samplerate) {
-  struct sampler *sampler = (struct sampler *)calloc(1, sizeof(struct sampler));
+static void init_voices(struct sampler *sampler) {
   int i;
+  for (i = 0; i < sampler->num_voices; i++) {
+    voice_init(&sampler->voices[i], sampler->samplerate);
+  }
+}
 
+struct sampler *sampler_new(int num_voices, int samplerate, struct humanizer_settings *hsettings) {
+  struct sampler *sampler = (struct sampler *)calloc(1, sizeof(struct sampler));
+
+  sampler->hsettings = hsettings;
+  sampler->samplerate = samplerate;
   sampler->num_voices = num_voices;
   sampler->voices = (struct voice *)calloc(num_voices, sizeof(struct voice));
-  for (i = 0; i < num_voices; i++) {
-    voice_init(&sampler->voices[i], samplerate);
-  }
+  init_voices(sampler);
   return sampler;
 }
 
@@ -26,16 +32,26 @@ void sampler_free(struct sampler *sampler) {
 }
 
 void sampler_set_drumkit(struct sampler *sampler, struct drumkit *kit) {
+  int i;
+
   if (sampler->kit) {
+    // Re-initialize all voices to avoid pending references to freed samples.
+    init_voices(sampler);
     drumkit_free(sampler->kit);
     free(sampler->kit);
+    free(sampler->humanizers);
   }
   sampler->kit = kit;
+  sampler->humanizers = (struct humanizer *)calloc(kit->num_instruments, sizeof(struct humanizer));
+  for (i = 0; i < kit->num_instruments; i++) {
+    humanizer_init(&sampler->humanizers[i], sampler->hsettings);
+  }
+  sampler->ticks = 0;
 }
 
 void sampler_noteon(struct sampler *sampler, int note, int velocity) {
   struct instrument *ins;
-  int i;
+  int i, offset;
 
   if (!sampler->kit)
     return;
@@ -66,8 +82,11 @@ void sampler_noteon(struct sampler *sampler, int note, int velocity) {
     return;
   }
   
-  // Humanize: modify preamble.
-  voice_noteon(voice, ins, note, velocity, 0);
+  // Humanize: modify preamble and velocity.
+  offset = humanize_latency_get_next_offset(&sampler->humanizers[ins->index], sampler->ticks);
+  velocity = humanize_velocity(&sampler->humanizers[ins->index], velocity);
+  
+  voice_noteon(voice, ins, note, velocity, offset);
 }
 
 void sampler_noteoff(struct sampler *sampler, int note) {
@@ -90,4 +109,10 @@ void sampler_output(struct sampler *sampler, float *out_l, float *out_r, int num
   for (i = 0; i < sampler->num_voices; i++) {
     voice_process(&sampler->voices[i], out_l, out_r, num_frames);
   }
+
+  sampler->ticks += num_frames;
+}
+
+int sampler_get_latency(struct sampler *sampler) {
+  return humanize_get_latency(sampler->hsettings);
 }
diff --git a/engine/sampler.h b/engine/sampler.h
index eb15f26f163e45e236396c13fd55e7bcd796ab01..01151683457b733f926fcbbd418077f7acad7c79 100644
--- a/engine/sampler.h
+++ b/engine/sampler.h
@@ -3,18 +3,24 @@
 
 #include "drumkit.h"
 #include "voice.h"
+#include "humanize.h"
 
 struct sampler {
   struct drumkit *kit;
+  int samplerate;
   int num_voices;
   struct voice *voices;
+  struct humanizer_settings *hsettings;
+  struct humanizer *humanizers;
+  int ticks;
 };
 
-struct sampler *sampler_new(int, int);
+struct sampler *sampler_new(int, int, struct humanizer_settings *);
 void sampler_set_drumkit(struct sampler *, struct drumkit *);
 void sampler_free(struct sampler *);
 void sampler_noteon(struct sampler *, int, int);
 void sampler_noteoff(struct sampler *, int);
 void sampler_output(struct sampler *, float *, float *, int);
+int sampler_get_latency(struct sampler *);
 
 #endif
diff --git a/plugin/plugin.c b/plugin/plugin.c
index 0b4864b13e2c02cdd4e012f35eed798f47baf12e..89ab72de0a972cfacdcbc20512213d935467a543 100644
--- a/plugin/plugin.c
+++ b/plugin/plugin.c
@@ -46,6 +46,7 @@ struct sampler_plugin {
 
   plugin_uris uris;
 
+  struct humanizer_settings hsettings;
   struct sampler *sampler;
 
   int activated;
@@ -148,7 +149,8 @@ static LV2_Handle instantiate(const LV2_Descriptor *descriptor, double rate,
   struct sampler_plugin *plugin =
       (struct sampler_plugin *)calloc(1, sizeof(struct sampler_plugin));
 
-  plugin->sampler = sampler_new(NUM_VOICES, (int)rate);
+  humanizer_settings_init(&plugin->hsettings, (int)rate);
+  plugin->sampler = sampler_new(NUM_VOICES, (int)rate, &plugin->hsettings);
   plugin->samplerate = (int)rate;
 
   // Get host features