stew

a monorepo of some sort
Log | Files | Refs

commit 436441120de6e062654ee883f9f671a832b665a9
parent ae120b537f186f631aaf681bc68ea25377c45545
Author: rpa <rpa@laika>
Date:   Wed,  4 Jan 2023 19:35:40 +0000

src/wave: bits and pieces of wavetable synth

Diffstat:
Asrc/wave/draw.c | 371+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/wave/fade.c | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/wave/harm.c | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/wave/loop.c | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/wave/mkfile | 7+++++++
Asrc/wave/piano.c | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/wave/sampler.c | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/wave/unconv | 4++++
Asrc/wave/util.h | 23+++++++++++++++++++++++
Asrc/wave/waveform | 20++++++++++++++++++++
10 files changed, 887 insertions(+), 0 deletions(-)

diff --git a/src/wave/draw.c b/src/wave/draw.c @@ -0,0 +1,371 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <thread.h> +#include <mouse.h> +#include <keyboard.h> + +#include "util.h" + +#define DefLength 169 +/* + * This DefLength is equal to standard sample rate of 44100 + * divided by C4 note from chromatic scale frequency of 261.626 + */ + +char *path; +int autosaveflag, dirtyflag; +u32int pcm[MaxLength]; +usize length; +Mousectl *mctl; +Keyboardctl *kctl; +Image *bg, *lo, *hi; + +int readpcm(void); +int writepcm(void); +void keyboard(Rune); +void mouse(Mouse); +void resize(int *); +void redraw(u32int *, u32int *); + +void sine(void); +void noise(void); + +void save(void); + + +void (*menucmd[])(void) = { + sine, noise +}; + +char *menuitems[] = { + "sine", + "noise", + nil, +}; + +Menu menu = {menuitems, nil, 0}; + +void +usage(void) +{ + fprint(2, "usage: [-a] %s [file]\n", argv0); + threadexitsall("usage"); +} + +void +threadmain(int argc, char **argv) +{ + ARGBEGIN{ + case 'a': + autosaveflag = 1; + break; + default: + usage(); + }ARGEND + + if (argc > 1) usage(); + else if (argc == 0) { + path = strdup(""); + length = DefLength; + sine(); + } else { + path = strdup(argv[0]); + if (readpcm() != 0) { + fprint(2, "failed to open file %s: %r\n", argv[0]); + threadexitsall("failed to open file"); + } + } + + if (initdraw(nil, nil, "pcm/draw") < 0) sysfatal("initdraw: %r"); + if ((mctl = initmouse(nil, screen)) == nil) sysfatal("initmouse: %r"); + if ((kctl = initkeyboard(nil)) == nil) sysfatal("initkeyboard: %r"); + + bg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DBlack); + lo = allocimagemix(display, DRed, DBlack); + hi = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DRed); + + draw(screen, screen->r, bg, nil, ZP); + redraw(nil, nil); + flushimage(display, 1); + + Rune kv; + Mouse mv; + int rv[2]; + + Alt alts[] = { + {kctl->c, &kv, CHANRCV}, + {mctl->c, &mv, CHANRCV}, + {mctl->resizec, rv, CHANRCV}, + {nil, nil, CHANEND}, + }; + + for (;;) { + switch (alt(alts)) { + case 0: + keyboard(kv); + break; + case 1: + mouse(mv); + break; + case 2: + resize(rv); + break; + } + } +} + +int +readpcm(void) +{ + int fd; + if ((fd = open(path, OREAD)) < 0) { + fprint(2, "failed to open %s: %r\n", path); + return 1; + } + length = MaxLength; + length = read(fd, pcm, MaxLength * 4) / 4; + close(fd); + return 0; +} + +int +writepcm(void) +{ + int fd; + if ((fd = create(path, OWRITE, 0666)) < 0) { + fprint(2, "failed to open %s: %r\n", path); + return 1; + } + write(fd, pcm, length * 4); + close(fd); + return 0; +} + +void +keyboard(Rune rv) +{ + usize newlength; + char *rpt, ebuf[1024]; + + switch (rv) { + case 'r': + case 'w': + snprint(ebuf, sizeof(ebuf), "%C%s", rv, path); + break; + case 'l': + snprint(ebuf, sizeof(ebuf), "%C%ulld", rv, length); + break; + default: + snprint(ebuf, sizeof(ebuf), "%C", rv); + } + + if (enter("% ", ebuf, sizeof(ebuf), mctl, kctl, nil) < 0) return; + + switch(ebuf[0]) { + case 'q': + threadexitsall(nil); + break; + case 'w': + free(path); + path = strdup(ebuf + 1); + writepcm(); + break; + case 'r': + free(path); + path = strdup(ebuf + 1); + readpcm(); + redraw(nil, nil); + flushimage(display, 1); + break; + case 'l': + newlength = strtoull(ebuf + 1, &rpt, 0); + if ((rpt != ebuf + 1) && (newlength < MaxLength)) { + usize oldlength = length; + length = newlength; + int i; + if (oldlength != 0) { + for (i = oldlength; i < newlength; i++) pcm[i] = pcm[i%oldlength]; + } + if (oldlength < newlength) { + redraw(pcm + oldlength, pcm + newlength); + } else { + draw(screen, rectaddpt(screen->r, Pt(newlength, 0)), bg, nil, ZP); + } + flushimage(display, 1); + } + } +} + +void +mouse(Mouse mv) +{ + u32int *buf; + Frame n; + int x, i, bsize, dir, h; + static Point oldpt; + Point pt = subpt(mv.xy, screen->r.min); + double d, val; + + h = Dy(screen->r); + val = 1 - 2 * (double)pt.y / (double)h; + + if (val < -1) val = -1; + if (val > 1) val = 1; + pt.y = val * (double)0x7fff; + + switch (mv.buttons) { + case 0: + save(); + oldpt = pt; + break; + case 1: + /* draw line in pcm from oldpt to pt) */ + bsize = pt.x - oldpt.x; + dir = 1; + if (bsize < 0) bsize = -bsize, dir = -1; + for ( + i = 0, x = oldpt.x; + x != pt.x; + i++, x += dir) { + d = (double)i / (double)bsize; + n.c1 = (pt.y * d + oldpt.y * (1 - d)); + n.c2 = n.c1; + if ((x >= 0) && (x < length)) { + Frame *fr = (Frame *)&pcm[x]; + fr->c1 = n.c1; + fr->c2 = n.c2; + } + } + if ((pt.x >= 0) && (pt.x < length)) { + Frame *fr = (Frame *)&pcm[pt.x]; + fr->c1 = pt.y; + fr->c2 = pt.y; + } + + if (dir > 0) { + redraw(pcm + oldpt.x, pcm + pt.x + 2); + } else { + redraw(pcm + pt.x, pcm + oldpt.x + 2); + } + flushimage(display, 1); + + oldpt = pt; + dirtyflag = 1; + break; + case 2: + /* shift pcm data left/right */ + bsize = oldpt.x - pt.x; + while (bsize < 0) bsize += length; + bsize = (length == 0) ? 0 : bsize % length; + + buf = malloc(length * 4); + + memcpy(buf, pcm + bsize, (length - bsize) * 4); + memcpy(buf + length - bsize, pcm, (bsize) * 4); + memcpy(pcm, buf, length * 4); + free(buf); + + redraw(nil, nil); + flushimage(display, 1); + + oldpt = pt; + dirtyflag = 1; + break; + case 4: + /* menu */ + x = menuhit(3, mctl, &menu, nil); + if (x >= 0) { + menucmd[x](); + redraw(nil, nil); + flushimage(display, 1); + dirtyflag = 1; + } + break; + } +} + +void +resize(int *) +{ + if (getwindow(display, Refnone) < 0) sysfatal("resize: %r"); + draw(screen, screen->r, bg, nil, ZP); + redraw(nil, nil); + flushimage(display, 1); +} + +void +redraw(u32int *start, u32int *end) +{ + Frame *fr; + int h = Dy(screen->r); + int c = screen->r.min.y + h / 2; + int x, yv, yp = c; + + if (start == nil) start = pcm; + if (end == nil) end = &pcm[length]; + if (start > end) { + void *b; + b = end, end = start, start = b; + } + if (start > pcm) { + fr = (Frame *)(start - 1); + yp = c - (double)h * ((double)(fr->c1) / (double)0xffff); + } + if (start < pcm) start = pcm; + if (end > pcm + length) end = pcm + length; + + u32int *p; + for (p = start, x = screen->r.min.x + (p - pcm); p < end; p++, x++) { + if (x > screen->r.max.x) break; + + fr = (Frame *)p; + yv = c - (double)h * ((double)(fr->c1) / (double)0xffff); + + Rectangle rlo, rhi; + + rlo = canonrect(Rect(x, yv, x + 1, c)); + rhi = canonrect(Rect(x, yv, x + 1, yp)); + if (rhi.min.y == rhi.max.y) rhi.min.y--, rhi.max.y++; + + draw(screen, Rect(x, screen->r.min.y, x + 1, screen->r.max.y), bg, nil, ZP); + draw(screen, rlo, lo, nil, ZP); + draw(screen, rhi, hi, nil, ZP); + + yp = yv; + } +} + +void +sine(void) +{ + int i; + Frame *fr; + for (i = 0; i < length; i++) { + fr = (Frame *)&pcm[i]; + fr->c1 = 0x7fff * sin((double)i / (double)length * PI * 2.0); + fr->c2 = fr->c1; + } +} + +void +noise(void) +{ + int i; + Frame *fr; + srand(time(0) + lrand()); + for (i = 0; i < length; i++) { + fr = (Frame *)&pcm[i]; + fr->c1 = lrand() & 0xFFFF; + fr->c2 = fr->c1; + } +} + +void +save(void) +{ + if ((autosaveflag > 0) && (dirtyflag > 0)) { + writepcm(); + dirtyflag = 0; + } +} diff --git a/src/wave/fade.c b/src/wave/fade.c @@ -0,0 +1,111 @@ +#include <u.h> +#include <libc.h> + +#include "util.h" + +#define DefLength 8957 +/* + * Given 44100 samplerate, this DefLength should result + * in roughly 0.2 ms long output, which should be around + * the point where changes in timbre start to be perceptable + * by human ear. + * + * Probably. + * + * It is also a whole number multiple of draw's DefLength, + * which should give us product that loops nicely. + */ + + +Aud *files, output, envelope; +int fcount; + +void load(Aud *aud, char *path); +double env(double); +double slope(double); +double slope(double); +void apply(Aud *, Aud *, double); + +void +usage(void) +{ + fprint(2, "usage: %s [-e file] [-n count] wavefile1 wavefile2 ...\n", argv0); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + output.n = DefLength; + + ARGBEGIN { + case 'n': + output.n = strtol(EARGF(usage()), nil, 0); + break; + default: + usage(); + } ARGEND + if (argc < 2) usage(); + + fcount = argc; + files = malloc(sizeof(Aud) * fcount); + + int i; + for (i = 0; i < fcount; i++) { + load(files + i, argv[i]); + apply(&output, &files[i], - (double)i); + } + write(1, output.p, output.n * 4); +} + +void +load(Aud *aud, char *path) +{ + int fd; + if ((fd = open(path, OREAD)) < 0) sysfatal("load: %r"); + if ((aud->n = read(fd, aud->p, sizeof(aud->p))) < 0) sysfatal("load: %r"); + aud->n = aud->n / 4; + close(fd); +} + +void +apply(Aud *dst, Aud *src, double dx) +{ + int i, j; + Frame *fs, *fd; + for (i = 0; i < output.n; i++) { + + double p = ((double)fcount - 1) * (double)i / (double)output.n; + double v = slope(p + dx); + + j = i % src->n; + + fd = (Frame *)&dst->p[i]; + fs = (Frame *)&src->p[j]; + fd->c[0] += fs->c[0] * v; + fd->c[1] += fs->c[1] * v; + } +} + +double +slope(double x) +{ + /* TODO: maybe I should use sine-shaped slope instead of triangle one */ + if (x < 0) x = -x; + if (x > 1) x = 1; + return 1 - x; +} + +double +env(double I) +{ + return I; + + /*int i; + double delta, a ,b; + i = (int)(I * tolen(envelope.n)) & (-2); + delta = (I * tolen(envelope.n)) - (double)i; + a = (double)(envelope.p[i] + envelope.p[i + 1]) / 4.0 / (double)0x7fff + 0.5; + b = (double)(envelope.p[i + 2] + envelope.p[i + 3]) / 4.0 / (double)0x7fff + 0.5; + return a * delta + b * (1 - delta);*/ +} diff --git a/src/wave/harm.c b/src/wave/harm.c @@ -0,0 +1,79 @@ +/* +Input is a list of floats. +Output is a PCM audio of sum of harmonics with corresponding amplitudes. +*/ + +#include <u.h> +#include <libc.h> +#include <bio.h> + +#include "util.h" + +long baselen = 0, outlen = 8363; // samplerate of FastTracker samples +double amp = 1; + +void +usage(void) +{ + fprint(2, "usage: %s [-a amplitude] [-b length] [-l length]\n", argv0); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + ARGBEGIN{ + case 'a': + amp = strtod(EARGF(usage()), nil); + break; + case 'b': + baselen = strtol(EARGF(usage()), nil, 0); + if (baselen <= 0) { + fprint(2, "invalid baselen\n"); + exits("wrong args"); + } + break; + case 'l': + outlen = strtol(EARGF(usage()), nil, 0); + if (outlen <= 0) { + fprint(2, "invalid outlen\n"); + exits("wrong args"); + } + break; + default: + usage(); + } ARGEND + if (argc != 0) usage(); + + if (baselen == 0) baselen = outlen; + + double *buf = mallocz(sizeof(double) * outlen, 1); + Biobuf *bp = Bfdopen(0, OREAD); + char *s; + double h = 1; + int i; + while((s = Brdstr(bp, '\n', 1)) != nil) { + double d = strtod(s, nil); + for (i = 0; i < outlen; i++) { + buf[i] += sin(2.0 * PI * ((double)i / (double)baselen) * h) * d; + } + h = h + 1.0; + if (h > baselen / 2) break; // no point to freqs bigger than what audio resolution would give us + } + + u32int *out = malloc(sizeof(u32int) * outlen); + + for (i = 0; i < outlen; i++) { + double d = buf[i] * amp; + if (d > 1.0) d = 1; + if (d < -1.0) d = -1; + Frame *f; + f = (Frame *)(&out[i]); + f->c1 = d * 0x7fff; + f->c2 = f->c1; + } + + write(1, out, outlen * sizeof(u32int)); + + exits(0); +} diff --git a/src/wave/loop.c b/src/wave/loop.c @@ -0,0 +1,63 @@ +#include <u.h> +#include <libc.h> + +#include "util.h" + +enum { + MSample, + MFrame, +}; + +void +usage(void) +{ + fprint(2, "usage: %s [-f] count < file\n", argv0); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + int mode; + s16int in[MaxLength * 2], *out; + long i, inlen, outlen, arg; + char *rptr; + + mode = MSample; + outlen = 0; + + ARGBEGIN { + case 'f': + mode = MFrame; + break; + default: + usage(); + } ARGEND + + if (argc != 1) usage(); + arg = strtoul(argv[0], &rptr, 0); + if (rptr == argv[0]) usage(); + + inlen = read(0, in, sizeof(s16int) * MaxLength * 2); + if (inlen < 0) sysfatal("%r"); + inlen /= sizeof(s16int); + + switch (mode) { + case MSample: + outlen = inlen * arg; + break; + case MFrame: + outlen = arg * 2; + break; + } + + out = malloc(outlen * sizeof(s16int)); + + for (i = 0; i < outlen; i += 2) { + out[i] = in[i % inlen]; + out[i + 1] = in[(i + 1) % inlen]; + } + + outlen = write(1, out, outlen * sizeof(s16int)); + if (outlen < 0) sysfatal("%r"); +} diff --git a/src/wave/mkfile b/src/wave/mkfile @@ -0,0 +1,7 @@ +</$objtype/mkfile + +TARG=draw loop fade piano sampler harm +BIN=/$objtype/bin +HFILES=util.h + +</sys/src/cmd/mkmany diff --git a/src/wave/piano.c b/src/wave/piano.c @@ -0,0 +1,106 @@ +/* + * lets user play samples from PCM files + * using computer keyboard + */ + +#include <u.h> +#include <libc.h> + +#define BSIZE 4096 + +Rune *keys = L"zsxdcvgbhnjmq2w3er5t6y7ui9o0p["; +Rune *notes = L"CCDDEFFGGAAB"; +Rune *semi = L"-#-#--#-#-#-"; + +/* + * Frequencies for equal-tempered scale + * from https://pages.mtu.edu/~suits/notefreqs.html + */ + +double freq[] = { + 261.63, // C-4 + 277.18, + 293.66, + 311.13, + 329.63, + 349.23, + 369.99, + 392.00, + 415.30, + 440.00, // A-4 + 466.16, + 493.88, + 523.25, // C-5 + 554.37, + 587.33, + 622.25, + 659.25, + 698.46, + 739.99, + 783.99, + 830.61, + 880.00, // A-5 + 932.33, + 987.77, + 1046.50, + 1108.73, + 1174.66, + 1244.51, + 1318.51, + 1396.91 // F-6 +}; + +void +usage(void) +{ + fprint(2, "usage: %s\n", argv0); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + int kbd; + long n; + char buf[BSIZE]; + Rune last; + + ARGBEGIN { + default: + usage(); + } ARGEND + if (argc != 0) usage(); + + kbd = open("/dev/kbd", OREAD); + last = 0; + while((n = read(kbd, buf, BSIZE)) > 0) { + char *bp; + for (bp = buf; bp < buf + n; bp += strlen(bp) + 1) { + Rune r[BSIZE], *rp; + runesnprint(r, BSIZE, "%s", bp); + switch(r[0]) { + case 'c': + if (r[1] == 0x7f) exits(nil); + break; + case 'k': + last = r[runestrlen(r) - 1]; + rp = runestrchr(keys, last); + if (rp != nil) { + int k = (rp - keys) % 12; + int o = (rp - keys) / 12; + fprint(2, "%C%C%d, %f ", notes[k], semi[k], o + 4, freq[rp-keys]); + fprint(1, "%f\n", freq[rp-keys]); + } else last = 0; + break; + case 'K': + if ((last > 0) && (last != r[runestrlen(r) - 1])) { + fprint(2, "off\n"); + fprint(1, "0\n"); + last = 0; + } + break; + } + } + } +} + diff --git a/src/wave/sampler.c b/src/wave/sampler.c @@ -0,0 +1,103 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <thread.h> + +#include "util.h" + +#define BufSize 256 +#define BufSizeInBytes (BufSize * 2 * sizeof(s16int)) +#define BaseFreq 523.25 + +Aud sample; + +s16int buf[BufSize * 2]; +int audio, fid; +Channel *ctl; + +void synth(void *); +Frame * lookup(void *, double); + +void +usage(void) +{ + fprint(2, "usage: %s file\n", argv0); + threadexitsall("usage"); +} + +void +threadmain(int argc, char **argv) +{ + double in; + ARGBEGIN { + default: + usage(); + } ARGEND + if (argc != 1) usage(); + + fid = open(argv[0], OREAD); + if (fid <= 0) sysfatal("%r"); + sample.n = read(fid, sample.p, MaxLength * sizeof(u32int)) / sizeof(u32int); + close(fid); + + audio = open("/dev/audio", OWRITE); + if (audio <= 0) sysfatal("%r"); + + ctl = chancreate(sizeof(double), 8); + proccreate(synth, nil, 64 * 1024); + Biobuf *bp; + bp = Bfdopen(0, OREAD); + for (;;) { + // TODO: use Brdstr or Brdline and extract double from it's output instead of this. + Bgetd(bp, &in); + send(ctl, &in); + if (Bgetc(bp) == Beof) break; + } + chanclose(ctl); + threadexitsall(nil); +} + +void +synth(void *) +{ + int n; + double t, freq, v; + freq = 0; + t = 0; + for(n = 0; n >= 0; n = nbrecv(ctl, &v)){ + if (n > 0) { + freq = v; + if (freq == 0) t = 0; + } + int i; + for (i = 0; i < BufSize; i++) { + Frame *fr = lookup(&sample, t); + buf[i * 2] = fr->c1; + buf[1 + i * 2] = fr->c2; + t += freq / BaseFreq; + } + if (write(audio, buf, BufSizeInBytes) < BufSizeInBytes) { + sysfatal("synth: write to /dev/audio failed, %r"); + } + } +} + +/* +s16int +lookup(void *, double t) +{ + return 0x7fff * sin(t / 44100.0 * BaseFreq * PI * 2.0); +} +*/ + +Frame * +lookup(void *v, double t) +{ + Aud *aud; + Frame *fr; + long T; + aud = (Aud *)v; + T = t; + fr = (Frame *)(&aud->p[T%aud->n]); + return fr; +} diff --git a/src/wave/unconv b/src/wave/unconv @@ -0,0 +1,4 @@ +#!/bin/rc +# convert to fasttracker samplerate + +audio/pcmconv -i 's16c1r44100' -o 's8c1r8363' diff --git a/src/wave/util.h b/src/wave/util.h @@ -0,0 +1,23 @@ +#define MaxLength (44100 * 10) +/* + * This is 10 seconds in standard PCM samplerate + * TODO: MaxLength should be equal to max size of 9p message + */ + +typedef struct Frame Frame; +typedef struct Aud Aud; + +struct Frame { + union { + s16int c[2]; + struct { + s16int c1; + s16int c2; + }; + }; +}; + +struct Aud{ + u32int p[MaxLength]; + long n; +}; diff --git a/src/wave/waveform b/src/wave/waveform @@ -0,0 +1,20 @@ +#!/bin/rc + +rfork e + +dir=/tmp/wave + +mkdir -p $dir +touch $dir'/'^(f1 f2 f3 f4 fade) + +for (f in $dir'/'^(f1 f2 f3 f4)) { + dd -if /dev/zero -of $f -bs 169 -count 4 -quiet 1 +} +dd -if /dev/zero -of $dir/fade -bs 169 -count 16 -quiet 1 + +window -m -r 0 0 200 200 6.draw -a $dir/f1 +window -m -r 200 0 400 200 6.draw -a $dir/f2 +window -m -r 400 0 600 200 6.draw -a $dir/f3 +window -m -r 600 0 800 200 6.draw -a $dir/f4 + +window -m -r 0 200 800 400 6.draw $dir/fade