Blob Blame History Raw

#include <math.h>
#include <time.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>

//#define MUSICINDEX 0

#define RATE      48000
#define NOISERATE 2000
#define NOISETIME 10
#define HALFCHUNK 60 // must be multiple of NOISETIME
#define CHUNKTIME (HALFCHUNK*2)

#define EARDIST   0.2
#define MAXDIST   16.0
#define MINDIST   1.0
#define BASEDIST  4.0

#define DROPBOOP  1.0
#define DROPT0    0.001
#define DROPT1    0.004
#define DROPT2    0.004//*10
#define DPS       100

#define NOISEVOL  0.25

#define PI        3.141592653589793
#define PRECISION 1e-6


typedef struct {
    unsigned seed;
    double dist;
    double freq;
    double voll;
    double volr;
    double boop;
    double len0;
    double len1;
    double len2;
} Drop;


typedef struct {
    const char *notes;
    int step;
    int dtone;
    int steps;
} Music;


typedef struct {
    unsigned seed;
    unsigned musicidx[CHUNKTIME*RATE];
    double samples[CHUNKTIME*RATE*2];
    double noisel[CHUNKTIME*NOISERATE];
    double noiser[CHUNKTIME*NOISERATE];
    double noisev[CHUNKTIME/NOISETIME];
    double noiseb[CHUNKTIME/NOISETIME];
} Chunk;



//   --E4--
//     D3
//   --C3--
//     B3
//   --A3--       --c--
//     g2           b
// ----f2-----------a-------------
//     e2           G
// ----d2-----------F-------------
//     c2           E
// ----b2-----------D--------------
//     a2           C
// ----G------------B-------------
//     F            A
// ----E------------g-------------
//     D            f
//   --C--        --e--
//     B            d
//   --A--        --c--
//     g0
//   --f0-
Music musics[] = {
    {   "----" // hotyd
        " E:-B:-E-F#:-B:-F#-"
        " G:-B:-G-A2:-G:-F#-"
        " G----F#----G:F#:E-- ----"
        " E:-B:-E-F#:-B:-F#-"
        " G:-B:-G-A2:-G:-D2-"
        " B2----A2:--G:F#:G:F#:E-- ---- B"
        "----", RATE*60/192/4, 12 },
    {   "----" // cot
        " D#-D#-D#--D--C--A#--D----"
        " D#-D#-D#--D--C--A#--C----"
        " D#-D#-D#--D--C--D--F----"
        " G#-G-G--D#-D--------"
        " D#-D#-D#--D--C--A#--D----"
        " D#-D#-D#--D--C--A#--C----"
        " D#-D#-D#--D--C--D--F----"
        " G#-G-G--D#-D"
        "----", RATE*60/120/8, 12 },
    {   "----" // cot
        " D#-D#-D#--D--C--A#--D----"
        " D#-D#-D#--D--C--A#--C----"
        " D#-D#-D#--D--C--D--F----"
        " G#-G-G--D#-D--------"
        " D#-D#-D#--D--C--A#--D----"
        " D#-D#-D#--D--C--A#--C----"
        " D#-D#-D#--D--C--D--F----"
        " G#-G-G--D#-D"
        "----", RATE*60/120/8, 12 },
    {   "----" // hal
        ":G0#:G0#C#:G0#:G0#D#:G0#:G0#C#:G0#:"
        ":G0#:G0#C#:G0#:G0#D#:G0#:G0#C#:G0#:"
        ":G0#:G0#C#:G0#:G0#D#:G0#:G0#E#:G0#: G0#F#:G0#:G0#E#:G0#:G0#D#:G0#:G0#C#:G0#:"
        ":G0#:G0#C#:G0#:G0#D#:G0#:G0#C#:G0#:"
        ":G0#:G0#C#:G0#:G0#D#:G0#:G0#C#:G0#:"
        ":G0#:G0#C#:G0#:G0#D#:G0#:G0#E#:G0#: G0#F#:G0#:G0#E#:G0#:G0#D#:G0#:G0#C#:G0#:"
        "----", RATE*60/188/4, 12 }, // #DEFG
    {   "----" // sun
        " D-A-D-A-E-A-F-A-"
        " D-A-D-A-E-G0-C-E-"
        " D-A-D-A-E-A-F-A-"
        " D-A-D-A-E-G0-C-E-"
        " ---F-F--F-G--G-F-E-F----F-F-G--G-F-E-F--F-F-F-G-G-F--E-F--F-F-F-A2--A2-G-F-D--DF-DF-DF-EG-EG-DF-CE-DF--DF-DF-DF-FA2--FA2-EG-DF"
        "----", RATE*60/56/16, 24 },
    {   "----" // sun
        " D-A-D-A-E-A-F-A-"
        " D-A-D-A-E-G0-C-E-"
        " D-A-D-A-E-A-F-A-"
        " D-A-D-A-E-G0-C-E-"
        " ---F-F--F-G--G-F-E-F----F-F-G--G-F-E-F--F-F-F-G-G-F--E-F--F-F-F-A2--A2-G-F-D--DF-DF-DF-EG-EG-DF-CE-DF--DF-DF-DF-FA2--FA2-EG-DF"
        "----", RATE*60/56/16, 24 },
};



