git: af21e0057acc - stable/13 - beep(1): Initial version of utility to create terminal beep via soundcard.

From: Hans Petter Selasky <hselasky_at_FreeBSD.org>
Date: Fri, 12 Nov 2021 14:36:57 UTC
The branch stable/13 has been updated by hselasky:

URL: https://cgit.FreeBSD.org/src/commit/?id=af21e0057acc06d606a06f8babc9e1524c3cd66e

commit af21e0057acc06d606a06f8babc9e1524c3cd66e
Author:     Hans Petter Selasky <hselasky@FreeBSD.org>
AuthorDate: 2021-10-26 17:13:00 +0000
Commit:     Hans Petter Selasky <hselasky@FreeBSD.org>
CommitDate: 2021-11-12 14:33:10 +0000

    beep(1): Initial version of utility to create terminal beep via soundcard.
    
    Reviewed by:    imp@, emaste@ and pstef@
    Differential Revision:  https://reviews.freebsd.org/D32672
    Sponsored by:   NVIDIA Networking
    
    (cherry picked from commit 8abfbe5a79b19bb95430f574d970843607f5809c)
---
 usr.bin/Makefile      |   1 +
 usr.bin/beep/Makefile |   8 ++
 usr.bin/beep/beep.1   |  84 +++++++++++++++
 usr.bin/beep/beep.c   | 275 ++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 368 insertions(+)

diff --git a/usr.bin/Makefile b/usr.bin/Makefile
index 9b38cc2512b7..6126e93b28a8 100644
--- a/usr.bin/Makefile
+++ b/usr.bin/Makefile
@@ -10,6 +10,7 @@ SUBDIR=	alias \
 	backlight \
 	banner \
 	basename \
+	beep \
 	brandelf \
 	bsdcat \
 	bsdiff \
