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 49 SND_DECLARE_FILE("$FreeBSD$"); 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 95 static int feeder_eq_exact_rate = 0; 96 97 #ifdef _KERNEL 98 static char feeder_eq_presets[] = FEEDER_EQ_PRESETS; 99 SYSCTL_STRING(_hw_snd, OID_AUTO, feeder_eq_presets, CTLFLAG_RD, 100 &feeder_eq_presets, 0, "compile-time eq presets"); 101 102 SYSCTL_INT(_hw_snd, OID_AUTO, feeder_eq_exact_rate, CTLFLAG_RWTUN, 103 &feeder_eq_exact_rate, 0, "force exact rate validation"); 104 #endif 105 106 struct feed_eq_info; 107 108 typedef void (*feed_eq_t)(struct feed_eq_info *, uint8_t *, uint32_t); 109 110 struct 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 118 struct 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) \ 145 static void \ 146 feed_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) 217 FEEDEQ_DECLARE(S, 16, LE) 218 FEEDEQ_DECLARE(S, 32, LE) 219 #endif 220 #if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) 221 FEEDEQ_DECLARE(S, 16, BE) 222 FEEDEQ_DECLARE(S, 32, BE) 223 #endif 224 #ifdef SND_FEEDER_MULTIFORMAT 225 FEEDEQ_DECLARE(S, 8, NE) 226 FEEDEQ_DECLARE(S, 24, LE) 227 FEEDEQ_DECLARE(S, 24, BE) 228 FEEDEQ_DECLARE(U, 8, NE) 229 FEEDEQ_DECLARE(U, 16, LE) 230 FEEDEQ_DECLARE(U, 24, LE) 231 FEEDEQ_DECLARE(U, 32, LE) 232 FEEDEQ_DECLARE(U, 16, BE) 233 FEEDEQ_DECLARE(U, 24, BE) 234 FEEDEQ_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 244 static 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 273 static struct feed_eq_coeff * 274 feed_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 299 int 300 feeder_eq_validrate(uint32_t rate) 301 { 302 303 if (feed_eq_coeff_rate(rate) != NULL) 304 return (1); 305 306 return (0); 307 } 308 309 static void 310 feed_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 326 static int 327 feed_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 339 static int 340 feed_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 379 static int 380 feed_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 431 static int 432 feed_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 445 static int 446 feed_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 487 static struct pcm_feederdesc feeder_eq_desc[] = { 488 { FEEDER_EQ, 0, 0, 0, 0 }, 489 { 0, 0, 0, 0, 0 } 490 }; 491 492 static 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 500 FEEDER_DECLARE(feeder_eq, NULL); 501 502 static int32_t 503 feed_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 544 static int 545 sysctl_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 604 static int 605 sysctl_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 665 void 666 feeder_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