commit ef4624e45334281860e4b8666626bd9f1a1d2108
parent 95a6c58dc965c7bbce03bf9d3e2a0100bce816f1
Author: glenda <glenda@device>
Date: Mon, 24 Oct 2022 21:10:40 +0000
add src/mu
Diffstat:
7 files changed, 442 insertions(+), 0 deletions(-)
diff --git a/src/mu/mkfile b/src/mu/mkfile
@@ -0,0 +1,8 @@
+</$objtype/mkfile
+
+TARG=trk mubox throttle
+
+</sys/src/cmd/mkmany
+
+test:V: $O.trk $O.mubox track
+ $O.trk < track | $O.mubox -r > /dev/audio
diff --git a/src/mu/mubox.c b/src/mu/mubox.c
@@ -0,0 +1,273 @@
+/* simplistic audio synth,
+ - 6-voice polyphony, 1 noise channel, 1 kick channel
+ - only triangle osc, only freq, vol and decay ctls
+ - controlled via stdin */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+
+#define MaxArgs 32
+#define BSize 441
+
+typedef struct Voice Voice;
+struct Voice {
+ double stime;
+ double freq;
+ double amplitude;
+ double volume;
+ double decay;
+ double bend;
+};
+
+enum {
+ VKick = 6,
+ VNoise = 7,
+};
+
+Voice v[8], vv, *vp;
+double T, mix[2];
+Biobuf *in;
+int out;
+
+int realtime;
+
+/*
+ * Frequencies for equal-tempered scale
+ * from https://pages.mtu.edu/~suits/notefreqs.html
+ */
+
+struct {
+ char *s;
+ double f;
+} notes[12] = {
+ {"C-", 261.63},
+ {"C#", 277.18},
+ {"D-", 293.66},
+ {"D#", 311.13},
+ {"E-", 329.63},
+ {"F-", 349.23},
+ {"F#", 369.99},
+ {"G-", 392.0 },
+ {"G#", 415.30},
+ {"A-", 440.0 },
+ {"A#", 466.16},
+ {"B-", 493.88},
+};
+
+Voice *
+newvoice(double freq)
+{
+ static int n = 0;
+ n = (n + 1) % 6;
+ v[n] = vv;
+ v[n].amplitude = 1;
+ v[n].stime = T;
+ v[n].freq = freq;
+ return &v[n];
+}
+
+int
+note(char *s)
+{
+ int i, oct;
+ if ((s[2] < '0') || (s[2] > '9')) return -1;
+ oct = atoi(s+2) - 4;
+ for (i = 0; i < 12; i++) {
+ if (strncmp(s, notes[i].s, 2) == 0) {
+ double freq = notes[i].f;
+ while (oct < 0) {
+ freq = freq / 2;
+ oct++;
+ }
+ while (oct > 0) {
+ freq = freq * 2;
+ oct--;
+ }
+ vp = newvoice(freq);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+void
+cmd(char *s)
+{
+ ulong i;
+ double f;
+ if (note(s) != 0) switch(s[0]) {
+ // noise channel commands
+ case 'N':
+ i = strtoul(s+1, nil, 16);
+ v[VNoise].volume = (double)i / 256.0;
+ v[VNoise].amplitude = 1;
+ break;
+ case 'n':
+ i = strtoul(s+1, nil, 16);
+ v[VNoise].decay = (double)i / 256.0;
+ break;
+
+ // kick channel commands
+ case 'k':
+ v[VKick].stime = T;
+ v[VKick].freq = 200;
+ v[VKick].amplitude = 1;
+ break;
+
+ // voice channel commands
+ case 'f': // freq
+ f = atof(s+1);
+ vp->freq = f;
+ vv.freq = f;
+ break;
+ case 'b': // bend
+ f = atof(s+1);
+ vp->bend = f;
+ vv.bend = f;
+ break;
+ case 'd': // decay;
+ f = atof(s+1);
+ vp->decay = f;
+ vv.decay = f;
+ break;
+ case 'v':
+ f = atof(s+1);
+ vp = newvoice(f);
+ break;
+ }
+}
+
+void
+threadread(void *)
+{
+ for (;;) {
+ char *s = Brdstr(in, '\n', 1);
+ if (s == nil) threadexitsall(nil);
+ char *args[MaxArgs];
+ int n = tokenize(s, args, MaxArgs);
+ int i;
+ for (i = 0; i < n; i++) cmd(args[i]);
+ free(s);
+ }
+}
+
+double
+tri(double T)
+{
+ static double env[5] = { 0.0, 1.0, 0.0, -1.0, 0.0 };
+ T = 4.0 * (T - floor(T));
+ int i = floor(T);
+ double X = T - i;
+ return env[i+1] * X + env[i] * (1 - X);
+}
+
+void
+voice(Voice *v)
+{
+ double x = tri((T - v->stime) * v->freq) * v->amplitude * v->volume;
+ mix[0] += x;
+ mix[1] += x;
+}
+
+void
+noise(void)
+{
+ mix[0] += (1 - frand() * 2) * v[VNoise].amplitude * v[VNoise].volume;
+ mix[1] += (1 - frand() * 2) * v[VNoise].amplitude * v[VNoise].volume;
+}
+
+void
+kick(void)
+{
+ double x = sin(2.0 * PI * (T - v[VKick].stime) * v[VKick].freq) * v[VKick].amplitude * v[VKick].volume;
+ mix[0] += x;
+ mix[1] += x;
+}
+
+void
+modvoice(Voice *v)
+{
+ v->amplitude -= v->decay;
+ v->freq *= v->bend;
+ if (v->amplitude < 0) v->amplitude = 0;
+ if (v->freq < 0) v->freq = 0;
+}
+
+void
+output(void)
+{
+ s16int buf[BSize * 2];
+ int i;
+ for (i = 0; i < BSize; i++) {
+ mix[0] = 0;
+ mix[1] = 0;
+ voice(&v[0]);
+ voice(&v[1]);
+ voice(&v[2]);
+ voice(&v[3]);
+ voice(&v[4]);
+ voice(&v[5]);
+ noise();
+ kick();
+ buf[i * 2] = 0x7fff * (mix[0] / 8);
+ buf[i * 2 + 1] = 0x7fff * (mix[1] / 8);
+ T += 1.0/44100.0;
+ }
+ for (i = 0; i < 8; i++) modvoice(&v[i]);
+ write(out, buf, BSize * 4);
+ //Dir *d = dirfstat(out);
+ //fprint(2, "%lld\n", d->length);
+ //free(d);
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-r]\n", argv0);
+ threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ ARGBEGIN {
+ case 'r':
+ realtime = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+ if (argc != 0) usage();
+ v[VKick] = (Voice) {
+ 0,
+ 200,
+ 0,
+ 1,
+ 0.25,
+ 25.45/255.0,
+ };
+ v[VNoise].decay = 1;
+ vv = (Voice) {
+ 0,
+ 440,
+ 1,
+ 1,
+ 0,
+ 1,
+ };
+
+ in = Bfdopen(0, OREAD);
+ // out = open("/dev/audio", OWRITE);
+ out = 1;
+ vp = &v[1];
+ proccreate(threadread, nil, 1024 * 1024 * 64);
+
+ vlong t = nsec() / 1000000;
+ for (;;) {
+ output();
+ t += 10;
+ if (realtime != 0) sleep(t - (nsec() / 1000000));
+ }
+}
diff --git a/src/mu/throttle.c b/src/mu/throttle.c
@@ -0,0 +1,38 @@
+#include <u.h>
+#include <libc.h>
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-t millisec] [-b bytes]\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ long b = 44 * 4;
+ long t = 1;
+ long T;
+ char *buf;
+ ARGBEGIN{
+ case 't':
+ t = strtol(EARGF(usage()), nil, 10);
+ break;
+ case 'b':
+ b = strtol(EARGF(usage()), nil, 10);
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ buf = sbrk(b);
+ long n;
+ T = nsec() / 1000000;
+ while ((n = read(0, buf, b)) > 0) {
+ write(1, buf, n);
+ T += t;
+ sleep(T - (nsec()/1000000));
+ }
+ if (n != 0) fprint(2, "%r\n");
+}
diff --git a/src/mu/tplay b/src/mu/tplay
@@ -0,0 +1,3 @@
+#!/bin/rc
+rfork e
+6.trk | 6.mubox -r > /dev/audio
diff --git a/src/mu/track b/src/mu/track
@@ -0,0 +1,52 @@
+k
+
+k
+
+k
+
+n02 N1f
+
+##############
+n04 N7f k v110 d0.01
+n08 N1f v110
+n04 N3f k v110 v220 v330
+n08 N1f v110
+n02 N5f k v220
+n08 N1f v440
+n04 N3f v110
+n08 N1f v220
+n04 N7f k v550
+n08 N1f v110
+n04 N3f k v220
+n04 N3f v440
+n02 N5f k v110
+n08 N1f v220
+n04 N3f v330
+n08 N1f v440
+##############
+n04 N7f k v110
+n08 N1f v110
+n04 N3f k v220 v165 v110
+n08 N1f v330
+n02 N5f k v110
+n08 N1f v165
+n04 N3f v330
+n08 N1f v220
+n04 N7f k v440
+n08 N1f v110
+n04 N3f k v330
+n04 N3f v110
+n02 N5f k v220
+n08 N1f v110
+n04 N3f v165
+n08 N1f v330
+##############
+v220 k
+v110
+v55 k
+v27
+v13 k
+
+
+
+# END
diff --git a/src/mu/track2 b/src/mu/track2
@@ -0,0 +1,35 @@
+#C-2 C-5 k
+#C#5 k
+#E-5 N4f n02
+#F-5 k
+#G-5 k
+#
+#N4f
+#
+# END
+k
+
+k
+
+k
+
+k
+
+b0.999999 C-4 d0.01 k N3f n0f
+C-5 d0.01 N3f
+D#5 d0.01 N3f
+F-5 d0.01 N3f
+G-5 d0.01 N7f n08
+N3f n0f
+A#5 k N3f
+N3f
+G-5 N3f
+N3f
+F-5 N3f
+N3f
+G-5 N7f n08
+N2f n0f
+k N1f
+N1f
+
+# END
diff --git a/src/mu/trk.c b/src/mu/trk.c
@@ -0,0 +1,33 @@
+/* read lines from stdin and print them at specified rate */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+void
+cmd(char *)
+{
+}
+
+void
+main(void)
+{
+ vlong t;
+ Biobuf *in;
+ in = Bfdopen(0, OREAD);
+ t = nsec();
+ for (;;) {
+ char *s = Brdstr(in, '\n', 1);
+ if (s == nil) break;
+ else if (s[0] == ':'); // labels
+ else if (s[0] == '.') cmd(s); // commands
+ else if (s[0] != '#'){
+ t += 1000000000 / 4;
+ // fprint(2, "%lld ", t - nsec());
+ print("%s\n", s);
+
+ sleep((t - nsec()) / 1000000);
+ }
+ free(s);
+ }
+}