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
>
>

Reply via email to