xref: /dflybsd-src/sys/dev/sound/pcm/feeder_eq.c (revision ed183f8c2f9bb14cbccb8377f3cdd29e0971d8a0)
12a1ad637SFrançois Tigeot /*-
22a1ad637SFrançois Tigeot  * Copyright (c) 2008-2009 Ariff Abdullah <ariff@FreeBSD.org>
32a1ad637SFrançois Tigeot  * All rights reserved.
42a1ad637SFrançois Tigeot  *
52a1ad637SFrançois Tigeot  * Redistribution and use in source and binary forms, with or without
62a1ad637SFrançois Tigeot  * modification, are permitted provided that the following conditions
72a1ad637SFrançois Tigeot  * are met:
82a1ad637SFrançois Tigeot  * 1. Redistributions of source code must retain the above copyright
92a1ad637SFrançois Tigeot  *    notice, this list of conditions and the following disclaimer.
102a1ad637SFrançois Tigeot  * 2. Redistributions in binary form must reproduce the above copyright
112a1ad637SFrançois Tigeot  *    notice, this list of conditions and the following disclaimer in the
122a1ad637SFrançois Tigeot  *    documentation and/or other materials provided with the distribution.
132a1ad637SFrançois Tigeot  *
142a1ad637SFrançois Tigeot  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
152a1ad637SFrançois Tigeot  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
162a1ad637SFrançois Tigeot  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
172a1ad637SFrançois Tigeot  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
182a1ad637SFrançois Tigeot  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
192a1ad637SFrançois Tigeot  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
202a1ad637SFrançois Tigeot  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
212a1ad637SFrançois Tigeot  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
222a1ad637SFrançois Tigeot  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
232a1ad637SFrançois Tigeot  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
242a1ad637SFrançois Tigeot  * SUCH DAMAGE.
252a1ad637SFrançois Tigeot  */
262a1ad637SFrançois Tigeot 
272a1ad637SFrançois Tigeot /*
282a1ad637SFrançois Tigeot  * feeder_eq: Parametric (compile time) Software Equalizer. Though accidental,
292a1ad637SFrançois Tigeot  *            it proves good enough for educational and general consumption.
302a1ad637SFrançois Tigeot  *
312a1ad637SFrançois Tigeot  * "Cookbook formulae for audio EQ biquad filter coefficients"
322a1ad637SFrançois Tigeot  *    by Robert Bristow-Johnson  <rbj@audioimagination.com>
332a1ad637SFrançois Tigeot  *    -  http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
342a1ad637SFrançois Tigeot  */
352a1ad637SFrançois Tigeot 
362a1ad637SFrançois Tigeot #ifdef _KERNEL
372a1ad637SFrançois Tigeot #ifdef HAVE_KERNEL_OPTION_HEADERS
382a1ad637SFrançois Tigeot #include "opt_snd.h"
392a1ad637SFrançois Tigeot #endif
402a1ad637SFrançois Tigeot #include <dev/sound/pcm/sound.h>
412a1ad637SFrançois Tigeot #include <dev/sound/pcm/pcm.h>
422a1ad637SFrançois Tigeot #include "feeder_if.h"
432a1ad637SFrançois Tigeot 
442a1ad637SFrançois Tigeot #define SND_USE_FXDIV
452a1ad637SFrançois Tigeot #include "snd_fxdiv_gen.h"
462a1ad637SFrançois Tigeot 
472a1ad637SFrançois Tigeot SND_DECLARE_FILE("$FreeBSD: head/sys/dev/sound/pcm/feeder_eq.c 267992 2014-06-28 03:56:17Z hselasky $");
482a1ad637SFrançois Tigeot #endif
492a1ad637SFrançois Tigeot 
502a1ad637SFrançois Tigeot #include "feeder_eq_gen.h"
512a1ad637SFrançois Tigeot 
522a1ad637SFrançois Tigeot #define FEEDEQ_LEVELS							\
532a1ad637SFrançois Tigeot 	(((FEEDEQ_GAIN_MAX - FEEDEQ_GAIN_MIN) *				\
542a1ad637SFrançois Tigeot 	(FEEDEQ_GAIN_DIV / FEEDEQ_GAIN_STEP)) + 1)
552a1ad637SFrançois Tigeot 
562a1ad637SFrançois Tigeot #define FEEDEQ_L2GAIN(v)						\
572a1ad637SFrançois Tigeot 	((int)min(((v) * FEEDEQ_LEVELS) / 100, FEEDEQ_LEVELS - 1))
582a1ad637SFrançois Tigeot 
592a1ad637SFrançois Tigeot #define FEEDEQ_PREAMP_IPART(x)		(abs(x) >> FEEDEQ_GAIN_SHIFT)
602a1ad637SFrançois Tigeot #define FEEDEQ_PREAMP_FPART(x)		(abs(x) & FEEDEQ_GAIN_FMASK)
612a1ad637SFrançois Tigeot #define FEEDEQ_PREAMP_SIGNVAL(x)	((x) < 0 ? -1 : 1)
622a1ad637SFrançois Tigeot #define FEEDEQ_PREAMP_SIGNMARK(x)	(((x) < 0) ? '-' : '+')
632a1ad637SFrançois Tigeot 
642a1ad637SFrançois Tigeot #define FEEDEQ_PREAMP_IMIN	-192
652a1ad637SFrançois Tigeot #define FEEDEQ_PREAMP_IMAX	192
662a1ad637SFrançois Tigeot #define FEEDEQ_PREAMP_FMIN	0
672a1ad637SFrançois Tigeot #define FEEDEQ_PREAMP_FMAX	9
682a1ad637SFrançois Tigeot 
692a1ad637SFrançois Tigeot #define FEEDEQ_PREAMP_INVALID	INT_MAX
702a1ad637SFrançois Tigeot 
712a1ad637SFrançois Tigeot #define FEEDEQ_IF2PREAMP(i, f)						\
722a1ad637SFrançois Tigeot 	((abs(i) << FEEDEQ_GAIN_SHIFT) |				\
73*ed183f8cSSascha Wildner 	(rounddown(abs(f), FEEDEQ_GAIN_STEP) &				\
742a1ad637SFrançois Tigeot 	FEEDEQ_GAIN_FMASK))
752a1ad637SFrançois Tigeot 
762a1ad637SFrançois Tigeot #define FEEDEQ_PREAMP_MIN						\
772a1ad637SFrançois Tigeot 	(FEEDEQ_PREAMP_SIGNVAL(FEEDEQ_GAIN_MIN) *			\
782a1ad637SFrançois Tigeot 	FEEDEQ_IF2PREAMP(FEEDEQ_GAIN_MIN, 0))
792a1ad637SFrançois Tigeot 
802a1ad637SFrançois Tigeot #define FEEDEQ_PREAMP_MAX						\
812a1ad637SFrançois Tigeot 	(FEEDEQ_PREAMP_SIGNVAL(FEEDEQ_GAIN_MAX) *			\
822a1ad637SFrançois Tigeot 	FEEDEQ_IF2PREAMP(FEEDEQ_GAIN_MAX, 0))
832a1ad637SFrançois Tigeot 
842a1ad637SFrançois Tigeot #define FEEDEQ_PREAMP_DEFAULT	FEEDEQ_IF2PREAMP(0, 0)
852a1ad637SFrançois Tigeot 
862a1ad637SFrançois Tigeot #define FEEDEQ_PREAMP2IDX(v)						\
872a1ad637SFrançois Tigeot 	((int32_t)((FEEDEQ_GAIN_MAX * (FEEDEQ_GAIN_DIV /		\
882a1ad637SFrançois Tigeot 	FEEDEQ_GAIN_STEP)) + (FEEDEQ_PREAMP_SIGNVAL(v) *		\
892a1ad637SFrançois Tigeot 	FEEDEQ_PREAMP_IPART(v) * (FEEDEQ_GAIN_DIV /			\
902a1ad637SFrançois Tigeot 	FEEDEQ_GAIN_STEP)) + (FEEDEQ_PREAMP_SIGNVAL(v) *		\
912a1ad637SFrançois Tigeot 	(FEEDEQ_PREAMP_FPART(v) / FEEDEQ_GAIN_STEP))))
922a1ad637SFrançois Tigeot 
932a1ad637SFrançois Tigeot static int feeder_eq_exact_rate = 0;
942a1ad637SFrançois Tigeot 
952a1ad637SFrançois Tigeot #ifdef _KERNEL
962a1ad637SFrançois Tigeot static char feeder_eq_presets[] = FEEDER_EQ_PRESETS;
972a1ad637SFrançois Tigeot SYSCTL_STRING(_hw_snd, OID_AUTO, feeder_eq_presets, CTLFLAG_RD,
982a1ad637SFrançois Tigeot     &feeder_eq_presets, 0, "compile-time eq presets");
992a1ad637SFrançois Tigeot 
100c4356965SFrançois Tigeot TUNABLE_INT("hw.snd.feeder_eq_exact_rate", &feeder_eq_exact_rate);
101c4356965SFrançois Tigeot SYSCTL_INT(_hw_snd, OID_AUTO, feeder_eq_exact_rate, CTLFLAG_RW,
1022a1ad637SFrançois Tigeot     &feeder_eq_exact_rate, 0, "force exact rate validation");
1032a1ad637SFrançois Tigeot #endif
1042a1ad637SFrançois Tigeot 
1052a1ad637SFrançois Tigeot struct feed_eq_info;
1062a1ad637SFrançois Tigeot 
1072a1ad637SFrançois Tigeot typedef void (*feed_eq_t)(struct feed_eq_info *, uint8_t *, uint32_t);
1082a1ad637SFrançois Tigeot 
1092a1ad637SFrançois Tigeot struct feed_eq_tone {
1102a1ad637SFrançois Tigeot 	intpcm_t o1[SND_CHN_MAX];
1112a1ad637SFrançois Tigeot 	intpcm_t o2[SND_CHN_MAX];
1122a1ad637SFrançois Tigeot 	intpcm_t i1[SND_CHN_MAX];
1132a1ad637SFrançois Tigeot 	intpcm_t i2[SND_CHN_MAX];
1142a1ad637SFrançois Tigeot 	int gain;
1152a1ad637SFrançois Tigeot };
1162a1ad637SFrançois Tigeot 
1172a1ad637SFrançois Tigeot struct feed_eq_info {
1182a1ad637SFrançois Tigeot 	struct feed_eq_tone treble;
1192a1ad637SFrançois Tigeot 	struct feed_eq_tone bass;
1202a1ad637SFrançois Tigeot 	struct feed_eq_coeff *coeff;
1212a1ad637SFrançois Tigeot 	feed_eq_t biquad;
1222a1ad637SFrançois Tigeot 	uint32_t channels;
1232a1ad637SFrançois Tigeot 	uint32_t rate;
1242a1ad637SFrançois Tigeot 	uint32_t align;
1252a1ad637SFrançois Tigeot 	int32_t preamp;
1262a1ad637SFrançois Tigeot 	int state;
1272a1ad637SFrançois Tigeot };
1282a1ad637SFrançois Tigeot 
1292a1ad637SFrançois Tigeot #if !defined(_KERNEL) && defined(FEEDEQ_ERR_CLIP)
1302a1ad637SFrançois Tigeot #define FEEDEQ_ERR_CLIP_CHECK(t, v)	do {				\
1312a1ad637SFrançois Tigeot 	if ((v) < PCM_S32_MIN || (v) > PCM_S32_MAX)			\
1322a1ad637SFrançois Tigeot 		errx(1, "\n\n%s(): ["#t"] Sample clipping: %jd\n",	\
1332a1ad637SFrançois Tigeot 		    __func__, (intmax_t)(v));				\
1342a1ad637SFrançois Tigeot } while (0)
1352a1ad637SFrançois Tigeot #else
1362a1ad637SFrançois Tigeot #define FEEDEQ_ERR_CLIP_CHECK(...)
1372a1ad637SFrançois Tigeot #endif
1382a1ad637SFrançois Tigeot 
1392a1ad637SFrançois Tigeot #define FEEDEQ_CLAMP(v)		(((v) > PCM_S32_MAX) ? PCM_S32_MAX :	\
1402a1ad637SFrançois Tigeot 				(((v) < PCM_S32_MIN) ? PCM_S32_MIN :	\
1412a1ad637SFrançois Tigeot 				  (v)))
1422a1ad637SFrançois Tigeot 
1432a1ad637SFrançois Tigeot #define FEEDEQ_DECLARE(SIGN, BIT, ENDIAN)					\
1442a1ad637SFrançois Tigeot static void									\
1452a1ad637SFrançois Tigeot feed_eq_biquad_##SIGN##BIT##ENDIAN(struct feed_eq_info *info,			\
1462a1ad637SFrançois Tigeot     uint8_t *dst, uint32_t count)						\
1472a1ad637SFrançois Tigeot {										\
1482a1ad637SFrançois Tigeot 	struct feed_eq_coeff_tone *treble, *bass;				\
1492a1ad637SFrançois Tigeot 	intpcm64_t w;								\
1502a1ad637SFrançois Tigeot 	intpcm_t v;								\
1512a1ad637SFrançois Tigeot 	uint32_t i, j;								\
1522a1ad637SFrançois Tigeot 	int32_t pmul, pshift;							\
1532a1ad637SFrançois Tigeot 										\
1542a1ad637SFrançois Tigeot 	pmul = feed_eq_preamp[info->preamp].mul;				\
1552a1ad637SFrançois Tigeot 	pshift = feed_eq_preamp[info->preamp].shift;				\
1562a1ad637SFrançois Tigeot 										\
1572a1ad637SFrançois Tigeot 	if (info->state == FEEDEQ_DISABLE) {					\
1582a1ad637SFrançois Tigeot 		j = count * info->channels;					\
1592a1ad637SFrançois Tigeot 		dst += j * PCM_##BIT##_BPS;					\
1602a1ad637SFrançois Tigeot 		do {								\
1612a1ad637SFrançois Tigeot 			dst -= PCM_##BIT##_BPS;					\
1622a1ad637SFrançois Tigeot 			v = _PCM_READ_##SIGN##BIT##_##ENDIAN(dst);		\
1632a1ad637SFrançois Tigeot 			v = ((intpcm64_t)pmul * v) >> pshift;			\
1642a1ad637SFrançois Tigeot 			_PCM_WRITE_##SIGN##BIT##_##ENDIAN(dst, v);		\
1652a1ad637SFrançois Tigeot 		} while (--j != 0);						\
1662a1ad637SFrançois Tigeot 										\
1672a1ad637SFrançois Tigeot 		return;								\
1682a1ad637SFrançois Tigeot 	}									\
1692a1ad637SFrançois Tigeot 										\
1702a1ad637SFrançois Tigeot 	treble = &(info->coeff[info->treble.gain].treble);			\
1712a1ad637SFrançois Tigeot 	bass   = &(info->coeff[info->bass.gain].bass);				\
1722a1ad637SFrançois Tigeot 										\
1732a1ad637SFrançois Tigeot 	do {									\
1742a1ad637SFrançois Tigeot 		i = 0;								\
1752a1ad637SFrançois Tigeot 		j = info->channels;						\
1762a1ad637SFrançois Tigeot 		do {								\
1772a1ad637SFrançois Tigeot 			v = _PCM_READ_##SIGN##BIT##_##ENDIAN(dst);		\
1782a1ad637SFrançois Tigeot 			v <<= 32 - BIT;						\
1792a1ad637SFrançois Tigeot 			v = ((intpcm64_t)pmul * v) >> pshift;			\
1802a1ad637SFrançois Tigeot 										\
1812a1ad637SFrançois Tigeot 			w  = (intpcm64_t)v * treble->b0;			\
1822a1ad637SFrançois Tigeot 			w += (intpcm64_t)info->treble.i1[i] * treble->b1;	\
1832a1ad637SFrançois Tigeot 			w += (intpcm64_t)info->treble.i2[i] * treble->b2;	\
1842a1ad637SFrançois Tigeot 			w -= (intpcm64_t)info->treble.o1[i] * treble->a1;	\
1852a1ad637SFrançois Tigeot 			w -= (intpcm64_t)info->treble.o2[i] * treble->a2;	\
1862a1ad637SFrançois Tigeot 			info->treble.i2[i] = info->treble.i1[i];		\
1872a1ad637SFrançois Tigeot 			info->treble.i1[i] = v;					\
1882a1ad637SFrançois Tigeot 			info->treble.o2[i] = info->treble.o1[i];		\
1892a1ad637SFrançois Tigeot 			w >>= FEEDEQ_COEFF_SHIFT;				\
1902a1ad637SFrançois Tigeot 			FEEDEQ_ERR_CLIP_CHECK(treble, w);			\
1912a1ad637SFrançois Tigeot 			v = FEEDEQ_CLAMP(w);					\
1922a1ad637SFrançois Tigeot 			info->treble.o1[i] = v;					\
1932a1ad637SFrançois Tigeot 										\
1942a1ad637SFrançois Tigeot 			w  = (intpcm64_t)v * bass->b0;				\
1952a1ad637SFrançois Tigeot 			w += (intpcm64_t)info->bass.i1[i] * bass->b1;		\
1962a1ad637SFrançois Tigeot 			w += (intpcm64_t)info->bass.i2[i] * bass->b2;		\
1972a1ad637SFrançois Tigeot 			w -= (intpcm64_t)info->bass.o1[i] * bass->a1;		\
1982a1ad637SFrançois Tigeot 			w -= (intpcm64_t)info->bass.o2[i] * bass->a2;		\
1992a1ad637SFrançois Tigeot 			info->bass.i2[i] = info->bass.i1[i];			\
2002a1ad637SFrançois Tigeot 			info->bass.i1[i] = v;					\
2012a1ad637SFrançois Tigeot 			info->bass.o2[i] = info->bass.o1[i];			\
2022a1ad637SFrançois Tigeot 			w >>= FEEDEQ_COEFF_SHIFT;				\
2032a1ad637SFrançois Tigeot 			FEEDEQ_ERR_CLIP_CHECK(bass, w);				\
2042a1ad637SFrançois Tigeot 			v = FEEDEQ_CLAMP(w);					\
2052a1ad637SFrançois Tigeot 			info->bass.o1[i] = v;					\
2062a1ad637SFrançois Tigeot 										\
2072a1ad637SFrançois Tigeot 			v >>= 32 - BIT;						\
2082a1ad637SFrançois Tigeot 			_PCM_WRITE_##SIGN##BIT##_##ENDIAN(dst, v);		\
2092a1ad637SFrançois Tigeot 			dst += PCM_##BIT##_BPS;					\
2102a1ad637SFrançois Tigeot 			i++;							\
2112a1ad637SFrançois Tigeot 		} while (--j != 0);						\
2122a1ad637SFrançois Tigeot 	} while (--count != 0);							\
2132a1ad637SFrançois Tigeot }
2142a1ad637SFrançois Tigeot 
2152a1ad637SFrançois Tigeot #if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
2162a1ad637SFrançois Tigeot FEEDEQ_DECLARE(S, 16, LE)
2172a1ad637SFrançois Tigeot FEEDEQ_DECLARE(S, 32, LE)
2182a1ad637SFrançois Tigeot #endif
2192a1ad637SFrançois Tigeot #if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
2202a1ad637SFrançois Tigeot FEEDEQ_DECLARE(S, 16, BE)
2212a1ad637SFrançois Tigeot FEEDEQ_DECLARE(S, 32, BE)
2222a1ad637SFrançois Tigeot #endif
2232a1ad637SFrançois Tigeot #ifdef SND_FEEDER_MULTIFORMAT
2242a1ad637SFrançois Tigeot FEEDEQ_DECLARE(S,  8, NE)
2252a1ad637SFrançois Tigeot FEEDEQ_DECLARE(S, 24, LE)
2262a1ad637SFrançois Tigeot FEEDEQ_DECLARE(S, 24, BE)
2272a1ad637SFrançois Tigeot FEEDEQ_DECLARE(U,  8, NE)
2282a1ad637SFrançois Tigeot FEEDEQ_DECLARE(U, 16, LE)
2292a1ad637SFrançois Tigeot FEEDEQ_DECLARE(U, 24, LE)
2302a1ad637SFrançois Tigeot FEEDEQ_DECLARE(U, 32, LE)
2312a1ad637SFrançois Tigeot FEEDEQ_DECLARE(U, 16, BE)
2322a1ad637SFrançois Tigeot FEEDEQ_DECLARE(U, 24, BE)
2332a1ad637SFrançois Tigeot FEEDEQ_DECLARE(U, 32, BE)
2342a1ad637SFrançois Tigeot #endif
2352a1ad637SFrançois Tigeot 
2362a1ad637SFrançois Tigeot #define FEEDEQ_ENTRY(SIGN, BIT, ENDIAN)					\
2372a1ad637SFrançois Tigeot 	{								\
2382a1ad637SFrançois Tigeot 		AFMT_##SIGN##BIT##_##ENDIAN,				\
2392a1ad637SFrançois Tigeot 		feed_eq_biquad_##SIGN##BIT##ENDIAN			\
2402a1ad637SFrançois Tigeot 	}
2412a1ad637SFrançois Tigeot 
2422a1ad637SFrançois Tigeot 
2432a1ad637SFrançois Tigeot static const struct {
2442a1ad637SFrançois Tigeot 	uint32_t format;
2452a1ad637SFrançois Tigeot 	feed_eq_t biquad;
2462a1ad637SFrançois Tigeot } feed_eq_biquad_tab[] = {
2472a1ad637SFrançois Tigeot #if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
2482a1ad637SFrançois Tigeot 	FEEDEQ_ENTRY(S, 16, LE),
2492a1ad637SFrançois Tigeot 	FEEDEQ_ENTRY(S, 32, LE),
2502a1ad637SFrançois Tigeot #endif
2512a1ad637SFrançois Tigeot #if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
2522a1ad637SFrançois Tigeot 	FEEDEQ_ENTRY(S, 16, BE),
2532a1ad637SFrançois Tigeot 	FEEDEQ_ENTRY(S, 32, BE),
2542a1ad637SFrançois Tigeot #endif
2552a1ad637SFrançois Tigeot #ifdef SND_FEEDER_MULTIFORMAT
2562a1ad637SFrançois Tigeot 	FEEDEQ_ENTRY(S,  8, NE),
2572a1ad637SFrançois Tigeot 	FEEDEQ_ENTRY(S, 24, LE),
2582a1ad637SFrançois Tigeot 	FEEDEQ_ENTRY(S, 24, BE),
2592a1ad637SFrançois Tigeot 	FEEDEQ_ENTRY(U,  8, NE),
2602a1ad637SFrançois Tigeot 	FEEDEQ_ENTRY(U, 16, LE),
2612a1ad637SFrançois Tigeot 	FEEDEQ_ENTRY(U, 24, LE),
2622a1ad637SFrançois Tigeot 	FEEDEQ_ENTRY(U, 32, LE),
2632a1ad637SFrançois Tigeot 	FEEDEQ_ENTRY(U, 16, BE),
2642a1ad637SFrançois Tigeot 	FEEDEQ_ENTRY(U, 24, BE),
2652a1ad637SFrançois Tigeot 	FEEDEQ_ENTRY(U, 32, BE)
2662a1ad637SFrançois Tigeot #endif
2672a1ad637SFrançois Tigeot };
2682a1ad637SFrançois Tigeot 
2692a1ad637SFrançois Tigeot #define FEEDEQ_BIQUAD_TAB_SIZE						\
2702a1ad637SFrançois Tigeot 	((int32_t)(sizeof(feed_eq_biquad_tab) / sizeof(feed_eq_biquad_tab[0])))
2712a1ad637SFrançois Tigeot 
2722a1ad637SFrançois Tigeot static struct feed_eq_coeff *
feed_eq_coeff_rate(uint32_t rate)2732a1ad637SFrançois Tigeot feed_eq_coeff_rate(uint32_t rate)
2742a1ad637SFrançois Tigeot {
2752a1ad637SFrançois Tigeot 	uint32_t spd, threshold;
2762a1ad637SFrançois Tigeot 	int i;
2772a1ad637SFrançois Tigeot 
2782a1ad637SFrançois Tigeot 	if (rate < FEEDEQ_RATE_MIN || rate > FEEDEQ_RATE_MAX)
2792a1ad637SFrançois Tigeot 		return (NULL);
2802a1ad637SFrançois Tigeot 
2812a1ad637SFrançois Tigeot 	/*
2822a1ad637SFrançois Tigeot 	 * Not all rates are supported. Choose the best rate that we can to
2832a1ad637SFrançois Tigeot 	 * allow 'sloppy' conversion. Good enough for naive listeners.
2842a1ad637SFrançois Tigeot 	 */
2852a1ad637SFrançois Tigeot 	for (i = 0; i < FEEDEQ_TAB_SIZE; i++) {
2862a1ad637SFrançois Tigeot 		spd = feed_eq_tab[i].rate;
2872a1ad637SFrançois Tigeot 		threshold = spd + ((i < (FEEDEQ_TAB_SIZE - 1) &&
2882a1ad637SFrançois Tigeot 		    feed_eq_tab[i + 1].rate > spd) ?
2892a1ad637SFrançois Tigeot 		    ((feed_eq_tab[i + 1].rate - spd) >> 1) : 0);
2902a1ad637SFrançois Tigeot 		if (rate == spd ||
2912a1ad637SFrançois Tigeot 		    (feeder_eq_exact_rate == 0 && rate <= threshold))
2922a1ad637SFrançois Tigeot 			return (feed_eq_tab[i].coeff);
2932a1ad637SFrançois Tigeot 	}
2942a1ad637SFrançois Tigeot 
2952a1ad637SFrançois Tigeot 	return (NULL);
2962a1ad637SFrançois Tigeot }
2972a1ad637SFrançois Tigeot 
2982a1ad637SFrançois Tigeot int
feeder_eq_validrate(uint32_t rate)2992a1ad637SFrançois Tigeot feeder_eq_validrate(uint32_t rate)
3002a1ad637SFrançois Tigeot {
3012a1ad637SFrançois Tigeot 
3022a1ad637SFrançois Tigeot 	if (feed_eq_coeff_rate(rate) != NULL)
3032a1ad637SFrançois Tigeot 		return (1);
3042a1ad637SFrançois Tigeot 
3052a1ad637SFrançois Tigeot 	return (0);
3062a1ad637SFrançois Tigeot }
3072a1ad637SFrançois Tigeot 
3082a1ad637SFrançois Tigeot static void
feed_eq_reset(struct feed_eq_info * info)3092a1ad637SFrançois Tigeot feed_eq_reset(struct feed_eq_info *info)
3102a1ad637SFrançois Tigeot {
3112a1ad637SFrançois Tigeot 	uint32_t i;
3122a1ad637SFrançois Tigeot 
3132a1ad637SFrançois Tigeot 	for (i = 0; i < info->channels; i++) {
3142a1ad637SFrançois Tigeot 		info->treble.i1[i] = 0;
3152a1ad637SFrançois Tigeot 		info->treble.i2[i] = 0;
3162a1ad637SFrançois Tigeot 		info->treble.o1[i] = 0;
3172a1ad637SFrançois Tigeot 		info->treble.o2[i] = 0;
3182a1ad637SFrançois Tigeot 		info->bass.i1[i] = 0;
3192a1ad637SFrançois Tigeot 		info->bass.i2[i] = 0;
3202a1ad637SFrançois Tigeot 		info->bass.o1[i] = 0;
3212a1ad637SFrançois Tigeot 		info->bass.o2[i] = 0;
3222a1ad637SFrançois Tigeot 	}
3232a1ad637SFrançois Tigeot }
3242a1ad637SFrançois Tigeot 
3252a1ad637SFrançois Tigeot static int
feed_eq_setup(struct feed_eq_info * info)3262a1ad637SFrançois Tigeot feed_eq_setup(struct feed_eq_info *info)
3272a1ad637SFrançois Tigeot {
3282a1ad637SFrançois Tigeot 
3292a1ad637SFrançois Tigeot 	info->coeff = feed_eq_coeff_rate(info->rate);
3302a1ad637SFrançois Tigeot 	if (info->coeff == NULL)
3312a1ad637SFrançois Tigeot 		return (EINVAL);
3322a1ad637SFrançois Tigeot 
3332a1ad637SFrançois Tigeot 	feed_eq_reset(info);
3342a1ad637SFrançois Tigeot 
3352a1ad637SFrançois Tigeot 	return (0);
3362a1ad637SFrançois Tigeot }
3372a1ad637SFrançois Tigeot 
3382a1ad637SFrançois Tigeot static int
feed_eq_init(struct pcm_feeder * f)3392a1ad637SFrançois Tigeot feed_eq_init(struct pcm_feeder *f)
3402a1ad637SFrançois Tigeot {
3412a1ad637SFrançois Tigeot 	struct feed_eq_info *info;
3422a1ad637SFrançois Tigeot 	feed_eq_t biquad_op;
3432a1ad637SFrançois Tigeot 	int i;
3442a1ad637SFrançois Tigeot 
3452a1ad637SFrançois Tigeot 	if (f->desc->in != f->desc->out)
3462a1ad637SFrançois Tigeot 		return (EINVAL);
3472a1ad637SFrançois Tigeot 
3482a1ad637SFrançois Tigeot 	biquad_op = NULL;
3492a1ad637SFrançois Tigeot 
3502a1ad637SFrançois Tigeot 	for (i = 0; i < FEEDEQ_BIQUAD_TAB_SIZE && biquad_op == NULL; i++) {
3512a1ad637SFrançois Tigeot 		if (AFMT_ENCODING(f->desc->in) == feed_eq_biquad_tab[i].format)
3522a1ad637SFrançois Tigeot 			biquad_op = feed_eq_biquad_tab[i].biquad;
3532a1ad637SFrançois Tigeot 	}
3542a1ad637SFrançois Tigeot 
3552a1ad637SFrançois Tigeot 	if (biquad_op == NULL)
3562a1ad637SFrançois Tigeot 		return (EINVAL);
3572a1ad637SFrançois Tigeot 
3584e8e900cSMatthew Dillon 	info = kmalloc(sizeof(*info), M_DEVBUF, M_WAITOK | M_ZERO);
3592a1ad637SFrançois Tigeot 	if (info == NULL)
3602a1ad637SFrançois Tigeot 		return (ENOMEM);
3612a1ad637SFrançois Tigeot 
3622a1ad637SFrançois Tigeot 	info->channels = AFMT_CHANNEL(f->desc->in);
3632a1ad637SFrançois Tigeot 	info->align = info->channels * AFMT_BPS(f->desc->in);
3642a1ad637SFrançois Tigeot 
3652a1ad637SFrançois Tigeot 	info->rate = FEEDEQ_RATE_MIN;
3662a1ad637SFrançois Tigeot 	info->treble.gain = FEEDEQ_L2GAIN(50);
3672a1ad637SFrançois Tigeot 	info->bass.gain = FEEDEQ_L2GAIN(50);
3682a1ad637SFrançois Tigeot 	info->preamp = FEEDEQ_PREAMP2IDX(FEEDEQ_PREAMP_DEFAULT);
3692a1ad637SFrançois Tigeot 	info->state = FEEDEQ_UNKNOWN;
3702a1ad637SFrançois Tigeot 
3712a1ad637SFrançois Tigeot 	info->biquad = biquad_op;
3722a1ad637SFrançois Tigeot 
3732a1ad637SFrançois Tigeot 	f->data = info;
3742a1ad637SFrançois Tigeot 
3752a1ad637SFrançois Tigeot 	return (feed_eq_setup(info));
3762a1ad637SFrançois Tigeot }
3772a1ad637SFrançois Tigeot 
3782a1ad637SFrançois Tigeot static int
feed_eq_set(struct pcm_feeder * f,int what,int value)3792a1ad637SFrançois Tigeot feed_eq_set(struct pcm_feeder *f, int what, int value)
3802a1ad637SFrançois Tigeot {
3812a1ad637SFrançois Tigeot 	struct feed_eq_info *info;
3822a1ad637SFrançois Tigeot 
3832a1ad637SFrançois Tigeot 	info = f->data;
3842a1ad637SFrançois Tigeot 
3852a1ad637SFrançois Tigeot 	switch (what) {
3862a1ad637SFrançois Tigeot 	case FEEDEQ_CHANNELS:
3872a1ad637SFrançois Tigeot 		if (value < SND_CHN_MIN || value > SND_CHN_MAX)
3882a1ad637SFrançois Tigeot 			return (EINVAL);
3892a1ad637SFrançois Tigeot 		info->channels = (uint32_t)value;
3902a1ad637SFrançois Tigeot 		info->align = info->channels * AFMT_BPS(f->desc->in);
3912a1ad637SFrançois Tigeot 		feed_eq_reset(info);
3922a1ad637SFrançois Tigeot 		break;
3932a1ad637SFrançois Tigeot 	case FEEDEQ_RATE:
3942a1ad637SFrançois Tigeot 		if (feeder_eq_validrate(value) == 0)
3952a1ad637SFrançois Tigeot 			return (EINVAL);
3962a1ad637SFrançois Tigeot 		info->rate = (uint32_t)value;
3972a1ad637SFrançois Tigeot 		if (info->state == FEEDEQ_UNKNOWN)
3982a1ad637SFrançois Tigeot 			info->state = FEEDEQ_ENABLE;
3992a1ad637SFrançois Tigeot 		return (feed_eq_setup(info));
4002a1ad637SFrançois Tigeot 		break;
4012a1ad637SFrançois Tigeot 	case FEEDEQ_TREBLE:
4022a1ad637SFrançois Tigeot 	case FEEDEQ_BASS:
4032a1ad637SFrançois Tigeot 		if (value < 0 || value > 100)
4042a1ad637SFrançois Tigeot 			return (EINVAL);
4052a1ad637SFrançois Tigeot 		if (what == FEEDEQ_TREBLE)
4062a1ad637SFrançois Tigeot 			info->treble.gain = FEEDEQ_L2GAIN(value);
4072a1ad637SFrançois Tigeot 		else
4082a1ad637SFrançois Tigeot 			info->bass.gain = FEEDEQ_L2GAIN(value);
4092a1ad637SFrançois Tigeot 		break;
4102a1ad637SFrançois Tigeot 	case FEEDEQ_PREAMP:
4112a1ad637SFrançois Tigeot 		if (value < FEEDEQ_PREAMP_MIN || value > FEEDEQ_PREAMP_MAX)
4122a1ad637SFrançois Tigeot 			return (EINVAL);
4132a1ad637SFrançois Tigeot 		info->preamp = FEEDEQ_PREAMP2IDX(value);
4142a1ad637SFrançois Tigeot 		break;
4152a1ad637SFrançois Tigeot 	case FEEDEQ_STATE:
4162a1ad637SFrançois Tigeot 		if (!(value == FEEDEQ_BYPASS || value == FEEDEQ_ENABLE ||
4172a1ad637SFrançois Tigeot 		    value == FEEDEQ_DISABLE))
4182a1ad637SFrançois Tigeot 			return (EINVAL);
4192a1ad637SFrançois Tigeot 		info->state = value;
4202a1ad637SFrançois Tigeot 		feed_eq_reset(info);
4212a1ad637SFrançois Tigeot 		break;
4222a1ad637SFrançois Tigeot 	default:
4232a1ad637SFrançois Tigeot 		return (EINVAL);
4242a1ad637SFrançois Tigeot 		break;
4252a1ad637SFrançois Tigeot 	}
4262a1ad637SFrançois Tigeot 
4272a1ad637SFrançois Tigeot 	return (0);
4282a1ad637SFrançois Tigeot }
4292a1ad637SFrançois Tigeot 
4302a1ad637SFrançois Tigeot static int
feed_eq_free(struct pcm_feeder * f)4312a1ad637SFrançois Tigeot feed_eq_free(struct pcm_feeder *f)
4322a1ad637SFrançois Tigeot {
4332a1ad637SFrançois Tigeot 	struct feed_eq_info *info;
4342a1ad637SFrançois Tigeot 
4352a1ad637SFrançois Tigeot 	info = f->data;
4362a1ad637SFrançois Tigeot 	if (info != NULL)
43767931cc4SFrançois Tigeot 		kfree(info, M_DEVBUF);
4382a1ad637SFrançois Tigeot 
4392a1ad637SFrançois Tigeot 	f->data = NULL;
4402a1ad637SFrançois Tigeot 
4412a1ad637SFrançois Tigeot 	return (0);
4422a1ad637SFrançois Tigeot }
4432a1ad637SFrançois Tigeot 
4442a1ad637SFrançois Tigeot static int
feed_eq_feed(struct pcm_feeder * f,struct pcm_channel * c,uint8_t * b,uint32_t count,void * source)4452a1ad637SFrançois Tigeot feed_eq_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
4462a1ad637SFrançois Tigeot     uint32_t count, void *source)
4472a1ad637SFrançois Tigeot {
4482a1ad637SFrançois Tigeot 	struct feed_eq_info *info;
4492a1ad637SFrançois Tigeot 	uint32_t j;
4502a1ad637SFrançois Tigeot 	uint8_t *dst;
4512a1ad637SFrançois Tigeot 
4522a1ad637SFrançois Tigeot 	info = f->data;
4532a1ad637SFrançois Tigeot 
4542a1ad637SFrançois Tigeot 	/*
4552a1ad637SFrançois Tigeot 	 * 3 major states:
4562a1ad637SFrançois Tigeot 	 * 	FEEDEQ_BYPASS  - Bypass entirely, nothing happened.
4572a1ad637SFrançois Tigeot 	 *      FEEDEQ_ENABLE  - Preamp+biquad filtering.
4582a1ad637SFrançois Tigeot 	 *      FEEDEQ_DISABLE - Preamp only.
4592a1ad637SFrançois Tigeot 	 */
4602a1ad637SFrançois Tigeot 	if (info->state == FEEDEQ_BYPASS)
4612a1ad637SFrançois Tigeot 		return (FEEDER_FEED(f->source, c, b, count, source));
4622a1ad637SFrançois Tigeot 
4632a1ad637SFrançois Tigeot 	dst = b;
4642a1ad637SFrançois Tigeot 	count = SND_FXROUND(count, info->align);
4652a1ad637SFrançois Tigeot 
4662a1ad637SFrançois Tigeot 	do {
4672a1ad637SFrançois Tigeot 		if (count < info->align)
4682a1ad637SFrançois Tigeot 			break;
4692a1ad637SFrançois Tigeot 
4702a1ad637SFrançois Tigeot 		j = SND_FXDIV(FEEDER_FEED(f->source, c, dst, count, source),
4712a1ad637SFrançois Tigeot 		    info->align);
4722a1ad637SFrançois Tigeot 		if (j == 0)
4732a1ad637SFrançois Tigeot 			break;
4742a1ad637SFrançois Tigeot 
4752a1ad637SFrançois Tigeot 		info->biquad(info, dst, j);
4762a1ad637SFrançois Tigeot 
4772a1ad637SFrançois Tigeot 		j *= info->align;
4782a1ad637SFrançois Tigeot 		dst += j;
4792a1ad637SFrançois Tigeot 		count -= j;
4802a1ad637SFrançois Tigeot 
4812a1ad637SFrançois Tigeot 	} while (count != 0);
4822a1ad637SFrançois Tigeot 
4832a1ad637SFrançois Tigeot 	return (dst - b);
4842a1ad637SFrançois Tigeot }
4852a1ad637SFrançois Tigeot 
4862a1ad637SFrançois Tigeot static struct pcm_feederdesc feeder_eq_desc[] = {
4872a1ad637SFrançois Tigeot 	{ FEEDER_EQ, 0, 0, 0, 0 },
4882a1ad637SFrançois Tigeot 	{ 0, 0, 0, 0, 0 }
4892a1ad637SFrançois Tigeot };
4902a1ad637SFrançois Tigeot 
4912a1ad637SFrançois Tigeot static kobj_method_t feeder_eq_methods[] = {
4922a1ad637SFrançois Tigeot 	KOBJMETHOD(feeder_init,		feed_eq_init),
4932a1ad637SFrançois Tigeot 	KOBJMETHOD(feeder_free,		feed_eq_free),
4942a1ad637SFrançois Tigeot 	KOBJMETHOD(feeder_set,		feed_eq_set),
4952a1ad637SFrançois Tigeot 	KOBJMETHOD(feeder_feed,		feed_eq_feed),
4962a1ad637SFrançois Tigeot 	KOBJMETHOD_END
4972a1ad637SFrançois Tigeot };
4982a1ad637SFrançois Tigeot 
4992a1ad637SFrançois Tigeot FEEDER_DECLARE(feeder_eq, NULL);
5002a1ad637SFrançois Tigeot 
5012a1ad637SFrançois Tigeot static int32_t
feed_eq_scan_preamp_arg(const char * s)5022a1ad637SFrançois Tigeot feed_eq_scan_preamp_arg(const char *s)
5032a1ad637SFrançois Tigeot {
5042a1ad637SFrançois Tigeot 	int r, i, f;
5052a1ad637SFrançois Tigeot 	size_t len;
5062a1ad637SFrançois Tigeot 	char buf[32];
5072a1ad637SFrançois Tigeot 
5082a1ad637SFrançois Tigeot 	bzero(buf, sizeof(buf));
5092a1ad637SFrançois Tigeot 
5102a1ad637SFrançois Tigeot 	/* XXX kind of ugly, but works for now.. */
5112a1ad637SFrançois Tigeot 
51267931cc4SFrançois Tigeot 	r = ksscanf(s, "%d.%d", &i, &f);
5132a1ad637SFrançois Tigeot 
5142a1ad637SFrançois Tigeot 	if (r == 1 && !(i < FEEDEQ_PREAMP_IMIN || i > FEEDEQ_PREAMP_IMAX)) {
51567931cc4SFrançois Tigeot 		ksnprintf(buf, sizeof(buf), "%c%d",
5162a1ad637SFrançois Tigeot 		    FEEDEQ_PREAMP_SIGNMARK(i), abs(i));
5172a1ad637SFrançois Tigeot 		f = 0;
5182a1ad637SFrançois Tigeot 	} else if (r == 2 &&
5192a1ad637SFrançois Tigeot 	    !(i < FEEDEQ_PREAMP_IMIN || i > FEEDEQ_PREAMP_IMAX ||
5202a1ad637SFrançois Tigeot 	    f < FEEDEQ_PREAMP_FMIN || f > FEEDEQ_PREAMP_FMAX))
52167931cc4SFrançois Tigeot 		ksnprintf(buf, sizeof(buf), "%c%d.%d",
5222a1ad637SFrançois Tigeot 		    FEEDEQ_PREAMP_SIGNMARK(i), abs(i), f);
5232a1ad637SFrançois Tigeot 	else
5242a1ad637SFrançois Tigeot 		return (FEEDEQ_PREAMP_INVALID);
5252a1ad637SFrançois Tigeot 
5262a1ad637SFrançois Tigeot 	len = strlen(s);
5272a1ad637SFrançois Tigeot 	if (len > 2 && strcasecmp(s + len - 2, "dB") == 0)
5282a1ad637SFrançois Tigeot 		strlcat(buf, "dB", sizeof(buf));
5292a1ad637SFrançois Tigeot 
5302a1ad637SFrançois Tigeot 	if (i == 0 && *s == '-')
5312a1ad637SFrançois Tigeot 		*buf = '-';
5322a1ad637SFrançois Tigeot 
5332a1ad637SFrançois Tigeot 	if (strcasecmp(buf + ((*s >= '0' && *s <= '9') ? 1 : 0), s) != 0)
5342a1ad637SFrançois Tigeot 		return (FEEDEQ_PREAMP_INVALID);
5352a1ad637SFrançois Tigeot 
5362a1ad637SFrançois Tigeot 	while ((f / FEEDEQ_GAIN_DIV) > 0)
5372a1ad637SFrançois Tigeot 		f /= FEEDEQ_GAIN_DIV;
5382a1ad637SFrançois Tigeot 
5392a1ad637SFrançois Tigeot 	return (((i < 0 || *buf == '-') ? -1 : 1) * FEEDEQ_IF2PREAMP(i, f));
5402a1ad637SFrançois Tigeot }
5412a1ad637SFrançois Tigeot 
5422a1ad637SFrançois Tigeot #ifdef _KERNEL
5432a1ad637SFrançois Tigeot static int
sysctl_dev_pcm_eq(SYSCTL_HANDLER_ARGS)5442a1ad637SFrançois Tigeot sysctl_dev_pcm_eq(SYSCTL_HANDLER_ARGS)
5452a1ad637SFrançois Tigeot {
5462a1ad637SFrançois Tigeot 	struct snddev_info *d;
5472a1ad637SFrançois Tigeot 	struct pcm_channel *c;
5482a1ad637SFrançois Tigeot 	struct pcm_feeder *f;
5492a1ad637SFrançois Tigeot 	int err, val, oval;
5502a1ad637SFrançois Tigeot 
5512a1ad637SFrançois Tigeot 	d = oidp->oid_arg1;
5522a1ad637SFrançois Tigeot 	if (!PCM_REGISTERED(d))
5532a1ad637SFrançois Tigeot 		return (ENODEV);
5542a1ad637SFrançois Tigeot 
5552a1ad637SFrançois Tigeot 	PCM_LOCK(d);
5562a1ad637SFrançois Tigeot 	PCM_WAIT(d);
5572a1ad637SFrançois Tigeot 	if (d->flags & SD_F_EQ_BYPASSED)
5582a1ad637SFrançois Tigeot 		val = 2;
5592a1ad637SFrançois Tigeot 	else if (d->flags & SD_F_EQ_ENABLED)
5602a1ad637SFrançois Tigeot 		val = 1;
5612a1ad637SFrançois Tigeot 	else
5622a1ad637SFrançois Tigeot 		val = 0;
5632a1ad637SFrançois Tigeot 	PCM_ACQUIRE(d);
5642a1ad637SFrançois Tigeot 	PCM_UNLOCK(d);
5652a1ad637SFrançois Tigeot 
5662a1ad637SFrançois Tigeot 	oval = val;
5672a1ad637SFrançois Tigeot 	err = sysctl_handle_int(oidp, &val, 0, req);
5682a1ad637SFrançois Tigeot 
5692a1ad637SFrançois Tigeot 	if (err == 0 && req->newptr != NULL && val != oval) {
5702a1ad637SFrançois Tigeot 		if (!(val == 0 || val == 1 || val == 2)) {
5712a1ad637SFrançois Tigeot 			PCM_RELEASE_QUICK(d);
5722a1ad637SFrançois Tigeot 			return (EINVAL);
5732a1ad637SFrançois Tigeot 		}
5742a1ad637SFrançois Tigeot 
5752a1ad637SFrançois Tigeot 		PCM_LOCK(d);
5762a1ad637SFrançois Tigeot 
5772a1ad637SFrançois Tigeot 		d->flags &= ~(SD_F_EQ_ENABLED | SD_F_EQ_BYPASSED);
5782a1ad637SFrançois Tigeot 		if (val == 2) {
5792a1ad637SFrançois Tigeot 			val = FEEDEQ_BYPASS;
5802a1ad637SFrançois Tigeot 			d->flags |= SD_F_EQ_BYPASSED;
5812a1ad637SFrançois Tigeot 		} else if (val == 1) {
5822a1ad637SFrançois Tigeot 			val = FEEDEQ_ENABLE;
5832a1ad637SFrançois Tigeot 			d->flags |= SD_F_EQ_ENABLED;
5842a1ad637SFrançois Tigeot 		} else
5852a1ad637SFrançois Tigeot 			val = FEEDEQ_DISABLE;
5862a1ad637SFrançois Tigeot 
5872a1ad637SFrançois Tigeot 		CHN_FOREACH(c, d, channels.pcm.busy) {
5882a1ad637SFrançois Tigeot 			CHN_LOCK(c);
5892a1ad637SFrançois Tigeot 			f = chn_findfeeder(c, FEEDER_EQ);
5902a1ad637SFrançois Tigeot 			if (f != NULL)
5912a1ad637SFrançois Tigeot 				(void)FEEDER_SET(f, FEEDEQ_STATE, val);
5922a1ad637SFrançois Tigeot 			CHN_UNLOCK(c);
5932a1ad637SFrançois Tigeot 		}
5942a1ad637SFrançois Tigeot 
5952a1ad637SFrançois Tigeot 		PCM_RELEASE(d);
5962a1ad637SFrançois Tigeot 		PCM_UNLOCK(d);
5972a1ad637SFrançois Tigeot 	} else
5982a1ad637SFrançois Tigeot 		PCM_RELEASE_QUICK(d);
5992a1ad637SFrançois Tigeot 
6002a1ad637SFrançois Tigeot 	return (err);
6012a1ad637SFrançois Tigeot }
6022a1ad637SFrançois Tigeot 
6032a1ad637SFrançois Tigeot static int
sysctl_dev_pcm_eq_preamp(SYSCTL_HANDLER_ARGS)6042a1ad637SFrançois Tigeot sysctl_dev_pcm_eq_preamp(SYSCTL_HANDLER_ARGS)
6052a1ad637SFrançois Tigeot {
6062a1ad637SFrançois Tigeot 	struct snddev_info *d;
6072a1ad637SFrançois Tigeot 	struct pcm_channel *c;
6082a1ad637SFrançois Tigeot 	struct pcm_feeder *f;
6092a1ad637SFrançois Tigeot 	int err, val, oval;
6102a1ad637SFrançois Tigeot 	char buf[32];
6112a1ad637SFrançois Tigeot 
6122a1ad637SFrançois Tigeot 	d = oidp->oid_arg1;
6132a1ad637SFrançois Tigeot 	if (!PCM_REGISTERED(d))
6142a1ad637SFrançois Tigeot 		return (ENODEV);
6152a1ad637SFrançois Tigeot 
6162a1ad637SFrançois Tigeot 	PCM_LOCK(d);
6172a1ad637SFrançois Tigeot 	PCM_WAIT(d);
6182a1ad637SFrançois Tigeot 	val = d->eqpreamp;
6192a1ad637SFrançois Tigeot 	bzero(buf, sizeof(buf));
62067931cc4SFrançois Tigeot 	(void)ksnprintf(buf, sizeof(buf), "%c%d.%ddB",
6212a1ad637SFrançois Tigeot 	    FEEDEQ_PREAMP_SIGNMARK(val), FEEDEQ_PREAMP_IPART(val),
6222a1ad637SFrançois Tigeot 	    FEEDEQ_PREAMP_FPART(val));
6232a1ad637SFrançois Tigeot 	PCM_ACQUIRE(d);
6242a1ad637SFrançois Tigeot 	PCM_UNLOCK(d);
6252a1ad637SFrançois Tigeot 
6262a1ad637SFrançois Tigeot 	oval = val;
6272a1ad637SFrançois Tigeot 	err = sysctl_handle_string(oidp, buf, sizeof(buf), req);
6282a1ad637SFrançois Tigeot 
6292a1ad637SFrançois Tigeot 	if (err == 0 && req->newptr != NULL) {
6302a1ad637SFrançois Tigeot 		val = feed_eq_scan_preamp_arg(buf);
6312a1ad637SFrançois Tigeot 		if (val == FEEDEQ_PREAMP_INVALID) {
6322a1ad637SFrançois Tigeot 			PCM_RELEASE_QUICK(d);
6332a1ad637SFrançois Tigeot 			return (EINVAL);
6342a1ad637SFrançois Tigeot 		}
6352a1ad637SFrançois Tigeot 
6362a1ad637SFrançois Tigeot 		PCM_LOCK(d);
6372a1ad637SFrançois Tigeot 
6382a1ad637SFrançois Tigeot 		if (val != oval) {
6392a1ad637SFrançois Tigeot 			if (val < FEEDEQ_PREAMP_MIN)
6402a1ad637SFrançois Tigeot 				val = FEEDEQ_PREAMP_MIN;
6412a1ad637SFrançois Tigeot 			else if (val > FEEDEQ_PREAMP_MAX)
6422a1ad637SFrançois Tigeot 				val = FEEDEQ_PREAMP_MAX;
6432a1ad637SFrançois Tigeot 
6442a1ad637SFrançois Tigeot 			d->eqpreamp = val;
6452a1ad637SFrançois Tigeot 
6462a1ad637SFrançois Tigeot 			CHN_FOREACH(c, d, channels.pcm.busy) {
6472a1ad637SFrançois Tigeot 				CHN_LOCK(c);
6482a1ad637SFrançois Tigeot 				f = chn_findfeeder(c, FEEDER_EQ);
6492a1ad637SFrançois Tigeot 				if (f != NULL)
6502a1ad637SFrançois Tigeot 					(void)FEEDER_SET(f, FEEDEQ_PREAMP, val);
6512a1ad637SFrançois Tigeot 				CHN_UNLOCK(c);
6522a1ad637SFrançois Tigeot 			}
6532a1ad637SFrançois Tigeot 
6542a1ad637SFrançois Tigeot 		}
6552a1ad637SFrançois Tigeot 
6562a1ad637SFrançois Tigeot 		PCM_RELEASE(d);
6572a1ad637SFrançois Tigeot 		PCM_UNLOCK(d);
6582a1ad637SFrançois Tigeot 	} else
6592a1ad637SFrançois Tigeot 		PCM_RELEASE_QUICK(d);
6602a1ad637SFrançois Tigeot 
6612a1ad637SFrançois Tigeot 	return (err);
6622a1ad637SFrançois Tigeot }
6632a1ad637SFrançois Tigeot 
6642a1ad637SFrançois Tigeot void
feeder_eq_initsys(device_t dev)6652a1ad637SFrançois Tigeot feeder_eq_initsys(device_t dev)
6662a1ad637SFrançois Tigeot {
6672a1ad637SFrançois Tigeot 	struct snddev_info *d;
6682a1ad637SFrançois Tigeot 	const char *preamp;
6692a1ad637SFrançois Tigeot 	char buf[64];
6702a1ad637SFrançois Tigeot 
6712a1ad637SFrançois Tigeot 	d = device_get_softc(dev);
6722a1ad637SFrançois Tigeot 
6732a1ad637SFrançois Tigeot 	if (!(resource_string_value(device_get_name(dev), device_get_unit(dev),
6742a1ad637SFrançois Tigeot 	    "eq_preamp", &preamp) == 0 &&
6752a1ad637SFrançois Tigeot 	    (d->eqpreamp = feed_eq_scan_preamp_arg(preamp)) !=
6762a1ad637SFrançois Tigeot 	    FEEDEQ_PREAMP_INVALID))
6772a1ad637SFrançois Tigeot 		d->eqpreamp = FEEDEQ_PREAMP_DEFAULT;
6782a1ad637SFrançois Tigeot 
6792a1ad637SFrançois Tigeot 	if (d->eqpreamp < FEEDEQ_PREAMP_MIN)
6802a1ad637SFrançois Tigeot 		d->eqpreamp = FEEDEQ_PREAMP_MIN;
6812a1ad637SFrançois Tigeot 	else if (d->eqpreamp > FEEDEQ_PREAMP_MAX)
6822a1ad637SFrançois Tigeot 		d->eqpreamp = FEEDEQ_PREAMP_MAX;
6832a1ad637SFrançois Tigeot 
6842a1ad637SFrançois Tigeot 	SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
6852a1ad637SFrançois Tigeot 	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
6862a1ad637SFrançois Tigeot 	    "eq", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d),
6872a1ad637SFrançois Tigeot 	    sysctl_dev_pcm_eq, "I",
6882a1ad637SFrançois Tigeot 	    "Bass/Treble Equalizer (0=disable, 1=enable, 2=bypass)");
6892a1ad637SFrançois Tigeot 
6902a1ad637SFrançois Tigeot 	bzero(buf, sizeof(buf));
6912a1ad637SFrançois Tigeot 
69267931cc4SFrançois Tigeot 	(void)ksnprintf(buf, sizeof(buf), "Bass/Treble Equalizer Preamp "
6932a1ad637SFrançois Tigeot 	    "(-/+ %d.0dB , %d.%ddB step)",
6942a1ad637SFrançois Tigeot 	    FEEDEQ_GAIN_MAX, FEEDEQ_GAIN_STEP / FEEDEQ_GAIN_DIV,
695*ed183f8cSSascha Wildner 	    FEEDEQ_GAIN_STEP - rounddown(FEEDEQ_GAIN_STEP, FEEDEQ_GAIN_DIV));
6962a1ad637SFrançois Tigeot 
6972a1ad637SFrançois Tigeot 	SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
6982a1ad637SFrançois Tigeot 	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
6992a1ad637SFrançois Tigeot 	    "eq_preamp", CTLTYPE_STRING | CTLFLAG_RW, d, sizeof(d),
7002a1ad637SFrançois Tigeot 	    sysctl_dev_pcm_eq_preamp, "A", buf);
7012a1ad637SFrançois Tigeot }
7022a1ad637SFrançois Tigeot #endif
703