diff --git a/Source/Maker/main.c b/Source/Maker/main.c index 64a1ec7..e3dffb3 100644 --- a/Source/Maker/main.c +++ b/Source/Maker/main.c @@ -1,5 +1,6 @@ #include +#include #define SOKOL_IMPL #define SOKOL_GLCORE @@ -9,6 +10,7 @@ #include "../config.h" #include "../envelope.c" +// #include "../interp.c" #include "../oscillators.c" #include "../phaser.c" @@ -20,17 +22,45 @@ static struct stfu_envelope envelope; static float key_frequency = STFU_C4_FREQUENCY; +#define CROSSFADE_RING_RANGE \ + (STFU_CROSSFADE_BUFFER_FRAMES * STFU_AUDIO_CHANNEL_COUNT) +static float crossfade_ring[CROSSFADE_RING_RANGE] = {0}; +static size_t crossfade_ring_needle = 0; +static unsigned int crossfade_frames_left = 0; + +static void sample(float buffer[static 2]) { + phaser0 = stfu_pump_phaser(phaser0); + phaser1 = stfu_pump_phaser(phaser1); + phaser2 = stfu_pump_phaser(phaser2); + envelope = stfu_pump_envelope(envelope); + + float left = envelope.v * sinf(phaser2.v + sinf(phaser0.v + sinf(phaser1.v))); + float right = + envelope.v * sinf(phaser2.v + sinf(phaser0.v + sinf(phaser1.v))); + + buffer[0] = left; + buffer[1] = right; +} + static void stream(float *buffer, int num_frames, int num_channels) { - for (int i = 0; i < num_frames; ++i) { - // sine0 = stfu_pump_sinewave(sine0); // Carrier - phaser0 = stfu_pump_phaser(phaser0); - phaser1 = stfu_pump_phaser(phaser1); - 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))); + /* Fade away */ + for (size_t i = 0; crossfade_frames_left > 0; + crossfade_frames_left--, i += 2) { + crossfade_ring[(crossfade_ring_needle + i) % CROSSFADE_RING_RANGE] *= + (float)crossfade_frames_left / STFU_CROSSFADE_BUFFER_FRAMES; + crossfade_ring[(crossfade_ring_needle + i + 1) % CROSSFADE_RING_RANGE] *= + (float)crossfade_frames_left / STFU_CROSSFADE_BUFFER_FRAMES; + } + + /* Drive the ring buffer */ + // todo: Use memcpy for left and right side of the ring. + while (num_frames--) { + buffer[0] = crossfade_ring[crossfade_ring_needle % CROSSFADE_RING_RANGE]; + buffer[1] = + crossfade_ring[(crossfade_ring_needle + 1) % CROSSFADE_RING_RANGE]; + sample(&crossfade_ring[crossfade_ring_needle % CROSSFADE_RING_RANGE]); + buffer = &buffer[2]; + crossfade_ring_needle += 2; } } @@ -46,9 +76,10 @@ static void init(void) { 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); + (struct stfu_envelope_point[]){[0] = {.dur = 0.02, .vol = 1.0}, + [1] = {.dur = 0.5, .vol = 0.33}, + [2] = {.dur = 1.0, .vol = 0}}, + 3); } static void cleanup(void) { saudio_shutdown(); } @@ -102,10 +133,11 @@ static void event(const sapp_event *e) { break; } - 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); + phaser0 = stfu_set_phaser_frequency(phaser0, key_frequency); + phaser1 = stfu_set_phaser_frequency(phaser1, key_frequency * 0.25); + phaser2 = stfu_set_phaser_frequency(phaser2, key_frequency * 2.0); envelope = stfu_trigger_envelope(envelope); + crossfade_frames_left = STFU_CROSSFADE_BUFFER_FRAMES; break; } diff --git a/Source/config.h b/Source/config.h index c946095..84344e9 100644 --- a/Source/config.h +++ b/Source/config.h @@ -8,4 +8,8 @@ #define STFU_NOTE_UPSCALE_FACTOR 1.059463094f #define STFU_NOTE_DOWNSCALE_FACTOR 0.943874313f +/* Introduces delayed buffering for purposes of crossfading channels when notes + * are retriggered, needed for filtering clicks */ +#define STFU_CROSSFADE_BUFFER_FRAMES 64 + // todo: Array of precomputed note frequencies? diff --git a/Source/envelope.c b/Source/envelope.c index 24e3902..1c05c87 100644 --- a/Source/envelope.c +++ b/Source/envelope.c @@ -9,7 +9,7 @@ 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). + * First virtual point is always silent (0.0f). * * todo: Sustain? */ @@ -39,7 +39,7 @@ struct stfu_envelope stfu_pump_envelope(struct stfu_envelope envelope) { 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; + float pv = i == 0 ? 0.0f : envelope.pnts[i - 1].vol; envelope.v = stfu_linear_interpolate(p, pv, ends, envelope.pnts[i].vol, envelope.p); break; @@ -60,6 +60,6 @@ float stfu_get_envelope_duration(struct stfu_envelope envelope) { struct stfu_envelope stfu_trigger_envelope(struct stfu_envelope envelope) { envelope.p = 0.0f; - envelope.v = 1.0f; + envelope.v = 0.0f; return envelope; } diff --git a/readme.md b/readme.md index cece551..c7270a5 100644 --- a/readme.md +++ b/readme.md @@ -1,10 +1,11 @@ # STFU ! SoundTracker Format Unirepo -Grassroot music format specifically designed for game soundtracks with accent on binary efficiency in its various forms. +Grassroot music format specifically designed for lofi game soundtracks with accent on binary efficiency in its various forms. - Separation of Maker and Player binaries, with Player being absolutely dependency free. - Samples and instruments are stored outside of tracks in a shared data base allowing reuse. -- Sound synthesis from piping basic blocks. +- Sound synthesis (FM). - Live and proceduraly editable. +- Compression friendly, always compressed. ## Maker Crossplatform Sokol headers based live editor. Interface is touch first, allowing for touch screen and mouse editing.