NetBSD/sys/dev/audiobell.c

190 lines
5.7 KiB
C

/* $NetBSD: audiobell.c,v 1.12 2016/12/13 20:18:32 christos Exp $ */
/*
* Copyright (c) 1999 Richard Earnshaw
* Copyright (c) 2004 Ben Harris
*
* 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the RiscBSD team.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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/types.h>
__KERNEL_RCSID(0, "$NetBSD: audiobell.c,v 1.12 2016/12/13 20:18:32 christos Exp $");
#include <sys/audioio.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/malloc.h>
#include <sys/null.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <dev/audio_if.h>
#include <dev/audiobellvar.h>
extern dev_type_open(audioopen);
extern dev_type_ioctl(audioioctl);
extern dev_type_write(audiowrite);
extern dev_type_close(audioclose);
/* Convert a %age volume to an amount to add to u-law values */
/* XXX Probably highly inaccurate -- should be regenerated */
static const uint8_t volmap[] = {
0x7f, 0x67, 0x5b, 0x53, 0x49, 0x45, 0x41, 0x3e, 0x3a, 0x38,
0x36, 0x32, 0x30, 0x2f, 0x2e, 0x2c, 0x2b, 0x2a, 0x28, 0x27,
0x26, 0x25, 0x23, 0x22, 0x21, 0x1f, 0x1f, 0x1e, 0x1e, 0x1d,
0x1c, 0x1c, 0x1b, 0x1a, 0x1a, 0x19, 0x18, 0x18, 0x17, 0x17,
0x16, 0x15, 0x15, 0x14, 0x13, 0x13, 0x12, 0x11, 0x11, 0x10,
0x0f, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d, 0x0d,
0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b, 0x0a, 0x0a, 0x0a, 0x09,
0x09, 0x09, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x06,
0x06, 0x06, 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x03, 0x03,
0x03, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
0x00
};
/* 1/4 cycle sine wave in u-law */
/* XXX Probably highly inaccurate -- should be regenerated */
static const uint8_t sinewave[] = {
0xff, 0xd3, 0xc5, 0xbc, 0xb6, 0xb0, 0xad, 0xaa,
0xa7, 0xa3, 0xa0, 0x9e, 0x9d, 0x9b, 0x9a, 0x98,
0x97, 0x96, 0x94, 0x93, 0x91, 0x90, 0x8f, 0x8e,
0x8e, 0x8d, 0x8c, 0x8c, 0x8b, 0x8b, 0x8a, 0x89,
0x89, 0x88, 0x88, 0x87, 0x87, 0x86, 0x86, 0x85,
0x85, 0x84, 0x84, 0x84, 0x83, 0x83, 0x83, 0x82,
0x82, 0x82, 0x81, 0x81, 0x81, 0x81, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80,
};
static inline uint8_t
audiobell_ulawscale(uint8_t val, uint8_t vol)
{
uint8_t result;
result = val + vol;
/* Spot underflow and just return silence */
if ((result ^ val) & 0x80)
return 0x7f;
return result;
}
static inline void
audiobell_expandwave(uint8_t *buf, int volume)
{
u_int i;
int uvol;
KASSERT(volume >= 0 && volume <= 100);
uvol = volmap[volume];
for (i = 0; i < 65; i++)
buf[i] = audiobell_ulawscale(sinewave[i], uvol);
for (i = 65; i < 128; i++)
buf[i] = buf[128 - i];
for (i = 128; i < 256; i++)
buf[i] = buf[i - 128] ^ 0x80;
}
/*
* The algorithm here is based on that described in the RISC OS Programmer's
* Reference Manual (pp1624--1628).
*/
static inline int
audiobell_synthesize(uint8_t *buf, u_int pitch, u_int period, u_int volume)
{
uint8_t *wave;
uint16_t phase;
wave = malloc(256, M_TEMP, M_WAITOK);
if (wave == NULL) return -1;
audiobell_expandwave(wave, volume);
pitch = pitch * 65536 / 8000;
period = period * 8; /* 8000 / 1000 */
phase = 0;
for (; period != 0; period--) {
*buf++ = wave[phase >> 8];
phase += pitch;
}
free(wave, M_TEMP);
return 0;
}
void
audiobell(void *v, u_int pitch, u_int period, u_int volume, int poll)
{
uint8_t *buf;
struct audio_info ai;
struct uio auio;
struct iovec aiov;
int size, len, offset;
dev_t audio = AUDIO_DEVICE | device_unit((device_t)v);
/* The audio system isn't built for polling. */
if (poll) return;
/* If not configured, we can't beep. */
if (audioopen(audio, FWRITE, 0, NULL) != 0)
return;
if (audioioctl(audio, AUDIO_GETINFO, &ai, 0, NULL) != 0)
return;
buf = NULL;
len = period * 8;
size = min(len, ai.blocksize);
if (size == 0) goto out;
buf = malloc(size, M_TEMP, M_WAITOK);
if (buf == NULL) goto out;
offset = 0;
while (len > 0) {
size = min(len, ai.blocksize);
if (audiobell_synthesize(buf, pitch, size / 8, volume) != 0)
goto out;
aiov.iov_base = (void *)buf;
aiov.iov_len = size;
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
auio.uio_offset = 0;
auio.uio_resid = size;
auio.uio_rw = UIO_WRITE;
UIO_SETUP_SYSSPACE(&auio);
audiowrite(audio, &auio, 0);
len -= size;
offset += size;
}
out:
if (buf != NULL) free(buf, M_TEMP);
audioclose(audio, FWRITE, 0, NULL);
}