2 * \file pcm/pcm_softvol.c
4 * \brief PCM Soft Volume Plugin Interface
5 * \author Takashi Iwai <tiwai@suse.de>
9 * PCM - Soft Volume Plugin
10 * Copyright (c) 2004 by Takashi Iwai <tiwai@suse.de>
13 * This library is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU Lesser General Public License as
15 * published by the Free Software Foundation; either version 2.1 of
16 * the License, or (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License for more details.
23 * You should have received a copy of the GNU Lesser General Public
24 * License along with this library; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31 #include "pcm_local.h"
32 #include "pcm_plugin.h"
35 /* entry for static linking */
36 const char *_snd_module_pcm_softvol = "";
42 /* This field need to be the first */
43 snd_pcm_plugin_t plug;
44 snd_pcm_format_t sformat;
45 unsigned int cchannels;
47 snd_ctl_elem_value_t elem;
48 unsigned int cur_vol[2];
49 unsigned int max_val; /* max index */
50 unsigned int zero_dB_val; /* index at 0 dB */
53 unsigned int *dB_value;
56 #define VOL_SCALE_SHIFT 16
57 #define VOL_SCALE_MASK ((1 << VOL_SCALE_SHIFT) - 1)
59 #define PRESET_RESOLUTION 256
60 #define PRESET_MIN_DB -51.0
62 #define MAX_DB_UPPER_LIMIT 50
64 static const unsigned int preset_dB_value[PRESET_RESOLUTION] = {
65 0x00b8, 0x00bd, 0x00c1, 0x00c5, 0x00ca, 0x00cf, 0x00d4, 0x00d9,
66 0x00de, 0x00e3, 0x00e8, 0x00ed, 0x00f3, 0x00f9, 0x00fe, 0x0104,
67 0x010a, 0x0111, 0x0117, 0x011e, 0x0124, 0x012b, 0x0132, 0x0139,
68 0x0140, 0x0148, 0x0150, 0x0157, 0x015f, 0x0168, 0x0170, 0x0179,
69 0x0181, 0x018a, 0x0194, 0x019d, 0x01a7, 0x01b0, 0x01bb, 0x01c5,
70 0x01cf, 0x01da, 0x01e5, 0x01f1, 0x01fc, 0x0208, 0x0214, 0x0221,
71 0x022d, 0x023a, 0x0248, 0x0255, 0x0263, 0x0271, 0x0280, 0x028f,
72 0x029e, 0x02ae, 0x02be, 0x02ce, 0x02df, 0x02f0, 0x0301, 0x0313,
73 0x0326, 0x0339, 0x034c, 0x035f, 0x0374, 0x0388, 0x039d, 0x03b3,
74 0x03c9, 0x03df, 0x03f7, 0x040e, 0x0426, 0x043f, 0x0458, 0x0472,
75 0x048d, 0x04a8, 0x04c4, 0x04e0, 0x04fd, 0x051b, 0x053a, 0x0559,
76 0x0579, 0x0599, 0x05bb, 0x05dd, 0x0600, 0x0624, 0x0648, 0x066e,
77 0x0694, 0x06bb, 0x06e3, 0x070c, 0x0737, 0x0762, 0x078e, 0x07bb,
78 0x07e9, 0x0818, 0x0848, 0x087a, 0x08ac, 0x08e0, 0x0915, 0x094b,
79 0x0982, 0x09bb, 0x09f5, 0x0a30, 0x0a6d, 0x0aab, 0x0aeb, 0x0b2c,
80 0x0b6f, 0x0bb3, 0x0bf9, 0x0c40, 0x0c89, 0x0cd4, 0x0d21, 0x0d6f,
81 0x0dbf, 0x0e11, 0x0e65, 0x0ebb, 0x0f12, 0x0f6c, 0x0fc8, 0x1026,
82 0x1087, 0x10e9, 0x114e, 0x11b5, 0x121f, 0x128b, 0x12fa, 0x136b,
83 0x13df, 0x1455, 0x14ce, 0x154a, 0x15c9, 0x164b, 0x16d0, 0x1758,
84 0x17e4, 0x1872, 0x1904, 0x1999, 0x1a32, 0x1ace, 0x1b6e, 0x1c11,
85 0x1cb9, 0x1d64, 0x1e13, 0x1ec7, 0x1f7e, 0x203a, 0x20fa, 0x21bf,
86 0x2288, 0x2356, 0x2429, 0x2500, 0x25dd, 0x26bf, 0x27a6, 0x2892,
87 0x2984, 0x2a7c, 0x2b79, 0x2c7c, 0x2d85, 0x2e95, 0x2fab, 0x30c7,
88 0x31ea, 0x3313, 0x3444, 0x357c, 0x36bb, 0x3801, 0x394f, 0x3aa5,
89 0x3c02, 0x3d68, 0x3ed6, 0x404d, 0x41cd, 0x4355, 0x44e6, 0x4681,
90 0x4826, 0x49d4, 0x4b8c, 0x4d4f, 0x4f1c, 0x50f3, 0x52d6, 0x54c4,
91 0x56be, 0x58c3, 0x5ad4, 0x5cf2, 0x5f1c, 0x6153, 0x6398, 0x65e9,
92 0x6849, 0x6ab7, 0x6d33, 0x6fbf, 0x7259, 0x7503, 0x77bd, 0x7a87,
93 0x7d61, 0x804d, 0x834a, 0x8659, 0x897a, 0x8cae, 0x8ff5, 0x934f,
94 0x96bd, 0x9a40, 0x9dd8, 0xa185, 0xa548, 0xa922, 0xad13, 0xb11b,
95 0xb53b, 0xb973, 0xbdc5, 0xc231, 0xc6b7, 0xcb58, 0xd014, 0xd4ed,
96 0xd9e3, 0xdef6, 0xe428, 0xe978, 0xeee8, 0xf479, 0xfa2b, 0xffff,
99 /* (32bit x 16bit) >> 16 */
104 static inline int MULTI_DIV_32x16(int a, unsigned short b)
109 #if __BYTE_ORDER == __LITTLE_ENDIAN
110 x.i = (unsigned short)v.s[0];
113 y.i += (int)v.s[1] * b;
115 x.i = (unsigned int)v.s[1] * b;
117 y.i += (int)v.s[0] * b;
122 static inline int MULTI_DIV_int(int a, unsigned int b, int swap)
124 unsigned int gain = (b >> VOL_SCALE_SHIFT);
126 a = swap ? (int)bswap_32(a) : a;
127 fraction = MULTI_DIV_32x16(a, b & VOL_SCALE_MASK);
129 long long amp = (long long)a * gain + fraction;
130 if (amp > (int)0x7fffffff)
131 amp = (int)0x7fffffff;
132 else if (amp < (int)0x80000000)
133 amp = (int)0x80000000;
134 return swap ? (int)bswap_32((int)amp) : (int)amp;
136 return swap ? (int)bswap_32(fraction) : fraction;
139 /* always little endian */
140 static inline int MULTI_DIV_24(int a, unsigned int b)
142 unsigned int gain = b >> VOL_SCALE_SHIFT;
144 fraction = MULTI_DIV_32x16(a, b & VOL_SCALE_MASK);
146 long long amp = (long long)a * gain + fraction;
147 if (amp > (int)0x7fffff)
149 else if (amp < (int)0x800000)
156 static inline short MULTI_DIV_short(short a, unsigned int b, int swap)
158 unsigned int gain = b >> VOL_SCALE_SHIFT;
160 a = swap ? (short)bswap_16(a) : a;
161 fraction = (int)(a * (b & VOL_SCALE_MASK)) >> VOL_SCALE_SHIFT;
163 int amp = a * gain + fraction;
164 if (abs(amp) > 0x7fff)
165 amp = (a<0) ? (short)0x8000 : (short)0x7fff;
166 return swap ? (short)bswap_16((short)amp) : (short)amp;
168 return swap ? (short)bswap_16((short)fraction) : (short)fraction;
171 #endif /* DOC_HIDDEN */
174 * apply volumue attenuation
176 * TODO: use SIMD operations
180 #define CONVERT_AREA(TYPE, swap) do { \
181 unsigned int ch, fr; \
183 for (ch = 0; ch < channels; ch++) { \
184 src_area = &src_areas[ch]; \
185 dst_area = &dst_areas[ch]; \
186 src = snd_pcm_channel_area_addr(src_area, src_offset); \
187 dst = snd_pcm_channel_area_addr(dst_area, dst_offset); \
188 src_step = snd_pcm_channel_area_step(src_area) / sizeof(TYPE); \
189 dst_step = snd_pcm_channel_area_step(dst_area) / sizeof(TYPE); \
197 } else if (vol_scale == 0xffff) { \
205 *dst = (TYPE) MULTI_DIV_##TYPE(*src, vol_scale, swap); \
213 #define CONVERT_AREA_S24_3LE() do { \
214 unsigned int ch, fr; \
215 unsigned char *src, *dst; \
217 for (ch = 0; ch < channels; ch++) { \
218 src_area = &src_areas[ch]; \
219 dst_area = &dst_areas[ch]; \
220 src = snd_pcm_channel_area_addr(src_area, src_offset); \
221 dst = snd_pcm_channel_area_addr(dst_area, dst_offset); \
222 src_step = snd_pcm_channel_area_step(src_area); \
223 dst_step = snd_pcm_channel_area_step(dst_area); \
228 dst[0] = dst[1] = dst[2] = 0; \
231 } else if (vol_scale == 0xffff) { \
243 (((signed char *) src)[2] << 16); \
244 tmp = MULTI_DIV_24(tmp, vol_scale); \
247 dst[2] = tmp >> 16; \
255 #define GET_VOL_SCALE \
259 vol_scale = (channels == ch + 1) ? vol_c : vol[0]; \
266 vol_scale = vol[ch & 1]; \
270 #endif /* DOC_HIDDEN */
272 /* 2-channel stereo control */
273 static void softvol_convert_stereo_vol(snd_pcm_softvol_t *svol,
274 const snd_pcm_channel_area_t *dst_areas,
275 snd_pcm_uframes_t dst_offset,
276 const snd_pcm_channel_area_t *src_areas,
277 snd_pcm_uframes_t src_offset,
278 unsigned int channels,
279 snd_pcm_uframes_t frames)
281 const snd_pcm_channel_area_t *dst_area, *src_area;
282 unsigned int src_step, dst_step;
283 unsigned int vol_scale, vol[2], vol_c;
285 if (svol->cur_vol[0] == 0 && svol->cur_vol[1] == 0) {
286 snd_pcm_areas_silence(dst_areas, dst_offset, channels, frames,
289 } else if (svol->zero_dB_val && svol->cur_vol[0] == svol->zero_dB_val &&
290 svol->cur_vol[1] == svol->zero_dB_val) {
291 snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
292 channels, frames, svol->sformat);
296 if (svol->max_val == 1) {
297 vol[0] = svol->cur_vol[0] ? 0xffff : 0;
298 vol[1] = svol->cur_vol[1] ? 0xffff : 0;
299 vol_c = vol[0] | vol[1];
301 vol[0] = svol->dB_value[svol->cur_vol[0]];
302 vol[1] = svol->dB_value[svol->cur_vol[1]];
303 vol_c = svol->dB_value[(svol->cur_vol[0] + svol->cur_vol[1]) / 2];
305 switch (svol->sformat) {
306 case SND_PCM_FORMAT_S16_LE:
307 case SND_PCM_FORMAT_S16_BE:
310 !snd_pcm_format_cpu_endian(svol->sformat));
312 case SND_PCM_FORMAT_S32_LE:
313 case SND_PCM_FORMAT_S32_BE:
316 !snd_pcm_format_cpu_endian(svol->sformat));
318 case SND_PCM_FORMAT_S24_3LE:
319 CONVERT_AREA_S24_3LE();
327 #define GET_VOL_SCALE
330 static void softvol_convert_mono_vol(snd_pcm_softvol_t *svol,
331 const snd_pcm_channel_area_t *dst_areas,
332 snd_pcm_uframes_t dst_offset,
333 const snd_pcm_channel_area_t *src_areas,
334 snd_pcm_uframes_t src_offset,
335 unsigned int channels,
336 snd_pcm_uframes_t frames)
338 const snd_pcm_channel_area_t *dst_area, *src_area;
339 unsigned int src_step, dst_step;
340 unsigned int vol_scale;
342 if (svol->cur_vol[0] == 0) {
343 snd_pcm_areas_silence(dst_areas, dst_offset, channels, frames,
346 } else if (svol->zero_dB_val && svol->cur_vol[0] == svol->zero_dB_val) {
347 snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
348 channels, frames, svol->sformat);
352 if (svol->max_val == 1)
353 vol_scale = svol->cur_vol[0] ? 0xffff : 0;
355 vol_scale = svol->dB_value[svol->cur_vol[0]];
356 switch (svol->sformat) {
357 case SND_PCM_FORMAT_S16_LE:
358 case SND_PCM_FORMAT_S16_BE:
361 !snd_pcm_format_cpu_endian(svol->sformat));
363 case SND_PCM_FORMAT_S32_LE:
364 case SND_PCM_FORMAT_S32_BE:
367 !snd_pcm_format_cpu_endian(svol->sformat));
369 case SND_PCM_FORMAT_S24_3LE:
370 CONVERT_AREA_S24_3LE();
378 * get the current volume value from driver
380 * TODO: mmap support?
382 static void get_current_volume(snd_pcm_softvol_t *svol)
387 if (snd_ctl_elem_read(svol->ctl, &svol->elem) < 0)
389 for (i = 0; i < svol->cchannels; i++) {
390 val = svol->elem.value.integer.value[i];
391 if (val > svol->max_val)
393 svol->cur_vol[i] = val;
397 static void softvol_free(snd_pcm_softvol_t *svol)
399 if (svol->plug.gen.close_slave)
400 snd_pcm_close(svol->plug.gen.slave);
402 snd_ctl_close(svol->ctl);
403 if (svol->dB_value && svol->dB_value != preset_dB_value)
404 free(svol->dB_value);
408 static int snd_pcm_softvol_close(snd_pcm_t *pcm)
410 snd_pcm_softvol_t *svol = pcm->private_data;
415 static int snd_pcm_softvol_hw_refine_cprepare(snd_pcm_t *pcm,
416 snd_pcm_hw_params_t *params)
419 snd_pcm_softvol_t *svol = pcm->private_data;
420 snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
421 snd_pcm_format_mask_t format_mask = {
423 (1ULL << SND_PCM_FORMAT_S16_LE) |
424 (1ULL << SND_PCM_FORMAT_S16_BE) |
425 (1ULL << SND_PCM_FORMAT_S32_LE) |
426 (1ULL << SND_PCM_FORMAT_S32_BE),
427 (1ULL << (SND_PCM_FORMAT_S24_3LE - 32))
430 if (svol->sformat != SND_PCM_FORMAT_UNKNOWN) {
431 snd_pcm_format_mask_none(&format_mask);
432 snd_pcm_format_mask_set(&format_mask, svol->sformat);
434 err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
438 err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT,
442 err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD);
445 err = _snd_pcm_hw_param_set_min(params, SND_PCM_HW_PARAM_CHANNELS, 1, 0);
448 params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
452 static int snd_pcm_softvol_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
454 snd_pcm_softvol_t *svol = pcm->private_data;
455 snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
456 _snd_pcm_hw_params_any(sparams);
457 _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
459 if (svol->sformat != SND_PCM_FORMAT_UNKNOWN) {
460 _snd_pcm_hw_params_set_format(sparams, svol->sformat);
461 _snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
467 * refine the access mask
469 static int check_access_mask(snd_pcm_hw_params_t *src,
470 snd_pcm_hw_params_t *dst)
472 const snd_pcm_access_mask_t *mask;
473 snd_pcm_access_mask_t smask;
475 mask = snd_pcm_hw_param_get_mask(src, SND_PCM_HW_PARAM_ACCESS);
476 snd_mask_none(&smask);
477 if (snd_pcm_access_mask_test(mask, SND_PCM_ACCESS_RW_INTERLEAVED) ||
478 snd_pcm_access_mask_test(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED)) {
479 snd_pcm_access_mask_set(&smask,
480 SND_PCM_ACCESS_RW_INTERLEAVED);
481 snd_pcm_access_mask_set(&smask,
482 SND_PCM_ACCESS_MMAP_INTERLEAVED);
484 if (snd_pcm_access_mask_test(mask, SND_PCM_ACCESS_RW_NONINTERLEAVED) ||
485 snd_pcm_access_mask_test(mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) {
486 snd_pcm_access_mask_set(&smask,
487 SND_PCM_ACCESS_RW_NONINTERLEAVED);
488 snd_pcm_access_mask_set(&smask,
489 SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
491 if (snd_pcm_access_mask_test(mask, SND_PCM_ACCESS_MMAP_COMPLEX))
492 snd_pcm_access_mask_set(&smask,
493 SND_PCM_ACCESS_MMAP_COMPLEX);
495 return _snd_pcm_hw_param_set_mask(dst, SND_PCM_HW_PARAM_ACCESS, &smask);
498 static int snd_pcm_softvol_hw_refine_schange(snd_pcm_t *pcm,
499 snd_pcm_hw_params_t *params,
500 snd_pcm_hw_params_t *sparams)
502 snd_pcm_softvol_t *svol = pcm->private_data;
504 unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
505 SND_PCM_HW_PARBIT_RATE |
506 SND_PCM_HW_PARBIT_PERIODS |
507 SND_PCM_HW_PARBIT_PERIOD_SIZE |
508 SND_PCM_HW_PARBIT_PERIOD_TIME |
509 SND_PCM_HW_PARBIT_BUFFER_SIZE |
510 SND_PCM_HW_PARBIT_BUFFER_TIME |
511 SND_PCM_HW_PARBIT_TICK_TIME);
512 if (svol->sformat == SND_PCM_FORMAT_UNKNOWN)
513 links |= (SND_PCM_HW_PARBIT_FORMAT |
514 SND_PCM_HW_PARBIT_SUBFORMAT |
515 SND_PCM_HW_PARBIT_SAMPLE_BITS);
516 err = _snd_pcm_hw_params_refine(sparams, links, params);
520 err = check_access_mask(params, sparams);
527 static int snd_pcm_softvol_hw_refine_cchange(snd_pcm_t *pcm,
528 snd_pcm_hw_params_t *params,
529 snd_pcm_hw_params_t *sparams)
531 snd_pcm_softvol_t *svol = pcm->private_data;
533 unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
534 SND_PCM_HW_PARBIT_RATE |
535 SND_PCM_HW_PARBIT_PERIODS |
536 SND_PCM_HW_PARBIT_PERIOD_SIZE |
537 SND_PCM_HW_PARBIT_PERIOD_TIME |
538 SND_PCM_HW_PARBIT_BUFFER_SIZE |
539 SND_PCM_HW_PARBIT_BUFFER_TIME |
540 SND_PCM_HW_PARBIT_TICK_TIME);
541 if (svol->sformat == SND_PCM_FORMAT_UNKNOWN)
542 links |= (SND_PCM_HW_PARBIT_FORMAT |
543 SND_PCM_HW_PARBIT_SUBFORMAT |
544 SND_PCM_HW_PARBIT_SAMPLE_BITS);
545 err = _snd_pcm_hw_params_refine(params, links, sparams);
549 err = check_access_mask(sparams, params);
556 static int snd_pcm_softvol_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
558 return snd_pcm_hw_refine_slave(pcm, params,
559 snd_pcm_softvol_hw_refine_cprepare,
560 snd_pcm_softvol_hw_refine_cchange,
561 snd_pcm_softvol_hw_refine_sprepare,
562 snd_pcm_softvol_hw_refine_schange,
563 snd_pcm_generic_hw_refine);
566 static int snd_pcm_softvol_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
568 snd_pcm_softvol_t *svol = pcm->private_data;
569 snd_pcm_t *slave = svol->plug.gen.slave;
570 int err = snd_pcm_hw_params_slave(pcm, params,
571 snd_pcm_softvol_hw_refine_cchange,
572 snd_pcm_softvol_hw_refine_sprepare,
573 snd_pcm_softvol_hw_refine_schange,
574 snd_pcm_generic_hw_params);
577 if (slave->format != SND_PCM_FORMAT_S16_LE &&
578 slave->format != SND_PCM_FORMAT_S16_BE &&
579 slave->format != SND_PCM_FORMAT_S24_3LE &&
580 slave->format != SND_PCM_FORMAT_S32_LE &&
581 slave->format != SND_PCM_FORMAT_S32_BE) {
582 SNDERR("softvol supports only S16_LE, S16_BE, S24_3LE, S32_LE "
586 svol->sformat = slave->format;
590 static snd_pcm_uframes_t
591 snd_pcm_softvol_write_areas(snd_pcm_t *pcm,
592 const snd_pcm_channel_area_t *areas,
593 snd_pcm_uframes_t offset,
594 snd_pcm_uframes_t size,
595 const snd_pcm_channel_area_t *slave_areas,
596 snd_pcm_uframes_t slave_offset,
597 snd_pcm_uframes_t *slave_sizep)
599 snd_pcm_softvol_t *svol = pcm->private_data;
600 if (size > *slave_sizep)
602 get_current_volume(svol);
603 if (svol->cchannels == 1)
604 softvol_convert_mono_vol(svol, slave_areas, slave_offset,
605 areas, offset, pcm->channels, size);
607 softvol_convert_stereo_vol(svol, slave_areas, slave_offset,
608 areas, offset, pcm->channels, size);
613 static snd_pcm_uframes_t
614 snd_pcm_softvol_read_areas(snd_pcm_t *pcm,
615 const snd_pcm_channel_area_t *areas,
616 snd_pcm_uframes_t offset,
617 snd_pcm_uframes_t size,
618 const snd_pcm_channel_area_t *slave_areas,
619 snd_pcm_uframes_t slave_offset,
620 snd_pcm_uframes_t *slave_sizep)
622 snd_pcm_softvol_t *svol = pcm->private_data;
623 if (size > *slave_sizep)
625 get_current_volume(svol);
626 if (svol->cchannels == 1)
627 softvol_convert_mono_vol(svol, areas, offset, slave_areas,
628 slave_offset, pcm->channels, size);
630 softvol_convert_stereo_vol(svol, areas, offset, slave_areas,
631 slave_offset, pcm->channels, size);
636 static void snd_pcm_softvol_dump(snd_pcm_t *pcm, snd_output_t *out)
638 snd_pcm_softvol_t *svol = pcm->private_data;
639 snd_output_printf(out, "Soft volume PCM\n");
640 snd_output_printf(out, "Control: %s\n", svol->elem.id.name);
641 if (svol->max_val == 1)
642 snd_output_printf(out, "boolean\n");
644 snd_output_printf(out, "min_dB: %g\n", svol->min_dB);
645 snd_output_printf(out, "max_dB: %g\n", svol->max_dB);
646 snd_output_printf(out, "resolution: %d\n", svol->max_val + 1);
649 snd_output_printf(out, "Its setup is:\n");
650 snd_pcm_dump_setup(pcm, out);
652 snd_output_printf(out, "Slave: ");
653 snd_pcm_dump(svol->plug.gen.slave, out);
656 static int add_tlv_info(snd_pcm_softvol_t *svol, snd_ctl_elem_info_t *cinfo)
659 tlv[0] = SND_CTL_TLVT_DB_SCALE;
660 tlv[1] = 2 * sizeof(int);
661 tlv[2] = svol->min_dB * 100;
662 tlv[3] = (svol->max_dB - svol->min_dB) * 100 / svol->max_val;
663 return snd_ctl_elem_tlv_write(svol->ctl, &cinfo->id, tlv);
666 static int add_user_ctl(snd_pcm_softvol_t *svol, snd_ctl_elem_info_t *cinfo, int count)
670 unsigned int def_val;
672 if (svol->max_val == 1)
673 err = snd_ctl_elem_add_boolean(svol->ctl, &cinfo->id, count);
675 err = snd_ctl_elem_add_integer(svol->ctl, &cinfo->id, count,
676 0, svol->max_val, 0);
679 if (svol->max_val == 1)
682 add_tlv_info(svol, cinfo);
683 /* set zero dB value as default, or max_val if
684 there is no 0 dB setting */
685 def_val = svol->zero_dB_val ? svol->zero_dB_val : svol->max_val;
687 for (i = 0; i < count; i++)
688 svol->elem.value.integer.value[i] = def_val;
689 return snd_ctl_elem_write(svol->ctl, &svol->elem);
693 * load and set up user-control
694 * returns 0 if the user-control is found or created,
695 * returns 1 if the control is a hw control,
696 * or a negative error code
698 static int softvol_load_control(snd_pcm_t *pcm, snd_pcm_softvol_t *svol,
699 int ctl_card, snd_ctl_elem_id_t *ctl_id,
700 int cchannels, double min_dB, double max_dB,
704 snd_pcm_info_t *info;
705 snd_ctl_elem_info_t *cinfo;
710 snd_pcm_info_alloca(&info);
711 err = snd_pcm_info(pcm, info);
714 ctl_card = snd_pcm_info_get_card(info);
716 SNDERR("No card defined for softvol control");
720 sprintf(tmp_name, "hw:%d", ctl_card);
721 err = snd_ctl_open(&svol->ctl, tmp_name, 0);
723 SNDERR("Cannot open CTL %s", tmp_name);
727 svol->elem.id = *ctl_id;
728 svol->max_val = resolution - 1;
729 svol->min_dB = min_dB;
730 svol->max_dB = max_dB;
731 if (svol->max_val == 1 || svol->max_dB == ZERO_DB)
732 svol->zero_dB_val = svol->max_val;
733 else if (svol->max_dB < 0)
734 svol->zero_dB_val = 0; /* there is no 0 dB setting */
736 svol->zero_dB_val = (min_dB / (min_dB - max_dB)) * svol->max_val;
738 snd_ctl_elem_info_alloca(&cinfo);
739 snd_ctl_elem_info_set_id(cinfo, ctl_id);
740 if ((err = snd_ctl_elem_info(svol->ctl, cinfo)) < 0) {
741 if (err != -ENOENT) {
742 SNDERR("Cannot get info for CTL %s", tmp_name);
745 err = add_user_ctl(svol, cinfo, cchannels);
747 SNDERR("Cannot add a control");
751 if (! (cinfo->access & SNDRV_CTL_ELEM_ACCESS_USER)) {
752 /* hardware control exists */
753 return 1; /* notify */
755 } else if ((cinfo->type != SND_CTL_ELEM_TYPE_INTEGER &&
756 cinfo->type != SND_CTL_ELEM_TYPE_BOOLEAN) ||
757 cinfo->count != (unsigned int)cchannels ||
758 cinfo->value.integer.min != 0 ||
759 cinfo->value.integer.max != resolution - 1) {
760 if ((err = snd_ctl_elem_remove(svol->ctl, &cinfo->id)) < 0) {
761 SNDERR("Control %s mismatch", tmp_name);
764 snd_ctl_elem_info_set_id(cinfo, ctl_id); /* reset numid */
765 if ((err = add_user_ctl(svol, cinfo, cchannels)) < 0) {
766 SNDERR("Cannot add a control");
769 } else if (svol->max_val > 1) {
770 /* check TLV availability */
772 err = snd_ctl_elem_tlv_read(svol->ctl, &cinfo->id, tlv, sizeof(tlv));
774 add_tlv_info(svol, cinfo);
778 if (svol->max_val == 1)
781 /* set up dB table */
782 if (min_dB == PRESET_MIN_DB && max_dB == ZERO_DB && resolution == PRESET_RESOLUTION)
783 svol->dB_value = (unsigned int*)preset_dB_value;
785 #ifndef HAVE_SOFT_FLOAT
786 svol->dB_value = calloc(resolution, sizeof(unsigned int));
787 if (! svol->dB_value) {
788 SNDERR("cannot allocate dB table");
791 svol->min_dB = min_dB;
792 svol->max_dB = max_dB;
793 for (i = 0; i <= svol->max_val; i++) {
794 double db = svol->min_dB + (i * (svol->max_dB - svol->min_dB)) / svol->max_val;
795 double v = (pow(10.0, db / 20.0) * (double)(1 << VOL_SCALE_SHIFT));
796 svol->dB_value[i] = (unsigned int)v;
798 if (svol->zero_dB_val)
799 svol->dB_value[svol->zero_dB_val] = 65535;
801 SNDERR("Cannot handle the given dB range and resolution");
808 static const snd_pcm_ops_t snd_pcm_softvol_ops = {
809 .close = snd_pcm_softvol_close,
810 .info = snd_pcm_generic_info,
811 .hw_refine = snd_pcm_softvol_hw_refine,
812 .hw_params = snd_pcm_softvol_hw_params,
813 .hw_free = snd_pcm_generic_hw_free,
814 .sw_params = snd_pcm_generic_sw_params,
815 .channel_info = snd_pcm_generic_channel_info,
816 .dump = snd_pcm_softvol_dump,
817 .nonblock = snd_pcm_generic_nonblock,
818 .async = snd_pcm_generic_async,
819 .mmap = snd_pcm_generic_mmap,
820 .munmap = snd_pcm_generic_munmap,
821 .query_chmaps = snd_pcm_generic_query_chmaps,
822 .get_chmap = snd_pcm_generic_get_chmap,
823 .set_chmap = snd_pcm_generic_set_chmap,
827 * \brief Creates a new SoftVolume PCM
828 * \param pcmp Returns created PCM handle
829 * \param name Name of PCM
830 * \param sformat Slave format
831 * \param ctl_card card index of the control
832 * \param ctl_id The control element
833 * \param cchannels PCM channels
834 * \param min_dB minimal dB value
835 * \param max_dB maximal dB value
836 * \param resolution resolution of control
837 * \param slave Slave PCM handle
838 * \param close_slave When set, the slave PCM handle is closed with copy PCM
839 * \retval zero on success otherwise a negative error code
840 * \warning Using of this function might be dangerous in the sense
841 * of compatibility reasons. The prototype might be freely
844 int snd_pcm_softvol_open(snd_pcm_t **pcmp, const char *name,
845 snd_pcm_format_t sformat,
846 int ctl_card, snd_ctl_elem_id_t *ctl_id,
848 double min_dB, double max_dB, int resolution,
849 snd_pcm_t *slave, int close_slave)
852 snd_pcm_softvol_t *svol;
854 assert(pcmp && slave);
855 if (sformat != SND_PCM_FORMAT_UNKNOWN &&
856 sformat != SND_PCM_FORMAT_S16_LE &&
857 sformat != SND_PCM_FORMAT_S16_BE &&
858 sformat != SND_PCM_FORMAT_S24_3LE &&
859 sformat != SND_PCM_FORMAT_S32_LE &&
860 sformat != SND_PCM_FORMAT_S32_BE)
862 svol = calloc(1, sizeof(*svol));
865 err = softvol_load_control(slave, svol, ctl_card, ctl_id, cchannels,
866 min_dB, max_dB, resolution);
871 if (err > 0) { /* hardware control - no need for softvol! */
873 *pcmp = slave; /* just pass the slave */
874 if (!slave->name && name)
875 slave->name = strdup(name);
880 snd_pcm_plugin_init(&svol->plug);
881 svol->sformat = sformat;
882 svol->cchannels = cchannels;
883 svol->plug.read = snd_pcm_softvol_read_areas;
884 svol->plug.write = snd_pcm_softvol_write_areas;
885 svol->plug.undo_read = snd_pcm_plugin_undo_read_generic;
886 svol->plug.undo_write = snd_pcm_plugin_undo_write_generic;
887 svol->plug.gen.slave = slave;
888 svol->plug.gen.close_slave = close_slave;
890 err = snd_pcm_new(&pcm, SND_PCM_TYPE_SOFTVOL, name, slave->stream, slave->mode);
895 pcm->ops = &snd_pcm_softvol_ops;
896 pcm->fast_ops = &snd_pcm_plugin_fast_ops;
897 pcm->private_data = svol;
898 pcm->poll_fd = slave->poll_fd;
899 pcm->poll_events = slave->poll_events;
901 * Since the softvol converts on the place, and the format/channels
902 * must be identical between source and destination, we don't need
905 pcm->mmap_shadow = 1;
906 pcm->monotonic = slave->monotonic;
907 snd_pcm_set_hw_ptr(pcm, &svol->plug.hw_ptr, -1, 0);
908 snd_pcm_set_appl_ptr(pcm, &svol->plug.appl_ptr, -1, 0);
915 int snd_pcm_parse_control_id(snd_config_t *conf, snd_ctl_elem_id_t *ctl_id, int *cardp,
916 int *cchannelsp, int *hwctlp);
918 /*! \page pcm_plugins
920 \section pcm_plugins_softvol Plugin: Soft Volume
922 This plugin applies the software volume attenuation.
923 The format, rate and channels must match for both of source and destination.
925 When the control is stereo (count=2), the channels are assumed to be either
926 mono, 2.0, 2.1, 4.0, 4.1, 5.1 or 7.1.
928 If the control already exists and it's a system control (i.e. no
929 user-defined control), the plugin simply passes its slave without
934 type softvol # Soft Volume conversion PCM
935 slave STR # Slave name
937 slave { # Slave definition
938 pcm STR # Slave PCM name
940 pcm { } # Slave PCM definition
941 [format STR] # Slave format
944 name STR # control element id string
945 [card STR] # control card index
946 [iface STR] # interface of the element
947 [index INT] # index of the element
948 [device INT] # device number of the element
949 [subdevice INT] # subdevice number of the element
950 [count INT] # control channels 1 or 2 (default: 2)
952 [min_dB REAL] # minimal dB value (default: -51.0)
953 [max_dB REAL] # maximal dB value (default: 0.0)
954 [resolution INT] # resolution (default: 256)
955 # resolution = 2 means a mute switch
959 \subsection pcm_plugins_softvol_funcref Function reference
962 <LI>snd_pcm_softvol_open()
963 <LI>_snd_pcm_softvol_open()
969 * \brief Creates a new Soft Volume PCM
970 * \param pcmp Returns created PCM handle
971 * \param name Name of PCM
972 * \param root Root configuration node
973 * \param conf Configuration node with Soft Volume PCM description
974 * \param stream Stream type
975 * \param mode Stream mode
976 * \retval zero on success otherwise a negative error code
977 * \warning Using of this function might be dangerous in the sense
978 * of compatibility reasons. The prototype might be freely
981 int _snd_pcm_softvol_open(snd_pcm_t **pcmp, const char *name,
982 snd_config_t *root, snd_config_t *conf,
983 snd_pcm_stream_t stream, int mode)
985 snd_config_iterator_t i, next;
988 snd_config_t *slave = NULL, *sconf;
989 snd_config_t *control = NULL;
990 snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN;
991 snd_ctl_elem_id_t *ctl_id;
992 int resolution = PRESET_RESOLUTION;
993 double min_dB = PRESET_MIN_DB;
994 double max_dB = ZERO_DB;
995 int card = -1, cchannels = 2;
997 snd_config_for_each(i, next, conf) {
998 snd_config_t *n = snd_config_iterator_entry(i);
1000 if (snd_config_get_id(n, &id) < 0)
1002 if (snd_pcm_conf_generic_id(id))
1004 if (strcmp(id, "slave") == 0) {
1008 if (strcmp(id, "control") == 0) {
1012 if (strcmp(id, "resolution") == 0) {
1014 err = snd_config_get_integer(n, &v);
1016 SNDERR("Invalid resolution value");
1022 if (strcmp(id, "min_dB") == 0) {
1023 err = snd_config_get_real(n, &min_dB);
1025 SNDERR("Invalid min_dB value");
1030 if (strcmp(id, "max_dB") == 0) {
1031 err = snd_config_get_real(n, &max_dB);
1033 SNDERR("Invalid max_dB value");
1038 SNDERR("Unknown field %s", id);
1042 SNDERR("slave is not defined");
1046 SNDERR("control is not defined");
1050 SNDERR("min_dB must be a negative value");
1053 if (max_dB <= min_dB || max_dB > MAX_DB_UPPER_LIMIT) {
1054 SNDERR("max_dB must be larger than min_dB and less than %d dB",
1055 MAX_DB_UPPER_LIMIT);
1058 if (resolution <= 1 || resolution > 1024) {
1059 SNDERR("Invalid resolution value %d", resolution);
1062 if (mode & SND_PCM_NO_SOFTVOL) {
1063 err = snd_pcm_slave_conf(root, slave, &sconf, 0);
1066 err = snd_pcm_open_named_slave(pcmp, name, root, sconf, stream,
1068 snd_config_delete(sconf);
1070 snd_ctl_elem_id_alloca(&ctl_id);
1071 err = snd_pcm_slave_conf(root, slave, &sconf, 1,
1072 SND_PCM_HW_PARAM_FORMAT, 0, &sformat);
1075 if (sformat != SND_PCM_FORMAT_UNKNOWN &&
1076 sformat != SND_PCM_FORMAT_S16_LE &&
1077 sformat != SND_PCM_FORMAT_S16_BE &&
1078 sformat != SND_PCM_FORMAT_S24_3LE &&
1079 sformat != SND_PCM_FORMAT_S32_LE &&
1080 sformat != SND_PCM_FORMAT_S32_BE) {
1081 SNDERR("only S16_LE, S16_BE, S24_3LE, S32_LE or S32_BE format "
1083 snd_config_delete(sconf);
1086 err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
1087 snd_config_delete(sconf);
1090 if ((err = snd_pcm_parse_control_id(control, ctl_id, &card, &cchannels, NULL)) < 0) {
1091 snd_pcm_close(spcm);
1094 err = snd_pcm_softvol_open(pcmp, name, sformat, card, ctl_id, cchannels,
1095 min_dB, max_dB, resolution, spcm, 1);
1097 snd_pcm_close(spcm);
1102 SND_DLSYM_BUILD_VERSION(_snd_pcm_softvol_open, SND_PCM_DLSYM_VERSION);