Changeset View
Changeset View
Standalone View
Standalone View
usr.bin/beep/beep.c
- This file was added.
/*- | |||||
* Copyright (c) 2021 Hans Petter Selasky <hselasky@freebsd.org> | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* 1. Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* 2. Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
* SUCH DAMAGE. | |||||
*/ | |||||
#include <sys/soundcard.h> | |||||
#include <err.h> | |||||
#include <errno.h> | |||||
#include <fcntl.h> | |||||
#include <math.h> | |||||
#include <paths.h> | |||||
#include <stdbool.h> | |||||
#include <stdint.h> | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <unistd.h> | |||||
#define SAMPLE_RATE_DEF 48000 /* hz */ | |||||
#define SAMPLE_RATE_MAX 48000 /* hz */ | |||||
#define SAMPLE_RATE_MIN 8000 /* hz */ | |||||
#define DURATION_DEF 150 /* ms */ | |||||
#define DURATION_MAX 2000 /* ms */ | |||||
#define DURATION_MIN 50 /* ms */ | |||||
#define GAIN_DEF 75 | |||||
#define GAIN_MAX 100 | |||||
#define GAIN_MIN 0 | |||||
#define WAVE_POWER 1.25f | |||||
#define DEFAULT_HZ 440 | |||||
#define DEFAULT_DEVICE _PATH_DEV "dsp" | |||||
static int frequency = DEFAULT_HZ; | |||||
static int duration_ms = DURATION_DEF; | |||||
static int sample_rate = SAMPLE_RATE_DEF; | |||||
static int gain = GAIN_DEF; | |||||
static const char *oss_dev = DEFAULT_DEVICE; | |||||
static bool background; | |||||
/* | |||||
* wave_function_16 | |||||
* | |||||
* "phase" should be in the range [0.0f .. 1.0f> | |||||
* "power" should be in the range <0.0f .. 2.0f> | |||||
* | |||||
* The return value is in the range [-1.0f .. 1.0f] | |||||
*/ | |||||
static float | |||||
wave_function_16(float phase, float power) | |||||
{ | |||||
uint16_t x = phase * (1U << 16); | |||||
float retval; | |||||
uint8_t num; | |||||
/* Handle special cases, if any */ | |||||
switch (x) { | |||||
case 0xffff: | |||||
case 0x0000: | |||||
return (1.0f); | |||||
case 0x3fff: | |||||
case 0x4000: | |||||
case 0xBfff: | |||||
case 0xC000: | |||||
return (0.0f); | |||||
case 0x7FFF: | |||||
case 0x8000: | |||||
return (-1.0f); | |||||
default: | |||||
break; | |||||
} | |||||
/* Apply Gray coding */ | |||||
for (uint16_t mask = 1U << 15; mask != 1; mask /= 2) { | |||||
if (x & mask) | |||||
x ^= (mask - 1); | |||||
} | |||||
/* Find first set bit */ | |||||
for (num = 0; num != 14; num++) { | |||||
if (x & (1U << num)) { | |||||
num++; | |||||
break; | |||||
} | |||||
} | |||||
/* Initialize return value */ | |||||
retval = 0.0; | |||||
/* Compute the rest of the power series */ | |||||
for (; num != 14; num++) { | |||||
if (x & (1U << num)) { | |||||
retval = (1.0f - retval) / 2.0f; | |||||
retval = powf(retval, power); | |||||
} else { | |||||
retval = (1.0f + retval) / 2.0f; | |||||
retval = powf(retval, power); | |||||
} | |||||
} | |||||
/* Check if halfway */ | |||||
if (x & (1ULL << 14)) | |||||
retval = -retval; | |||||
return (retval); | |||||
} | |||||
static void | |||||
usage(void) | |||||
{ | |||||
fprintf(stderr, "Usage: %s [parameters]\n" | |||||
"\t" "-F <frequency in HZ, default %dHz>\n" | |||||
"\t" "-D <duration in ms, from %dms to %dms, default %dms>\n" | |||||
"\t" "-r <sample rate in HZ, from %dHz to %dHz, default %dHz>\n" | |||||
"\t" "-d <OSS device (default %s)>\n" | |||||
"\t" "-g <gain from %d to %d, default %d>\n" | |||||
"\t" "-B Run in background\n" | |||||
"\t" "-h Show usage\n", | |||||
getprogname(), | |||||
DEFAULT_HZ, | |||||
DURATION_MIN, DURATION_MAX, DURATION_DEF, | |||||
SAMPLE_RATE_MIN, SAMPLE_RATE_MAX, SAMPLE_RATE_DEF, | |||||
DEFAULT_DEVICE, | |||||
GAIN_MIN, GAIN_MAX, GAIN_DEF); | |||||
exit(1); | |||||
} | |||||
int | |||||
main(int argc, char **argv) | |||||
{ | |||||
int32_t *buffer; | |||||
size_t slope; | |||||
size_t size; | |||||
size_t off; | |||||
float a; | |||||
float d; | |||||
float p; | |||||
int c; | |||||
int f; | |||||
while ((c = getopt(argc, argv, "BF:D:r:g:d:h")) != -1) { | |||||
switch (c) { | |||||
case 'F': | |||||
frequency = strtol(optarg, NULL, 10); | |||||
break; | |||||
case 'D': | |||||
duration_ms = strtol(optarg, NULL, 10); | |||||
if (duration_ms < DURATION_MIN || | |||||
duration_ms > DURATION_MAX) | |||||
usage(); | |||||
break; | |||||
case 'r': | |||||
sample_rate = strtol(optarg, NULL, 10); | |||||
if (sample_rate < SAMPLE_RATE_MIN || | |||||
sample_rate > SAMPLE_RATE_MAX) | |||||
usage(); | |||||
break; | |||||
case 'g': | |||||
gain = strtol(optarg, NULL, 10); | |||||
if (gain < GAIN_MIN || | |||||
gain > GAIN_MAX) | |||||
usage(); | |||||
break; | |||||
case 'd': | |||||
oss_dev = optarg; | |||||
break; | |||||
case 'B': | |||||
background = true; | |||||
break; | |||||
default: | |||||
usage(); | |||||
break; | |||||
} | |||||
} | |||||
if (background && daemon(0, 0) != 0) | |||||
errx(1, "daemon(0,0) failed"); | |||||
f = open(oss_dev, O_WRONLY); | |||||
if (f < 0) | |||||
errx(1, "Failed to open '%s'", oss_dev); | |||||
c = 1; /* mono */ | |||||
if (ioctl(f, SOUND_PCM_WRITE_CHANNELS, &c) != 0) | |||||
errx(1, "ioctl SOUND_PCM_WRITE_CHANNELS(1) failed"); | |||||
c = AFMT_S32_NE; | |||||
if (ioctl(f, SNDCTL_DSP_SETFMT, &c) != 0) | |||||
errx(1, "ioctl SNDCTL_DSP_SETFMT(AFMT_S32_NE) failed"); | |||||
if (ioctl(f, SNDCTL_DSP_SPEED, &sample_rate) != 0) | |||||
errx(1, "ioctl SNDCTL_DSP_SPEED(%d) failed", sample_rate); | |||||
c = (2 << 16); | |||||
while ((1ULL << (c & 63)) < (size_t)(4 * sample_rate / 50)) | |||||
c++; | |||||
if (ioctl(f, SNDCTL_DSP_SETFRAGMENT, &c)) | |||||
errx(1, "ioctl SNDCTL_DSP_SETFRAGMENT(0x%x) failed", c); | |||||
if (ioctl(f, SNDCTL_DSP_GETODELAY, &c) != 0) | |||||
errx(1, "ioctl SNDCTL_DSP_GETODELAY failed"); | |||||
size = ((sample_rate * duration_ms) + 999) / 1000; | |||||
buffer = malloc(sizeof(buffer[0]) * size); | |||||
if (buffer == NULL) | |||||
errx(1, "out of memory"); | |||||
/* compute slope duration in samples */ | |||||
slope = (DURATION_MIN * sample_rate) / 2000; | |||||
/* compute base gain */ | |||||
a = powf(65536.0f, (float)gain / (float)GAIN_MAX) / 65536.0f; | |||||
/* set initial phase and delta */ | |||||
p = 0; | |||||
d = (float)frequency / (float)sample_rate; | |||||
/* compute wave */ | |||||
for (p = off = 0; off != size; off++, p += d) { | |||||
float sample; | |||||
p = p - floorf(p); | |||||
sample = a * wave_function_16(p, WAVE_POWER); | |||||
if (off < slope) | |||||
sample = sample * off / (float)slope; | |||||
else if (off > (size - slope)) | |||||
sample = sample * (size - off - 1) / (float)slope; | |||||
buffer[off] = sample * 0x7fffff00; | |||||
} | |||||
if (write(f, buffer, size * sizeof(buffer[0])) != | |||||
(ssize_t)(size * sizeof(buffer[0]))) | |||||
errx(1, "failed writing to DSP device(%s)", oss_dev); | |||||
free(buffer); | |||||
/* wait for data to be written */ | |||||
while (ioctl(f, SNDCTL_DSP_GETODELAY, &c) == 0) { | |||||
if (c == 0) | |||||
break; | |||||
usleep(10000); | |||||
} | |||||
/* wait for audio to go out */ | |||||
usleep(50000); | |||||
close(f); | |||||
return (0); | |||||
} |