1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2005-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 /* feeder_volume, a long 'Lost Technology' rather than a new feature. */ 30 31 #ifdef _KERNEL 32 #ifdef HAVE_KERNEL_OPTION_HEADERS 33 #include "opt_snd.h" 34 #endif 35 #include <dev/sound/pcm/sound.h> 36 #include <dev/sound/pcm/pcm.h> 37 #include "feeder_if.h" 38 39 #define SND_USE_FXDIV 40 #include "snd_fxdiv_gen.h" 41 42 SND_DECLARE_FILE("$FreeBSD$"); 43 #endif 44 45 typedef void (*feed_volume_t)(int *, int *, uint32_t, uint8_t *, uint32_t); 46 47 #define FEEDVOLUME_CALC8(s, v) (SND_VOL_CALC_SAMPLE((intpcm_t) \ 48 (s) << 8, v) >> 8) 49 #define FEEDVOLUME_CALC16(s, v) SND_VOL_CALC_SAMPLE((intpcm_t)(s), v) 50 #define FEEDVOLUME_CALC24(s, v) SND_VOL_CALC_SAMPLE((intpcm64_t)(s), v) 51 #define FEEDVOLUME_CALC32(s, v) SND_VOL_CALC_SAMPLE((intpcm64_t)(s), v) 52 53 #define FEEDVOLUME_DECLARE(SIGN, BIT, ENDIAN) \ 54 static void \ 55 feed_volume_##SIGN##BIT##ENDIAN(int *vol, int *matrix, \ 56 uint32_t channels, uint8_t *dst, uint32_t count) \ 57 { \ 58 intpcm##BIT##_t v; \ 59 intpcm_t x; \ 60 uint32_t i; \ 61 \ 62 dst += count * PCM_##BIT##_BPS * channels; \ 63 do { \ 64 i = channels; \ 65 do { \ 66 dst -= PCM_##BIT##_BPS; \ 67 i--; \ 68 x = PCM_READ_##SIGN##BIT##_##ENDIAN(dst); \ 69 v = FEEDVOLUME_CALC##BIT(x, vol[matrix[i]]); \ 70 x = PCM_CLAMP_##SIGN##BIT(v); \ 71 _PCM_WRITE_##SIGN##BIT##_##ENDIAN(dst, x); \ 72 } while (i != 0); \ 73 } while (--count != 0); \ 74 } 75 76 #if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) 77 FEEDVOLUME_DECLARE(S, 16, LE) 78 FEEDVOLUME_DECLARE(S, 32, LE) 79 #endif 80 #if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) 81 FEEDVOLUME_DECLARE(S, 16, BE) 82 FEEDVOLUME_DECLARE(S, 32, BE) 83 #endif 84 #ifdef SND_FEEDER_MULTIFORMAT 85 FEEDVOLUME_DECLARE(S, 8, NE) 86 FEEDVOLUME_DECLARE(S, 24, LE) 87 FEEDVOLUME_DECLARE(S, 24, BE) 88 FEEDVOLUME_DECLARE(U, 8, NE) 89 FEEDVOLUME_DECLARE(U, 16, LE) 90 FEEDVOLUME_DECLARE(U, 24, LE) 91 FEEDVOLUME_DECLARE(U, 32, LE) 92 FEEDVOLUME_DECLARE(U, 16, BE) 93 FEEDVOLUME_DECLARE(U, 24, BE) 94 FEEDVOLUME_DECLARE(U, 32, BE) 95 #endif 96 97 struct feed_volume_info { 98 uint32_t bps, channels; 99 feed_volume_t apply; 100 int volume_class; 101 int state; 102 int matrix[SND_CHN_MAX]; 103 }; 104 105 #define FEEDVOLUME_ENTRY(SIGN, BIT, ENDIAN) \ 106 { \ 107 AFMT_##SIGN##BIT##_##ENDIAN, \ 108 feed_volume_##SIGN##BIT##ENDIAN \ 109 } 110 111 static const struct { 112 uint32_t format; 113 feed_volume_t apply; 114 } feed_volume_info_tab[] = { 115 #if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) 116 FEEDVOLUME_ENTRY(S, 16, LE), 117 FEEDVOLUME_ENTRY(S, 32, LE), 118 #endif 119 #if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) 120 FEEDVOLUME_ENTRY(S, 16, BE), 121 FEEDVOLUME_ENTRY(S, 32, BE), 122 #endif 123 #ifdef SND_FEEDER_MULTIFORMAT 124 FEEDVOLUME_ENTRY(S, 8, NE), 125 FEEDVOLUME_ENTRY(S, 24, LE), 126 FEEDVOLUME_ENTRY(S, 24, BE), 127 FEEDVOLUME_ENTRY(U, 8, NE), 128 FEEDVOLUME_ENTRY(U, 16, LE), 129 FEEDVOLUME_ENTRY(U, 24, LE), 130 FEEDVOLUME_ENTRY(U, 32, LE), 131 FEEDVOLUME_ENTRY(U, 16, BE), 132 FEEDVOLUME_ENTRY(U, 24, BE), 133 FEEDVOLUME_ENTRY(U, 32, BE) 134 #endif 135 }; 136 137 #define FEEDVOLUME_TAB_SIZE ((int32_t) \ 138 (sizeof(feed_volume_info_tab) / \ 139 sizeof(feed_volume_info_tab[0]))) 140 141 static int 142 feed_volume_init(struct pcm_feeder *f) 143 { 144 struct feed_volume_info *info; 145 struct pcmchan_matrix *m; 146 uint32_t i; 147 int ret; 148 149 if (f->desc->in != f->desc->out || 150 AFMT_CHANNEL(f->desc->in) > SND_CHN_MAX) 151 return (EINVAL); 152 153 for (i = 0; i < FEEDVOLUME_TAB_SIZE; i++) { 154 if (AFMT_ENCODING(f->desc->in) == 155 feed_volume_info_tab[i].format) { 156 info = malloc(sizeof(*info), M_DEVBUF, 157 M_NOWAIT | M_ZERO); 158 if (info == NULL) 159 return (ENOMEM); 160 161 info->bps = AFMT_BPS(f->desc->in); 162 info->channels = AFMT_CHANNEL(f->desc->in); 163 info->apply = feed_volume_info_tab[i].apply; 164 info->volume_class = SND_VOL_C_PCM; 165 info->state = FEEDVOLUME_ENABLE; 166 167 f->data = info; 168 m = feeder_matrix_default_channel_map(info->channels); 169 if (m == NULL) { 170 free(info, M_DEVBUF); 171 return (EINVAL); 172 } 173 174 ret = feeder_volume_apply_matrix(f, m); 175 if (ret != 0) 176 free(info, M_DEVBUF); 177 178 return (ret); 179 } 180 } 181 182 return (EINVAL); 183 } 184 185 static int 186 feed_volume_free(struct pcm_feeder *f) 187 { 188 struct feed_volume_info *info; 189 190 info = f->data; 191 if (info != NULL) 192 free(info, M_DEVBUF); 193 194 f->data = NULL; 195 196 return (0); 197 } 198 199 static int 200 feed_volume_set(struct pcm_feeder *f, int what, int value) 201 { 202 struct feed_volume_info *info; 203 struct pcmchan_matrix *m; 204 int ret; 205 206 info = f->data; 207 ret = 0; 208 209 switch (what) { 210 case FEEDVOLUME_CLASS: 211 if (value < SND_VOL_C_BEGIN || value > SND_VOL_C_END) 212 return (EINVAL); 213 info->volume_class = value; 214 break; 215 case FEEDVOLUME_CHANNELS: 216 if (value < SND_CHN_MIN || value > SND_CHN_MAX) 217 return (EINVAL); 218 m = feeder_matrix_default_channel_map(value); 219 if (m == NULL) 220 return (EINVAL); 221 ret = feeder_volume_apply_matrix(f, m); 222 break; 223 case FEEDVOLUME_STATE: 224 if (!(value == FEEDVOLUME_ENABLE || value == FEEDVOLUME_BYPASS)) 225 return (EINVAL); 226 info->state = value; 227 break; 228 default: 229 return (EINVAL); 230 break; 231 } 232 233 return (ret); 234 } 235 236 static int 237 feed_volume_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, 238 uint32_t count, void *source) 239 { 240 int temp_vol[SND_CHN_T_VOL_MAX]; 241 struct feed_volume_info *info; 242 uint32_t j, align; 243 int i, *matrix; 244 uint8_t *dst; 245 const int16_t *vol; 246 const int8_t *muted; 247 248 /* 249 * Fetch filter data operation. 250 */ 251 info = f->data; 252 253 if (info->state == FEEDVOLUME_BYPASS) 254 return (FEEDER_FEED(f->source, c, b, count, source)); 255 256 vol = c->volume[SND_VOL_C_VAL(info->volume_class)]; 257 muted = c->muted[SND_VOL_C_VAL(info->volume_class)]; 258 matrix = info->matrix; 259 260 /* 261 * First, let see if we really need to apply gain at all. 262 */ 263 j = 0; 264 i = info->channels; 265 while (i--) { 266 if (vol[matrix[i]] != SND_VOL_FLAT || 267 muted[matrix[i]] != 0) { 268 j = 1; 269 break; 270 } 271 } 272 273 /* Nope, just bypass entirely. */ 274 if (j == 0) 275 return (FEEDER_FEED(f->source, c, b, count, source)); 276 277 /* Check if any controls are muted. */ 278 for (j = 0; j != SND_CHN_T_VOL_MAX; j++) 279 temp_vol[j] = muted[j] ? 0 : vol[j]; 280 281 dst = b; 282 align = info->bps * info->channels; 283 284 do { 285 if (count < align) 286 break; 287 288 j = SND_FXDIV(FEEDER_FEED(f->source, c, dst, count, source), 289 align); 290 if (j == 0) 291 break; 292 293 info->apply(temp_vol, matrix, info->channels, dst, j); 294 295 j *= align; 296 dst += j; 297 count -= j; 298 299 } while (count != 0); 300 301 return (dst - b); 302 } 303 304 static struct pcm_feederdesc feeder_volume_desc[] = { 305 { FEEDER_VOLUME, 0, 0, 0, 0 }, 306 { 0, 0, 0, 0, 0 } 307 }; 308 309 static kobj_method_t feeder_volume_methods[] = { 310 KOBJMETHOD(feeder_init, feed_volume_init), 311 KOBJMETHOD(feeder_free, feed_volume_free), 312 KOBJMETHOD(feeder_set, feed_volume_set), 313 KOBJMETHOD(feeder_feed, feed_volume_feed), 314 KOBJMETHOD_END 315 }; 316 317 FEEDER_DECLARE(feeder_volume, NULL); 318 319 /* Extern */ 320 321 /* 322 * feeder_volume_apply_matrix(): For given matrix map, apply its configuration 323 * to feeder_volume matrix structure. There are 324 * possibilites that feeder_volume be inserted 325 * before or after feeder_matrix, which in this 326 * case feeder_volume must be in a good terms 327 * with _current_ matrix. 328 */ 329 int 330 feeder_volume_apply_matrix(struct pcm_feeder *f, struct pcmchan_matrix *m) 331 { 332 struct feed_volume_info *info; 333 uint32_t i; 334 335 if (f == NULL || f->desc == NULL || f->desc->type != FEEDER_VOLUME || 336 f->data == NULL || m == NULL || m->channels < SND_CHN_MIN || 337 m->channels > SND_CHN_MAX) 338 return (EINVAL); 339 340 info = f->data; 341 342 for (i = 0; i < (sizeof(info->matrix) / sizeof(info->matrix[0])); i++) { 343 if (i < m->channels) 344 info->matrix[i] = m->map[i].type; 345 else 346 info->matrix[i] = SND_CHN_T_FL; 347 } 348 349 info->channels = m->channels; 350 351 return (0); 352 } 353