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 */ | ||||
#define GAIN_DEF 75 | #define GAIN_DEF 75 | ||||
#define GAIN_MAX 100 | #define GAIN_MAX 100 | ||||
#define GAIN_MIN 0 | #define GAIN_MIN 0 | ||||
#define WAVE_POWER 1.25f | #define WAVE_POWER 1.25f | ||||
#define DEFAULT_HZ 440 | #define DEFAULT_HZ 440 | ||||
#define DEFAULT_DEVICE _PATH_DEV "dsp" | #define DEFAULT_DEVICE _PATH_DEV "dsp" | ||||
#define MUSIC_CHARS 2ULL | |||||
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_ptr; | |||||
static uint64_t number_parsed; | |||||
static struct { | |||||
uint8_t a, b, c; /* frequency exponent in 12ths */ | |||||
int32_t *audio; /* rendered audio */ | |||||
} music_char[MUSIC_CHARS] = { | |||||
{ 0, 4, 7, NULL }, /* Major */ | |||||
{ 0, 3, 6, NULL }, /* Diminished */ | |||||
}; | |||||
/* | /* | ||||
* 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 freq, 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 = freq / (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_music_char(uint8_t n, int32_t *buffer, float freq, float amp, size_t size) | |||||
{ | |||||
assert(n < MUSIC_CHARS); | |||||
if (music_char[n].audio == NULL) { | |||||
music_char[n].audio = calloc(sizeof(music_char[0].audio[0]) * size, 1); | |||||
if (music_char[n].audio == NULL) | |||||
errx(1, "out of memory"); | |||||
render_beep(music_char[n].audio, size, | |||||
freq * powf(2.0f, music_char[n].a / 12.0f), amp / 3.0f); | |||||
render_beep(music_char[n].audio, size, | |||||
freq * powf(2.0f, music_char[n].b / 12.0f), amp / 3.0f); | |||||
render_beep(music_char[n].audio, size, | |||||
freq * powf(2.0f, music_char[n].c / 12.0f), amp / 3.0f); | |||||
} | |||||
memcpy(buffer, music_char[n].audio, size * sizeof(buffer[0])); | |||||
} | |||||
static uint32_t | |||||
get_unicode_char(const uint8_t **pptr) | |||||
{ | |||||
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; | |||||
goto done; | |||||
case 2: /* 17-bit */ | |||||
ch = (*pptr)[0] & 0x1f; | |||||
ch <<= 6; | |||||
ch |= (*pptr)[1] & 0x3f; | |||||
ch <<= 6; | |||||
ch |= (*pptr)[2] & 0x3f; | |||||
*pptr = p8; | |||||
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; | |||||
goto done; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
/* not UTF-8 */ | |||||
ch = **pptr; | |||||
(*pptr) ++; | |||||
done: | |||||
return (ch); | |||||
} | |||||
static size_t | |||||
number_length_in_units(uint64_t number) | |||||
{ | |||||
size_t units = 0; | |||||
do { | |||||
units++; | |||||
} while ((number /= MUSIC_CHARS) != 0); | |||||
return (units); | |||||
} | |||||
static size_t | |||||
text_length_in_units(const uint8_t *ptr) | |||||
{ | |||||
size_t units = 0; | |||||
while (*ptr) { | |||||
units += number_length_in_units(get_unicode_char(&ptr)); | |||||
units += 1; | |||||
} | |||||
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" "-n <render given 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; | |||||
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:n: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 'n': | |||||
number_ptr = optarg; | |||||
break; | |||||
case 't': | |||||
text = optarg; | |||||
break; | |||||
default: | default: | ||||
usage(); | usage(); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
if (text != NULL && number_ptr != 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_ptr != NULL) { | |||||
char *end; | |||||
number_parsed = strtoull(number_ptr, &end, 0); | |||||
if (*end != 0) | |||||
errx(1, "invalid number '%s'", number_ptr); | |||||
units = number_length_in_units(number_parsed); | |||||
} 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 = calloc(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 (number_ptr != NULL) { | ||||
p = 0; | off = 0; | ||||
d = (float)frequency / (float)sample_rate; | |||||
/* compute wave */ | do { | ||||
for (p = off = 0; off != size; off++, p += d) { | render_music_char(number_parsed % MUSIC_CHARS, | ||||
float sample; | buffer + off, frequency, a, size); | ||||
off += size; | |||||
} while ((number_parsed /= MUSIC_CHARS) != 0); | |||||
} else if (text != NULL) { | |||||
off = 0; | |||||
p = p - floorf(p); | while (*text != 0) { | ||||
sample = a * wave_function_16(p, WAVE_POWER); | uint32_t ch; | ||||
if (off < slope) | ch = get_unicode_char(&text); | ||||
sample = sample * off / (float)slope; | |||||
else if (off > (size - slope)) | |||||
sample = sample * (size - off - 1) / (float)slope; | |||||
buffer[off] = sample * 0x7fffff00; | do { | ||||
render_music_char(ch % MUSIC_CHARS, | |||||
buffer + off, frequency, a, size); | |||||
off += size; | |||||
} while ((ch /= MUSIC_CHARS) != 0); | |||||
off += size; | |||||
} | } | ||||
} else { | |||||
render_beep(buffer, size, frequency, a); | |||||
off = size; | |||||
} | |||||
if (write(f, buffer, size * sizeof(buffer[0])) != | /* check whole buffer was filled */ | ||||
(ssize_t)(size * sizeof(buffer[0]))) | assert(off == (size * units)); | ||||
for (unsigned i = 0; i != MUSIC_CHARS; i++) | |||||
free(music_char[i].audio); | |||||
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 |