windows build, separation of synth code, 3-op now, modulator feedback

This commit is contained in:
veclavtalica 2024-06-24 10:58:31 +03:00
parent ce5cfdd95b
commit 561dece028
11 changed files with 139 additions and 62 deletions

View File

@ -0,0 +1,2 @@
# Trojka
3-op minimalist FM synth to the mega!

View File

@ -0,0 +1,56 @@
#if defined(STFU_MAIN) && !defined(STFU_TROJKA)
#define STFU_TROJKA
#include "../../phaser.c"
#include "../../envelope.c"
#include "../../config.h"
#define STFU_TROJKA_OP_COUNT 3
/* https://multimed.org/student/eim/en/04-FM.pdf */
// todo: A few more algorithm configurations.
// todo: Other oscillators.
struct stfu_trojka {
struct stfu_phaser phasers[STFU_TROJKA_OP_COUNT];
struct stfu_envelope envelopes[STFU_TROJKA_OP_COUNT];
float gains[STFU_TROJKA_OP_COUNT];
float freq_scales[STFU_TROJKA_OP_COUNT];
float key_frequency;
float feedback_gain; // todo: Separate gain for channels?
float left_feedback, right_feedback;
};
static struct stfu_trojka stfu_sample_trojka(struct stfu_trojka synth, float buffer[static 2]) {
for (int i = 0; i < STFU_TROJKA_OP_COUNT; ++i) {
synth.phasers[i] = stfu_pump_phaser(synth.phasers[i]);
synth.envelopes[i] = stfu_pump_envelope(synth.envelopes[i]);
}
float left_modulator_stack = synth.envelopes[1].v * sinf(synth.phasers[1].v + synth.envelopes[0].v * sinf(synth.phasers[0].v)) + synth.left_feedback * synth.feedback_gain;
float right_modulator_stack = synth.envelopes[1].v * sinf(synth.phasers[1].v + synth.envelopes[0].v * sinf(synth.phasers[0].v)) + synth.right_feedback * synth.feedback_gain;
synth.left_feedback = left_modulator_stack;
synth.right_feedback = right_modulator_stack;
float left = synth.envelopes[2].v * sinf(synth.phasers[2].v + left_modulator_stack);
float right =
synth.envelopes[2].v * sinf(synth.phasers[2].v + right_modulator_stack);
buffer[0] = left;
buffer[1] = right;
return synth;
}
static struct stfu_trojka stfu_press_trojka(struct stfu_trojka synth, float key_frequency) {
for (int i = 0; i < STFU_TROJKA_OP_COUNT; ++i) {
synth.phasers[i] = stfu_set_phaser_frequency(synth.phasers[i], key_frequency * synth.freq_scales[i]);
synth.envelopes[i] = stfu_trigger_envelope(synth.envelopes[i]);
}
return synth;
}
#endif

Binary file not shown.

View File

@ -0,0 +1 @@
clang ../main.c -lkernel32 -luser32 -lshell32 -ld3d11 -ldxgi -lgdi32 -lole32 -DSOKOL_D3D11 -o stfu-maker.exe -O3

View File

