crossfade buffer for removing clicks on note changes
This commit is contained in:
parent
b35498e230
commit
ce5cfdd95b
@ -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;
|
||||||
|
|
||||||
static void stream(float *buffer, int num_frames, int num_channels) {
|
#define CROSSFADE_RING_RANGE \
|
||||||
for (int i = 0; i < num_frames; ++i) {
|
(STFU_CROSSFADE_BUFFER_FRAMES * STFU_AUDIO_CHANNEL_COUNT)
|
||||||
// sine0 = stfu_pump_sinewave(sine0); // Carrier
|
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);
|
phaser0 = stfu_pump_phaser(phaser0);
|
||||||
phaser1 = stfu_pump_phaser(phaser1);
|
phaser1 = stfu_pump_phaser(phaser1);
|
||||||
phaser2 = stfu_pump_phaser(phaser2);
|
phaser2 = stfu_pump_phaser(phaser2);
|
||||||
envelope = stfu_pump_envelope(envelope);
|
envelope = stfu_pump_envelope(envelope);
|
||||||
buffer[2 * i + 0] =
|
|
||||||
envelope.v * sinf(phaser2.v + sinf(phaser0.v + sinf(phaser1.v)));
|
float left = envelope.v * sinf(phaser2.v + sinf(phaser0.v + sinf(phaser1.v)));
|
||||||
buffer[2 * i + 1] =
|
float right =
|
||||||
envelope.v * sinf(phaser2.v + sinf(phaser0.v + sinf(phaser1.v)));
|
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) {
|
||||||
|
/* 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);
|
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;
|
||||||
}
|
}
|
||||||
|
@ -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?
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user