xref: /freebsd/usr.bin/beep/beep.c (revision 55d98b024f25403f60efe04f90a391014b6bc388)
1 /*-
2  * Copyright (c) 2021 Hans Petter Selasky <hselasky@freebsd.org>
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  */
25 
26 #include <sys/soundcard.h>
27 
28 #include <capsicum_helpers.h>
29 #include <err.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <math.h>
33 #include <paths.h>
34 #include <stdbool.h>
35 #include <stdint.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 
41 #define	SAMPLE_RATE_DEF 48000		/* hz */
42 #define	SAMPLE_RATE_MAX 48000		/* hz */
43 #define	SAMPLE_RATE_MIN 8000		/* hz */
44 
45 #define	DURATION_DEF 150		/* ms */
46 #define	DURATION_MAX 2000		/* ms */
47 #define	DURATION_MIN 50			/* ms */
48 
49 #define	GAIN_DEF 75
50 #define	GAIN_MAX 100
51 #define	GAIN_MIN 0
52 
53 #define	WAVE_POWER 1.25f
54 
55 #define	DEFAULT_HZ 440
56 
57 #define	DEFAULT_DEVICE _PATH_DEV "dsp"
58 
59 static int frequency = DEFAULT_HZ;
60 static int duration_ms = DURATION_DEF;
61 static int sample_rate = SAMPLE_RATE_DEF;
62 static int gain = GAIN_DEF;
63 static const char *oss_dev = DEFAULT_DEVICE;
64 static bool background;
65 
66 /*
67  * wave_function_16
68  *
69  * "phase" should be in the range [0.0f .. 1.0f>
70  * "power" should be in the range <0.0f .. 2.0f>
71  *
72  * The return value is in the range [-1.0f .. 1.0f]
73  */
74 static float
wave_function_16(float phase,float power)75 wave_function_16(float phase, float power)
76 {
77 	uint16_t x = phase * (1U << 16);
78 	float retval;
79 	uint8_t num;
80 
81 	/* Handle special cases, if any */
82 	switch (x) {
83 	case 0xffff:
84 	case 0x0000:
85 		return (1.0f);
86 	case 0x3fff:
87 	case 0x4000:
88 	case 0xBfff:
89 	case 0xC000:
90 		return (0.0f);
91 	case 0x7FFF:
92 	case 0x8000:
93 		return (-1.0f);
94 	default:
95 		break;
96 	}
97 
98 	/* Apply Gray coding */
99 	for (uint16_t mask = 1U << 15; mask != 1; mask /= 2) {
100 		if (x & mask)
101 			x ^= (mask - 1);
102 	}
103 
104 	/* Find first set bit */
105 	for (num = 0; num != 14; num++) {
106 		if (x & (1U << num)) {
107 			num++;
108 			break;
109 		}
110 	}
111 
112 	/* Initialize return value */
113 	retval = 0.0;
114 
115 	/* Compute the rest of the power series */
116 	for (; num != 14; num++) {
117 		if (x & (1U << num)) {
118 			retval = (1.0f - retval) / 2.0f;
119 			retval = powf(retval, power);
120 		} else {
121 			retval = (1.0f + retval) / 2.0f;
122 			retval = powf(retval, power);
123 		}
124 	}
125 
126 	/* Check if halfway */
127 	if (x & (1ULL << 14))
128 		retval = -retval;
129 
130 	return (retval);
131 }
132 
133 static void
usage(void)134 usage(void)
135 {
136 	fprintf(stderr, "Usage: %s [-Bh] [-D duration_ms] [-F frequency_hz] "
137 	    "[-d oss_device] [-g gain] [-r sample_rate_hz]\n"
138 	    "\t" "-B Run in background\n"
139 	    "\t" "-D <duration in ms, from %d ms to %d ms, default %d ms>\n"
140 	    "\t" "-F <frequency in Hz, default %d Hz>\n"
141 	    "\t" "-d <OSS device, default %s>\n"
142 	    "\t" "-g <gain from %d to %d, default %d>\n"
143 	    "\t" "-h Show usage\n"
144 	    "\t" "-r <sample rate in Hz, from %d Hz to %d Hz, default %d Hz>\n",
145 	    getprogname(),
146 	    DURATION_MIN, DURATION_MAX, DURATION_DEF,
147 	    DEFAULT_HZ,
148 	    DEFAULT_DEVICE,
149 	    GAIN_MIN, GAIN_MAX, GAIN_DEF,
150 	    SAMPLE_RATE_MIN, SAMPLE_RATE_MAX, SAMPLE_RATE_DEF);
151 	exit(1);
152 }
153 
154 int
main(int argc,char ** argv)155 main(int argc, char **argv)
156 {
157 	float *buffer;
158 	size_t slope;
159 	size_t size;
160 	size_t off;
161 	float a;
162 	float d;
163 	float p;
164 	int c;
165 	int f;
166 
167 	while ((c = getopt(argc, argv, "BF:D:r:g:d:h")) != -1) {
168 		switch (c) {
169 		case 'F':
170 			frequency = strtol(optarg, NULL, 10);
171 			break;
172 		case 'D':
173 			duration_ms = strtol(optarg, NULL, 10);
174 			if (duration_ms < DURATION_MIN ||
175 			    duration_ms > DURATION_MAX)
176 				usage();
177 			break;
178 		case 'r':
179 			sample_rate = strtol(optarg, NULL, 10);
180 			if (sample_rate < SAMPLE_RATE_MIN ||
181 			    sample_rate > SAMPLE_RATE_MAX)
182 				usage();
183 			break;
184 		case 'g':
185 			gain = strtol(optarg, NULL, 10);
186 			if (gain < GAIN_MIN ||
187 			    gain > GAIN_MAX)
188 				usage();
189 			break;
190 		case 'd':
191 			oss_dev = optarg;
192 			break;
193 		case 'B':
194 			background = true;
195 			break;
196 		default:
197 			usage();
198 			break;
199 		}
200 	}
201 
202 	if (background && daemon(0, 0) != 0)
203 		errx(1, "daemon(0,0) failed");
204 
205 	f = open(oss_dev, O_WRONLY);
206 	if (f < 0)
207 		err(1, "Failed to open '%s'", oss_dev);
208 
209 	if (caph_enter() == -1)
210 		err(1, "Failed to enter capability mode");
211 
212 	c = 1;				/* mono */
213 	if (ioctl(f, SOUND_PCM_WRITE_CHANNELS, &c) != 0)
214 		errx(1, "ioctl SOUND_PCM_WRITE_CHANNELS(1) failed");
215 
216 	c = AFMT_FLOAT;
217 	if (ioctl(f, SNDCTL_DSP_SETFMT, &c) != 0)
218 		errx(1, "ioctl SNDCTL_DSP_SETFMT(AFMT_FLOAT) failed");
219 
220 	if (ioctl(f, SNDCTL_DSP_SPEED, &sample_rate) != 0)
221 		errx(1, "ioctl SNDCTL_DSP_SPEED(%d) failed", sample_rate);
222 
223 	c = (2 << 16);
224 	while ((1ULL << (c & 63)) < (size_t)(4 * sample_rate / 50))
225 		c++;
226 	if (ioctl(f, SNDCTL_DSP_SETFRAGMENT, &c))
227 		errx(1, "ioctl SNDCTL_DSP_SETFRAGMENT(0x%x) failed", c);
228 
229 	if (ioctl(f, SNDCTL_DSP_GETODELAY, &c) != 0)
230 		errx(1, "ioctl SNDCTL_DSP_GETODELAY failed");
231 
232 	size = ((sample_rate * duration_ms) + 999) / 1000;
233 	buffer = malloc(sizeof(buffer[0]) * size);
234 	if (buffer == NULL)
235 		errx(1, "out of memory");
236 
237 	/* compute slope duration in samples */
238 	slope = (DURATION_MIN * sample_rate) / 2000;
239 
240 	/* compute base gain */
241 	a = powf(65536.0f, (float)gain / (float)GAIN_MAX) / 65536.0f;
242 
243 	/* set initial phase and delta */
244 	p = 0;
245 	d = (float)frequency / (float)sample_rate;
246 
247 	/* compute wave */
248 	for (p = off = 0; off != size; off++, p += d) {
249 		float sample;
250 
251 		p = p - floorf(p);
252 		sample = a * wave_function_16(p, WAVE_POWER);
253 
254 		if (off < slope)
255 			sample = sample * off / (float)slope;
256 		else if (off > (size - slope))
257 			sample = sample * (size - off - 1) / (float)slope;
258 
259 		buffer[off] = sample;
260 	}
261 
262 	if (write(f, buffer, size * sizeof(buffer[0])) !=
263 	    (ssize_t)(size * sizeof(buffer[0])))
264 		errx(1, "failed writing to DSP device(%s)", oss_dev);
265 
266 	free(buffer);
267 
268 	/* wait for data to be written */
269 	while (ioctl(f, SNDCTL_DSP_GETODELAY, &c) == 0) {
270 		if (c == 0)
271 			break;
272 		usleep(10000);
273 	}
274 
275 	/* wait for audio to go out */
276 	usleep(50000);
277 	close(f);
278 
279 	return (0);
280 }
281