diff --git a/usr.bin/beep/beep.1 b/usr.bin/beep/beep.1 --- a/usr.bin/beep/beep.1 +++ b/usr.bin/beep/beep.1 @@ -1,5 +1,5 @@ .\"- -.\" Copyright (c) 2021 Hans Petter Selasky +.\" Copyright (c) 2021-2022 Hans Petter Selasky .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions @@ -23,7 +23,7 @@ .\" .\" $FreeBSD$ .\" -.Dd November 4, 2021 +.Dd July 18, 2022 .Dt beep 1 .Os .Sh NAME @@ -36,6 +36,8 @@ .Op Fl r Ar sample_rate_hz .Op Fl d Ar oss_device .Op Fl g Ar gain +.Op Fl t Ar UTF-8-text +.Op Fl n Ar number .Op Fl B .Op Fl h .Sh DESCRIPTION @@ -60,6 +62,15 @@ .It Fl g Sets the waveform gain, between 0 and 100 inclusively. The default is 75. +.It Fl t +Render the given UTF-8 text starting at the least significant bit, similarly to Braille. +Each full character is output separately. +Up to 22 bits may be output by a single character. +A small pause separates each character. +This option can also be combined with other options, like duration and frequency. +.It Fl n +Render the given number as binary, starting at the least significant digit. +This option can also be combined with other options, like duration and frequency. .It Fl B Runs the .Nm @@ -67,12 +78,27 @@ .It Fl h Display summary of options. .El +.Sh AUDIO RENDERING +There are two audio symbols. +Binary zero is encoded like a major chord. +Binary one is encoded like a diminished chord. +Refer to western or 12-tone scale music theory for more information. .Sh EXAMPLES .Pp Playback default beep sound using /dev/dsp . .Bl -tag -width Ds -offset indent .It $ beep .El +.Pp +Render the value of zero: +.Bl -tag -width Ds -offset indent +.It $ beep -n 0 +.El +.Pp +Slowly output the alphabeth +.Bl -tag -width Ds -offset indent +.It $ beep -t abcdefghijklmnopqrstuvwxyz -D 1000 +.El .Sh SEE ALSO .Xr mixer 3 , .Xr sound 4 , diff --git a/usr.bin/beep/beep.c b/usr.bin/beep/beep.c --- a/usr.bin/beep/beep.c +++ b/usr.bin/beep/beep.c @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2021 Hans Petter Selasky + * Copyright (c) 2021-2022 Hans Petter Selasky * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -36,6 +36,7 @@ #include #include #include +#include #define SAMPLE_RATE_DEF 48000 /* hz */ #define SAMPLE_RATE_MAX 48000 /* hz */ @@ -55,12 +56,24 @@ #define DEFAULT_DEVICE _PATH_DEV "dsp" +#define MUSIC_CHARS 2ULL + static int frequency = DEFAULT_HZ; static int duration_ms = DURATION_DEF; static int sample_rate = SAMPLE_RATE_DEF; static int gain = GAIN_DEF; static const char *oss_dev = DEFAULT_DEVICE; static bool background; +static const uint8_t *text; +static const uint8_t *number_ptr; +static uint64_t number_parsed; +static struct { + uint8_t a, b, c; /* frequency exponent in 12ths */ + int32_t *audio; /* rendered audio */ +} music_char[MUSIC_CHARS] = { + { 0, 4, 7, NULL }, /* Major */ + { 0, 3, 6, NULL }, /* Diminished */ +}; /* * wave_function_16 @@ -129,6 +142,131 @@ return (retval); } +static void +render_beep(int32_t *buffer, size_t size, float freq, float amp) +{ + size_t slope; + size_t off; /* buffer offset */ + float p; /* phase */ + float d; /* delta phase */ + + /* compute slope duration in samples */ + slope = (DURATION_MIN * sample_rate) / 2000; + + /* set initial phase and delta */ + p = 0; + d = freq / (float)sample_rate; + + /* compute wave */ + for (p = off = 0; off != size; off++, p += d) { + float sample; + + p = p - floorf(p); + sample = amp * wave_function_16(p, WAVE_POWER); + + if (off < slope) + sample = sample * off / (float)slope; + else if (off > (size - slope)) + sample = sample * (size - off - 1) / (float)slope; + + buffer[off] += sample * 0x7fffff00; + } +} + +static void +render_music_char(uint8_t n, int32_t *buffer, float freq, float amp, size_t size) +{ + assert(n < MUSIC_CHARS); + + if (music_char[n].audio == NULL) { + music_char[n].audio = calloc(sizeof(music_char[0].audio[0]) * size, 1); + if (music_char[n].audio == NULL) + errx(1, "out of memory"); + render_beep(music_char[n].audio, size, + freq * powf(2.0f, music_char[n].a / 12.0f), amp / 3.0f); + render_beep(music_char[n].audio, size, + freq * powf(2.0f, music_char[n].b / 12.0f), amp / 3.0f); + render_beep(music_char[n].audio, size, + freq * powf(2.0f, music_char[n].c / 12.0f), amp / 3.0f); + } + memcpy(buffer, music_char[n].audio, size * sizeof(buffer[0])); +} + +static uint32_t +get_unicode_char(const uint8_t **pptr) +{ + const uint8_t *p8; + uint32_t ch; + + p8 = *pptr; + + if ((*p8 & 0xc0) == 0xc0) { + p8++; + while ((*p8 & 0xc0) == 0x80) + p8++; + switch (p8 - *pptr) { + case 1: /* 12-bit */ + ch = (*pptr)[0] & 0x3f; + ch <<= 6; + ch |= (*pptr)[1] & 0x3f; + + *pptr = p8; + goto done; + case 2: /* 17-bit */ + ch = (*pptr)[0] & 0x1f; + ch <<= 6; + ch |= (*pptr)[1] & 0x3f; + ch <<= 6; + ch |= (*pptr)[2] & 0x3f; + + *pptr = p8; + goto done; + case 3: /* 22-bit */ + ch = (*pptr)[0] & 0x0f; + ch <<= 6; + ch |= (*pptr)[1] & 0x3f; + ch <<= 6; + ch |= (*pptr)[2] & 0x3f; + ch <<= 6; + ch |= (*pptr)[3] & 0x3f; + + *pptr = p8; + goto done; + default: + break; + } + } + /* not UTF-8 */ + ch = **pptr; + (*pptr) ++; +done: + return (ch); +} + +static size_t +number_length_in_units(uint64_t number) +{ + size_t units = 0; + + do { + units++; + } while ((number /= MUSIC_CHARS) != 0); + + return (units); +} + +static size_t +text_length_in_units(const uint8_t *ptr) +{ + size_t units = 0; + + while (*ptr) { + units += number_length_in_units(get_unicode_char(&ptr)); + units += 1; + } + return (units); +} + static void usage(void) { @@ -138,6 +276,8 @@ "\t" "-r \n" "\t" "-d \n" "\t" "-g \n" + "\t" "-t \n" + "\t" "-n \n" "\t" "-B Run in background\n" "\t" "-h Show usage\n", getprogname(), @@ -153,16 +293,14 @@ main(int argc, char **argv) { int32_t *buffer; - size_t slope; size_t size; + size_t units; size_t off; float a; - float d; - float p; int c; int f; - while ((c = getopt(argc, argv, "BF:D:r:g:d:h")) != -1) { + while ((c = getopt(argc, argv, "BF:D:r:n:t:g:d:h")) != -1) { switch (c) { case 'F': frequency = strtol(optarg, NULL, 10); @@ -191,12 +329,21 @@ case 'B': background = true; break; + case 'n': + number_ptr = optarg; + break; + case 't': + text = optarg; + break; default: usage(); break; } } + if (text != NULL && number_ptr != NULL) + errx(1, "-t and -s options are mutually exclusive"); + if (background && daemon(0, 0) != 0) errx(1, "daemon(0,0) failed"); @@ -224,38 +371,63 @@ if (ioctl(f, SNDCTL_DSP_GETODELAY, &c) != 0) errx(1, "ioctl SNDCTL_DSP_GETODELAY failed"); + if (number_ptr != NULL) { + char *end; + number_parsed = strtoull(number_ptr, &end, 0); + if (*end != 0) + errx(1, "invalid number '%s'", number_ptr); + units = number_length_in_units(number_parsed); + } else if (text != NULL) { + units = text_length_in_units(text); + } else { + units = 1; + } + size = ((sample_rate * duration_ms) + 999) / 1000; - buffer = malloc(sizeof(buffer[0]) * size); + buffer = calloc(sizeof(buffer[0]) * size, units); if (buffer == NULL) errx(1, "out of memory"); - /* compute slope duration in samples */ - slope = (DURATION_MIN * sample_rate) / 2000; - /* compute base gain */ a = powf(65536.0f, (float)gain / (float)GAIN_MAX) / 65536.0f; - /* set initial phase and delta */ - p = 0; - d = (float)frequency / (float)sample_rate; + if (number_ptr != NULL) { + off = 0; - /* compute wave */ - for (p = off = 0; off != size; off++, p += d) { - float sample; + do { + render_music_char(number_parsed % MUSIC_CHARS, + buffer + off, frequency, a, size); + off += size; + } while ((number_parsed /= MUSIC_CHARS) != 0); + } else if (text != NULL) { + off = 0; - p = p - floorf(p); - sample = a * wave_function_16(p, WAVE_POWER); + while (*text != 0) { + uint32_t ch; - if (off < slope) - sample = sample * off / (float)slope; - else if (off > (size - slope)) - sample = sample * (size - off - 1) / (float)slope; + ch = get_unicode_char(&text); - buffer[off] = sample * 0x7fffff00; + do { + render_music_char(ch % MUSIC_CHARS, + buffer + off, frequency, a, size); + off += size; + } while ((ch /= MUSIC_CHARS) != 0); + + off += size; + } + } else { + render_beep(buffer, size, frequency, a); + off = size; } - if (write(f, buffer, size * sizeof(buffer[0])) != - (ssize_t)(size * sizeof(buffer[0]))) + /* check whole buffer was filled */ + assert(off == (size * units)); + + for (unsigned i = 0; i != MUSIC_CHARS; i++) + free(music_char[i].audio); + + if (write(f, buffer, size * units * sizeof(buffer[0])) != + (ssize_t)(size * units * sizeof(buffer[0]))) errx(1, "failed writing to DSP device(%s)", oss_dev); free(buffer);