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 <err.h>
29#include <errno.h>
30#include <fcntl.h>
31#include <math.h>
32#include <paths.h>
33#include <stdbool.h>
34#include <stdint.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <unistd.h>
39
40#define	SAMPLE_RATE_DEF 48000		/* hz */
41#define	SAMPLE_RATE_MAX 48000		/* hz */
42#define	SAMPLE_RATE_MIN 8000		/* hz */
43
44#define	DURATION_DEF 150		/* ms */
45#define	DURATION_MAX 2000		/* ms */
46#define	DURATION_MIN 50			/* ms */
47
48#define	GAIN_DEF 75
49#define	GAIN_MAX 100
50#define	GAIN_MIN 0
51
52#define	WAVE_POWER 1.25f
53
54#define	DEFAULT_HZ 440
55
56#define	DEFAULT_DEVICE _PATH_DEV "dsp"
57
58static int frequency = DEFAULT_HZ;
59static int duration_ms = DURATION_DEF;
60static int sample_rate = SAMPLE_RATE_DEF;
61static int gain = GAIN_DEF;
62static const char *oss_dev = DEFAULT_DEVICE;
63static bool background;
64
65/*
66 * wave_function_16
67 *
68 * "phase" should be in the range [0.0f .. 1.0f>
69 * "power" should be in the range <0.0f .. 2.0f>
70 *
71 * The return value is in the range [-1.0f .. 1.0f]
72 */
73static float
74wave_function_16(float phase, float power)
75{
76	uint16_t x = phase * (1U << 16);
77	float retval;
78	uint8_t num;
79
80	/* Handle special cases, if any */
81	switch (x) {
82	case 0xffff:
83	case 0x0000:
84		return (1.0f);
85	case 0x3fff:
86	case 0x4000:
87	case 0xBfff:
88	case 0xC000:
89		return (0.0f);
90	case 0x7FFF:
91	case 0x8000:
92		return (-1.0f);
93	default:
94		break;
95	}
96
97	/* Apply Gray coding */
98	for (uint16_t mask = 1U << 15; mask != 1; mask /= 2) {
99		if (x & mask)
100			x ^= (mask - 1);
101	}
102
103	/* Find first set bit */
104	for (num = 0; num != 14; num++) {
105		if (x & (1U << num)) {
106			num++;
107			break;
108		}
109	}
110
111	/* Initialize return value */
112	retval = 0.0;
113
114	/* Compute the rest of the power series */
115	for (; num != 14; num++) {
116		if (x & (1U << num)) {
117			retval = (1.0f - retval) / 2.0f;
118			retval = powf(retval, power);
119		} else {
120			retval = (1.0f + retval) / 2.0f;
121			retval = powf(retval, power);
122		}
123	}
124
125	/* Check if halfway */
126	if (x & (1ULL << 14))
127		retval = -retval;
128
129	return (retval);
130}
131
132static void
133usage(void)
134{
135	fprintf(stderr, "Usage: %s [parameters]\n"
136	    "\t" "-F <frequency in HZ, default %d Hz>\n"
137	    "\t" "-D <duration in ms, from %d ms to %d ms, default %d ms>\n"
138	    "\t" "-r <sample rate in HZ, from %d Hz to %d Hz, default %d Hz>\n"
139	    "\t" "-d <OSS device (default %s)>\n"
140	    "\t" "-g <gain from %d to %d, default %d>\n"
141	    "\t" "-B Run in background\n"
142	    "\t" "-h Show usage\n",
143	    getprogname(),
144	    DEFAULT_HZ,
145	    DURATION_MIN, DURATION_MAX, DURATION_DEF,
146	    SAMPLE_RATE_MIN, SAMPLE_RATE_MAX, SAMPLE_RATE_DEF,
147	    DEFAULT_DEVICE,
148	    GAIN_MIN, GAIN_MAX, GAIN_DEF);
149	exit(1);
150}
151
152int
153main(int argc, char **argv)
154{
155	int32_t *buffer;
156	size_t slope;
157	size_t size;
158	size_t off;
159	float a;
160	float d;
161	float p;
162	int c;
163	int f;
164
165	while ((c = getopt(argc, argv, "BF:D:r:g:d:h")) != -1) {
166		switch (c) {
167		case 'F':
168			frequency = strtol(optarg, NULL, 10);
169			break;
170		case 'D':
171			duration_ms = strtol(optarg, NULL, 10);
172			if (duration_ms < DURATION_MIN ||
173			    duration_ms > DURATION_MAX)
174				usage();
175			break;
176		case 'r':
177			sample_rate = strtol(optarg, NULL, 10);
178			if (sample_rate < SAMPLE_RATE_MIN ||
179			    sample_rate > SAMPLE_RATE_MAX)
180				usage();
181			break;
182		case 'g':
183			gain = strtol(optarg, NULL, 10);
184			if (gain < GAIN_MIN ||
185			    gain > GAIN_MAX)
186				usage();
187			break;
188		case 'd':
189			oss_dev = optarg;
190			break;
191		case 'B':
192			background = true;
193			break;
194		default:
195			usage();
196			break;
197		}
198	}
199
200	if (background && daemon(0, 0) != 0)
201		errx(1, "daemon(0,0) failed");
202
203	f = open(oss_dev, O_WRONLY);
204	if (f < 0)
205		errx(1, "Failed to open '%s'", oss_dev);
206
207	c = 1;				/* mono */
208	if (ioctl(f, SOUND_PCM_WRITE_CHANNELS, &c) != 0)
209		errx(1, "ioctl SOUND_PCM_WRITE_CHANNELS(1) failed");
210
211	c = AFMT_S32_NE;
212	if (ioctl(f, SNDCTL_DSP_SETFMT, &c) != 0)
213		errx(1, "ioctl SNDCTL_DSP_SETFMT(AFMT_S32_NE) failed");
214
215	if (ioctl(f, SNDCTL_DSP_SPEED, &sample_rate) != 0)
216		errx(1, "ioctl SNDCTL_DSP_SPEED(%d) failed", sample_rate);
217
218	c = (2 << 16);
219	while ((1ULL << (c & 63)) < (size_t)(4 * sample_rate / 50))
220		c++;
221	if (ioctl(f, SNDCTL_DSP_SETFRAGMENT, &c))
222		errx(1, "ioctl SNDCTL_DSP_SETFRAGMENT(0x%x) failed", c);
223
224	if (ioctl(f, SNDCTL_DSP_GETODELAY, &c) != 0)
225		errx(1, "ioctl SNDCTL_DSP_GETODELAY failed");
226
227	size = ((sample_rate * duration_ms) + 999) / 1000;
228	buffer = malloc(sizeof(buffer[0]) * size);
229	if (buffer == NULL)
230		errx(1, "out of memory");
231
232	/* compute slope duration in samples */
233	slope = (DURATION_MIN * sample_rate) / 2000;
234
235	/* compute base gain */
236	a = powf(65536.0f, (float)gain / (float)GAIN_MAX) / 65536.0f;
237
238	/* set initial phase and delta */
239	p = 0;
240	d = (float)frequency / (float)sample_rate;
241
242	/* compute wave */
243	for (p = off = 0; off != size; off++, p += d) {
244		float sample;
245
246		p = p - floorf(p);
247		sample = a * wave_function_16(p, WAVE_POWER);
248
249		if (off < slope)
250			sample = sample * off / (float)slope;
251		else if (off > (size - slope))
252			sample = sample * (size - off - 1) / (float)slope;
253
254		buffer[off] = sample * 0x7fffff00;
255	}
256
257	if (write(f, buffer, size * sizeof(buffer[0])) !=
258	    (ssize_t)(size * sizeof(buffer[0])))
259		errx(1, "failed writing to DSP device(%s)", oss_dev);
260
261	free(buffer);
262
263	/* wait for data to be written */
264	while (ioctl(f, SNDCTL_DSP_GETODELAY, &c) == 0) {
265		if (c == 0)
266			break;
267		usleep(10000);
268	}
269
270	/* wait for audio to go out */
271	usleep(50000);
272	close(f);
273
274	return (0);
275}
276