Synthesizes kick, snare, hi-hat, bass, and acid lead from scratch. 32-bar song structure with filter sweeps and pattern variation. Outputs raw float32 stereo at 44.1 kHz to stdout. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
429 lines
12 KiB
C
429 lines
12 KiB
C
/*
|
|
* techno-gen — generates infinite techno music as raw float32 stereo audio
|
|
*
|
|
* Build: gcc -O2 -lm -o techno-gen techno-gen.c
|
|
* Play: ./techno-gen | paplay --format=float32le --rate=44100 --channels=2
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
|
|
#define SR 44100
|
|
#define BPM 138.0
|
|
#define TWO_PI (2.0 * M_PI)
|
|
#define SPB ((int)(SR * 60.0 / BPM)) /* samples per beat (quarter note) */
|
|
#define SP16 (SPB / 4) /* samples per 16th note */
|
|
|
|
static inline double freq_of(double root, int semitones) {
|
|
return root * pow(2.0, semitones / 12.0);
|
|
}
|
|
|
|
/* ── PRNG ── */
|
|
static uint32_t rng_state = 0xdeadbeef;
|
|
static double randf(void) {
|
|
rng_state ^= rng_state << 13;
|
|
rng_state ^= rng_state >> 17;
|
|
rng_state ^= rng_state << 5;
|
|
return (double)(int32_t)rng_state * (1.0 / 2147483648.0);
|
|
}
|
|
|
|
/* ── Biquad filter ── */
|
|
typedef struct { double b0, b1, b2, a1, a2, x1, x2, y1, y2; } Bq;
|
|
|
|
static void bq_lp(Bq *f, double fc, double q) {
|
|
double w = TWO_PI * fc / SR;
|
|
double alpha = sin(w) / (2.0 * q);
|
|
double c = cos(w), a0 = 1.0 + alpha;
|
|
f->b0 = (1.0 - c) * 0.5 / a0;
|
|
f->b1 = (1.0 - c) / a0;
|
|
f->b2 = f->b0;
|
|
f->a1 = -2.0 * c / a0;
|
|
f->a2 = (1.0 - alpha) / a0;
|
|
}
|
|
|
|
static void bq_hp(Bq *f, double fc, double q) {
|
|
double w = TWO_PI * fc / SR;
|
|
double alpha = sin(w) / (2.0 * q);
|
|
double c = cos(w), a0 = 1.0 + alpha;
|
|
f->b0 = (1.0 + c) * 0.5 / a0;
|
|
f->b1 = -(1.0 + c) / a0;
|
|
f->b2 = f->b0;
|
|
f->a1 = -2.0 * c / a0;
|
|
f->a2 = (1.0 - alpha) / a0;
|
|
}
|
|
|
|
static double bq_run(Bq *f, double x) {
|
|
double y = f->b0*x + f->b1*f->x1 + f->b2*f->x2
|
|
- f->a1*f->y1 - f->a2*f->y2;
|
|
f->x2 = f->x1; f->x1 = x;
|
|
f->y2 = f->y1; f->y1 = y;
|
|
return y;
|
|
}
|
|
|
|
/* ── Kick drum ──
|
|
* Sine wave with pitch drop (200 → 45 Hz) and amp decay */
|
|
typedef struct { double phase, amp, pitch; } Kick;
|
|
|
|
static void kick_trig(Kick *k) {
|
|
k->phase = 0.0; k->amp = 1.0; k->pitch = 1.0;
|
|
}
|
|
|
|
static double kick_tick(Kick *k) {
|
|
if (k->amp < 0.001) { return 0.0; }
|
|
double freq = 45.0 + 175.0 * k->pitch;
|
|
k->phase += TWO_PI * freq / SR;
|
|
if (k->phase > TWO_PI) { k->phase -= TWO_PI; }
|
|
double click = (k->amp > 0.97) ? randf() * 0.25 : 0.0;
|
|
double s = (sin(k->phase) * 0.95 + click) * k->amp;
|
|
k->amp *= 0.9997;
|
|
k->pitch *= 0.9982;
|
|
return s * 0.85;
|
|
}
|
|
|
|
/* ── Snare/clap ──
|
|
* Noise burst + low-tone blend */
|
|
typedef struct { double env, tone_phase; Bq hp; } Snare;
|
|
|
|
static void snare_trig(Snare *s) {
|
|
s->env = 1.0; s->tone_phase = 0.0;
|
|
}
|
|
|
|
static double snare_tick(Snare *s) {
|
|
if (s->env < 0.001) { return 0.0; }
|
|
s->tone_phase += TWO_PI * 185.0 / SR;
|
|
if (s->tone_phase > TWO_PI) { s->tone_phase -= TWO_PI; }
|
|
double noise = bq_run(&s->hp, randf());
|
|
double out = (noise * 0.70 + sin(s->tone_phase) * 0.30) * s->env;
|
|
s->env *= 0.9935;
|
|
return out * 0.55;
|
|
}
|
|
|
|
/* ── Hi-hat ──
|
|
* Bandpass-ish noise, two modes: closed / open */
|
|
typedef struct { double env, decay; Bq hp; } Hat;
|
|
|
|
static void hat_trig(Hat *h, int open) {
|
|
h->env = 1.0;
|
|
h->decay = open ? 0.9993 : 0.9958;
|
|
}
|
|
|
|
static double hat_tick(Hat *h) {
|
|
if (h->env < 0.001) { return 0.0; }
|
|
double out = bq_run(&h->hp, randf()) * h->env;
|
|
h->env *= h->decay;
|
|
return out * 0.38;
|
|
}
|
|
|
|
/* ── Bass synth ──
|
|
* Sawtooth through resonant low-pass with filter envelope */
|
|
typedef struct { double phase, freq, tgt, amp_env, flt_env; Bq lpf; } Bass;
|
|
|
|
static void bass_note(Bass *b, double freq) {
|
|
b->tgt = freq;
|
|
b->flt_env = 1.0;
|
|
b->amp_env = 1.0;
|
|
}
|
|
|
|
static double bass_tick(Bass *b, double cutoff_base) {
|
|
b->freq += (b->tgt - b->freq) * 0.06;
|
|
b->phase += TWO_PI * b->freq / SR;
|
|
if (b->phase > TWO_PI) { b->phase -= TWO_PI; }
|
|
double saw = (b->phase / M_PI) - 1.0;
|
|
double fc = cutoff_base + 3500.0 * b->flt_env;
|
|
if (fc < 60.0) { fc = 60.0; }
|
|
if (fc > 18000.0) { fc = 18000.0; }
|
|
bq_lp(&b->lpf, fc, 4.5);
|
|
double out = bq_run(&b->lpf, saw) * b->amp_env;
|
|
b->flt_env *= 0.9991;
|
|
b->amp_env *= 0.9999;
|
|
return out * 0.55;
|
|
}
|
|
|
|
/* ── Acid lead ──
|
|
* Sawtooth through very resonant LP, slightly detuned for bite */
|
|
typedef struct { double phase, phase2, freq, tgt, amp_env, flt_env; Bq lpf; } Acid;
|
|
|
|
static void acid_note(Acid *a, double freq) {
|
|
a->tgt = freq;
|
|
a->flt_env = 1.0;
|
|
a->amp_env = 1.0;
|
|
}
|
|
|
|
static double acid_tick(Acid *a, double cutoff_base) {
|
|
if (a->amp_env < 0.001) { return 0.0; }
|
|
a->freq += (a->tgt - a->freq) * 0.09;
|
|
a->phase += TWO_PI * a->freq / SR;
|
|
a->phase2 += TWO_PI * (a->freq * 1.008) / SR; /* slight detune */
|
|
if (a->phase > TWO_PI) { a->phase -= TWO_PI; }
|
|
if (a->phase2 > TWO_PI) { a->phase2 -= TWO_PI; }
|
|
double saw = ((a->phase / M_PI) - 1.0) * 0.6
|
|
+ ((a->phase2 / M_PI) - 1.0) * 0.4;
|
|
double fc = cutoff_base + 5000.0 * a->flt_env;
|
|
if (fc < 80.0) { fc = 80.0; }
|
|
if (fc > 18000.0) { fc = 18000.0; }
|
|
bq_lp(&a->lpf, fc, 6.0);
|
|
double out = bq_run(&a->lpf, saw) * a->amp_env;
|
|
a->flt_env *= 0.9992;
|
|
a->amp_env *= 0.9997;
|
|
return out * 0.32;
|
|
}
|
|
|
|
/* ════════════════════════════════════════════════════════════
|
|
* Patterns (16 steps per bar, -1 = rest / hold)
|
|
* Notes are semitone offsets from root A1 = 55 Hz
|
|
* ════════════════════════════════════════════════════════════ */
|
|
|
|
static const int BASS_PAT[4][16] = {
|
|
/* 0: driving quarter-note anchor */
|
|
{ 0,-1, 0,-1, 7,-1, 0,-1, 3,-1, 5,-1, 7,-1, 3,-1 },
|
|
/* 1: offbeat pushes */
|
|
{ 0, 0,-1,12, 0, 7,-1, 5, 3,-1, 3, 5, 7,-1, 7, 3 },
|
|
/* 2: dark and sparse */
|
|
{ 0,-1,-1, 0,-1, 7, 5,-1, 0,-1,-1, 3,-1, 5, 0,-1 },
|
|
/* 3: more frantic */
|
|
{ 0, 3, 0, 7, 0,12, 7, 0, 3, 0, 5, 3, 0, 7, 5, 0 },
|
|
};
|
|
|
|
static const int ACID_PAT[2][16] = {
|
|
{ 12,-1,-1,14,-1,-1,12,-1,10,-1,-1,12,-1,-1,10,-1 },
|
|
{ 12,-1,15,-1,14,-1,12,10,-1,12,-1,15,17,-1,15,12 },
|
|
};
|
|
|
|
/* 0=off, 1=closed, 2=open */
|
|
static const int HAT_PAT[2][16] = {
|
|
{ 1,0,1,0, 1,0,1,0, 1,0,1,0, 1,0,1,2 },
|
|
{ 1,1,1,0, 1,1,2,0, 1,1,1,0, 1,0,2,0 },
|
|
};
|
|
|
|
/* 0=off, 1=kick */
|
|
static const int KICK_PAT[2][16] = {
|
|
{ 1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0 }, /* 4-on-the-floor */
|
|
{ 1,0,0,0, 1,0,0,1, 1,0,0,0, 1,0,1,0 }, /* syncopated */
|
|
};
|
|
|
|
/* ════════════════════════════════════════════════════════════
|
|
* Section map (cycles every 32 bars):
|
|
*
|
|
* 0-3 : intro — kick + bass only, filter closed
|
|
* 4-7 : rise — add hats, filter opens
|
|
* 8-15 : full — kick + hats + snare + bass + acid, sweeps
|
|
* 16-19 : break — bass only, everything stripped
|
|
* 20-23 : build — kick returns, filter rises
|
|
* 24-31 : heavy — syncopated kick, acid pattern 2, wide sweep
|
|
* ════════════════════════════════════════════════════════════ */
|
|
|
|
typedef struct {
|
|
int kick_pat;
|
|
int hat_pat;
|
|
int bass_pat;
|
|
int acid_pat;
|
|
int use_hats;
|
|
int use_snare;
|
|
int use_acid;
|
|
/* cutoff_base for bass and acid as function of bar-in-section */
|
|
double bass_fc_lo, bass_fc_hi;
|
|
double acid_fc_lo, acid_fc_hi;
|
|
} Section;
|
|
|
|
static void get_section(int bar, Section *s) {
|
|
int b = bar % 32;
|
|
double t = 0.0; /* 0..1 within section for sweeps */
|
|
|
|
if (b < 4) {
|
|
t = b / 4.0;
|
|
s->kick_pat = 0;
|
|
s->hat_pat = 0;
|
|
s->bass_pat = 0;
|
|
s->acid_pat = 0;
|
|
s->use_hats = 0;
|
|
s->use_snare = 0;
|
|
s->use_acid = 0;
|
|
s->bass_fc_lo = 200.0;
|
|
s->bass_fc_hi = 200.0 + 400.0 * t;
|
|
s->acid_fc_lo = 0;
|
|
s->acid_fc_hi = 0;
|
|
} else if (b < 8) {
|
|
t = (b - 4) / 4.0;
|
|
s->kick_pat = 0;
|
|
s->hat_pat = 0;
|
|
s->bass_pat = 1;
|
|
s->acid_pat = 0;
|
|
s->use_hats = 1;
|
|
s->use_snare = 0;
|
|
s->use_acid = 0;
|
|
s->bass_fc_lo = 350.0 + 600.0 * t;
|
|
s->bass_fc_hi = s->bass_fc_lo + 400.0;
|
|
s->acid_fc_lo = 0;
|
|
s->acid_fc_hi = 0;
|
|
} else if (b < 16) {
|
|
t = (b - 8) / 8.0;
|
|
s->kick_pat = 0;
|
|
s->hat_pat = 0;
|
|
s->bass_pat = (b < 12) ? 1 : 2;
|
|
s->acid_pat = 0;
|
|
s->use_hats = 1;
|
|
s->use_snare = 1;
|
|
s->use_acid = 1;
|
|
/* sweeping filter: sine lfo over 4 bars */
|
|
double lfo = sin(t * TWO_PI * 2.0) * 0.5 + 0.5;
|
|
s->bass_fc_lo = 500.0 + 1200.0 * lfo;
|
|
s->bass_fc_hi = s->bass_fc_lo + 600.0;
|
|
s->acid_fc_lo = 400.0 + 1800.0 * lfo;
|
|
s->acid_fc_hi = s->acid_fc_lo + 800.0;
|
|
} else if (b < 20) {
|
|
/* breakdown */
|
|
s->kick_pat = 0;
|
|
s->hat_pat = 0;
|
|
s->bass_pat = 2;
|
|
s->acid_pat = 0;
|
|
s->use_hats = 0;
|
|
s->use_snare = 0;
|
|
s->use_acid = 0;
|
|
s->bass_fc_lo = 180.0;
|
|
s->bass_fc_hi = 240.0;
|
|
s->acid_fc_lo = 0;
|
|
s->acid_fc_hi = 0;
|
|
} else if (b < 24) {
|
|
/* build */
|
|
t = (b - 20) / 4.0;
|
|
s->kick_pat = 0;
|
|
s->hat_pat = 1;
|
|
s->bass_pat = 3;
|
|
s->acid_pat = 0;
|
|
s->use_hats = 1;
|
|
s->use_snare = 1;
|
|
s->use_acid = (t > 0.5);
|
|
s->bass_fc_lo = 200.0 + 1200.0 * t * t;
|
|
s->bass_fc_hi = s->bass_fc_lo + 800.0;
|
|
s->acid_fc_lo = 200.0 + 2000.0 * t * t;
|
|
s->acid_fc_hi = s->acid_fc_lo + 1200.0;
|
|
} else {
|
|
/* heavy */
|
|
t = (b - 24) / 8.0;
|
|
double lfo = sin(t * TWO_PI * 3.0) * 0.5 + 0.5;
|
|
s->kick_pat = 1;
|
|
s->hat_pat = 1;
|
|
s->bass_pat = 3;
|
|
s->acid_pat = 1;
|
|
s->use_hats = 1;
|
|
s->use_snare = 1;
|
|
s->use_acid = 1;
|
|
s->bass_fc_lo = 700.0 + 1500.0 * lfo;
|
|
s->bass_fc_hi = s->bass_fc_lo + 900.0;
|
|
s->acid_fc_lo = 600.0 + 2400.0 * lfo;
|
|
s->acid_fc_hi = s->acid_fc_lo + 1400.0;
|
|
}
|
|
}
|
|
|
|
/* ════════════════════════════════════════════════════════════ */
|
|
|
|
int main(void) {
|
|
double root = 55.0; /* A1 */
|
|
|
|
Kick kick = {0};
|
|
Snare snare = {0};
|
|
Hat hat = {0};
|
|
Bass bass = {0};
|
|
Acid acid = {0};
|
|
|
|
bq_hp(&snare.hp, 280.0, 0.7);
|
|
bq_hp(&hat.hp, 7200.0, 0.9);
|
|
|
|
bass.freq = root;
|
|
bass.tgt = root;
|
|
acid.freq = freq_of(root, 12);
|
|
acid.tgt = acid.freq;
|
|
|
|
/* Stereo chorus/width: short delay line */
|
|
#define DLEN 2048
|
|
double dbuf[DLEN] = {0};
|
|
int dpos = 0;
|
|
int dtap = (int)(0.011 * SR) % DLEN;
|
|
|
|
int step = 0;
|
|
int bar = 0;
|
|
int sample_in_step = 0;
|
|
|
|
/* For per-step cutoff interpolation */
|
|
double bass_fc = 500.0;
|
|
double acid_fc = 400.0;
|
|
|
|
while (1) {
|
|
if (sample_in_step == 0) {
|
|
Section sec;
|
|
get_section(bar, &sec);
|
|
|
|
/* Interpolate cutoff: step position within bar 0..15 */
|
|
double sbar_t = step / 16.0;
|
|
bass_fc = sec.bass_fc_lo + (sec.bass_fc_hi - sec.bass_fc_lo) * sbar_t;
|
|
acid_fc = sec.acid_fc_lo + (sec.acid_fc_hi - sec.acid_fc_lo) * sbar_t;
|
|
|
|
/* Kick */
|
|
if (KICK_PAT[sec.kick_pat][step]) {
|
|
kick_trig(&kick);
|
|
}
|
|
/* Hi-hats */
|
|
if (sec.use_hats) {
|
|
int hv = HAT_PAT[sec.hat_pat][step];
|
|
if (hv > 0) { hat_trig(&hat, hv == 2); }
|
|
}
|
|
/* Snare on steps 4 and 12 */
|
|
if (sec.use_snare && (step == 4 || step == 12)) {
|
|
snare_trig(&snare);
|
|
}
|
|
/* Bass */
|
|
{
|
|
int note = BASS_PAT[sec.bass_pat][step];
|
|
if (note >= 0) {
|
|
bass_note(&bass, freq_of(root, note));
|
|
}
|
|
}
|
|
/* Acid */
|
|
if (sec.use_acid) {
|
|
int note = ACID_PAT[sec.acid_pat][step];
|
|
if (note >= 0) {
|
|
acid_note(&acid, freq_of(root, note));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ── Synthesize ── */
|
|
double k = kick_tick(&kick);
|
|
double h = hat_tick(&hat);
|
|
double sn = snare_tick(&snare);
|
|
double b = bass_tick(&bass, bass_fc);
|
|
double a = acid_tick(&acid, acid_fc);
|
|
|
|
/* Stereo mix: slight panning differences */
|
|
double mid = k + sn * 0.9 + b;
|
|
double l = mid + h * 0.7 + a * 0.9;
|
|
double r = mid + h * 0.9 + a * 0.6;
|
|
|
|
/* Delay-based stereo width (ping-pong on hats + acid) */
|
|
dbuf[dpos] = h * 0.25 + a * 0.18;
|
|
double delayed = dbuf[(dpos - dtap + DLEN) % DLEN];
|
|
l += delayed * 0.35;
|
|
r -= delayed * 0.20;
|
|
dpos = (dpos + 1) % DLEN;
|
|
|
|
/* Soft saturation (tanh limiter) */
|
|
l = tanh(l * 0.82);
|
|
r = tanh(r * 0.82);
|
|
|
|
float out[2] = { (float)l, (float)r };
|
|
fwrite(out, sizeof(float), 2, stdout);
|
|
|
|
/* ── Advance sequencer ── */
|
|
if (++sample_in_step >= SP16) {
|
|
sample_in_step = 0;
|
|
step = (step + 1) % 16;
|
|
if (step == 0) { bar++; }
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|