unsigned rnd(unsigned seed) { return seed*1103515245u + 12345u; }
unsigned rndu(unsigned *seed) { return *seed = rnd(*seed); }
double rndf(unsigned *seed) { return rndu(seed)/(double)UINT_MAX; }
double rndf2(unsigned *seed) { return rndf(seed)*2 - 1; }
double rndnorm(unsigned *seed) { return sqrt(-2*log(rndf(seed)))*sin(2*PI*rndf(seed)); }
double rndlognorm(unsigned *seed, double d) { return exp(d*rndnorm(seed)); }


double cubic(double *v, double t) {
    double tt = t*t, ttt = tt*t;
    return v[0]*( -0.5*ttt +     tt - 0.5*t )
         + v[1]*(  1.5*ttt - 2.5*tt + 1.0   )
         + v[2]*( -1.5*ttt + 2.0*tt + 0.5*t )
         + v[3]*(  0.5*ttt - 0.5*tt         );
}


void droplog(Drop *d, const char *prefix) {
    fprintf(stderr, "%s: drop: d%g f%g v%g/%g/%g l%g/%g/%g #%x\n",
        prefix, d->dist, d->freq, d->voll, d->volr, d->boop, d->len0, d->len1, d->len2, d->seed);
}


void dropgen(Drop *d, unsigned seed, double dist, double freq, double boop, double len0, double len1, double len2) {
    double d0 = dist*dist + EARDIST*EARDIST;
    double de = 2*EARDIST*dist*cos(rndf(&seed)*2*PI);
    d->seed = seed;
    d->dist = dist;
    d->freq = freq;
    d->boop = boop;
    d->len0 = len0;
    d->len1 = len1;
    d->len2 = len2;
    d->voll = 1/(1 + d0 - de);
    d->volr = 1/(1 + d0 + de);
}


void dropgenfull(Drop *d, unsigned seed) {
    double da = rndf(&seed);
    double db = rndf(&seed);
    double dist = MINDIST + (da > db ? da : db)*MAXDIST;
    double freq = 3000*pow(2, rndf2(&seed));
    double boop = DROPBOOP;
    double len0 = DROPT0;//pow(2, rndf2(&seed));
    double len1 = DROPT1;//pow(2, rndf2(&seed));
    double len2 = DROPT2;//pow(2, rndf2(&seed));
    dropgen(d, seed, dist, freq, boop, len0, len1, len2);
}


void dropbakeboop(Drop *d, double *samples, double vol, double len) {
    //fprintf(stderr, "boop: v%g l%g\n", vol, len);
    double dt = 1.0/RATE;
    for(double t = 0; t < len; t += dt) {
        double x = 2*t/len - 1;
        x *= x;
        if (x < PRECISION) continue;
        double v = vol*sqrt(1 - x);
        *samples++ += v*d->voll;
        *samples++ += v*d->volr;
    }
}


void dropbakewave(Drop *d, double *samples, double freq, double vol, double len) {
    //fprintf(stderr, "wave: f%g v%g l%g\n", freq, vol, len);
    double dt = 1.0/RATE;
    double t1 = len*10;
    double kv = pow(0.5, dt/len);
    double ka = freq*2*PI;
    for(double t = 0; t < t1; t += dt, vol *= kv) {
        double v = sin(t*ka)*vol*pow(0.5, t/len);
        *samples++ += v*d->voll;
        *samples++ += v*d->volr;
    }
}


void dropbake(Drop *d, double *samples) {
    unsigned seed = d->seed;
    double vb = rndu(&seed)%2*2.0 - 1;
    double vw = rndu(&seed)%2*2.0 - 1;
    dropbakeboop(d, samples, vb*d->boop, d->len0);  // boop
    samples += (int)(d->len1*RATE + 0.5)*2;         // silence
    dropbakewave(d, samples, d->freq, vw, d->len2); // wave
}


