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