Changeset View
Changeset View
Standalone View
Standalone View
usr.bin/beep/beep.c
/*- | /*- | ||||
* Copyright (c) 2021 Hans Petter Selasky <hselasky@freebsd.org> | * Copyright (c) 2021-2022 Hans Petter Selasky <hselasky@freebsd.org> | ||||
* | * | ||||
* Redistribution and use in source and binary forms, with or without | * Redistribution and use in source and binary forms, with or without | ||||
* modification, are permitted provided that the following conditions | * modification, are permitted provided that the following conditions | ||||
* are met: | * are met: | ||||
* 1. Redistributions of source code must retain the above copyright | * 1. Redistributions of source code must retain the above copyright | ||||
* notice, this list of conditions and the following disclaimer. | * notice, this list of conditions and the following disclaimer. | ||||
* 2. Redistributions in binary form must reproduce the above copyright | * 2. Redistributions in binary form must reproduce the above copyright | ||||
* notice, this list of conditions and the following disclaimer in the | * notice, this list of conditions and the following disclaimer in the | ||||
Show All 20 Lines | |||||
#include <math.h> | #include <math.h> | ||||
#include <paths.h> | #include <paths.h> | ||||
#include <stdbool.h> | #include <stdbool.h> | ||||
#include <stdint.h> | #include <stdint.h> | ||||
#include <stdio.h> | #include <stdio.h> | ||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <string.h> | #include <string.h> | ||||
#include <unistd.h> | #include <unistd.h> | ||||
#include <assert.h> | |||||
#define SAMPLE_RATE_DEF 48000 /* hz */ | #define SAMPLE_RATE_DEF 48000 /* hz */ | ||||
#define SAMPLE_RATE_MAX 48000 /* hz */ | #define SAMPLE_RATE_MAX 48000 /* hz */ | ||||
#define SAMPLE_RATE_MIN 8000 /* hz */ | #define SAMPLE_RATE_MIN 8000 /* hz */ | ||||
#define DURATION_DEF 150 /* ms */ | #define DURATION_DEF 150 /* ms */ | ||||
#define DURATION_MAX 2000 /* ms */ | #define DURATION_MAX 2000 /* ms */ | ||||
#define DURATION_MIN 50 /* ms */ | #define DURATION_MIN 50 /* ms */ | ||||
Show All 9 Lines | |||||
#define DEFAULT_DEVICE _PATH_DEV "dsp" | #define DEFAULT_DEVICE _PATH_DEV "dsp" | ||||
static int frequency = DEFAULT_HZ; | static int frequency = DEFAULT_HZ; | ||||
static int duration_ms = DURATION_DEF; | static int duration_ms = DURATION_DEF; | ||||
static int sample_rate = SAMPLE_RATE_DEF; | static int sample_rate = SAMPLE_RATE_DEF; | ||||
static int gain = GAIN_DEF; | static int gain = GAIN_DEF; | ||||
static const char *oss_dev = DEFAULT_DEVICE; | static const char *oss_dev = DEFAULT_DEVICE; | ||||
static bool background; | static bool background; | ||||
static const uint8_t *text; | |||||
static const uint8_t *number; | |||||
static uint32_t number_parsed; | |||||
static uint8_t number_bits; | |||||
/* | /* | ||||
* wave_function_16 | * wave_function_16 | ||||
* | * | ||||
* "phase" should be in the range [0.0f .. 1.0f> | * "phase" should be in the range [0.0f .. 1.0f> | ||||
* "power" should be in the range <0.0f .. 2.0f> | * "power" should be in the range <0.0f .. 2.0f> | ||||
* | * | ||||
* The return value is in the range [-1.0f .. 1.0f] | * The return value is in the range [-1.0f .. 1.0f] | ||||
▲ Show 20 Lines • Show All 53 Lines • ▼ Show 20 Lines | wave_function_16(float phase, float power) | ||||
/* Check if halfway */ | /* Check if halfway */ | ||||
if (x & (1ULL << 14)) | if (x & (1ULL << 14)) | ||||
retval = -retval; | retval = -retval; | ||||
return (retval); | return (retval); | ||||
} | } | ||||
static void | static void | ||||
render_beep(int32_t *buffer, size_t size, float amp) | |||||
{ | |||||
size_t slope; | |||||
size_t off; /* buffer offset */ | |||||
float p; /* phase */ | |||||
float d; /* delta phase */ | |||||
/* compute slope duration in samples */ | |||||
slope = (DURATION_MIN * sample_rate) / 2000; | |||||
/* 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 = amp * 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; | |||||
} | |||||
} | |||||
static void | |||||
render_silence(int32_t *buffer, size_t size) | |||||
{ | |||||
for (size_t x = 0; x != size; x++) | |||||
buffer[x] = 0; | |||||
} | |||||
static uint32_t | |||||
get_unicode_char(const uint8_t **pptr, uint8_t *bits) | |||||
{ | |||||
const uint8_t *p8; | |||||
uint32_t ch; | |||||
p8 = *pptr; | |||||
if ((*p8 & 0xc0) == 0xc0) { | |||||
p8++; | |||||
while ((*p8 & 0xc0) == 0x80) | |||||
p8++; | |||||
switch (p8 - *pptr) { | |||||
case 1: /* 12-bit */ | |||||
ch = (*pptr)[0] & 0x3f; | |||||
ch <<= 6; | |||||
ch |= (*pptr)[1] & 0x3f; | |||||
*pptr = p8; | |||||
*bits = 12; | |||||
goto done; | |||||
case 2: /* 17-bit */ | |||||
ch = (*pptr)[0] & 0x1f; | |||||
ch <<= 6; | |||||
ch |= (*pptr)[1] & 0x3f; | |||||
ch <<= 6; | |||||
ch |= (*pptr)[2] & 0x3f; | |||||
*pptr = p8; | |||||
*bits = 17; | |||||
goto done; | |||||
case 3: /* 22-bit */ | |||||
ch = (*pptr)[0] & 0x0f; | |||||
ch <<= 6; | |||||
ch |= (*pptr)[1] & 0x3f; | |||||
ch <<= 6; | |||||
ch |= (*pptr)[2] & 0x3f; | |||||
ch <<= 6; | |||||
ch |= (*pptr)[3] & 0x3f; | |||||
*pptr = p8; | |||||
*bits = 22; | |||||
goto done; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
/* not UTF-8 */ | |||||
ch = **pptr; | |||||
(*pptr) ++; | |||||
*bits = 8; | |||||
done: | |||||
return (ch); | |||||
} | |||||
static size_t | |||||
text_length_in_units(const uint8_t *ptr) | |||||
{ | |||||
size_t units = 0; | |||||
uint32_t ch; | |||||
uint8_t bits; | |||||
while (*ptr) { | |||||
ch = get_unicode_char(&ptr, &bits); | |||||
for (uint8_t x = 0; x != bits; x++) { | |||||
if ((ch >> x) & 1) | |||||
units += 2; | |||||
else | |||||
units += 4; | |||||
} | |||||
units += 4; | |||||
} | |||||
return (units); | |||||
} | |||||
static size_t | |||||
number_length_in_units(uint32_t num, uint8_t max) | |||||
{ | |||||
size_t units = 0; | |||||
for (uint8_t x = 0; x != max; x++) { | |||||
if ((num >> x) & 1) | |||||
units += 2; | |||||
else | |||||
units += 4; | |||||
} | |||||
return (units); | |||||
} | |||||
static void | |||||
usage(void) | usage(void) | ||||
{ | { | ||||
fprintf(stderr, "Usage: %s [parameters]\n" | fprintf(stderr, "Usage: %s [parameters]\n" | ||||
"\t" "-F <frequency in HZ, default %d Hz>\n" | "\t" "-F <frequency in HZ, default %d Hz>\n" | ||||
"\t" "-D <duration in ms, from %d ms to %d ms, default %d ms>\n" | "\t" "-D <duration in ms, from %d ms to %d ms, default %d ms>\n" | ||||
"\t" "-r <sample rate in HZ, from %d Hz to %d Hz, default %d Hz>\n" | "\t" "-r <sample rate in HZ, from %d Hz to %d Hz, default %d Hz>\n" | ||||
"\t" "-d <OSS device (default %s)>\n" | "\t" "-d <OSS device (default %s)>\n" | ||||
"\t" "-g <gain from %d to %d, default %d>\n" | "\t" "-g <gain from %d to %d, default %d>\n" | ||||
"\t" "-t <render given UTF-8 based text>\n" | |||||
"\t" "-b <render given 8-bit number>\n" | |||||
"\t" "-s <render given 16-bit number>\n" | |||||
"\t" "-i <render given 32-bit number>\n" | |||||
"\t" "-B Run in background\n" | "\t" "-B Run in background\n" | ||||
"\t" "-h Show usage\n", | "\t" "-h Show usage\n", | ||||
getprogname(), | getprogname(), | ||||
DEFAULT_HZ, | DEFAULT_HZ, | ||||
DURATION_MIN, DURATION_MAX, DURATION_DEF, | DURATION_MIN, DURATION_MAX, DURATION_DEF, | ||||
SAMPLE_RATE_MIN, SAMPLE_RATE_MAX, SAMPLE_RATE_DEF, | SAMPLE_RATE_MIN, SAMPLE_RATE_MAX, SAMPLE_RATE_DEF, | ||||
DEFAULT_DEVICE, | DEFAULT_DEVICE, | ||||
GAIN_MIN, GAIN_MAX, GAIN_DEF); | GAIN_MIN, GAIN_MAX, GAIN_DEF); | ||||
exit(1); | exit(1); | ||||
} | } | ||||
int | int | ||||
main(int argc, char **argv) | main(int argc, char **argv) | ||||
{ | { | ||||
int32_t *buffer; | int32_t *buffer; | ||||
size_t slope; | int32_t *buffer_short; | ||||
int32_t *buffer_long; | |||||
size_t size; | size_t size; | ||||
size_t units; | |||||
size_t off; | size_t off; | ||||
float a; | float a; | ||||
float d; | |||||
float p; | |||||
int c; | int c; | ||||
int f; | int f; | ||||
while ((c = getopt(argc, argv, "BF:D:r:g:d:h")) != -1) { | while ((c = getopt(argc, argv, "BF:D:r:b:s:i:t:g:d:h")) != -1) { | ||||
switch (c) { | switch (c) { | ||||
case 'F': | case 'F': | ||||
frequency = strtol(optarg, NULL, 10); | frequency = strtol(optarg, NULL, 10); | ||||
break; | break; | ||||
case 'D': | case 'D': | ||||
duration_ms = strtol(optarg, NULL, 10); | duration_ms = strtol(optarg, NULL, 10); | ||||
if (duration_ms < DURATION_MIN || | if (duration_ms < DURATION_MIN || | ||||
duration_ms > DURATION_MAX) | duration_ms > DURATION_MAX) | ||||
Show All 12 Lines | case 'g': | ||||
usage(); | usage(); | ||||
break; | break; | ||||
case 'd': | case 'd': | ||||
oss_dev = optarg; | oss_dev = optarg; | ||||
break; | break; | ||||
case 'B': | case 'B': | ||||
background = true; | background = true; | ||||
break; | break; | ||||
case 'b': | |||||
number = optarg; | |||||
number_bits = 8; | |||||
break; | |||||
case 's': | |||||
number = optarg; | |||||
number_bits = 16; | |||||
break; | |||||
case 'i': | |||||
number = optarg; | |||||
number_bits = 32; | |||||
break; | |||||
case 't': | |||||
text = optarg; | |||||
break; | |||||
default: | default: | ||||
usage(); | usage(); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
if (text != NULL && number != NULL) | |||||
errx(1, "-t and -s options are mutually exclusive"); | |||||
if (background && daemon(0, 0) != 0) | if (background && daemon(0, 0) != 0) | ||||
errx(1, "daemon(0,0) failed"); | errx(1, "daemon(0,0) failed"); | ||||
f = open(oss_dev, O_WRONLY); | f = open(oss_dev, O_WRONLY); | ||||
if (f < 0) | if (f < 0) | ||||
errx(1, "Failed to open '%s'", oss_dev); | errx(1, "Failed to open '%s'", oss_dev); | ||||
c = 1; /* mono */ | c = 1; /* mono */ | ||||
Show All 11 Lines | main(int argc, char **argv) | ||||
while ((1ULL << (c & 63)) < (size_t)(4 * sample_rate / 50)) | while ((1ULL << (c & 63)) < (size_t)(4 * sample_rate / 50)) | ||||
c++; | c++; | ||||
if (ioctl(f, SNDCTL_DSP_SETFRAGMENT, &c)) | if (ioctl(f, SNDCTL_DSP_SETFRAGMENT, &c)) | ||||
errx(1, "ioctl SNDCTL_DSP_SETFRAGMENT(0x%x) failed", c); | errx(1, "ioctl SNDCTL_DSP_SETFRAGMENT(0x%x) failed", c); | ||||
if (ioctl(f, SNDCTL_DSP_GETODELAY, &c) != 0) | if (ioctl(f, SNDCTL_DSP_GETODELAY, &c) != 0) | ||||
errx(1, "ioctl SNDCTL_DSP_GETODELAY failed"); | errx(1, "ioctl SNDCTL_DSP_GETODELAY failed"); | ||||
if (number != NULL) { | |||||
number_parsed = strtoul(number, NULL, 0); | |||||
units = number_length_in_units(number_parsed, number_bits); | |||||
} else if (text != NULL) { | |||||
units = text_length_in_units(text); | |||||
} else { | |||||
units = 1; | |||||
} | |||||
size = ((sample_rate * duration_ms) + 999) / 1000; | size = ((sample_rate * duration_ms) + 999) / 1000; | ||||
buffer = malloc(sizeof(buffer[0]) * size); | buffer = malloc(sizeof(buffer[0]) * size * units); | ||||
if (buffer == NULL) | if (buffer == NULL) | ||||
errx(1, "out of memory"); | errx(1, "out of memory"); | ||||
/* compute slope duration in samples */ | |||||
slope = (DURATION_MIN * sample_rate) / 2000; | |||||
/* compute base gain */ | /* compute base gain */ | ||||
a = powf(65536.0f, (float)gain / (float)GAIN_MAX) / 65536.0f; | a = powf(65536.0f, (float)gain / (float)GAIN_MAX) / 65536.0f; | ||||
/* set initial phase and delta */ | if (text != NULL || number != NULL) { | ||||
p = 0; | buffer_short = malloc(sizeof(buffer[0]) * size * 2); | ||||
d = (float)frequency / (float)sample_rate; | if (buffer_short == NULL) | ||||
errx(1, "out of memory"); | |||||
buffer_long = malloc(sizeof(buffer[0]) * size * 4); | |||||
if (buffer_long == NULL) | |||||
errx(1, "out of memory"); | |||||
/* compute wave */ | render_beep(buffer_short, size, a); | ||||
for (p = off = 0; off != size; off++, p += d) { | render_silence(buffer_short + size, size); | ||||
float sample; | |||||
p = p - floorf(p); | render_beep(buffer_long, 3 * size, a); | ||||
sample = a * wave_function_16(p, WAVE_POWER); | render_silence(buffer_long + 3 * size, size); | ||||
} else { | |||||
buffer_short = NULL; | |||||
buffer_long = NULL; | |||||
} | |||||
if (off < slope) | if (number != NULL) { | ||||
sample = sample * off / (float)slope; | off = 0; | ||||
else if (off > (size - slope)) | |||||
sample = sample * (size - off - 1) / (float)slope; | |||||
buffer[off] = sample * 0x7fffff00; | for (uint8_t x = 0; x != number_bits; x++) { | ||||
if ((number_parsed >> x) & 1) { | |||||
memcpy(buffer + off, buffer_short, 2 * size * sizeof(buffer[0])); | |||||
off += 2 * size; | |||||
} else { | |||||
memcpy(buffer + off, buffer_long, 4 * size * sizeof(buffer[0])); | |||||
off += 4 * size; | |||||
} | } | ||||
} | |||||
assert(off == (size * units)); | |||||
} else if (text != NULL) { | |||||
for (off = 0; *text != 0; ) { | |||||
uint8_t bits; | |||||
uint32_t ch; | |||||
if (write(f, buffer, size * sizeof(buffer[0])) != | ch = get_unicode_char(&text, &bits); | ||||
(ssize_t)(size * sizeof(buffer[0]))) | |||||
for (uint8_t x = 0; x != bits; x++) { | |||||
if ((ch >> x) & 1) { | |||||
memcpy(buffer + off, buffer_short, 2 * size * sizeof(buffer[0])); | |||||
off += 2 * size; | |||||
} else { | |||||
memcpy(buffer + off, buffer_long, 4 * size * sizeof(buffer[0])); | |||||
off += 4 * size; | |||||
} | |||||
} | |||||
render_silence(buffer + off, 4 * size); | |||||
off += 4 * size; | |||||
} | |||||
assert(off == (size * units)); | |||||
} else { | |||||
render_beep(buffer, size, a); | |||||
} | |||||
free(buffer_short); | |||||
free(buffer_long); | |||||
if (write(f, buffer, size * units * sizeof(buffer[0])) != | |||||
(ssize_t)(size * units * sizeof(buffer[0]))) | |||||
errx(1, "failed writing to DSP device(%s)", oss_dev); | errx(1, "failed writing to DSP device(%s)", oss_dev); | ||||
free(buffer); | free(buffer); | ||||
/* wait for data to be written */ | /* wait for data to be written */ | ||||
while (ioctl(f, SNDCTL_DSP_GETODELAY, &c) == 0) { | while (ioctl(f, SNDCTL_DSP_GETODELAY, &c) == 0) { | ||||
if (c == 0) | if (c == 0) | ||||
break; | break; | ||||
Show All 9 Lines |