feeder_eq.c revision 330897
1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2008-2009 Ariff Abdullah <ariff@FreeBSD.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29/*
30 * feeder_eq: Parametric (compile time) Software Equalizer. Though accidental,
31 *            it proves good enough for educational and general consumption.
32 *
33 * "Cookbook formulae for audio EQ biquad filter coefficients"
34 *    by Robert Bristow-Johnson  <rbj@audioimagination.com>
35 *    -  http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
36 */
37
38#ifdef _KERNEL
39#ifdef HAVE_KERNEL_OPTION_HEADERS
40#include "opt_snd.h"
41#endif
42#include <dev/sound/pcm/sound.h>
43#include <dev/sound/pcm/pcm.h>
44#include "feeder_if.h"
45
46#define SND_USE_FXDIV
47#include "snd_fxdiv_gen.h"
48
49SND_DECLARE_FILE("$FreeBSD: stable/11/sys/dev/sound/pcm/feeder_eq.c 330897 2018-03-14 03:19:51Z eadler $");
50#endif
51
52#include "feeder_eq_gen.h"
53
54#define FEEDEQ_LEVELS							\
55	(((FEEDEQ_GAIN_MAX - FEEDEQ_GAIN_MIN) *				\
56	(FEEDEQ_GAIN_DIV / FEEDEQ_GAIN_STEP)) + 1)
57
58#define FEEDEQ_L2GAIN(v)						\
59	((int)min(((v) * FEEDEQ_LEVELS) / 100, FEEDEQ_LEVELS - 1))
60
61#define FEEDEQ_PREAMP_IPART(x)		(abs(x) >> FEEDEQ_GAIN_SHIFT)
62#define FEEDEQ_PREAMP_FPART(x)		(abs(x) & FEEDEQ_GAIN_FMASK)
63#define FEEDEQ_PREAMP_SIGNVAL(x)	((x) < 0 ? -1 : 1)
64#define FEEDEQ_PREAMP_SIGNMARK(x)	(((x) < 0) ? '-' : '+')
65
66#define FEEDEQ_PREAMP_IMIN	-192
67#define FEEDEQ_PREAMP_IMAX	192
68#define FEEDEQ_PREAMP_FMIN	0
69#define FEEDEQ_PREAMP_FMAX	9
70
71#define FEEDEQ_PREAMP_INVALID	INT_MAX
72
73#define FEEDEQ_IF2PREAMP(i, f)						\
74	((abs(i) << FEEDEQ_GAIN_SHIFT) |				\
75	(((abs(f) / FEEDEQ_GAIN_STEP) * FEEDEQ_GAIN_STEP) &		\
76	FEEDEQ_GAIN_FMASK))
77
78#define FEEDEQ_PREAMP_MIN						\
79	(FEEDEQ_PREAMP_SIGNVAL(FEEDEQ_GAIN_MIN) *			\
80	FEEDEQ_IF2PREAMP(FEEDEQ_GAIN_MIN, 0))
81
82#define FEEDEQ_PREAMP_MAX						\
83	(FEEDEQ_PREAMP_SIGNVAL(FEEDEQ_GAIN_MAX) *			\
84	FEEDEQ_IF2PREAMP(FEEDEQ_GAIN_MAX, 0))
85
86#define FEEDEQ_PREAMP_DEFAULT	FEEDEQ_IF2PREAMP(0, 0)
87
88#define FEEDEQ_PREAMP2IDX(v)						\
89	((int32_t)((FEEDEQ_GAIN_MAX * (FEEDEQ_GAIN_DIV /		\
90	FEEDEQ_GAIN_STEP)) + (FEEDEQ_PREAMP_SIGNVAL(v) *		\
91	FEEDEQ_PREAMP_IPART(v) * (FEEDEQ_GAIN_DIV /			\
92	FEEDEQ_GAIN_STEP)) + (FEEDEQ_PREAMP_SIGNVAL(v) *		\
93	(FEEDEQ_PREAMP_FPART(v) / FEEDEQ_GAIN_STEP))))
94
95static int feeder_eq_exact_rate = 0;
96
97#ifdef _KERNEL
98static char feeder_eq_presets[] = FEEDER_EQ_PRESETS;
99SYSCTL_STRING(_hw_snd, OID_AUTO, feeder_eq_presets, CTLFLAG_RD,
100    &feeder_eq_presets, 0, "compile-time eq presets");
101
102SYSCTL_INT(_hw_snd, OID_AUTO, feeder_eq_exact_rate, CTLFLAG_RWTUN,
103    &feeder_eq_exact_rate, 0, "force exact rate validation");
104#endif
105
106struct feed_eq_info;
107
108typedef void (*feed_eq_t)(struct feed_eq_info *, uint8_t *, uint32_t);
109
110struct feed_eq_tone {
111	intpcm_t o1[SND_CHN_MAX];
112	intpcm_t o2[SND_CHN_MAX];
113	intpcm_t i1[SND_CHN_MAX];
114	intpcm_t i2[SND_CHN_MAX];
115	int gain;
116};
117
118struct feed_eq_info {
119	struct feed_eq_tone treble;
120	struct feed_eq_tone bass;
121	struct feed_eq_coeff *coeff;
122	feed_eq_t biquad;
123	uint32_t channels;
124	uint32_t rate;
125	uint32_t align;
126	int32_t preamp;
127	int state;
128};
129
130#if !defined(_KERNEL) && defined(FEEDEQ_ERR_CLIP)
131#define FEEDEQ_ERR_CLIP_CHECK(t, v)	do {				\
132	if ((v) < PCM_S32_MIN || (v) > PCM_S32_MAX)			\
133		errx(1, "\n\n%s(): ["#t"] Sample clipping: %jd\n",	\
134		    __func__, (intmax_t)(v));				\
135} while (0)
136#else
137#define FEEDEQ_ERR_CLIP_CHECK(...)
138#endif
139
140#define FEEDEQ_CLAMP(v)		(((v) > PCM_S32_MAX) ? PCM_S32_MAX :	\
141				(((v) < PCM_S32_MIN) ? PCM_S32_MIN :	\
142				  (v)))
143
144#define FEEDEQ_DECLARE(SIGN, BIT, ENDIAN)					\
145static void									\
146feed_eq_biquad_##SIGN##BIT##ENDIAN(struct feed_eq_info *info,			\
147    uint8_t *dst, uint32_t count)						\
148{										\
149	struct feed_eq_coeff_tone *treble, *bass;				\
150	intpcm64_t w;								\
151	intpcm_t v;								\
152	uint32_t i, j;								\
153	int32_t pmul, pshift;							\
154										\
155	pmul = feed_eq_preamp[info->preamp].mul;				\
156	pshift = feed_eq_preamp[info->preamp].shift;				\
157										\
158	if (info->state == FEEDEQ_DISABLE) {					\
159		j = count * info->channels;					\
160		dst += j * PCM_##BIT##_BPS;					\
161		do {								\
162			dst -= PCM_##BIT##_BPS;					\
163			v = _PCM_READ_##SIGN##BIT##_##ENDIAN(dst);		\
164			v = ((intpcm64_t)pmul * v) >> pshift;			\
165			_PCM_WRITE_##SIGN##BIT##_##ENDIAN(dst, v);		\
166		} while (--j != 0);						\
167										\
168		return;								\
169	}									\
170										\
171	treble = &(info->coeff[info->treble.gain].treble);			\
172	bass   = &(info->coeff[info->bass.gain].bass);				\
173										\
174	do {									\
175		i = 0;								\
176		j = info->channels;						\
177		do {								\
178			v = _PCM_READ_##SIGN##BIT##_##ENDIAN(dst);		\
179			v <<= 32 - BIT;						\
180			v = ((intpcm64_t)pmul * v) >> pshift;			\
181										\
182			w  = (intpcm64_t)v * treble->b0;			\
183			w += (intpcm64_t)info->treble.i1[i] * treble->b1;	\
184			w += (intpcm64_t)info->treble.i2[i] * treble->b2;	\
185			w -= (intpcm64_t)info->treble.o1[i] * treble->a1;	\
186			w -= (intpcm64_t)info->treble.o2[i] * treble->a2;	\
187			info->treble.i2[i] = info->treble.i1[i];		\
188			info->treble.i1[i] = v;					\
189			info->treble.o2[i] = info->treble.o1[i];		\
190			w >>= FEEDEQ_COEFF_SHIFT;				\
191			FEEDEQ_ERR_CLIP_CHECK(treble, w);			\
192			v = FEEDEQ_CLAMP(w);					\
193			info->treble.o1[i] = v;					\
194										\
195			w  = (intpcm64_t)v * bass->b0;				\
196			w += (intpcm64_t)info->bass.i1[i] * bass->b1;		\
197			w += (intpcm64_t)info->bass.i2[i] * bass->b2;		\
198			w -= (intpcm64_t)info->bass.o1[i] * bass->a1;		\
199			w -= (intpcm64_t)info->bass.o2[i] * bass->a2;		\
200			info->bass.i2[i] = info->bass.i1[i];			\
201			info->bass.i1[i] = v;					\
202			info->bass.o2[i] = info->bass.o1[i];			\
203			w >>= FEEDEQ_COEFF_SHIFT;				\
204			FEEDEQ_ERR_CLIP_CHECK(bass, w);				\
205			v = FEEDEQ_CLAMP(w);					\
206			info->bass.o1[i] = v;					\
207										\
208			v >>= 32 - BIT;						\
209			_PCM_WRITE_##SIGN##BIT##_##ENDIAN(dst, v);		\
210			dst += PCM_##BIT##_BPS;					\
211			i++;							\
212		} while (--j != 0);						\
213	} while (--count != 0);							\
214}
215
216#if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
217FEEDEQ_DECLARE(S, 16, LE)
218FEEDEQ_DECLARE(S, 32, LE)
219#endif
220#if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
221FEEDEQ_DECLARE(S, 16, BE)
222FEEDEQ_DECLARE(S, 32, BE)
223#endif
224#ifdef SND_FEEDER_MULTIFORMAT
225FEEDEQ_DECLARE(S,  8, NE)
226FEEDEQ_DECLARE(S, 24, LE)
227FEEDEQ_DECLARE(S, 24, BE)
228FEEDEQ_DECLARE(U,  8, NE)
229FEEDEQ_DECLARE(U, 16, LE)
230FEEDEQ_DECLARE(U, 24, LE)
231FEEDEQ_DECLARE(U, 32, LE)
232FEEDEQ_DECLARE(U, 16, BE)
233FEEDEQ_DECLARE(U, 24, BE)
234FEEDEQ_DECLARE(U, 32, BE)
235#endif
236
237#define FEEDEQ_ENTRY(SIGN, BIT, ENDIAN)					\
238	{								\
239		AFMT_##SIGN##BIT##_##ENDIAN,				\
240		feed_eq_biquad_##SIGN##BIT##ENDIAN			\
241	}
242
243
244static const struct {
245	uint32_t format;
246	feed_eq_t biquad;
247} feed_eq_biquad_tab[] = {
248#if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
249	FEEDEQ_ENTRY(S, 16, LE),
250	FEEDEQ_ENTRY(S, 32, LE),
251#endif
252#if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
253	FEEDEQ_ENTRY(S, 16, BE),
254	FEEDEQ_ENTRY(S, 32, BE),
255#endif
256#ifdef SND_FEEDER_MULTIFORMAT
257	FEEDEQ_ENTRY(S,  8, NE),
258	FEEDEQ_ENTRY(S, 24, LE),
259	FEEDEQ_ENTRY(S, 24, BE),
260	FEEDEQ_ENTRY(U,  8, NE),
261	FEEDEQ_ENTRY(U, 16, LE),
262	FEEDEQ_ENTRY(U, 24, LE),
263	FEEDEQ_ENTRY(U, 32, LE),
264	FEEDEQ_ENTRY(U, 16, BE),
265	FEEDEQ_ENTRY(U, 24, BE),
266	FEEDEQ_ENTRY(U, 32, BE)
267#endif
268};
269
270#define FEEDEQ_BIQUAD_TAB_SIZE						\
271	((int32_t)(sizeof(feed_eq_biquad_tab) / sizeof(feed_eq_biquad_tab[0])))
272
273static struct feed_eq_coeff *
274feed_eq_coeff_rate(uint32_t rate)
275{
276	uint32_t spd, threshold;
277	int i;
278
279	if (rate < FEEDEQ_RATE_MIN || rate > FEEDEQ_RATE_MAX)
280		return (NULL);
281
282	/*
283	 * Not all rates are supported. Choose the best rate that we can to
284	 * allow 'sloppy' conversion. Good enough for naive listeners.
285	 */
286	for (i = 0; i < FEEDEQ_TAB_SIZE; i++) {
287		spd = feed_eq_tab[i].rate;
288		threshold = spd + ((i < (FEEDEQ_TAB_SIZE - 1) &&
289		    feed_eq_tab[i + 1].rate > spd) ?
290		    ((feed_eq_tab[i + 1].rate - spd) >> 1) : 0);
291		if (rate == spd ||
292		    (feeder_eq_exact_rate == 0 && rate <= threshold))
293			return (feed_eq_tab[i].coeff);
294	}
295
296	return (NULL);
297}
298
299int
300feeder_eq_validrate(uint32_t rate)
301{
302
303	if (feed_eq_coeff_rate(rate) != NULL)
304		return (1);
305
306	return (0);
307}
308
309static void
310feed_eq_reset(struct feed_eq_info *info)
311{
312	uint32_t i;
313
314	for (i = 0; i < info->channels; i++) {
315		info->treble.i1[i] = 0;
316		info->treble.i2[i] = 0;
317		info->treble.o1[i] = 0;
318		info->treble.o2[i] = 0;
319		info->bass.i1[i] = 0;
320		info->bass.i2[i] = 0;
321		info->bass.o1[i] = 0;
322		info->bass.o2[i] = 0;
323	}
324}
325
326static int
327feed_eq_setup(struct feed_eq_info *info)
328{
329
330	info->coeff = feed_eq_coeff_rate(info->rate);
331	if (info->coeff == NULL)
332		return (EINVAL);
333
334	feed_eq_reset(info);
335
336	return (0);
337}
338
339static int
340feed_eq_init(struct pcm_feeder *f)
341{
342	struct feed_eq_info *info;
343	feed_eq_t biquad_op;
344	int i;
345
346	if (f->desc->in != f->desc->out)
347		return (EINVAL);
348
349	biquad_op = NULL;
350
351	for (i = 0; i < FEEDEQ_BIQUAD_TAB_SIZE && biquad_op == NULL; i++) {
352		if (AFMT_ENCODING(f->desc->in) == feed_eq_biquad_tab[i].format)
353			biquad_op = feed_eq_biquad_tab[i].biquad;
354	}
355
356	if (biquad_op == NULL)
357		return (EINVAL);
358
359	info = malloc(sizeof(*info), M_DEVBUF, M_NOWAIT | M_ZERO);
360	if (info == NULL)
361		return (ENOMEM);
362
363	info->channels = AFMT_CHANNEL(f->desc->in);
364	info->align = info->channels * AFMT_BPS(f->desc->in);
365
366	info->rate = FEEDEQ_RATE_MIN;
367	info->treble.gain = FEEDEQ_L2GAIN(50);
368	info->bass.gain = FEEDEQ_L2GAIN(50);
369	info->preamp = FEEDEQ_PREAMP2IDX(FEEDEQ_PREAMP_DEFAULT);
370	info->state = FEEDEQ_UNKNOWN;
371
372	info->biquad = biquad_op;
373
374	f->data = info;
375
376	return (feed_eq_setup(info));
377}
378
379static int
380feed_eq_set(struct pcm_feeder *f, int what, int value)
381{
382	struct feed_eq_info *info;
383
384	info = f->data;
385
386	switch (what) {
387	case FEEDEQ_CHANNELS:
388		if (value < SND_CHN_MIN || value > SND_CHN_MAX)
389			return (EINVAL);
390		info->channels = (uint32_t)value;
391		info->align = info->channels * AFMT_BPS(f->desc->in);
392		feed_eq_reset(info);
393		break;
394	case FEEDEQ_RATE:
395		if (feeder_eq_validrate(value) == 0)
396			return (EINVAL);
397		info->rate = (uint32_t)value;
398		if (info->state == FEEDEQ_UNKNOWN)
399			info->state = FEEDEQ_ENABLE;
400		return (feed_eq_setup(info));
401		break;
402	case FEEDEQ_TREBLE:
403	case FEEDEQ_BASS:
404		if (value < 0 || value > 100)
405			return (EINVAL);
406		if (what == FEEDEQ_TREBLE)
407			info->treble.gain = FEEDEQ_L2GAIN(value);
408		else
409			info->bass.gain = FEEDEQ_L2GAIN(value);
410		break;
411	case FEEDEQ_PREAMP:
412		if (value < FEEDEQ_PREAMP_MIN || value > FEEDEQ_PREAMP_MAX)
413			return (EINVAL);
414		info->preamp = FEEDEQ_PREAMP2IDX(value);
415		break;
416	case FEEDEQ_STATE:
417		if (!(value == FEEDEQ_BYPASS || value == FEEDEQ_ENABLE ||
418		    value == FEEDEQ_DISABLE))
419			return (EINVAL);
420		info->state = value;
421		feed_eq_reset(info);
422		break;
423	default:
424		return (EINVAL);
425		break;
426	}
427
428	return (0);
429}
430
431static int
432feed_eq_free(struct pcm_feeder *f)
433{
434	struct feed_eq_info *info;
435
436	info = f->data;
437	if (info != NULL)
438		free(info, M_DEVBUF);
439
440	f->data = NULL;
441
442	return (0);
443}
444
445static int
446feed_eq_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
447    uint32_t count, void *source)
448{
449	struct feed_eq_info *info;
450	uint32_t j;
451	uint8_t *dst;
452
453	info = f->data;
454
455	/*
456	 * 3 major states:
457	 * 	FEEDEQ_BYPASS  - Bypass entirely, nothing happened.
458	 *      FEEDEQ_ENABLE  - Preamp+biquad filtering.
459	 *      FEEDEQ_DISABLE - Preamp only.
460	 */
461	if (info->state == FEEDEQ_BYPASS)
462		return (FEEDER_FEED(f->source, c, b, count, source));
463
464	dst = b;
465	count = SND_FXROUND(count, info->align);
466
467	do {
468		if (count < info->align)
469			break;
470
471		j = SND_FXDIV(FEEDER_FEED(f->source, c, dst, count, source),
472		    info->align);
473		if (j == 0)
474			break;
475
476		info->biquad(info, dst, j);
477
478		j *= info->align;
479		dst += j;
480		count -= j;
481
482	} while (count != 0);
483
484	return (dst - b);
485}
486
487static struct pcm_feederdesc feeder_eq_desc[] = {
488	{ FEEDER_EQ, 0, 0, 0, 0 },
489	{ 0, 0, 0, 0, 0 }
490};
491
492static kobj_method_t feeder_eq_methods[] = {
493	KOBJMETHOD(feeder_init,		feed_eq_init),
494	KOBJMETHOD(feeder_free,		feed_eq_free),
495	KOBJMETHOD(feeder_set,		feed_eq_set),
496	KOBJMETHOD(feeder_feed,		feed_eq_feed),
497	KOBJMETHOD_END
498};
499
500FEEDER_DECLARE(feeder_eq, NULL);
501
502static int32_t
503feed_eq_scan_preamp_arg(const char *s)
504{
505	int r, i, f;
506	size_t len;
507	char buf[32];
508
509	bzero(buf, sizeof(buf));
510
511	/* XXX kind of ugly, but works for now.. */
512
513	r = sscanf(s, "%d.%d", &i, &f);
514
515	if (r == 1 && !(i < FEEDEQ_PREAMP_IMIN || i > FEEDEQ_PREAMP_IMAX)) {
516		snprintf(buf, sizeof(buf), "%c%d",
517		    FEEDEQ_PREAMP_SIGNMARK(i), abs(i));
518		f = 0;
519	} else if (r == 2 &&
520	    !(i < FEEDEQ_PREAMP_IMIN || i > FEEDEQ_PREAMP_IMAX ||
521	    f < FEEDEQ_PREAMP_FMIN || f > FEEDEQ_PREAMP_FMAX))
522		snprintf(buf, sizeof(buf), "%c%d.%d",
523		    FEEDEQ_PREAMP_SIGNMARK(i), abs(i), f);
524	else
525		return (FEEDEQ_PREAMP_INVALID);
526
527	len = strlen(s);
528	if (len > 2 && strcasecmp(s + len - 2, "dB") == 0)
529		strlcat(buf, "dB", sizeof(buf));
530
531	if (i == 0 && *s == '-')
532		*buf = '-';
533
534	if (strcasecmp(buf + ((*s >= '0' && *s <= '9') ? 1 : 0), s) != 0)
535		return (FEEDEQ_PREAMP_INVALID);
536
537	while ((f / FEEDEQ_GAIN_DIV) > 0)
538		f /= FEEDEQ_GAIN_DIV;
539
540	return (((i < 0 || *buf == '-') ? -1 : 1) * FEEDEQ_IF2PREAMP(i, f));
541}
542
543#ifdef _KERNEL
544static int
545sysctl_dev_pcm_eq(SYSCTL_HANDLER_ARGS)
546{
547	struct snddev_info *d;
548	struct pcm_channel *c;
549	struct pcm_feeder *f;
550	int err, val, oval;
551
552	d = oidp->oid_arg1;
553	if (!PCM_REGISTERED(d))
554		return (ENODEV);
555
556	PCM_LOCK(d);
557	PCM_WAIT(d);
558	if (d->flags & SD_F_EQ_BYPASSED)
559		val = 2;
560	else if (d->flags & SD_F_EQ_ENABLED)
561		val = 1;
562	else
563		val = 0;
564	PCM_ACQUIRE(d);
565	PCM_UNLOCK(d);
566
567	oval = val;
568	err = sysctl_handle_int(oidp, &val, 0, req);
569
570	if (err == 0 && req->newptr != NULL && val != oval) {
571		if (!(val == 0 || val == 1 || val == 2)) {
572			PCM_RELEASE_QUICK(d);
573			return (EINVAL);
574		}
575
576		PCM_LOCK(d);
577
578		d->flags &= ~(SD_F_EQ_ENABLED | SD_F_EQ_BYPASSED);
579		if (val == 2) {
580			val = FEEDEQ_BYPASS;
581			d->flags |= SD_F_EQ_BYPASSED;
582		} else if (val == 1) {
583			val = FEEDEQ_ENABLE;
584			d->flags |= SD_F_EQ_ENABLED;
585		} else
586			val = FEEDEQ_DISABLE;
587
588		CHN_FOREACH(c, d, channels.pcm.busy) {
589			CHN_LOCK(c);
590			f = chn_findfeeder(c, FEEDER_EQ);
591			if (f != NULL)
592				(void)FEEDER_SET(f, FEEDEQ_STATE, val);
593			CHN_UNLOCK(c);
594		}
595
596		PCM_RELEASE(d);
597		PCM_UNLOCK(d);
598	} else
599		PCM_RELEASE_QUICK(d);
600
601	return (err);
602}
603
604static int
605sysctl_dev_pcm_eq_preamp(SYSCTL_HANDLER_ARGS)
606{
607	struct snddev_info *d;
608	struct pcm_channel *c;
609	struct pcm_feeder *f;
610	int err, val, oval;
611	char buf[32];
612
613	d = oidp->oid_arg1;
614	if (!PCM_REGISTERED(d))
615		return (ENODEV);
616
617	PCM_LOCK(d);
618	PCM_WAIT(d);
619	val = d->eqpreamp;
620	bzero(buf, sizeof(buf));
621	(void)snprintf(buf, sizeof(buf), "%c%d.%ddB",
622	    FEEDEQ_PREAMP_SIGNMARK(val), FEEDEQ_PREAMP_IPART(val),
623	    FEEDEQ_PREAMP_FPART(val));
624	PCM_ACQUIRE(d);
625	PCM_UNLOCK(d);
626
627	oval = val;
628	err = sysctl_handle_string(oidp, buf, sizeof(buf), req);
629
630	if (err == 0 && req->newptr != NULL) {
631		val = feed_eq_scan_preamp_arg(buf);
632		if (val == FEEDEQ_PREAMP_INVALID) {
633			PCM_RELEASE_QUICK(d);
634			return (EINVAL);
635		}
636
637		PCM_LOCK(d);
638
639		if (val != oval) {
640			if (val < FEEDEQ_PREAMP_MIN)
641				val = FEEDEQ_PREAMP_MIN;
642			else if (val > FEEDEQ_PREAMP_MAX)
643				val = FEEDEQ_PREAMP_MAX;
644
645			d->eqpreamp = val;
646
647			CHN_FOREACH(c, d, channels.pcm.busy) {
648				CHN_LOCK(c);
649				f = chn_findfeeder(c, FEEDER_EQ);
650				if (f != NULL)
651					(void)FEEDER_SET(f, FEEDEQ_PREAMP, val);
652				CHN_UNLOCK(c);
653			}
654
655		}
656
657		PCM_RELEASE(d);
658		PCM_UNLOCK(d);
659	} else
660		PCM_RELEASE_QUICK(d);
661
662	return (err);
663}
664
665void
666feeder_eq_initsys(device_t dev)
667{
668	struct snddev_info *d;
669	const char *preamp;
670	char buf[64];
671
672	d = device_get_softc(dev);
673
674	if (!(resource_string_value(device_get_name(dev), device_get_unit(dev),
675	    "eq_preamp", &preamp) == 0 &&
676	    (d->eqpreamp = feed_eq_scan_preamp_arg(preamp)) !=
677	    FEEDEQ_PREAMP_INVALID))
678		d->eqpreamp = FEEDEQ_PREAMP_DEFAULT;
679
680	if (d->eqpreamp < FEEDEQ_PREAMP_MIN)
681		d->eqpreamp = FEEDEQ_PREAMP_MIN;
682	else if (d->eqpreamp > FEEDEQ_PREAMP_MAX)
683		d->eqpreamp = FEEDEQ_PREAMP_MAX;
684
685	SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
686	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
687	    "eq", CTLTYPE_INT | CTLFLAG_RWTUN, d, sizeof(d),
688	    sysctl_dev_pcm_eq, "I",
689	    "Bass/Treble Equalizer (0=disable, 1=enable, 2=bypass)");
690
691	(void)snprintf(buf, sizeof(buf), "Bass/Treble Equalizer Preamp "
692	    "(-/+ %d.0dB , %d.%ddB step)",
693	    FEEDEQ_GAIN_MAX, FEEDEQ_GAIN_STEP / FEEDEQ_GAIN_DIV,
694	    FEEDEQ_GAIN_STEP - ((FEEDEQ_GAIN_STEP / FEEDEQ_GAIN_DIV) *
695	    FEEDEQ_GAIN_DIV));
696
697	SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
698	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
699	    "eq_preamp", CTLTYPE_STRING | CTLFLAG_RWTUN, d, sizeof(d),
700	    sysctl_dev_pcm_eq_preamp, "A", buf);
701}
702#endif
703