]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/beep/beep.c
zfs: merge openzfs/zfs@bc3f12bfa (master) into main
[FreeBSD/FreeBSD.git] / usr.bin / beep / beep.c
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
58 static int frequency = DEFAULT_HZ;
59 static int duration_ms = DURATION_DEF;
60 static int sample_rate = SAMPLE_RATE_DEF;
61 static int gain = GAIN_DEF;
62 static const char *oss_dev = DEFAULT_DEVICE;
63 static 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  */
73 static float
74 wave_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
132 static void
133 usage(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
152 int
153 main(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 }