@ -3,45 +3,27 @@
#include <string.h> #include <string.h>
#define SOKOL_IMPL #define SOKOL_IMPL
#define SOKOL_GLCORE
#include "sokol_app.h" #include "sokol_app.h"
#include "sokol_audio.h" #include "sokol_audio.h"
#include "sokol_log.h" #include "sokol_log.h"
#define STFU_MAIN
#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"
static struct stfu_sinewave sine0; #include "../Instruments/Trojka/trojka.c"
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; static struct stfu_trojka synth = {0};
// todo: Separate to playback.c, generic for Player and Maker
#define CROSSFADE_RING_RANGE \ #define CROSSFADE_RING_RANGE \
(STFU_CROSSFADE_BUFFER_FRAMES * STFU_AUDIO_CHANNEL_COUNT) (STFU_CROSSFADE_BUFFER_FRAMES * STFU_AUDIO_CHANNEL_COUNT)
static float crossfade_ring[CROSSFADE_RING_RANGE] = {0}; static float crossfade_ring[CROSSFADE_RING_RANGE] = {0};
static size_t crossfade_ring_needle = 0; static size_t crossfade_ring_needle = 0;
static unsigned int crossfade_frames_left = 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) {
/* Fade away */ /* Fade away */
for (size_t i = 0; crossfade_frames_left > 0; for (size_t i = 0; crossfade_frames_left > 0;
@ -58,7 +40,7 @@ static void stream(float *buffer, int num_frames, int num_channels) {
buffer[0] = crossfade_ring[crossfade_ring_needle % CROSSFADE_RING_RANGE]; buffer[0] = crossfade_ring[crossfade_ring_needle % CROSSFADE_RING_RANGE];
buffer[1] = buffer[1] =
crossfade_ring[(crossfade_ring_needle + 1) % CROSSFADE_RING_RANGE]; crossfade_ring[(crossfade_ring_needle + 1) % CROSSFADE_RING_RANGE];
sample(&crossfade_ring[crossfade_ring_needle % CROSSFADE_RING_RANGE]); synth = stfu_sample_trojka(synth, &crossfade_ring[crossfade_ring_needle % CROSSFADE_RING_RANGE]);
buffer = &buffer[2]; buffer = &buffer[2];
crossfade_ring_needle += 2; crossfade_ring_needle += 2;
} }
@ -71,15 +53,19 @@ static void init(void) {
// .stream_cb = stream, // .stream_cb = stream,
}); });
// sine0 = stfu_init_sinewave(STFU_A4_FREQUENCY / 2.0f, 0.0f, 0.8f); synth.phasers[0] = stfu_init_phaser(220);
phaser0 = stfu_init_phaser(key_frequency); synth.phasers[1] = stfu_init_phaser(220 * 0.25);
phaser1 = stfu_init_phaser(key_frequency * 0.25); synth.phasers[2] = stfu_init_phaser(220 * 2.0);
phaser2 = stfu_init_phaser(key_frequency * 2.0); synth.feedback_gain = 0.25;
envelope = stfu_init_envelope( for (int i = 0; i < STFU_TROJKA_OP_COUNT; ++i) {
(struct stfu_envelope_point[]){[0] = {.dur = 0.02, .vol = 1.0}, synth.envelopes[i] = stfu_init_envelope(
[1] = {.dur = 0.5, .vol = 0.33}, (struct stfu_envelope_point[]){[0] = {.dur = 0.02, .vol = 1.0},
[2] = {.dur = 1.0, .vol = 0}}, [1] = {.dur = 0.5, .vol = 0.33},
3); [2] = {.dur = 1.0, .vol = 0}},
3);
synth.gains[i] = 1.0;
synth.freq_scales[i] = 1.0;
}
} }
static void cleanup(void) { saudio_shutdown(); } static void cleanup(void) { saudio_shutdown(); }
@ -92,53 +78,38 @@ static void event(const sapp_event *e) {
switch (e->key_code) { switch (e->key_code) {
case SAPP_KEYCODE_Z: case SAPP_KEYCODE_Z:
key_frequency = STFU_C4_FREQUENCY; synth = stfu_press_trojka(synth, STFU_SCALE[0]);
break;
case SAPP_KEYCODE_X: case SAPP_KEYCODE_X:
key_frequency = STFU_C4_FREQUENCY * STFU_NOTE_UPSCALE_FACTOR; synth = stfu_press_trojka(synth, STFU_SCALE[1]);
break; break;
case SAPP_KEYCODE_C: case SAPP_KEYCODE_C:
key_frequency = STFU_C4_FREQUENCY * STFU_NOTE_UPSCALE_FACTOR * synth = stfu_press_trojka(synth, STFU_SCALE[2]);
STFU_NOTE_UPSCALE_FACTOR;
break; break;
case SAPP_KEYCODE_V: case SAPP_KEYCODE_V:
key_frequency = STFU_C4_FREQUENCY * STFU_NOTE_UPSCALE_FACTOR * synth = stfu_press_trojka(synth, STFU_SCALE[3]);
STFU_NOTE_UPSCALE_FACTOR * STFU_NOTE_UPSCALE_FACTOR;
break; break;
case SAPP_KEYCODE_B: case SAPP_KEYCODE_B:
key_frequency = STFU_C4_FREQUENCY * STFU_NOTE_UPSCALE_FACTOR * synth = stfu_press_trojka(synth, STFU_SCALE[4]);
STFU_NOTE_UPSCALE_FACTOR * STFU_NOTE_UPSCALE_FACTOR *
STFU_NOTE_UPSCALE_FACTOR;
break; break;
case SAPP_KEYCODE_N: case SAPP_KEYCODE_N:
key_frequency = STFU_A4_FREQUENCY * STFU_NOTE_DOWNSCALE_FACTOR * synth = stfu_press_trojka(synth, STFU_SCALE[5]);
STFU_NOTE_DOWNSCALE_FACTOR * STFU_NOTE_DOWNSCALE_FACTOR *
STFU_NOTE_DOWNSCALE_FACTOR;
break; break;
case SAPP_KEYCODE_M: case SAPP_KEYCODE_M:
key_frequency = STFU_A4_FREQUENCY * STFU_NOTE_DOWNSCALE_FACTOR * synth = stfu_press_trojka(synth, STFU_SCALE[6]);
STFU_NOTE_DOWNSCALE_FACTOR * STFU_NOTE_DOWNSCALE_FACTOR;
break; break;
case SAPP_KEYCODE_COMMA: case SAPP_KEYCODE_COMMA:
key_frequency = STFU_A4_FREQUENCY * STFU_NOTE_DOWNSCALE_FACTOR * synth = stfu_press_trojka(synth, STFU_SCALE[7]);
STFU_NOTE_DOWNSCALE_FACTOR;
break; break;
case SAPP_KEYCODE_PERIOD: case SAPP_KEYCODE_PERIOD:
key_frequency = STFU_A4_FREQUENCY * STFU_NOTE_DOWNSCALE_FACTOR; synth = stfu_press_trojka(synth, STFU_SCALE[8]);
break; break;
case SAPP_KEYCODE_SLASH: case SAPP_KEYCODE_SLASH:
key_frequency = STFU_A4_FREQUENCY; synth = stfu_press_trojka(synth, STFU_SCALE[9]);
break; break;
default: default:
break; break;
} }
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; break;
} }
default: default:
@ -172,5 +143,6 @@ sapp_desc sokol_main(int argc, char *argv[]) {
.gl_major_version = 2, .gl_major_version = 2,
.gl_minor_version = 0, .gl_minor_version = 0,
// .swap_interval = 0, // .swap_interval = 0,
.window_title = "stfu-maker",
}; };
} }

