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