xref: /freebsd/usr.bin/beep/beep.c (revision 8abfbe5a)
18abfbe5aSHans Petter Selasky /*-
28abfbe5aSHans Petter Selasky  * Copyright (c) 2021 Hans Petter Selasky <hselasky@freebsd.org>
38abfbe5aSHans Petter Selasky  *
48abfbe5aSHans Petter Selasky  * Redistribution and use in source and binary forms, with or without
58abfbe5aSHans Petter Selasky  * modification, are permitted provided that the following conditions
68abfbe5aSHans Petter Selasky  * are met:
78abfbe5aSHans Petter Selasky  * 1. Redistributions of source code must retain the above copyright
88abfbe5aSHans Petter Selasky  *    notice, this list of conditions and the following disclaimer.
98abfbe5aSHans Petter Selasky  * 2. Redistributions in binary form must reproduce the above copyright
108abfbe5aSHans Petter Selasky  *    notice, this list of conditions and the following disclaimer in the
118abfbe5aSHans Petter Selasky  *    documentation and/or other materials provided with the distribution.
128abfbe5aSHans Petter Selasky  *
138abfbe5aSHans Petter Selasky  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
148abfbe5aSHans Petter Selasky  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
158abfbe5aSHans Petter Selasky  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
168abfbe5aSHans Petter Selasky  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
178abfbe5aSHans Petter Selasky  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
188abfbe5aSHans Petter Selasky  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
198abfbe5aSHans Petter Selasky  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
208abfbe5aSHans Petter Selasky  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
218abfbe5aSHans Petter Selasky  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
228abfbe5aSHans Petter Selasky  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
238abfbe5aSHans Petter Selasky  * SUCH DAMAGE.
248abfbe5aSHans Petter Selasky  */
258abfbe5aSHans Petter Selasky 
268abfbe5aSHans Petter Selasky #include <sys/soundcard.h>
278abfbe5aSHans Petter Selasky 
288abfbe5aSHans Petter Selasky #include <err.h>
298abfbe5aSHans Petter Selasky #include <errno.h>
308abfbe5aSHans Petter Selasky #include <fcntl.h>
318abfbe5aSHans Petter Selasky #include <math.h>
328abfbe5aSHans Petter Selasky #include <paths.h>
338abfbe5aSHans Petter Selasky #include <stdbool.h>
348abfbe5aSHans Petter Selasky #include <stdint.h>
358abfbe5aSHans Petter Selasky #include <stdio.h>
368abfbe5aSHans Petter Selasky #include <stdlib.h>
378abfbe5aSHans Petter Selasky #include <string.h>
388abfbe5aSHans Petter Selasky #include <unistd.h>
398abfbe5aSHans Petter Selasky 
408abfbe5aSHans Petter Selasky #define	SAMPLE_RATE_DEF 48000		/* hz */
418abfbe5aSHans Petter Selasky #define	SAMPLE_RATE_MAX 48000		/* hz */
428abfbe5aSHans Petter Selasky #define	SAMPLE_RATE_MIN 8000		/* hz */
438abfbe5aSHans Petter Selasky 
448abfbe5aSHans Petter Selasky #define	DURATION_DEF 150		/* ms */
458abfbe5aSHans Petter Selasky #define	DURATION_MAX 2000		/* ms */
468abfbe5aSHans Petter Selasky #define	DURATION_MIN 50			/* ms */
478abfbe5aSHans Petter Selasky 
488abfbe5aSHans Petter Selasky #define	GAIN_DEF 75
498abfbe5aSHans Petter Selasky #define	GAIN_MAX 100
508abfbe5aSHans Petter Selasky #define	GAIN_MIN 0
518abfbe5aSHans Petter Selasky 
528abfbe5aSHans Petter Selasky #define	WAVE_POWER 1.25f
538abfbe5aSHans Petter Selasky 
548abfbe5aSHans Petter Selasky #define	DEFAULT_HZ 440
558abfbe5aSHans Petter Selasky 
568abfbe5aSHans Petter Selasky #define	DEFAULT_DEVICE _PATH_DEV "dsp"
578abfbe5aSHans Petter Selasky 
588abfbe5aSHans Petter Selasky static int frequency = DEFAULT_HZ;
598abfbe5aSHans Petter Selasky static int duration_ms = DURATION_DEF;
608abfbe5aSHans Petter Selasky static int sample_rate = SAMPLE_RATE_DEF;
618abfbe5aSHans Petter Selasky static int gain = GAIN_DEF;
628abfbe5aSHans Petter Selasky static const char *oss_dev = DEFAULT_DEVICE;
638abfbe5aSHans Petter Selasky static bool background;
648abfbe5aSHans Petter Selasky 
658abfbe5aSHans Petter Selasky /*
668abfbe5aSHans Petter Selasky  * wave_function_16
678abfbe5aSHans Petter Selasky  *
688abfbe5aSHans Petter Selasky  * "phase" should be in the range [0.0f .. 1.0f>
698abfbe5aSHans Petter Selasky  * "power" should be in the range <0.0f .. 2.0f>
708abfbe5aSHans Petter Selasky  *
718abfbe5aSHans Petter Selasky  * The return value is in the range [-1.0f .. 1.0f]
728abfbe5aSHans Petter Selasky  */
738abfbe5aSHans Petter Selasky static float
wave_function_16(float phase,float power)748abfbe5aSHans Petter Selasky wave_function_16(float phase, float power)
758abfbe5aSHans Petter Selasky {
768abfbe5aSHans Petter Selasky 	uint16_t x = phase * (1U << 16);
778abfbe5aSHans Petter Selasky 	float retval;
788abfbe5aSHans Petter Selasky 	uint8_t num;
798abfbe5aSHans Petter Selasky 
808abfbe5aSHans Petter Selasky 	/* Handle special cases, if any */
818abfbe5aSHans Petter Selasky 	switch (x) {
828abfbe5aSHans Petter Selasky 	case 0xffff:
838abfbe5aSHans Petter Selasky 	case 0x0000:
848abfbe5aSHans Petter Selasky 		return (1.0f);
858abfbe5aSHans Petter Selasky 	case 0x3fff:
868abfbe5aSHans Petter Selasky 	case 0x4000:
878abfbe5aSHans Petter Selasky 	case 0xBfff:
888abfbe5aSHans Petter Selasky 	case 0xC000:
898abfbe5aSHans Petter Selasky 		return (0.0f);
908abfbe5aSHans Petter Selasky 	case 0x7FFF:
918abfbe5aSHans Petter Selasky 	case 0x8000:
928abfbe5aSHans Petter Selasky 		return (-1.0f);
938abfbe5aSHans Petter Selasky 	default:
948abfbe5aSHans Petter Selasky 		break;
958abfbe5aSHans Petter Selasky 	}
968abfbe5aSHans Petter Selasky 
978abfbe5aSHans Petter Selasky 	/* Apply Gray coding */
988abfbe5aSHans Petter Selasky 	for (uint16_t mask = 1U << 15; mask != 1; mask /= 2) {
998abfbe5aSHans Petter Selasky 		if (x & mask)
1008abfbe5aSHans Petter Selasky 			x ^= (mask - 1);
1018abfbe5aSHans Petter Selasky 	}
1028abfbe5aSHans Petter Selasky 
1038abfbe5aSHans Petter Selasky 	/* Find first set bit */
1048abfbe5aSHans Petter Selasky 	for (num = 0; num != 14; num++) {
1058abfbe5aSHans Petter Selasky 		if (x & (1U << num)) {
1068abfbe5aSHans Petter Selasky 			num++;
1078abfbe5aSHans Petter Selasky 			break;
1088abfbe5aSHans Petter Selasky 		}
1098abfbe5aSHans Petter Selasky 	}
1108abfbe5aSHans Petter Selasky 
1118abfbe5aSHans Petter Selasky 	/* Initialize return value */
1128abfbe5aSHans Petter Selasky 	retval = 0.0;
1138abfbe5aSHans Petter Selasky 
1148abfbe5aSHans Petter Selasky 	/* Compute the rest of the power series */
1158abfbe5aSHans Petter Selasky 	for (; num != 14; num++) {
1168abfbe5aSHans Petter Selasky 		if (x & (1U << num)) {
1178abfbe5aSHans Petter Selasky 			retval = (1.0f - retval) / 2.0f;
1188abfbe5aSHans Petter Selasky 			retval = powf(retval, power);
1198abfbe5aSHans Petter Selasky 		} else {
1208abfbe5aSHans Petter Selasky 			retval = (1.0f + retval) / 2.0f;
1218abfbe5aSHans Petter Selasky 			retval = powf(retval, power);
1228abfbe5aSHans Petter Selasky 		}
1238abfbe5aSHans Petter Selasky 	}
1248abfbe5aSHans Petter Selasky 
1258abfbe5aSHans Petter Selasky 	/* Check if halfway */
1268abfbe5aSHans Petter Selasky 	if (x & (1ULL << 14))
1278abfbe5aSHans Petter Selasky 		retval = -retval;
1288abfbe5aSHans Petter Selasky 
1298abfbe5aSHans Petter Selasky 	return (retval);
1308abfbe5aSHans Petter Selasky }
1318abfbe5aSHans Petter Selasky 
1328abfbe5aSHans Petter Selasky static void
usage(void)1338abfbe5aSHans Petter Selasky usage(void)
1348abfbe5aSHans Petter Selasky {
1358abfbe5aSHans Petter Selasky 	fprintf(stderr, "Usage: %s [parameters]\n"
1368abfbe5aSHans Petter Selasky 	    "\t" "-F <frequency in HZ, default %d Hz>\n"
1378abfbe5aSHans Petter Selasky 	    "\t" "-D <duration in ms, from %d ms to %d ms, default %d ms>\n"
1388abfbe5aSHans Petter Selasky 	    "\t" "-r <sample rate in HZ, from %d Hz to %d Hz, default %d Hz>\n"
1398abfbe5aSHans Petter Selasky 	    "\t" "-d <OSS device (default %s)>\n"
1408abfbe5aSHans Petter Selasky 	    "\t" "-g <gain from %d to %d, default %d>\n"
1418abfbe5aSHans Petter Selasky 	    "\t" "-B Run in background\n"
1428abfbe5aSHans Petter Selasky 	    "\t" "-h Show usage\n",
1438abfbe5aSHans Petter Selasky 	    getprogname(),
1448abfbe5aSHans Petter Selasky 	    DEFAULT_HZ,
1458abfbe5aSHans Petter Selasky 	    DURATION_MIN, DURATION_MAX, DURATION_DEF,
1468abfbe5aSHans Petter Selasky 	    SAMPLE_RATE_MIN, SAMPLE_RATE_MAX, SAMPLE_RATE_DEF,
1478abfbe5aSHans Petter Selasky 	    DEFAULT_DEVICE,
1488abfbe5aSHans Petter Selasky 	    GAIN_MIN, GAIN_MAX, GAIN_DEF);
1498abfbe5aSHans Petter Selasky 	exit(1);
1508abfbe5aSHans Petter Selasky }
1518abfbe5aSHans Petter Selasky 
1528abfbe5aSHans Petter Selasky int
main(int argc,char ** argv)1538abfbe5aSHans Petter Selasky main(int argc, char **argv)
1548abfbe5aSHans Petter Selasky {
1558abfbe5aSHans Petter Selasky 	int32_t *buffer;
1568abfbe5aSHans Petter Selasky 	size_t slope;
1578abfbe5aSHans Petter Selasky 	size_t size;
1588abfbe5aSHans Petter Selasky 	size_t off;
1598abfbe5aSHans Petter Selasky 	float a;
1608abfbe5aSHans Petter Selasky 	float d;
1618abfbe5aSHans Petter Selasky 	float p;
1628abfbe5aSHans Petter Selasky 	int c;
1638abfbe5aSHans Petter Selasky 	int f;
1648abfbe5aSHans Petter Selasky 
1658abfbe5aSHans Petter Selasky 	while ((c = getopt(argc, argv, "BF:D:r:g:d:h")) != -1) {
1668abfbe5aSHans Petter Selasky 		switch (c) {
1678abfbe5aSHans Petter Selasky 		case 'F':
1688abfbe5aSHans Petter Selasky 			frequency = strtol(optarg, NULL, 10);
1698abfbe5aSHans Petter Selasky 			break;
1708abfbe5aSHans Petter Selasky 		case 'D':
1718abfbe5aSHans Petter Selasky 			duration_ms = strtol(optarg, NULL, 10);
1728abfbe5aSHans Petter Selasky 			if (duration_ms < DURATION_MIN ||
1738abfbe5aSHans Petter Selasky 			    duration_ms > DURATION_MAX)
1748abfbe5aSHans Petter Selasky 				usage();
1758abfbe5aSHans Petter Selasky 			break;
1768abfbe5aSHans Petter Selasky 		case 'r':
1778abfbe5aSHans Petter Selasky 			sample_rate = strtol(optarg, NULL, 10);
1788abfbe5aSHans Petter Selasky 			if (sample_rate < SAMPLE_RATE_MIN ||
1798abfbe5aSHans Petter Selasky 			    sample_rate > SAMPLE_RATE_MAX)
1808abfbe5aSHans Petter Selasky 				usage();
1818abfbe5aSHans Petter Selasky 			break;
1828abfbe5aSHans Petter Selasky 		case 'g':
1838abfbe5aSHans Petter Selasky 			gain = strtol(optarg, NULL, 10);
1848abfbe5aSHans Petter Selasky 			if (gain < GAIN_MIN ||
1858abfbe5aSHans Petter Selasky 			    gain > GAIN_MAX)
1868abfbe5aSHans Petter Selasky 				usage();
1878abfbe5aSHans Petter Selasky 			break;
1888abfbe5aSHans Petter Selasky 		case 'd':
1898abfbe5aSHans Petter Selasky 			oss_dev = optarg;
1908abfbe5aSHans Petter Selasky 			break;
1918abfbe5aSHans Petter Selasky 		case 'B':
1928abfbe5aSHans Petter Selasky 			background = true;
1938abfbe5aSHans Petter Selasky 			break;
1948abfbe5aSHans Petter Selasky 		default:
1958abfbe5aSHans Petter Selasky 			usage();
1968abfbe5aSHans Petter Selasky 			break;
1978abfbe5aSHans Petter Selasky 		}
1988abfbe5aSHans Petter Selasky 	}
1998abfbe5aSHans Petter Selasky 
2008abfbe5aSHans Petter Selasky 	if (background && daemon(0, 0) != 0)
2018abfbe5aSHans Petter Selasky 		errx(1, "daemon(0,0) failed");
2028abfbe5aSHans Petter Selasky 
2038abfbe5aSHans Petter Selasky 	f = open(oss_dev, O_WRONLY);
2048abfbe5aSHans Petter Selasky 	if (f < 0)
2058abfbe5aSHans Petter Selasky 		errx(1, "Failed to open '%s'", oss_dev);
2068abfbe5aSHans Petter Selasky 
2078abfbe5aSHans Petter Selasky 	c = 1;				/* mono */
2088abfbe5aSHans Petter Selasky 	if (ioctl(f, SOUND_PCM_WRITE_CHANNELS, &c) != 0)
2098abfbe5aSHans Petter Selasky 		errx(1, "ioctl SOUND_PCM_WRITE_CHANNELS(1) failed");
2108abfbe5aSHans Petter Selasky 
2118abfbe5aSHans Petter Selasky 	c = AFMT_S32_NE;
2128abfbe5aSHans Petter Selasky 	if (ioctl(f, SNDCTL_DSP_SETFMT, &c) != 0)
2138abfbe5aSHans Petter Selasky 		errx(1, "ioctl SNDCTL_DSP_SETFMT(AFMT_S32_NE) failed");
2148abfbe5aSHans Petter Selasky 
2158abfbe5aSHans Petter Selasky 	if (ioctl(f, SNDCTL_DSP_SPEED, &sample_rate) != 0)
2168abfbe5aSHans Petter Selasky 		errx(1, "ioctl SNDCTL_DSP_SPEED(%d) failed", sample_rate);
2178abfbe5aSHans Petter Selasky 
2188abfbe5aSHans Petter Selasky 	c = (2 << 16);
2198abfbe5aSHans Petter Selasky 	while ((1ULL << (c & 63)) < (size_t)(4 * sample_rate / 50))
2208abfbe5aSHans Petter Selasky 		c++;
2218abfbe5aSHans Petter Selasky 	if (ioctl(f, SNDCTL_DSP_SETFRAGMENT, &c))
2228abfbe5aSHans Petter Selasky 		errx(1, "ioctl SNDCTL_DSP_SETFRAGMENT(0x%x) failed", c);
2238abfbe5aSHans Petter Selasky 
2248abfbe5aSHans Petter Selasky 	if (ioctl(f, SNDCTL_DSP_GETODELAY, &c) != 0)
2258abfbe5aSHans Petter Selasky 		errx(1, "ioctl SNDCTL_DSP_GETODELAY failed");
2268abfbe5aSHans Petter Selasky 
2278abfbe5aSHans Petter Selasky 	size = ((sample_rate * duration_ms) + 999) / 1000;
2288abfbe5aSHans Petter Selasky 	buffer = malloc(sizeof(buffer[0]) * size);
2298abfbe5aSHans Petter Selasky 	if (buffer == NULL)
2308abfbe5aSHans Petter Selasky 		errx(1, "out of memory");
2318abfbe5aSHans Petter Selasky 
2328abfbe5aSHans Petter Selasky 	/* compute slope duration in samples */
2338abfbe5aSHans Petter Selasky 	slope = (DURATION_MIN * sample_rate) / 2000;
2348abfbe5aSHans Petter Selasky 
2358abfbe5aSHans Petter Selasky 	/* compute base gain */
2368abfbe5aSHans Petter Selasky 	a = powf(65536.0f, (float)gain / (float)GAIN_MAX) / 65536.0f;
2378abfbe5aSHans Petter Selasky 
2388abfbe5aSHans Petter Selasky 	/* set initial phase and delta */
2398abfbe5aSHans Petter Selasky 	p = 0;
2408abfbe5aSHans Petter Selasky 	d = (float)frequency / (float)sample_rate;
2418abfbe5aSHans Petter Selasky 
2428abfbe5aSHans Petter Selasky 	/* compute wave */
2438abfbe5aSHans Petter Selasky 	for (p = off = 0; off != size; off++, p += d) {
2448abfbe5aSHans Petter Selasky 		float sample;
2458abfbe5aSHans Petter Selasky 
2468abfbe5aSHans Petter Selasky 		p = p - floorf(p);
2478abfbe5aSHans Petter Selasky 		sample = a * wave_function_16(p, WAVE_POWER);
2488abfbe5aSHans Petter Selasky 
2498abfbe5aSHans Petter Selasky 		if (off < slope)
2508abfbe5aSHans Petter Selasky 			sample = sample * off / (float)slope;
2518abfbe5aSHans Petter Selasky 		else if (off > (size - slope))
2528abfbe5aSHans Petter Selasky 			sample = sample * (size - off - 1) / (float)slope;
2538abfbe5aSHans Petter Selasky 
2548abfbe5aSHans Petter Selasky 		buffer[off] = sample * 0x7fffff00;
2558abfbe5aSHans Petter Selasky 	}
2568abfbe5aSHans Petter Selasky 
2578abfbe5aSHans Petter Selasky 	if (write(f, buffer, size * sizeof(buffer[0])) !=
2588abfbe5aSHans Petter Selasky 	    (ssize_t)(size * sizeof(buffer[0])))
2598abfbe5aSHans Petter Selasky 		errx(1, "failed writing to DSP device(%s)", oss_dev);
2608abfbe5aSHans Petter Selasky 
2618abfbe5aSHans Petter Selasky 	free(buffer);
2628abfbe5aSHans Petter Selasky 
2638abfbe5aSHans Petter Selasky 	/* wait for data to be written */
2648abfbe5aSHans Petter Selasky 	while (ioctl(f, SNDCTL_DSP_GETODELAY, &c) == 0) {
2658abfbe5aSHans Petter Selasky 		if (c == 0)
2668abfbe5aSHans Petter Selasky 			break;
2678abfbe5aSHans Petter Selasky 		usleep(10000);
2688abfbe5aSHans Petter Selasky 	}
2698abfbe5aSHans Petter Selasky 
2708abfbe5aSHans Petter Selasky 	/* wait for audio to go out */
2718abfbe5aSHans Petter Selasky 	usleep(50000);
2728abfbe5aSHans Petter Selasky 	close(f);
2738abfbe5aSHans Petter Selasky 
2748abfbe5aSHans Petter Selasky 	return (0);
2758abfbe5aSHans Petter Selasky }
276