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 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 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 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