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