crossfade buffer for removing clicks on note changes

This commit is contained in:
veclav talica 2024-06-14 13:18:00 +05:00
parent b35498e230
commit ce5cfdd95b
4 changed files with 58 additions and 21 deletions

View File

@ -1,5 +1,6 @@
#include <stdio.h> #include <stdio.h>
#include <string.h>
#define SOKOL_IMPL #define SOKOL_IMPL
#define SOKOL_GLCORE #define SOKOL_GLCORE
@ -9,6 +10,7 @@
#include "../config.h" #include "../config.h"
#include "../envelope.c" #include "../envelope.c"
// #include "../interp.c"
#include "../oscillators.c" #include "../oscillators.c"
#include "../phaser.c" #include "../phaser.c"
@ -20,17 +22,45 @@ static struct stfu_envelope envelope;
static float key_frequency = STFU_C4_FREQUENCY; 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) { static void stream(float *buffer, int num_frames, int num_channels) {
for (int i = 0; i < num_frames; ++i) { /* Fade away */
// sine0 = stfu_pump_sinewave(sine0); // Carrier for (size_t i = 0; crossfade_frames_left > 0;
phaser0 = stfu_pump_phaser(phaser0); crossfade_frames_left--, i += 2) {
phaser1 = stfu_pump_phaser(phaser1); crossfade_ring[(crossfade_ring_needle + i) % CROSSFADE_RING_RANGE] *=
phaser2 = stfu_pump_phaser(phaser2); (float)crossfade_frames_left / STFU_CROSSFADE_BUFFER_FRAMES;
envelope = stfu_pump_envelope(envelope); crossfade_ring[(crossfade_ring_needle + i + 1) % CROSSFADE_RING_RANGE] *=
buffer[2 * i + 0] = (float)crossfade_frames_left / STFU_CROSSFADE_BUFFER_FRAMES;
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))); /* 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); phaser1 = stfu_init_phaser(key_frequency * 0.25);
phaser2 = stfu_init_phaser(key_frequency * 2.0); phaser2 = stfu_init_phaser(key_frequency * 2.0);
envelope = stfu_init_envelope( envelope = stfu_init_envelope(
(struct stfu_envelope_point[]){ (struct stfu_envelope_point[]){[0] = {.dur = 0.02, .vol = 1.0},
[0] = {.dur = 0.5, .vol = 0.33}, [1] = {.dur = 1.0, .vol = 0}}, [1] = {.dur = 0.5, .vol = 0.33},
2); [2] = {.dur = 1.0, .vol = 0}},
3);
} }
static void cleanup(void) { saudio_shutdown(); } static void cleanup(void) { saudio_shutdown(); }
@ -102,10 +133,11 @@ static void event(const sapp_event *e) {
break; break;
} }
phaser0 = stfu_set_phaser_frequency(phaser0, key_frequency * 0.25); phaser0 = stfu_set_phaser_frequency(phaser0, key_frequency);
phaser1 = stfu_set_phaser_frequency(phaser1, key_frequency * 0.125); phaser1 = stfu_set_phaser_frequency(phaser1, key_frequency * 0.25);
phaser2 = stfu_set_phaser_frequency(phaser2, key_frequency); phaser2 = stfu_set_phaser_frequency(phaser2, key_frequency * 2.0);
envelope = stfu_trigger_envelope(envelope); envelope = stfu_trigger_envelope(envelope);
crossfade_frames_left = STFU_CROSSFADE_BUFFER_FRAMES;
break; break;
} }

View File

@ -8,4 +8,8 @@
#define STFU_NOTE_UPSCALE_FACTOR 1.059463094f #define STFU_NOTE_UPSCALE_FACTOR 1.059463094f
#define STFU_NOTE_DOWNSCALE_FACTOR 0.943874313f #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? // todo: Array of precomputed note frequencies?

View File

@ -9,7 +9,7 @@ float stfu_get_envelope_duration(struct stfu_envelope envelope);
struct stfu_envelope stfu_trigger_envelope(struct stfu_envelope envelope); struct stfu_envelope stfu_trigger_envelope(struct stfu_envelope envelope);
/* Basic linear envelope generator with up to 16 points max. /* 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? * todo: Sustain?
*/ */
@ -39,7 +39,7 @@ struct stfu_envelope stfu_pump_envelope(struct stfu_envelope envelope) {
for (uint8_t i = 0;; ++i) { for (uint8_t i = 0;; ++i) {
float ends = p + envelope.pnts[i].dur; float ends = p + envelope.pnts[i].dur;
if (envelope.p < ends) { 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.v = stfu_linear_interpolate(p, pv, ends, envelope.pnts[i].vol,
envelope.p); envelope.p);
break; break;
@ -60,6 +60,6 @@ float stfu_get_envelope_duration(struct stfu_envelope envelope) {
struct stfu_envelope stfu_trigger_envelope(struct stfu_envelope envelope) { struct stfu_envelope stfu_trigger_envelope(struct stfu_envelope envelope) {
envelope.p = 0.0f; envelope.p = 0.0f;
envelope.v = 1.0f; envelope.v = 0.0f;
return envelope; return envelope;
} }

View File

@ -1,10 +1,11 @@
# STFU ! SoundTracker Format Unirepo # 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. - 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. - 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. - Live and proceduraly editable.
- Compression friendly, always compressed.
## Maker ## Maker
Crossplatform Sokol headers based live editor. Interface is touch first, allowing for touch screen and mouse editing. Crossplatform Sokol headers based live editor. Interface is touch first, allowing for touch screen and mouse editing.