/* * 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 #include #include #include #include #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; }