View File

@ -1,3 +1,5 @@
#if !defined(STFU_CONFIG)
#define STFU_CONFIG
/* Determines output framerate, in times per second. */ /* Determines output framerate, in times per second. */
#define STFU_AUDIO_FRAME_RATE 44100 #define STFU_AUDIO_FRAME_RATE 44100
@ -10,6 +12,27 @@
/* Introduces delayed buffering for purposes of crossfading channels when notes /* Introduces delayed buffering for purposes of crossfading channels when notes
* are retriggered, needed for filtering clicks */ * are retriggered, needed for filtering clicks */
#define STFU_CROSSFADE_BUFFER_FRAMES 64 #define STFU_CROSSFADE_BUFFER_FRAMES 32
// todo: Array of precomputed note frequencies? static const float STFU_SCALE[12] = {
STFU_C4_FREQUENCY,
STFU_C4_FREQUENCY * STFU_NOTE_UPSCALE_FACTOR,
STFU_C4_FREQUENCY * STFU_NOTE_UPSCALE_FACTOR *
STFU_NOTE_UPSCALE_FACTOR,
STFU_C4_FREQUENCY * STFU_NOTE_UPSCALE_FACTOR *
STFU_NOTE_UPSCALE_FACTOR * STFU_NOTE_UPSCALE_FACTOR,
STFU_C4_FREQUENCY * STFU_NOTE_UPSCALE_FACTOR *
STFU_NOTE_UPSCALE_FACTOR * STFU_NOTE_UPSCALE_FACTOR *
STFU_NOTE_UPSCALE_FACTOR,
STFU_A4_FREQUENCY * STFU_NOTE_DOWNSCALE_FACTOR *
STFU_NOTE_DOWNSCALE_FACTOR * STFU_NOTE_DOWNSCALE_FACTOR *
STFU_NOTE_DOWNSCALE_FACTOR,
STFU_A4_FREQUENCY * STFU_NOTE_DOWNSCALE_FACTOR *
STFU_NOTE_DOWNSCALE_FACTOR * STFU_NOTE_DOWNSCALE_FACTOR,
STFU_A4_FREQUENCY * STFU_NOTE_DOWNSCALE_FACTOR *
STFU_NOTE_DOWNSCALE_FACTOR,
STFU_A4_FREQUENCY * STFU_NOTE_DOWNSCALE_FACTOR,
STFU_A4_FREQUENCY,
};
#endif

