Fun! On Sat, Dec 7, 2024 at 8:09 PM Simon Schwartz <simon_schwa...@icloud.com> wrote:
> Hi all! > > My roommate bought an old all-in-one from Goodwill, which we promptly > installed OpenBSD on. We left it out in our living room, and (being a > college dorm full of computer science majors), have been having quite some > fun writing and running various silly programs on it. I figured I would > share one we've been having a lot of fun with, in the hopes that someone > else might get a kick of it too: > > ========================================================================== > > NAME > piano - musical keyboard > > SYNOPSIS > piano [-p] [-a amplitude] [-d duration] > > DESCRIPTION > The piano program plays sounds when you press keys on the keyboard. > Pressing a letter key produces a note for one beat. "q" produces the > lowest note and "m" produces the highest note. Notes between "q" and > "m" are each a half step apart, ordered as they appear on a QWERTY > keyboard. The space (" ") character produces a rest (silence). > Entering a digit before a note or rest causes the note or rest to be > that many beats long. > > The options to piano are as follows: > > -a amplitude > Volume percentage. Must be a value between 0 and 100. > > -d duration > The duration (in milliseconds) of a beat. > > -p Use a piano-like layout instead of the default ascending > QWERTY layout. Keys in the middle keyboard row act as the > white keys on a piano, and keys in the top row act as the > black keys. > > EXAMPLES > Tetris theme: > > $ echo 'z ghk hgd dhz khg ghk z h d 2d' | piano -d 150 > > Megalovania: > > $ echo 'uul f2 d s p upsuul f2 d s p ups' | piano > > ========================================================================== > > Compile with `cc -lm -lsndio piano.c -o piano`: > > --- > diff --git a/piano.c b/piano.c > new file mode 100644 > index 0000000..6687bbd > --- /dev/null > +++ b/piano.c > @@ -0,0 +1,217 @@ > +/* > + * Copyright (c) 2024 Simon Schwartz and Benjamin Poulin > + * > + * Permission to use, copy, modify, and distribute this software for any > + * purpose with or without fee is hereby granted, provided that the above > + * copyright notice and this permission notice appear in all copies. > + * > + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL > WARRANTIES > + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > + */ > + > +#include <assert.h> > +#include <ctype.h> > +#include <err.h> > +#include <math.h> > +#include <sndio.h> > +#include <stdbool.h> > +#include <stdint.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <termios.h> > +#include <unistd.h> > + > +#define TWRT2 1.05946 /* 12th root of 2 */ > +#define TWOPI (3.14159 * 2.0) > +#define MAXBEATS 9 > + > +static bool piano_layout = false; > +static const char *chromatic_chars = "qwertyuiopasdfghjklzxcvbnm"; > +static const char *piano_chars = "awsedftgyhujkolp;']zbxncv"; > +static struct termios oldattr; > +static int beat_duration = 100; > +static unsigned int amplitude = INT16_MAX / 2; > +static size_t samps_per_beat; > +static struct sio_hdl *snd; > +static struct sio_par snd_par; > +static int16_t *sndbuf; > + > +static void > +initsound(void) > +{ > + snd = sio_open(SIO_DEVANY, SIO_PLAY, 0); > + if (snd == NULL) > + errx(1, "sio_open"); > + > + struct sio_par par; > + sio_initpar(&par); > + > + par.pchan = 1; > + par.bits = 16; > + par.bps = 2; > + par.sig = 1; > + > + if (sio_setpar(snd, &par) == 0) > + errx(1, "sio_setpar"); > + > + if (sio_getpar(snd, &snd_par) == 0) > + errx(1, "sio_getpar"); > + > + if (sio_start(snd) == 0) > + errx(1, "sio_start"); > + > + samps_per_beat = (snd_par.rate * beat_duration) / 1000; > + assert(snd_par.bps == sizeof(*sndbuf)); > + sndbuf = malloc(sizeof(*sndbuf) * samps_per_beat * MAXBEATS); > +} > + > +static void > +playsnd(size_t beats) > +{ > + assert(beats <= MAXBEATS); > + uint8_t *ptr = (uint8_t *)sndbuf; > + size_t nbytes = samps_per_beat * beats * sizeof(*sndbuf); > + while (nbytes) { > + size_t nwrite = sio_write(snd, ptr, nbytes); > + nbytes -= nwrite; > + ptr += nwrite; > + } > +} > + > +static void > +beep(int freq, int beats) > +{ > + assert(beats <= MAXBEATS); > + size_t nsamps = samps_per_beat * beats; > + float ease_width = nsamps / 8; > + float theta = 0; > + > + for (size_t i = 0; i < nsamps; i++) { > + float amp = amplitude; > + if (i < ease_width) > + amp *= (float)i / ease_width; > + else if (i > nsamps - ease_width) > + amp *= (float)(nsamps - i) / ease_width; > + > + sndbuf[i] = powf(sin(theta), 17.8) * amp; > + theta += TWOPI * ((float)freq) / ((float)snd_par.rate); > + } > + playsnd(beats); > +} > + > +static void > +rest(int beats) > +{ > + assert(beats <= MAXBEATS); > + memset(sndbuf, 0, sizeof(*sndbuf) * samps_per_beat * beats); > + playsnd(beats); > +} > + > +static void > +resetterm(void) > +{ > + tcsetattr(STDIN_FILENO, TCSANOW, &oldattr); > +} > + > +static void > +setterm(void) > +{ > + tcgetattr(STDIN_FILENO, &oldattr); > + > + struct termios attr; > + memcpy(&attr, &oldattr, sizeof(attr)); > + attr.c_lflag &= ~ICANON; > + attr.c_cc[VMIN] = 1; > + attr.c_cc[VTIME] = 0; > + > + tcsetattr(STDIN_FILENO, TCSAFLUSH, &attr); > + atexit(resetterm); > +} > + > +static int > +getfreq(char ch) > +{ > + const char *chars = piano_layout ? piano_chars : chromatic_chars; > + const char *cptr = strchr(chars, ch); > + if (cptr == NULL) > + return -1; > + size_t idx = cptr - chars; > + > + return 185.0 * powf(TWRT2, idx); > +} > + > +static void > +parseargs(int argc, char **argv) > +{ > + int ch; > + char *endptr; > + unsigned int a; > + while ((ch = getopt(argc, argv, "a:d:p")) != -1) { > + switch (ch) { > + case 'a': > + a = strtoul(optarg, &endptr, 10); > + if (*optarg == '\0' || *endptr != '\0' || a > 100) > + errx(1, "invalid amplitude"); > + amplitude = (float)INT16_MAX * ((float)a / 100.0); > + break; > + case 'd': > + beat_duration = strtoul(optarg, &endptr, 10); > + if (*optarg == '\0' || *endptr != '\0') > + errx(1, "invalid duration"); > + break; > + case 'p': > + piano_layout = true; > + break; > + default: > + exit(1); > + } > + } > + argc -= optind; > + argv += optind; > +} > + > +int > +main(int argc, char **argv) > +{ > + pledge("stdio tty unix rpath", NULL); > + parseargs(argc, argv); > + initsound(); > + > + int is_tty = isatty(STDIN_FILENO); > + if (is_tty) { > + pledge("stdio tty", NULL); > + setterm(); > + } else { > + pledge("stdio", NULL); > + } > + > + char ch; > + int nbeats = 1; > + ssize_t nread; > + while ((nread = read(STDIN_FILENO, &ch, sizeof(ch))) != 0) { > + if (nread == -1) > + err(1, "read"); > + if (is_tty && ch == oldattr.c_cc[VEOF]) > + break; > + > + int freq; > + if (isdigit(ch) && ch != '0') { > + nbeats = ch - '0'; > + if (nbeats > MAXBEATS) > + nbeats = MAXBEATS; > + continue; > + } else if (ch == ' ') { > + rest(nbeats); > + } else if ((freq = getfreq(ch)) != -1) { > + beep(freq, nbeats); > + } > + > + nbeats = 1; > + } > +} > diff --git a/harmony.sh b/harmony.sh > new file mode 100755 > index 0000000..cc84ce2 > --- /dev/null > +++ b/harmony.sh > @@ -0,0 +1,22 @@ > +#!/bin/sh > + > +echo ' > +4d2j2g2d2s2d2i2t2i3q 2t2i4i > +2 2i2d2i2p2t2y2e2i2i8q6 2d2d > +2d2d2d2i3i 2i2i2i2i2i2i3q 2d > +2j2g2d2s2d2i2t2i3q 2d2q4i2 2i > +2d2i2p2t2y2e2i2i8q' | piano -d150 -a70 & > + > +echo ' > +4d2j2g2d2s2d2i2d2g3j 2d2j4g > +2 2d2j2g2d2s2p2g2d2s8d6 2j2j > +2j2d2j2j3g 2g2g2g2s2g2g3d 2d > +2j2g2d2s2d2i2d2g3j 2d2j4g2 2d > +2j2g2d2s2p2g2d2s8d' | piano -d150 -a70 & > + > +echo ' > +4d2j2g2d2s2d2g2j2z3z 2j2z4z > +2 2j2z2k2j2j2k2k2j2g8j6 2j2z > +2z2j2z2z3k 2k2k2k2g2k2k3j 2d > +2j2g2d2s2d2g2j2z3z 2j2z4z2 2j > +2z2k2j2j2k2c2z2k8j' | piano -d150 -a70 > >