Initial release: procedural techno music generator

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>
This commit is contained in:
2026-03-21 08:47:22 +00:00
commit d58d0d3b09
3 changed files with 509 additions and 0 deletions

428
techno-gen.c Normal file
View File

@@ -0,0 +1,428 @@
/*
* 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;
}