💾 Archived View for thrig.me › blog › 2024 › 02 › 17 › tnats.c captured on 2024-03-21 at 15:43:11.
-=-=-=-=-=-=-
// tnats - throw numbers at a synth // there are some PORTABILITY concerns if you're not on OpenBSD #include <sys/wait.h> #include <err.h> #include <fcntl.h> #include <getopt.h> #include <math.h> #include <signal.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> // a positive number here indicates the note was turned on (or has // otherwise been blocked from being turned on) #define PITCHMAX 128U int pitches[PITCHMAX]; // a bad RNG, but who cares! we're throwing numbers at a synth uint16_t seed; #define RN_MIN 0 #define RN_MAX 0x7fff #define RN (((seed = seed * 11109 + 13849) & RN_MAX) >> 1) #define MS2NSEC 1000000U #define TICKLEN 4 // milliseconds typedef void (*notefn)(int fd, int pitchnum); sig_atomic_t bailout; inline static int coinflip(void) { return RN % 1000 >= 500; // RN & 1 is broken for this algo } inline static void pitch_decr(int fd, int minus, notefn offcall) { for (int i = 0; i < PITCHMAX; ++i) { int prev = pitches[i]; pitches[i] -= minus; if (pitches[i] < 0) pitches[i] = 0; if (pitches[i] == 0 && prev) offcall(fd, i); } } static void note_off(int fd, int note) { dprintf(fd, "\x80%c%c", note, 0); } static void note_on(int fd, int note, int velocity) { dprintf(fd, "\x90%c%c", note, velocity); } void whoops(int unused) { bailout = 1; } int main(int argc, char *argv[]) { // PORTABILITY assumes OpenBSD char *file = "/dev/rmidi0"; int ch; while ((ch = getopt(argc, argv, "d:")) != -1) { switch (ch) { case 'd': file = optarg; break; default: err(1, "... -d mididevice [record]"); } } argc -= optind; argv += optind; int fd = open(file, O_WRONLY); if (fd < 0) err(1, "open '%s'", file); // PORTABILITY assumes OpenBSD pid_t recording = 0; if (argc > 0 && strncmp(*argv, "rec", 3) == 0) { recording = fork(); if (recording < 0) err(1, "fork"); if (recording == 0) { execl("/usr/bin/aucat", "aucat", "-h", "wav", "-o", "out.wav", (char *) 0); err(1, "exec"); } signal(SIGINT, whoops); fprintf(stderr, "recording...\n"); } // PORTABILITY non-OpenBSD systems may need some other way to // seed the RNG seed = 1 + arc4random_uniform(UINT16_MAX - 1); // blind, preset 93 seed = 13019; printf("%u\n", seed); struct timespec ticklen; ticklen.tv_sec = 0; ticklen.tv_nsec = TICKLEN * MS2NSEC; // stats help show if and how much your features are working size_t iters = 0; size_t blocked = 0; size_t drained = 0; size_t onsets = 0; for (int i = 0; i < 8; ++i) { for (int nc = 0; nc < 64; ++nc) { int n = RN % 128; if (pitches[n] == 0) { note_on(fd, n, 96); onsets++; } else { blocked++; } int plus = 8 * (1 + RN % 16); // block the note pitches[n] += plus; nanosleep(&ticklen, NULL); if (bailout) goto CLEANUP; pitch_decr(fd, 1, note_off); iters++; } sleep(i % 3 == 0 ? 4 : 6); if (coinflip()) { for (int j = 0; j < PITCHMAX; ++j) { if (pitches[j] > 0) { drained++; note_off(fd, j); pitches[j] = 0; } } } } fprintf(stderr, "draining any notes left on...\n"); int decr = 1; while (1) { int done = 1; for (int i = 0; i < PITCHMAX; ++i) { if (pitches[i] > 0) { done = 0; break; } } if (done) break; nanosleep(&ticklen, NULL); if (bailout) goto CLEANUP; pitch_decr(fd, decr++, note_off); } if (recording) { int delay = 3; fprintf(stderr, "wait for synth... %d)\n", delay); sleep(delay); kill(recording, SIGTERM); } goto BAILOUT; CLEANUP: for (int i = 0; i < PITCHMAX; ++i) note_off(fd, i); if (recording) { kill(recording, SIGTERM); fprintf(stderr, "wait for recorder to exit...\n"); wait(0); } BAILOUT: fprintf(stderr, "total %zu blocked %zu onsets %zu drained %zu\n", iters, blocked, onsets, drained); exit(0); }