diff --git a/usr.bin/beep/Makefile b/usr.bin/beep/Makefile
new file mode 100644
index 000000000000..754656ef059b
--- /dev/null
+++ b/usr.bin/beep/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG=	beep
+MAN=	beep.1
+
+LDFLAGS= -lm
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/beep/beep.1 b/usr.bin/beep/beep.1
new file mode 100644
index 000000000000..55fe0173272a
--- /dev/null
+++ b/usr.bin/beep/beep.1
@@ -0,0 +1,84 @@
+.\"-
+.\" Copyright (c) 2021 Hans Petter Selasky <hselasky@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd November 4, 2021
+.Dt beep 1
+.Os
+.Sh NAME
+.Nm beep
+.Nd play a beep sound
+.Sh SYNOPSIS
+.Nm
+.Op Fl F Ar frequency
+.Op Fl D Ar duration_ms
+.Op Fl r Ar sample_rate_hz
+.Op Fl d Ar oss_device
+.Op Fl g Ar gain
+.Op Fl B
+.Op Fl h
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to playback a beep on the soundcard.
+.Pp
+The options are as follows:
+.Bl -tag -width "-f device"
+.It Fl F
+Sets the center frequency of the beep in Hz.
+The default is 440 Hz .
+.It Fl D
+Sets the duration of the beep in milliseconds.
+The default is 150 ms .
+.It Fl d
+Sets the soundcard to use.
+The default is /dev/dsp .
+.It Fl r
+Sets the soundcard samplerate in Hz.
+The default is 48000 Hz.
+.It Fl g
+Sets the waveform gain, between 0 and 100 inclusively.
+The default is 75.
+.It Fl B
+Runs the
+.Nm
+utility in the background.
+.It Fl h
+Display summary of options.
+.El
+.Sh EXAMPLES
+.Pp
+Playback default beep sound using /dev/dsp .
+.Bl -tag -width Ds -offset indent
+.It $ beep
+.El
+.Sh SEE ALSO
+.Xr mixer 3 ,
+.Xr sound 4 ,
+.Sh HISTORY
+The
+.Nm
+utility first appeared in FreeBSD 14.0.
+.Sh AUTHORS
+.An Hans Petter Selasky Aq Mt hselasky@FreeBSD.org
diff --git a/usr.bin/beep/beep.c b/usr.bin/beep/beep.c
new file mode 100644
index 000000000000..151236b4825b
--- /dev/null
+++ b/usr.bin/beep/beep.c
@@ -0,0 +1,275 @@
+/*-
+ * Copyright (c) 2021 Hans Petter Selasky <hselasky@freebsd.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/soundcard.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <paths.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define	SAMPLE_RATE_DEF 48000		/* hz */
+#define	SAMPLE_RATE_MAX 48000		/* hz */
+#define	SAMPLE_RATE_MIN 8000		/* hz */
+
+#define	DURATION_DEF 150		/* ms */
+#define	DURATION_MAX 2000		/* ms */
+#define	DURATION_MIN 50			/* ms */
+
+#define	GAIN_DEF 75
+#define	GAIN_MAX 100
+#define	GAIN_MIN 0
+
+#define	WAVE_POWER 1.25f
+
+#define	DEFAULT_HZ 440
+
+#define	DEFAULT_DEVICE _PATH_DEV "dsp"
+
+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;
+
+/*
+ * wave_function_16
+ *
+ * "phase" should be in the range [0.0f .. 1.0f>
+ * "power" should be in the range <0.0f .. 2.0f>
+ *
+ * The return value is in the range [-1.0f .. 1.0f]
+ */
+static float
+wave_function_16(float phase, float power)
+{
+	uint16_t x = phase * (1U << 16);
+	float retval;
+	uint8_t num;
+
+	/* Handle special cases, if any */
+	switch (x) {
+	case 0xffff:
+	case 0x0000:
+		return (1.0f);
+	case 0x3fff:
+	case 0x4000:
+	case 0xBfff:
+	case 0xC000:
+		return (0.0f);
+	case 0x7FFF:
+	case 0x8000:
+		return (-1.0f);
+	default:
+		break;
+	}
+
+	/* Apply Gray coding */
+	for (uint16_t mask = 1U << 15; mask != 1; mask /= 2) {
+		if (x & mask)
+			x ^= (mask - 1);
+	}
+
+	/* Find first set bit */
+	for (num = 0; num != 14; num++) {
+		if (x & (1U << num)) {
+			num++;
+			break;
+		}
+	}
+
+	/* Initialize return value */
+	retval = 0.0;
+
+	/* Compute the rest of the power series */
+	for (; num != 14; num++) {
+		if (x & (1U << num)) {
+			retval = (1.0f - retval) / 2.0f;
+			retval = powf(retval, power);
+		} else {
+			retval = (1.0f + retval) / 2.0f;
+			retval = powf(retval, power);
+		}
+	}
+
+	/* Check if halfway */
+	if (x & (1ULL << 14))
+		retval = -retval;
+
+	return (retval);
+}
+
+static void
+usage(void)
+{
+	fprintf(stderr, "Usage: %s [parameters]\n"
+	    "\t" "-F <frequency in HZ, default %d Hz>\n"
+	    "\t" "-D <duration in ms, from %d ms to %d ms, default %d ms>\n"
+	    "\t" "-r <sample rate in HZ, from %d Hz to %d Hz, default %d Hz>\n"
+	    "\t" "-d <OSS device (default %s)>\n"
+	    "\t" "-g <gain from %d to %d, default %d>\n"
+	    "\t" "-B Run in background\n"
+	    "\t" "-h Show usage\n",
+	    getprogname(),
+	    DEFAULT_HZ,
+	    DURATION_MIN, DURATION_MAX, DURATION_DEF,
+	    SAMPLE_RATE_MIN, SAMPLE_RATE_MAX, SAMPLE_RATE_DEF,
+	    DEFAULT_DEVICE,
+	    GAIN_MIN, GAIN_MAX, GAIN_DEF);
+	exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+	int32_t *buffer;
+	size_t slope;
+	size_t size;
+	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) {
+		switch (c) {
+		case 'F':
+			frequency = strtol(optarg, NULL, 10);
+			break;
+		case 'D':
+			duration_ms = strtol(optarg, NULL, 10);
+			if (duration_ms < DURATION_MIN ||
+			    duration_ms > DURATION_MAX)
+				usage();
+			break;
+		case 'r':
+			sample_rate = strtol(optarg, NULL, 10);
+			if (sample_rate < SAMPLE_RATE_MIN ||
+			    sample_rate > SAMPLE_RATE_MAX)
+				usage();
+			break;
+		case 'g':
+			gain = strtol(optarg, NULL, 10);
+			if (gain < GAIN_MIN ||
+			    gain > GAIN_MAX)
+				usage();
+			break;
+		case 'd':
+			oss_dev = optarg;
+			break;
+		case 'B':
+			background = true;
+			break;
+		default:
+			usage();
+			break;
+		}
+	}
+
+	if (background && daemon(0, 0) != 0)
+		errx(1, "daemon(0,0) failed");
+
+	f = open(oss_dev, O_WRONLY);
+	if (f < 0)
+		errx(1, "Failed to open '%s'", oss_dev);
+
+	c = 1;				/* mono */
+	if (ioctl(f, SOUND_PCM_WRITE_CHANNELS, &c) != 0)
+		errx(1, "ioctl SOUND_PCM_WRITE_CHANNELS(1) failed");
+
+	c = AFMT_S32_NE;
+	if (ioctl(f, SNDCTL_DSP_SETFMT, &c) != 0)
+		errx(1, "ioctl SNDCTL_DSP_SETFMT(AFMT_S32_NE) failed");
+
+	if (ioctl(f, SNDCTL_DSP_SPEED, &sample_rate) != 0)
+		errx(1, "ioctl SNDCTL_DSP_SPEED(%d) failed", sample_rate);
+
+	c = (2 << 16);
+	while ((1ULL << (c & 63)) < (size_t)(4 * sample_rate / 50))
+		c++;
+	if (ioctl(f, SNDCTL_DSP_SETFRAGMENT, &c))
+		errx(1, "ioctl SNDCTL_DSP_SETFRAGMENT(0x%x) failed", c);
+
+	if (ioctl(f, SNDCTL_DSP_GETODELAY, &c) != 0)
+		errx(1, "ioctl SNDCTL_DSP_GETODELAY failed");
+
+	size = ((sample_rate * duration_ms) + 999) / 1000;
+	buffer = malloc(sizeof(buffer[0]) * size);
+	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;
+
+	/* compute wave */
+	for (p = off = 0; off != size; off++, p += d) {
+		float sample;
+
+		p = p - floorf(p);
+		sample = a * 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;
+	}
+
+	if (write(f, buffer, size * sizeof(buffer[0])) !=
+	    (ssize_t)(size * sizeof(buffer[0])))
+		errx(1, "failed writing to DSP device(%s)", oss_dev);
+
+	free(buffer);
+
+	/* wait for data to be written */
+	while (ioctl(f, SNDCTL_DSP_GETODELAY, &c) == 0) {
+		if (c == 0)
+			break;
+		usleep(10000);
+	}
+
+	/* wait for audio to go out */
+	usleep(50000);
+	close(f);
+
+	return (0);
+}