int readnote(const char **s, int tone0, double *freq, int *steps) {
    static const int tones[] = { 0, 2, 3, 5, 7, 8, 10 };

    char c = **s;
    if (!c) return 0;
    double f = 0;
    if (c >= 'A' && c <= 'G') {
        ++*s;
        int tone = tone0 + tones[c - 'A'];
        while(1) {
            c = **s;
            if (c == '#') ++tone; else
            if (c == 'b') --tone; else
            if (c >= '0' && c <= '9') tone += (c - '1')*12; else
                break;
            ++*s;
        }
        f = 440*pow(2, tone/12.0);
    }

    int st = 0;
    while(1) {
        c = **s;
        if (!c) break;
        if (c >= 'A' && c <= 'G') break;
        st += c == '.' ? 1
            : c == ':' ? 2
            : c == '-' ? 4 : 0;
        ++*s;
    }

    if (freq) *freq = f;
    if (steps) *steps = st;
    return 1;
}


int musicsteps(const char *music) {
    int steps = 0, s = 0;
    while(readnote(&music, 0, NULL, &s)) steps += s;
    return steps;
}


void musicbake(double *samples, unsigned seed, const char *music, int step, int dtone, int maxsteps) {
    int steps = 0;
    double freq = 0;
    int sumsteps = 0;
    while(readnote(&music, dtone, &freq, &steps)) {
        if (freq) {
            Drop d = {};
            double dk = sumsteps/(double)maxsteps;
            double dist = BASEDIST*(1 + 0.5*dk*dk);
            dropgen(&d, rndu(&seed)^8, dist, freq, DROPBOOP*0.5, DROPT0, DROPT1, DROPT2*4);
            dropbake(&d, samples);
            //droplog(&d, "music");
        }
        //if (steps) fprintf(stderr, "steps: %d\n", steps);
        samples += steps*step*2;
        sumsteps += steps;
    }
}


void initmusics() {
    int mcnt = sizeof(musics)/sizeof(*musics);
    for(int i = 0; i < mcnt; ++i) {
        Music *m = musics + i;
        m->steps = musicsteps(m->notes);
        fprintf(stderr, "%s: %d\n", m->notes, m->steps);
        if (m->steps*m->step >= HALFCHUNK*RATE) fprintf(stderr, "bad music steps: %s: %d*%d\n", m->notes, m->steps, m->step);
    }
}


void genmusic(Chunk *c, int half) {
    unsigned seed = rndu(&c->seed)^9;
    int mcnt = sizeof(musics)/sizeof(*musics);
    for(int i = 0; i < 5; ++i) {
        #ifdef MUSICINDEX
        Music *m = musics + MUSICINDEX;
        int mlen = m->steps*m->step;
        int sidx = 0;
        #else
        Music *m = musics + rndu(&seed)%mcnt;
        int mlen = m->steps*m->step;
        int sidx = rndu(&seed)%(HALFCHUNK*RATE - half*(mlen + 2*RATE));
        #endif
        unsigned *mp = c->musicidx + sidx;
        if (*mp%2 || mp[mlen] != *mp)
            continue;

        fprintf(stderr, "%s: %g\n", m->notes, sidx/(double)RATE);
        if (sidx + mlen >= CHUNKTIME*RATE) fprintf(stderr, "overrun: %s: %d + %d\n", m->notes, sidx, mlen);

        for(unsigned *me = mp + mlen; mp < me; ++mp) ++*mp;
        for(unsigned *me = c->musicidx + CHUNKTIME*RATE; mp < me; ++mp) *mp += 2;
        fprintf(stderr, "%d\n", *(mp-1));

        musicbake(c->samples + sidx*2, rndu(&seed)^8, m->notes, m->step, m->dtone, m->steps);
    }
}


void genrain(Chunk *c) {
    int count = DPS*HALFCHUNK;
    unsigned seed = rndu(&c->seed)^2;
    while(count--) {
        if (count%DPS == 0) { fprintf(stderr, "."); fflush(stderr); }
        Drop d = {};
        dropgenfull(&d, rndu(&seed)^3);
        int sidx = rndu(&seed)%(HALFCHUNK*RATE);
        if (d.dist < MINDIST && c->musicidx[sidx]%2) continue;
        if (d.dist < MINDIST) continue;
        //droplog(&d, "rain");
        dropbake(&d, c->samples + sidx*2);
    }
    fprintf(stderr, "\n");
}


