On Sat, Dec 07, 2024 at 08:20:46PM -0500, Simon Schwartz 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:
reminds me of playing nokia ringtones (RTTTL)
/*
* Copyright (c) 2009-2014 Jonathan Gray <jsg@jsg.id.au>
*
* 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 <sndio.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <err.h>
#include <dev/isa/spkrio.h>
/*
* http://arcadetones.emuunlim.com
*
*/
#define CIRCUS \
"Circus:d=32,o=6,b=45:16c#,16c,b5,c,b5,a#5,16a5,16g#5,16g5,16g#5,16a#5,16a5,g#5,a5,g#5,g5,16f#5,16f5,16e5,16f5,16g#5,f#5,f#5,16d5,16d#5,16g#5,f#5,f#5,16d5,16d#5,c,c#,d,d#,e,f,f#,g,g#,a,a#,b,16a#,16g#"
char defmelody[] = CIRCUS;
int defdur = 4;
int defoct = 4;
int defmul = 1;
int bpm = 150;
enum wave {
WAVE_SQUARE = 0,
WAVE_TRIANGLE = 1,
WAVE_SAWTOOTH = 2,
WAVE_SINE = 3,
WAVE_NOISE = 4,
WAVE_MAX = 5,
};
typedef enum wave wavet;
int
note2key(char *note, int octave)
{
int key;
switch (note[0]) {
case 'c':
key = 0x18;
break;
case 'd':
key = 0x1a;
break;
case 'e':
key = 0x1c;
break;
case 'f':
key = 0x1d;
break;
case 'g':
key = 0x1f;
break;
case 'a':
key = 0x21;
break;
case 'b':
key = 0x23;
break;
case 'p':
return (-1);
break;
default:
warnx("unknown note '%c'\n", note[0]);
return (-2);
}
if (note[1] == '#')
key++;
key += (octave * 12);
// printf("translated note %s to key is %x\n", note, key);
return (key);
}
#define MIDI_NOTEON 0x90
#define MIDI_NOTEOFF 0x80
#define MIDI_CTLCHG 0xb0
#define MIDI_PRGCHG 0xc0
#define MIDI_RESET 0xff
#define MIDI_CTL_VOLUME 0x07
#define MIDI_VELOCITY 90
void
midi_reset(struct mio_hdl *hdl, int bank)
{
int channel = 0;
u_char data[] = { MIDI_RESET };
mio_write(hdl, data, sizeof(data));
/* GS bank select */
u_char ctl[] = { MIDI_CTLCHG | (channel & 0xf), 0x00, bank & 0xff };
mio_write(hdl, ctl, sizeof(ctl));
ctl[1] = 0x20;
ctl[2] = (bank >> 8) & 0xff;
mio_write(hdl, ctl, sizeof(ctl));
}
void
tgen_midi(struct mio_hdl *hdl, int key, int instrument, int dur)
{
u_char data[3];
int channel = 0;
data[0] = MIDI_PRGCHG | (channel & 0x0f);
data[1] = instrument;
mio_write(hdl, data, 2);
data[0] = MIDI_NOTEON | (channel & 0x0f);
data[1] = key;
data[2] = MIDI_VELOCITY;
mio_write(hdl, data, sizeof(data));
usleep(dur);
data[0] = MIDI_NOTEOFF | (channel & 0x0f);
mio_write(hdl, data, sizeof(data));
}
void
tgen_pcspkr(int fd, int key, int dur)
{
tone_t tone;
if (key < 0)
tone.frequency = 0;
else
tone.frequency = 6.875 * powf(2, (3 + key) / (double)12);
tone.duration = dur / 10000;
ioctl(fd, SPKRTONE, &tone);
}
short
tgen_sample(wavet wave, int t, int period)
{
short sample;
int m, x;
switch (wave) {
case WAVE_TRIANGLE:
m = 40000;
x = t + (period / 4.0);
x = (x % period) * 2 * (m / period);
sample = abs(x - m) - (m / 2);
break;
case WAVE_SAWTOOTH:
m = 30000;
x = t % period;
sample = ((x * m) / (period / 2)) - m;
break;
case WAVE_SINE:
m = 20000;
sample = m* (sin(M_PI +
((2 * M_PI) * (t / (double)period))));
break;
case WAVE_NOISE:
m = 40000;
sample = arc4random_uniform(m) - (m / 2);
break;
default:
case WAVE_SQUARE:
m = 10000;
sample = (t > period / 2) ? m : -m;
break;
}
// printf("%d %d\n", t, sample);
return (sample);
}
short
tgen_adsr(short sample, int t, int period)
{
/* ADSR envelope */
double attack = 0.15;
double decay = 0.30;
double sustain = 0.65;
double release = 1.0;
double svol = 0.55;
double pos = t / (double)period;
if (pos <= attack) {
double factor = t / (period * (double)attack);
sample *= factor;
} else if ((t / period) <= decay) {
int tn = t - (attack * period);
double factor = (tn * (1 - svol)) /
(period * (double)(decay - attack));
factor = 1 - factor;
sample *= factor;
} else if ((t / period) <= sustain) {
sample *= svol;
} else if ((t / period) <= release) {
int tn = t - (sustain * period);
double factor = (tn * svol) /
(period * (double)(release - sustain));
factor = svol - factor;
sample *= factor;
}
return (sample);
}
void
tgen_sndio(struct sio_hdl *hdl, struct sio_par par, wavet wave, int key,
int msdur, int envelope)
{
int i, j, period, t = 0;
double hz;
short *p, sample;
int nsamples;
size_t size;
short *buf;
hz = 6.875 * powf(2, (3 + key) / (double)12);
period = par.rate / hz;
nsamples = (par.rate / 1000000.0) * msdur;
/* round up to a multiple of the period */
nsamples = nsamples + period - 1 - (nsamples - 1) % period;
size = nsamples * par.pchan * par.bps;
buf = malloc(size);
if (key < 0) {
memset(buf, 0, size);
goto write;
}
p = buf;
for (i = 0; i < nsamples; i++) {
sample = tgen_sample(wave, t, period);
if (++t == period)
t = 0;
if (envelope)
sample = tgen_adsr(sample, i, nsamples);
for (j = 0; j < par.pchan; j++)
*p++ = sample;
}
write:
if (!sio_write(hdl, buf, size)) {
fprintf(stderr, "failed writing samples\n");
exit(1);
}
}
struct note {
int dur;
char note[3];
int oct;
};
/* <note> := [<duration>] <note> [<scale>] [<special-duration>] <delimiter> */
void
parse_note(struct note *sn, char *note, int defdur, int defoct)
{
size_t s;
char sdur[5];
const char *errstr;
int dur, oct;
char *p = note;
s = strspn(note, "0123456789");
if (s == 0)
dur = defdur;
else {
memset(sdur, 0, sizeof(sdur));
memcpy(sdur, note, s);
p += s;
dur = strtonum(sdur, 1, 32, &errstr);
if (errstr)
errx(1, "invalid duration %s: %s", errstr, note);
}
s = strspn(p, "pcdefgab#");
if (s == 0)
errx(1, "no note found in '%s'\n", p);
memset(sn->note, 0, sizeof(sn->note));
memcpy(sn->note, p, s);
p += s;
if (*p == '.') {
dur += (dur / 2);
p++;
}
s = strspn(p, "0123456789");
if (s == 0)
oct = defoct;
else {
oct = *p - 0x30;
p += s;
}
// printf("dur '%d', note '%s' oct '%d' ", dur, sn->note, oct);
sn->dur = dur;
sn->oct = oct;
return;
}
char *
parse_header(char *str)
{
size_t sz;
char *hdr = strchr(str, ':');
char *p, *s, *cmd;
const char *errstr;
int val;
if (hdr == NULL)
return (str);
hdr++;
sz = strcspn(hdr, ":");
p = malloc(sz + 1);
if (p == NULL)
err(1, "malloc hdr");
memcpy(p, hdr, sz);
p[sz] = '\0';
// printf("found header '%s'\n", p);
s = p;
for (;;) {
cmd= strsep(&s, ", ");
if (cmd == NULL)
break;
if (*cmd == '\0')
continue;
val = strtonum(&cmd[2], 1, 300, &errstr);
if (errstr)
errx(1, "invalid value %s: '%s'", errstr, cmd);
switch (cmd[0]) {
case 'd':
defdur = val;
break;
case 'o':
defoct = val;
break;
case 'b':
bpm = val;
break;
default:
errx(1, "unknown header '%c'", cmd[0]);
}
}
printf("using defaults duration %d, octave %d, bpm %d\n", defdur,
defoct, bpm);
free(p);
return (hdr + sz + 1);
}
/*
* 12 keys in an octive
* 0-127 MIDI keys
* 0-127 program/instrument changes
* 16 channels (chan 10 is percussion only)
* controller events
* sysex messages
*
*/
#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
int
init_sndio(struct sio_hdl **hdl, struct sio_par *par)
{
*hdl = sio_open(NULL, SIO_PLAY, 0);
if (*hdl == NULL) {
fprintf(stderr, "failed to open default audio device\n");
exit(1);
}
sio_initpar(par);
par->bits = 16;
par->bps = 2;
if (!sio_setpar(*hdl, par)) {
fprintf(stderr, "failed to set parameters\n");
exit(1);
}
if (!sio_getpar(*hdl, par)) {
fprintf(stderr, "failed to get parameters\n");
exit(1);
}
if (par->bits != 16 || par->bps != 2 || par->le != SIO_LE_NATIVE) {
fprintf(stderr, "unsupported audio format\n");
exit(1);
}
if (!sio_start(*hdl)) {
fprintf(stderr, "failed to start playback\n");
exit(1);
}
return (0);
}
enum output {
OUTPUT_PCSPKR = 0,
OUTPUT_SNDIO,
OUTPUT_MIDI,
};
extern char *__progname;
void
usage(void)
{
fprintf(stderr, "usage: %s [-e] [-m melody-string] [-w wave-num]"
" [-b bank] [-i ins] [-o output-type]\n", __progname);
fprintf(stderr, "\te: enable ADSR envelope\n");
fprintf(stderr, "\bi: MIDI instrument\n");
fprintf(stderr, "\ti: MIDI instrument\n");
fprintf(stderr, "\twaves: 0 (square) 1 (triangle) 2 (sawtooth)\n");
fprintf(stderr, "\t\t3 (sine) 4 (noise)\n");
fprintf(stderr, "\toutputs: p (pc speaker) s (sndio) m (midi)\n");
exit(1);
}
int
main(int argc, char *argv[])
{
struct sio_hdl *hdl;
struct mio_hdl *mhdl;
struct sio_par par;
struct note sn;
char *snote, *input;
int key = 0, instrument = arc4random() & 0x7f;
int bank = 0;
int fd, ch;
int msdur;
enum output out = OUTPUT_SNDIO;
char *melody = defmelody;
wavet wave = WAVE_SQUARE;
const char *errstr;
int envelope = 0;
while ((ch = getopt(argc, argv, "b:ei:m:o:w:")) != -1) {
switch (ch) {
case 'b':
bank = strtonum(optarg, 0, 128, &errstr);
if (errstr != NULL)
errx(1, "invalid bank : %s", errstr);
break;
case 'e':
envelope = 1;
break;
case 'i':
instrument = strtonum(optarg, 0, 127, &errstr);
if (errstr != NULL)
errx(1, "invalid instrument: %s", errstr);
break;
case 'm':
melody = optarg;
break;
case 'o':
switch (optarg[0]) {
case 'm':
out = OUTPUT_MIDI;
break;
case 'p':
out = OUTPUT_PCSPKR;
break;
case 's':
out = OUTPUT_SNDIO;
break;
default:
errx(1, "invalid output type");
}
break;
case 'w':
wave = strtonum(optarg, 0, WAVE_MAX - 1, &errstr);
if (errstr)
errx(1, "invalid wave type %s: %s",
errstr, optarg);
break;
default:
usage();
break;
}
}
switch (out) {
case OUTPUT_PCSPKR:
printf("using PC speaker\n");
fd = open("/dev/speaker", O_WRONLY, 0700);
if (fd == -1)
err(1, "open /dev/speaker");
break;
case OUTPUT_SNDIO:
init_sndio(&hdl, &par);
break;
case OUTPUT_MIDI:
mhdl = mio_open(NULL, MIO_OUT, 0);
if (mhdl == NULL)
errx(1, "mio_open failed");
midi_reset(mhdl, bank);
printf("using bank %d instrument %d!\n", bank, instrument);
break;
}
input = parse_header(melody);
for (;;) {
snote = strsep(&input, ",");
if (snote == NULL)
break;
parse_note(&sn, snote, defdur, defoct);
#ifdef DEBUG
printf("note %-2s octave %d dur %d", sn.note,
sn.oct - 1, sn.dur);
No comments:
Post a Comment