View File

@ -1,3 +1,6 @@
#if defined(STFU_MAIN) && !defined(STFU_ENVELOPE)
#define STFU_ENVELOPE
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
@ -29,12 +32,13 @@ struct stfu_envelope {
} }
struct stfu_envelope stfu_pump_envelope(struct stfu_envelope envelope) { 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); float d = stfu_get_envelope_duration(envelope);
if (envelope.p >= d) if (envelope.p >= d)
/* Needs retriggering after that point */ /* Needs retriggering after that point */
return envelope; return envelope;
envelope.p += 1.0f / STFU_AUDIO_FRAME_RATE;
float p = 0.0f; float p = 0.0f;
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;
@ -63,3 +67,5 @@ struct stfu_envelope stfu_trigger_envelope(struct stfu_envelope envelope) {
envelope.v = 0.0f; envelope.v = 0.0f;
return envelope; return envelope;
} }
#endif

View File

@ -1,4 +1,8 @@
#if defined(STFU_MAIN) && !defined(STFU_INTERP)
#define STFU_INTERP
float stfu_linear_interpolate(float x0, float y0, float x1, float y1, float n) { float stfu_linear_interpolate(float x0, float y0, float x1, float y1, float n) {
return (y0 * (x1 - n) + y1 * (n - x0)) / (x1 - x0); return (y0 * (x1 - n) + y1 * (n - x0)) / (x1 - x0);
} }
#endif

View File

@ -1,10 +1,11 @@
#if defined(STFU_MAIN) && !defined(STFU_OSCILLATORS)
#define STFU_OSCILLATORS
#include <math.h> #include <math.h>
#include <stdint.h> #include <stdint.h>
#include "config.h" #include "config.h"
/* https://multimed.org/student/eim/en/04-FM.pdf */
/* Intended to be executed offline with values then embedded in the binary. /* Intended to be executed offline with values then embedded in the binary.
* By having usage of glibc sin and cos functions strictly offline it's easier * By having usage of glibc sin and cos functions strictly offline it's easier
* to have it freestanding * to have it freestanding
@ -76,3 +77,5 @@ struct stfu_sawtooth stfu_pump_sawtooth(struct stfu_sawtooth wave) {
wave.v -= wave.a * 2.f; wave.v -= wave.a * 2.f;
return wave; return wave;
} }
#endif

View File

@ -1,3 +1,6 @@
#if defined(STFU_MAIN) && !defined(STFU_PHASER)
#define STFU_PHASER
#include <math.h> #include <math.h>
#include "config.h" #include "config.h"
@ -29,3 +32,5 @@ struct stfu_phaser stfu_set_phaser_frequency(struct stfu_phaser phaser,
phaser.a = 2 * ((float)M_PI / (float)STFU_AUDIO_FRAME_RATE) * frequency; phaser.a = 2 * ((float)M_PI / (float)STFU_AUDIO_FRAME_RATE) * frequency;
return phaser; return phaser;
} }
#endif

View File

@ -1,3 +1,6 @@
#if defined(STFU_MAIN) && !defined(STFU_TIMER)
#define STFU_TIMER
#include <math.h> #include <math.h>
#include "config.h" #include "config.h"
@ -18,3 +21,5 @@ struct stfu_timer stfu_pump_timer(struct stfu_timer wave) {
wave.v -= 1000; wave.v -= 1000;
return wave; return wave;
} }
#endif