void gennoise(Chunk *c) {
    unsigned seed = c->seed^17;
    size_t cntvb = CHUNKTIME/NOISETIME/2;
    size_t cntlr = CHUNKTIME*NOISERATE/2;
    for(double *n = c->noisev + cntvb + 2, *e = n + cntvb - 2; n < e; ) *n++ = rndf2(&seed);
    for(double *n = c->noiseb + cntvb + 2, *e = n + cntvb - 2; n < e; ) *n++ = rndf2(&seed);
    for(double *n = c->noisel + cntlr + 2, *e = n + cntlr - 2; n < e; ) *n++ = rndf2(&seed);
    for(double *n = c->noiser + cntlr + 2, *e = n + cntlr - 2; n < e; ) *n++ = rndf2(&seed);

    double *nv = c->noisev;
    double *nb = c->noiseb;
    double *nl = c->noisel;
    double *nr = c->noiser;
    double tvb = 0, tlr = 0;
    double dtvb = 1.0/RATE/NOISETIME;
    double dtlr = NOISERATE/(double)RATE;
    for(double *s = c->samples, *e = s + HALFCHUNK*RATE*2; s < e; tvb += dtvb, tlr += dtlr) {
        while(tvb > 1) { tvb -= 1; ++nv; ++nb; }
        while(tlr > 1) { tlr -= 1; ++nl; ++nr; }
        double v = (1 + cubic(nv, tvb)*0.7)/2 * NOISEVOL;
        double b = (1 + cubic(nb, tvb)*0.5)/2;
        *s++ += v*cubic(nl, tlr)*b;
        *s++ += v*cubic(nr, tlr)*(1-b);
    }
}


void movehalf(void* p, size_t size) {
    size /= 2;
    memcpy(p, (char*)p + size, size);
    memset((char*)p + size, 0, size);
}


void rotate(Chunk *c) {
    c->seed = rnd(c->seed^1);

    movehalf(c->samples, sizeof(c->samples));
    movehalf(c->musicidx, sizeof(c->musicidx));
    movehalf(c->noisev, sizeof(c->noisev));
    movehalf(c->noiseb, sizeof(c->noiseb));
    movehalf(c->noisel, sizeof(c->noisel));
    movehalf(c->noiser, sizeof(c->noiser));

    unsigned *m = c->musicidx + HALFCHUNK*RATE, v = *(m-1);
    if (v%2) { fprintf(stderr, "bad musicidx: %d\n", v); ++v; }
    for(unsigned *e = m + HALFCHUNK*RATE; m < e; ++m) *m = v;
}


void out(Chunk *c, double k) {
    //fprintf(stderr, "out: %g\n", k);
    for(double *s = c->samples, *e = s + HALFCHUNK*RATE*2; s < e; ++s) {
        //fprintf(stderr, "s: %g\n", *s);
        double d = *s*k;
        d = 1/(1+exp(-d));
        //fprintf(stderr, "s*k: %g * %g = %g\n", *s, k, d);
        int i = d*65536;
        if (i < 0) i = 0;
        if (i > 65535) i = 65535;
        i -= 32768;
        fputc(i & 0xff, stdout);
        fputc((i >> 8) & 0xff, stdout);
    }
}



Chunk chunk;

int main(int argc, char **argv) {
    int count = 0;
    unsigned seed = time(NULL);
    if (argc > 1) count = atoi(argv[1]);
    if (argc > 2) seed = atoll(argv[2]);
    fprintf(stderr, "aseed: %u\n", seed);
    fprintf(stderr, "arepeats: %u\n", count);

    initmusics();
    if (count <= 0) count = INT_MAX;

    chunk.seed = seed;
    gennoise(&chunk);

    rotate(&chunk);
    unsigned seed0 = chunk.seed;
    #ifndef MUSICINDEX
    genmusic(&chunk, 1);
    genrain(&chunk);
    gennoise(&chunk);
    #endif

    rotate(&chunk);
    unsigned seed1 = chunk.seed;
    #ifndef MUSICINDEX
    genmusic(&chunk, 0);
    genrain(&chunk);
    gennoise(&chunk);
    #endif

    fprintf(stderr, "begin: %u\n", count);
    while(count--) {
        rotate(&chunk);
        if (count == 1) chunk.seed = seed0;
        if (count == 0) chunk.seed = seed1;
        genmusic(&chunk, count == 0);
        #ifndef MUSICINDEX
        genrain(&chunk);
        gennoise(&chunk);
        #endif
        out(&chunk, 1/2.0);
        fprintf(stderr, "remain: %u\n", count);
    }
    return 0;
}