From acdc543c8b3f2d11af2c8370ed905e2af6dad9dd Mon Sep 17 00:00:00 2001 From: ale <ale@incal.net> Date: Sun, 10 Mar 2019 09:51:14 +0000 Subject: [PATCH] Initial support for timing/velocity humanization With an algorithm pulled from drumgizmo. --- Makefile.am | 3 +- engine/drumkit.c | 1 + engine/drumkit.h | 1 + engine/humanize.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++ engine/humanize.h | 29 +++++++++++++++++ engine/sampler.c | 41 +++++++++++++++++++----- engine/sampler.h | 8 ++++- plugin/plugin.c | 4 ++- 8 files changed, 157 insertions(+), 11 deletions(-) create mode 100644 engine/humanize.c create mode 100644 engine/humanize.h diff --git a/Makefile.am b/Makefile.am index f951194..60d7d17 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 ca1d071..98678b6 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 b74d25a..b349f5a 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 0000000..15c5290 --- /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 0000000..9353c2f --- /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 d8dccdc..7206b81 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 eb15f26..0115168 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 0b4864b..89ab72d 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 -- GitLab