From b35498e23019ee75c869e9b834a528b103ed0c79 Mon Sep 17 00:00:00 2001 From: veclav talica Date: Thu, 13 Jun 2024 17:47:09 +0500 Subject: [PATCH] envelope.c: point-based linear interpolating envelope generator --- Source/Maker/main.c | 22 ++++++++++++--- Source/envelope.c | 65 +++++++++++++++++++++++++++++++++++++++++++++ Source/interp.c | 4 +++ readme.md | 2 +- 4 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 Source/envelope.c create mode 100644 Source/interp.c diff --git a/Source/Maker/main.c b/Source/Maker/main.c index 56bbb98..64a1ec7 100644 --- a/Source/Maker/main.c +++ b/Source/Maker/main.c @@ -8,12 +8,15 @@ #include "sokol_log.h" #include "../config.h" +#include "../envelope.c" #include "../oscillators.c" #include "../phaser.c" static struct stfu_sinewave sine0; static struct stfu_phaser phaser0; static struct stfu_phaser phaser1; +static struct stfu_phaser phaser2; +static struct stfu_envelope envelope; static float key_frequency = STFU_C4_FREQUENCY; @@ -22,8 +25,12 @@ static void stream(float *buffer, int num_frames, int num_channels) { // sine0 = stfu_pump_sinewave(sine0); // Carrier phaser0 = stfu_pump_phaser(phaser0); phaser1 = stfu_pump_phaser(phaser1); - buffer[2 * i + 0] = sinf(phaser0.v + sinf(phaser1.v)); - buffer[2 * i + 1] = sinf(phaser0.v + sinf(phaser1.v)); + phaser2 = stfu_pump_phaser(phaser2); + envelope = stfu_pump_envelope(envelope); + buffer[2 * i + 0] = + envelope.v * sinf(phaser2.v + sinf(phaser0.v + sinf(phaser1.v))); + buffer[2 * i + 1] = + envelope.v * sinf(phaser2.v + sinf(phaser0.v + sinf(phaser1.v))); } } @@ -37,6 +44,11 @@ static void init(void) { // sine0 = stfu_init_sinewave(STFU_A4_FREQUENCY / 2.0f, 0.0f, 0.8f); phaser0 = stfu_init_phaser(key_frequency); phaser1 = stfu_init_phaser(key_frequency * 0.25); + phaser2 = stfu_init_phaser(key_frequency * 2.0); + envelope = stfu_init_envelope( + (struct stfu_envelope_point[]){ + [0] = {.dur = 0.5, .vol = 0.33}, [1] = {.dur = 1.0, .vol = 0}}, + 2); } static void cleanup(void) { saudio_shutdown(); } @@ -90,8 +102,10 @@ static void event(const sapp_event *e) { break; } - phaser0 = stfu_set_phaser_frequency(phaser0, key_frequency); - phaser1 = stfu_set_phaser_frequency(phaser1, key_frequency * 0.25); + phaser0 = stfu_set_phaser_frequency(phaser0, key_frequency * 0.25); + phaser1 = stfu_set_phaser_frequency(phaser1, key_frequency * 0.125); + phaser2 = stfu_set_phaser_frequency(phaser2, key_frequency); + envelope = stfu_trigger_envelope(envelope); break; } diff --git a/Source/envelope.c b/Source/envelope.c new file mode 100644 index 0000000..24e3902 --- /dev/null +++ b/Source/envelope.c @@ -0,0 +1,65 @@ +#include +#include + +#include "config.h" +#include "interp.c" + +struct stfu_envelope stfu_pump_envelope(struct stfu_envelope envelope); +float stfu_get_envelope_duration(struct stfu_envelope envelope); +struct stfu_envelope stfu_trigger_envelope(struct stfu_envelope envelope); + +/* Basic linear envelope generator with up to 16 points max. + * First virtual point is always max volume (1.0f). + * + * todo: Sustain? + */ +struct stfu_envelope { + /* Initial volume is assumed from previous point */ + struct stfu_envelope_point { + float dur, vol; + } pnts[16]; + uint8_t n_pnts; + float p, v; +} stfu_init_envelope(struct stfu_envelope_point *points, uint8_t n_points) { + struct stfu_envelope r = {0}; + memcpy(&r.pnts, points, sizeof(struct stfu_envelope_point) * n_points); + r.n_pnts = n_points; + r = stfu_trigger_envelope(r); + return r; +} + +struct stfu_envelope stfu_pump_envelope(struct stfu_envelope envelope) { + envelope.p += 1.0f / STFU_AUDIO_FRAME_RATE; + float d = stfu_get_envelope_duration(envelope); + if (envelope.p >= d) + /* Needs retriggering after that point */ + return envelope; + + float p = 0.0f; + for (uint8_t i = 0;; ++i) { + float ends = p + envelope.pnts[i].dur; + if (envelope.p < ends) { + float pv = i == 0 ? 1.0f : envelope.pnts[i - 1].vol; + envelope.v = stfu_linear_interpolate(p, pv, ends, envelope.pnts[i].vol, + envelope.p); + break; + } + p += envelope.pnts[i].dur; + } + + return envelope; +} + +float stfu_get_envelope_duration(struct stfu_envelope envelope) { + float r = 0.0f; + for (uint8_t i = 0; i < envelope.n_pnts; ++i) { + r += envelope.pnts[i].dur; + } + return r; +} + +struct stfu_envelope stfu_trigger_envelope(struct stfu_envelope envelope) { + envelope.p = 0.0f; + envelope.v = 1.0f; + return envelope; +} diff --git a/Source/interp.c b/Source/interp.c new file mode 100644 index 0000000..dbe170a --- /dev/null +++ b/Source/interp.c @@ -0,0 +1,4 @@ + +float stfu_linear_interpolate(float x0, float y0, float x1, float y1, float n) { + return (y0 * (x1 - n) + y1 * (n - x0)) / (x1 - x0); +} diff --git a/readme.md b/readme.md index cab4b47..cece551 100644 --- a/readme.md +++ b/readme.md @@ -7,4 +7,4 @@ Grassroot music format specifically designed for game soundtracks with accent on - Live and proceduraly editable. ## Maker -Crossplatform Sokol headers based live editor. +Crossplatform Sokol headers based live editor. Interface is touch first, allowing for touch screen and mouse editing.