xref: /freebsd/sys/dev/sound/pcm/feeder_matrix.c (revision c597c557923288eff527594619a692a44ade909a)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
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_matrix: Generic any-to-any channel matrixing. Probably not the
31  *                accurate way of doing things, but it should be fast and
32  *                transparent enough, not to mention capable of handling
33  *                possible non-standard way of multichannel interleaving
34  *                order. In other words, it is tough to break.
35  *
36  * The Good:
37  * + very generic and compact, provided that the supplied matrix map is in a
38  *   sane form.
39  * + should be fast enough.
40  *
41  * The Bad:
42  * + somebody might disagree with it.
43  * + 'matrix' is kind of 0x7a69, due to prolong mental block.
44  */
45 
46 #ifdef _KERNEL
47 #ifdef HAVE_KERNEL_OPTION_HEADERS
48 #include "opt_snd.h"
49 #endif
50 #include <dev/sound/pcm/sound.h>
51 #include <dev/sound/pcm/pcm.h>
52 #include "feeder_if.h"
53 
54 #define SND_USE_FXDIV
55 #include "snd_fxdiv_gen.h"
56 #endif
57 
58 #define FEEDMATRIX_RESERVOIR	(SND_CHN_MAX * PCM_32_BPS)
59 
60 #define SND_CHN_T_EOF		0x00e0fe0f
61 #define SND_CHN_T_NULL		0x0e0e0e0e
62 
63 struct feed_matrix_info;
64 
65 typedef void (*feed_matrix_t)(struct feed_matrix_info *, uint8_t *,
66     uint8_t *, uint32_t);
67 
68 struct feed_matrix_info {
69 	uint32_t bps;
70 	uint32_t ialign, oalign;
71 	uint32_t in, out;
72 	feed_matrix_t apply;
73 #ifdef FEEDMATRIX_GENERIC
74 	intpcm_read_t *rd;
75 	intpcm_write_t *wr;
76 #endif
77 	struct {
78 		int chn[SND_CHN_T_MAX + 1];
79 		int mul, shift;
80 	} matrix[SND_CHN_T_MAX + 1];
81 	uint8_t reservoir[FEEDMATRIX_RESERVOIR];
82 };
83 
84 static struct pcmchan_matrix feeder_matrix_maps[SND_CHN_MATRIX_MAX] = {
85 	[SND_CHN_MATRIX_1_0] = SND_CHN_MATRIX_MAP_1_0,
86 	[SND_CHN_MATRIX_2_0] = SND_CHN_MATRIX_MAP_2_0,
87 	[SND_CHN_MATRIX_2_1] = SND_CHN_MATRIX_MAP_2_1,
88 	[SND_CHN_MATRIX_3_0] = SND_CHN_MATRIX_MAP_3_0,
89 	[SND_CHN_MATRIX_3_1] = SND_CHN_MATRIX_MAP_3_1,
90 	[SND_CHN_MATRIX_4_0] = SND_CHN_MATRIX_MAP_4_0,
91 	[SND_CHN_MATRIX_4_1] = SND_CHN_MATRIX_MAP_4_1,
92 	[SND_CHN_MATRIX_5_0] = SND_CHN_MATRIX_MAP_5_0,
93 	[SND_CHN_MATRIX_5_1] = SND_CHN_MATRIX_MAP_5_1,
94 	[SND_CHN_MATRIX_6_0] = SND_CHN_MATRIX_MAP_6_0,
95 	[SND_CHN_MATRIX_6_1] = SND_CHN_MATRIX_MAP_6_1,
96 	[SND_CHN_MATRIX_7_0] = SND_CHN_MATRIX_MAP_7_0,
97 	[SND_CHN_MATRIX_7_1] = SND_CHN_MATRIX_MAP_7_1
98 };
99 
100 static int feeder_matrix_default_ids[9] = {
101 	[0] = SND_CHN_MATRIX_UNKNOWN,
102 	[1] = SND_CHN_MATRIX_1,
103 	[2] = SND_CHN_MATRIX_2,
104 	[3] = SND_CHN_MATRIX_3,
105 	[4] = SND_CHN_MATRIX_4,
106 	[5] = SND_CHN_MATRIX_5,
107 	[6] = SND_CHN_MATRIX_6,
108 	[7] = SND_CHN_MATRIX_7,
109 	[8] = SND_CHN_MATRIX_8
110 };
111 
112 #ifdef _KERNEL
113 #define FEEDMATRIX_CLIP_CHECK(...)
114 #else
115 #define FEEDMATRIX_CLIP_CHECK(v, BIT)	do {				\
116 	if ((v) < PCM_S##BIT##_MIN || (v) > PCM_S##BIT##_MAX)		\
117 	    errx(1, "\n\n%s(): Sample clipping: %jd\n",			\
118 		__func__, (intmax_t)(v));				\
119 } while (0)
120 #endif
121 
122 #define FEEDMATRIX_DECLARE(SIGN, BIT, ENDIAN)				\
123 static void								\
124 feed_matrix_##SIGN##BIT##ENDIAN(struct feed_matrix_info *info,		\
125     uint8_t *src, uint8_t *dst, uint32_t count)				\
126 {									\
127 	intpcm64_t accum;						\
128 	intpcm_t v;							\
129 	int i, j;							\
130 									\
131 	do {								\
132 		for (i = 0; info->matrix[i].chn[0] != SND_CHN_T_EOF;	\
133 		    i++) {						\
134 			if (info->matrix[i].chn[0] == SND_CHN_T_NULL) {	\
135 				_PCM_WRITE_##SIGN##BIT##_##ENDIAN(dst,	\
136 				    0);					\
137 				dst += PCM_##BIT##_BPS;			\
138 				continue;				\
139 			} else if (info->matrix[i].chn[1] ==		\
140 			    SND_CHN_T_EOF) {				\
141 				v = _PCM_READ_##SIGN##BIT##_##ENDIAN(	\
142 				    src + info->matrix[i].chn[0]);	\
143 				_PCM_WRITE_##SIGN##BIT##_##ENDIAN(dst,	\
144 				    v);					\
145 				dst += PCM_##BIT##_BPS;			\
146 				continue;				\
147 			}						\
148 									\
149 			accum = 0;					\
150 			for (j = 0;					\
151 			    info->matrix[i].chn[j] != SND_CHN_T_EOF;	\
152 			    j++) {					\
153 				v = _PCM_READ_##SIGN##BIT##_##ENDIAN(	\
154 				    src + info->matrix[i].chn[j]);	\
155 				accum += v;				\
156 			}						\
157 									\
158 			accum = (accum * info->matrix[i].mul) >>	\
159 			    info->matrix[i].shift;			\
160 									\
161 			FEEDMATRIX_CLIP_CHECK(accum, BIT);		\
162 									\
163 			v = (accum > PCM_S##BIT##_MAX) ?		\
164 			    PCM_S##BIT##_MAX :				\
165 			    ((accum < PCM_S##BIT##_MIN) ?		\
166 			    PCM_S##BIT##_MIN :				\
167 			    accum);					\
168 			_PCM_WRITE_##SIGN##BIT##_##ENDIAN(dst, v);	\
169 			dst += PCM_##BIT##_BPS;				\
170 		}							\
171 		src += info->ialign;					\
172 	} while (--count != 0);						\
173 }
174 
175 #if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
176 FEEDMATRIX_DECLARE(S, 16, LE)
177 FEEDMATRIX_DECLARE(S, 32, LE)
178 #endif
179 #if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
180 FEEDMATRIX_DECLARE(S, 16, BE)
181 FEEDMATRIX_DECLARE(S, 32, BE)
182 #endif
183 #ifdef SND_FEEDER_MULTIFORMAT
184 FEEDMATRIX_DECLARE(S,  8, NE)
185 FEEDMATRIX_DECLARE(S, 24, LE)
186 FEEDMATRIX_DECLARE(S, 24, BE)
187 FEEDMATRIX_DECLARE(U,  8, NE)
188 FEEDMATRIX_DECLARE(U, 16, LE)
189 FEEDMATRIX_DECLARE(U, 24, LE)
190 FEEDMATRIX_DECLARE(U, 32, LE)
191 FEEDMATRIX_DECLARE(U, 16, BE)
192 FEEDMATRIX_DECLARE(U, 24, BE)
193 FEEDMATRIX_DECLARE(U, 32, BE)
194 #endif
195 
196 #define FEEDMATRIX_ENTRY(SIGN, BIT, ENDIAN)				\
197 	{								\
198 		AFMT_##SIGN##BIT##_##ENDIAN,				\
199 		feed_matrix_##SIGN##BIT##ENDIAN				\
200 	}
201 
202 static const struct {
203 	uint32_t format;
204 	feed_matrix_t apply;
205 } feed_matrix_tab[] = {
206 #if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
207 	FEEDMATRIX_ENTRY(S, 16, LE),
208 	FEEDMATRIX_ENTRY(S, 32, LE),
209 #endif
210 #if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
211 	FEEDMATRIX_ENTRY(S, 16, BE),
212 	FEEDMATRIX_ENTRY(S, 32, BE),
213 #endif
214 #ifdef SND_FEEDER_MULTIFORMAT
215 	FEEDMATRIX_ENTRY(S,  8, NE),
216 	FEEDMATRIX_ENTRY(S, 24, LE),
217 	FEEDMATRIX_ENTRY(S, 24, BE),
218 	FEEDMATRIX_ENTRY(U,  8, NE),
219 	FEEDMATRIX_ENTRY(U, 16, LE),
220 	FEEDMATRIX_ENTRY(U, 24, LE),
221 	FEEDMATRIX_ENTRY(U, 32, LE),
222 	FEEDMATRIX_ENTRY(U, 16, BE),
223 	FEEDMATRIX_ENTRY(U, 24, BE),
224 	FEEDMATRIX_ENTRY(U, 32, BE)
225 #endif
226 };
227 
228 static void
229 feed_matrix_reset(struct feed_matrix_info *info)
230 {
231 	uint32_t i, j;
232 
233 	for (i = 0; i < nitems(info->matrix); i++) {
234 		for (j = 0;
235 		    j < (sizeof(info->matrix[i].chn) /
236 		    sizeof(info->matrix[i].chn[0])); j++) {
237 			info->matrix[i].chn[j] = SND_CHN_T_EOF;
238 		}
239 		info->matrix[i].mul   = 1;
240 		info->matrix[i].shift = 0;
241 	}
242 }
243 
244 #ifdef FEEDMATRIX_GENERIC
245 static void
246 feed_matrix_apply_generic(struct feed_matrix_info *info,
247     uint8_t *src, uint8_t *dst, uint32_t count)
248 {
249 	intpcm64_t accum;
250 	intpcm_t v;
251 	int i, j;
252 
253 	do {
254 		for (i = 0; info->matrix[i].chn[0] != SND_CHN_T_EOF;
255 		    i++) {
256 			if (info->matrix[i].chn[0] == SND_CHN_T_NULL) {
257 				info->wr(dst, 0);
258 				dst += info->bps;
259 				continue;
260 			} else if (info->matrix[i].chn[1] ==
261 			    SND_CHN_T_EOF) {
262 				v = info->rd(src + info->matrix[i].chn[0]);
263 				info->wr(dst, v);
264 				dst += info->bps;
265 				continue;
266 			}
267 
268 			accum = 0;
269 			for (j = 0;
270 			    info->matrix[i].chn[j] != SND_CHN_T_EOF;
271 			    j++) {
272 				v = info->rd(src + info->matrix[i].chn[j]);
273 				accum += v;
274 			}
275 
276 			accum = (accum * info->matrix[i].mul) >>
277 			    info->matrix[i].shift;
278 
279 			FEEDMATRIX_CLIP_CHECK(accum, 32);
280 
281 			v = (accum > PCM_S32_MAX) ? PCM_S32_MAX :
282 			    ((accum < PCM_S32_MIN) ? PCM_S32_MIN : accum);
283 			info->wr(dst, v);
284 			dst += info->bps;
285 		}
286 		src += info->ialign;
287 	} while (--count != 0);
288 }
289 #endif
290 
291 static int
292 feed_matrix_setup(struct feed_matrix_info *info, struct pcmchan_matrix *m_in,
293     struct pcmchan_matrix *m_out)
294 {
295 	uint32_t i, j, ch, in_mask, merge_mask;
296 	int mul, shift;
297 
298 	if (info == NULL || m_in == NULL || m_out == NULL ||
299 	    AFMT_CHANNEL(info->in) != m_in->channels ||
300 	    AFMT_CHANNEL(info->out) != m_out->channels ||
301 	    m_in->channels < SND_CHN_MIN || m_in->channels > SND_CHN_MAX ||
302 	    m_out->channels < SND_CHN_MIN || m_out->channels > SND_CHN_MAX)
303 		return (EINVAL);
304 
305 	feed_matrix_reset(info);
306 
307 	/*
308 	 * If both in and out are part of standard matrix and identical, skip
309 	 * everything altogether.
310 	 */
311 	if (m_in->id == m_out->id && !(m_in->id < SND_CHN_MATRIX_BEGIN ||
312 	    m_in->id > SND_CHN_MATRIX_END))
313 		return (0);
314 
315 	/*
316 	 * Special case for mono input matrix. If the output supports
317 	 * possible 'center' channel, route it there. Otherwise, let it be
318 	 * matrixed to left/right.
319 	 */
320 	if (m_in->id == SND_CHN_MATRIX_1_0) {
321 		if (m_out->id == SND_CHN_MATRIX_1_0)
322 			in_mask = SND_CHN_T_MASK_FL;
323 		else if (m_out->mask & SND_CHN_T_MASK_FC)
324 			in_mask = SND_CHN_T_MASK_FC;
325 		else
326 			in_mask = SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR;
327 	} else
328 		in_mask = m_in->mask;
329 
330 	/* Merge, reduce, expand all possibilites. */
331 	for (ch = SND_CHN_T_BEGIN; ch <= SND_CHN_T_END &&
332 	    m_out->map[ch].type != SND_CHN_T_MAX; ch += SND_CHN_T_STEP) {
333 		merge_mask = m_out->map[ch].members & in_mask;
334 		if (merge_mask == 0) {
335 			info->matrix[ch].chn[0] = SND_CHN_T_NULL;
336 			continue;
337 		}
338 
339 		j = 0;
340 		for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END;
341 		    i += SND_CHN_T_STEP) {
342 			if (merge_mask & (1 << i)) {
343 				if (m_in->offset[i] >= 0 &&
344 				    m_in->offset[i] < (int)m_in->channels)
345 					info->matrix[ch].chn[j++] =
346 					    m_in->offset[i] * info->bps;
347 				else {
348 					info->matrix[ch].chn[j++] =
349 					    SND_CHN_T_EOF;
350 					break;
351 				}
352 			}
353 		}
354 
355 #define FEEDMATRIX_ATTN_SHIFT	16
356 
357 		if (j > 1) {
358 			/*
359 			 * XXX For channel that require accumulation from
360 			 * multiple channels, apply a slight attenuation to
361 			 * avoid clipping.
362 			 */
363 			mul   = (1 << (FEEDMATRIX_ATTN_SHIFT - 1)) + 143 - j;
364 			shift = FEEDMATRIX_ATTN_SHIFT;
365 			while ((mul & 1) == 0 && shift > 0) {
366 				mul >>= 1;
367 				shift--;
368 			}
369 			info->matrix[ch].mul   = mul;
370 			info->matrix[ch].shift = shift;
371 		}
372 	}
373 
374 #ifndef _KERNEL
375 	fprintf(stderr, "Total: %d\n", ch);
376 
377 	for (i = 0; info->matrix[i].chn[0] != SND_CHN_T_EOF; i++) {
378 		fprintf(stderr, "%d: [", i);
379 		for (j = 0; info->matrix[i].chn[j] != SND_CHN_T_EOF; j++) {
380 			if (j != 0)
381 				fprintf(stderr, ", ");
382 			fprintf(stderr, "%d",
383 			    (info->matrix[i].chn[j] == SND_CHN_T_NULL) ?
384 			    0xffffffff : info->matrix[i].chn[j] / info->bps);
385 		}
386 		fprintf(stderr, "] attn: (x * %d) >> %d\n",
387 		    info->matrix[i].mul, info->matrix[i].shift);
388 	}
389 #endif
390 
391 	return (0);
392 }
393 
394 static int
395 feed_matrix_init(struct pcm_feeder *f)
396 {
397 	struct feed_matrix_info *info;
398 	struct pcmchan_matrix *m_in, *m_out;
399 	uint32_t i;
400 	int ret;
401 
402 	if (AFMT_ENCODING(f->desc->in) != AFMT_ENCODING(f->desc->out))
403 		return (EINVAL);
404 
405 	info = malloc(sizeof(*info), M_DEVBUF, M_NOWAIT | M_ZERO);
406 	if (info == NULL)
407 		return (ENOMEM);
408 
409 	info->in = f->desc->in;
410 	info->out = f->desc->out;
411 	info->bps = AFMT_BPS(info->in);
412 	info->ialign = AFMT_ALIGN(info->in);
413 	info->oalign = AFMT_ALIGN(info->out);
414 	info->apply = NULL;
415 
416 	for (i = 0; info->apply == NULL &&
417 	    i < (sizeof(feed_matrix_tab) / sizeof(feed_matrix_tab[0])); i++) {
418 		if (AFMT_ENCODING(info->in) == feed_matrix_tab[i].format)
419 			info->apply = feed_matrix_tab[i].apply;
420 	}
421 
422 	if (info->apply == NULL) {
423 #ifdef FEEDMATRIX_GENERIC
424 		info->rd = feeder_format_read_op(info->in);
425 		info->wr = feeder_format_write_op(info->out);
426 		if (info->rd == NULL || info->wr == NULL) {
427 			free(info, M_DEVBUF);
428 			return (EINVAL);
429 		}
430 		info->apply = feed_matrix_apply_generic;
431 #else
432 		free(info, M_DEVBUF);
433 		return (EINVAL);
434 #endif
435 	}
436 
437 	m_in  = feeder_matrix_format_map(info->in);
438 	m_out = feeder_matrix_format_map(info->out);
439 
440 	ret = feed_matrix_setup(info, m_in, m_out);
441 	if (ret != 0) {
442 		free(info, M_DEVBUF);
443 		return (ret);
444 	}
445 
446 	f->data = info;
447 
448 	return (0);
449 }
450 
451 static int
452 feed_matrix_free(struct pcm_feeder *f)
453 {
454 	struct feed_matrix_info *info;
455 
456 	info = f->data;
457 	if (info != NULL)
458 		free(info, M_DEVBUF);
459 
460 	f->data = NULL;
461 
462 	return (0);
463 }
464 
465 static int
466 feed_matrix_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
467     uint32_t count, void *source)
468 {
469 	struct feed_matrix_info *info;
470 	uint32_t j, inmax;
471 	uint8_t *src, *dst;
472 
473 	info = f->data;
474 	if (info->matrix[0].chn[0] == SND_CHN_T_EOF)
475 		return (FEEDER_FEED(f->source, c, b, count, source));
476 
477 	dst = b;
478 	count = SND_FXROUND(count, info->oalign);
479 	inmax = info->ialign + info->oalign;
480 
481 	/*
482 	 * This loop might look simmilar to other feeder_* loops, but be
483 	 * advised: matrixing might involve overlapping (think about
484 	 * swapping end to front or something like that). In this regard it
485 	 * might be simmilar to feeder_format, but feeder_format works on
486 	 * 'sample' domain where it can be fitted into single 32bit integer
487 	 * while matrixing works on 'sample frame' domain.
488 	 */
489 	do {
490 		if (count < info->oalign)
491 			break;
492 
493 		if (count < inmax) {
494 			src = info->reservoir;
495 			j = info->ialign;
496 		} else {
497 			if (info->ialign == info->oalign)
498 				j = count - info->oalign;
499 			else if (info->ialign > info->oalign)
500 				j = SND_FXROUND(count - info->oalign,
501 				    info->ialign);
502 			else
503 				j = (SND_FXDIV(count, info->oalign) - 1) *
504 				    info->ialign;
505 			src = dst + count - j;
506 		}
507 
508 		j = SND_FXDIV(FEEDER_FEED(f->source, c, src, j, source),
509 		    info->ialign);
510 		if (j == 0)
511 			break;
512 
513 		info->apply(info, src, dst, j);
514 
515 		j *= info->oalign;
516 		dst += j;
517 		count -= j;
518 
519 	} while (count != 0);
520 
521 	return (dst - b);
522 }
523 
524 static struct pcm_feederdesc feeder_matrix_desc[] = {
525 	{ FEEDER_MATRIX, 0, 0, 0, 0 },
526 	{ 0, 0, 0, 0, 0 }
527 };
528 
529 static kobj_method_t feeder_matrix_methods[] = {
530 	KOBJMETHOD(feeder_init,		feed_matrix_init),
531 	KOBJMETHOD(feeder_free,		feed_matrix_free),
532 	KOBJMETHOD(feeder_feed,		feed_matrix_feed),
533 	KOBJMETHOD_END
534 };
535 
536 FEEDER_DECLARE(feeder_matrix, NULL);
537 
538 /* External */
539 int
540 feeder_matrix_setup(struct pcm_feeder *f, struct pcmchan_matrix *m_in,
541     struct pcmchan_matrix *m_out)
542 {
543 
544 	if (f == NULL || f->desc == NULL || f->desc->type != FEEDER_MATRIX ||
545 	    f->data == NULL)
546 		return (EINVAL);
547 
548 	return (feed_matrix_setup(f->data, m_in, m_out));
549 }
550 
551 /*
552  * feeder_matrix_default_id(): For a given number of channels, return
553  *                             default preferred id (example: both 5.1 and
554  *                             6.0 are simply 6 channels, but 5.1 is more
555  *                             preferable).
556  */
557 int
558 feeder_matrix_default_id(uint32_t ch)
559 {
560 
561 	if (ch < feeder_matrix_maps[SND_CHN_MATRIX_BEGIN].channels ||
562 	    ch > feeder_matrix_maps[SND_CHN_MATRIX_END].channels)
563 		return (SND_CHN_MATRIX_UNKNOWN);
564 
565 	return (feeder_matrix_maps[feeder_matrix_default_ids[ch]].id);
566 }
567 
568 /*
569  * feeder_matrix_default_channel_map(): Ditto, but return matrix map
570  *                                      instead.
571  */
572 struct pcmchan_matrix *
573 feeder_matrix_default_channel_map(uint32_t ch)
574 {
575 
576 	if (ch < feeder_matrix_maps[SND_CHN_MATRIX_BEGIN].channels ||
577 	    ch > feeder_matrix_maps[SND_CHN_MATRIX_END].channels)
578 		return (NULL);
579 
580 	return (&feeder_matrix_maps[feeder_matrix_default_ids[ch]]);
581 }
582 
583 /*
584  * feeder_matrix_default_format(): For a given audio format, return the
585  *                                 proper audio format based on preferable
586  *                                 matrix.
587  */
588 uint32_t
589 feeder_matrix_default_format(uint32_t format)
590 {
591 	struct pcmchan_matrix *m;
592 	uint32_t i, ch, ext;
593 
594 	ch = AFMT_CHANNEL(format);
595 	ext = AFMT_EXTCHANNEL(format);
596 
597 	if (ext != 0) {
598 		for (i = SND_CHN_MATRIX_BEGIN; i <= SND_CHN_MATRIX_END; i++) {
599 			if (feeder_matrix_maps[i].channels == ch &&
600 			    feeder_matrix_maps[i].ext == ext)
601 			return (SND_FORMAT(format, ch, ext));
602 		}
603 	}
604 
605 	m = feeder_matrix_default_channel_map(ch);
606 	if (m == NULL)
607 		return (0x00000000);
608 
609 	return (SND_FORMAT(format, ch, m->ext));
610 }
611 
612 /*
613  * feeder_matrix_format_id(): For a given audio format, return its matrix
614  *                            id.
615  */
616 int
617 feeder_matrix_format_id(uint32_t format)
618 {
619 	uint32_t i, ch, ext;
620 
621 	ch = AFMT_CHANNEL(format);
622 	ext = AFMT_EXTCHANNEL(format);
623 
624 	for (i = SND_CHN_MATRIX_BEGIN; i <= SND_CHN_MATRIX_END; i++) {
625 		if (feeder_matrix_maps[i].channels == ch &&
626 		    feeder_matrix_maps[i].ext == ext)
627 			return (feeder_matrix_maps[i].id);
628 	}
629 
630 	return (SND_CHN_MATRIX_UNKNOWN);
631 }
632 
633 /*
634  * feeder_matrix_format_map(): For a given audio format, return its matrix
635  *                             map.
636  */
637 struct pcmchan_matrix *
638 feeder_matrix_format_map(uint32_t format)
639 {
640 	uint32_t i, ch, ext;
641 
642 	ch = AFMT_CHANNEL(format);
643 	ext = AFMT_EXTCHANNEL(format);
644 
645 	for (i = SND_CHN_MATRIX_BEGIN; i <= SND_CHN_MATRIX_END; i++) {
646 		if (feeder_matrix_maps[i].channels == ch &&
647 		    feeder_matrix_maps[i].ext == ext)
648 			return (&feeder_matrix_maps[i]);
649 	}
650 
651 	return (NULL);
652 }
653 
654 /*
655  * feeder_matrix_id_map(): For a given matrix id, return its matrix map.
656  */
657 struct pcmchan_matrix *
658 feeder_matrix_id_map(int id)
659 {
660 
661 	if (id < SND_CHN_MATRIX_BEGIN || id > SND_CHN_MATRIX_END)
662 		return (NULL);
663 
664 	return (&feeder_matrix_maps[id]);
665 }
666 
667 /*
668  * feeder_matrix_compare(): Compare the simmilarities of matrices.
669  */
670 int
671 feeder_matrix_compare(struct pcmchan_matrix *m_in, struct pcmchan_matrix *m_out)
672 {
673 	uint32_t i;
674 
675 	if (m_in == m_out)
676 		return (0);
677 
678 	if (m_in->channels != m_out->channels || m_in->ext != m_out->ext ||
679 	    m_in->mask != m_out->mask)
680 		return (1);
681 
682 	for (i = 0; i < nitems(m_in->map); i++) {
683 		if (m_in->map[i].type != m_out->map[i].type)
684 			return (1);
685 		if (m_in->map[i].type == SND_CHN_T_MAX)
686 			break;
687 		if (m_in->map[i].members != m_out->map[i].members)
688 			return (1);
689 		if (i <= SND_CHN_T_END) {
690 			if (m_in->offset[m_in->map[i].type] !=
691 			    m_out->offset[m_out->map[i].type])
692 				return (1);
693 		}
694 	}
695 
696 	return (0);
697 }
698 
699 /*
700  * XXX 4front interpretation of "surround" is ambigous and sort of
701  *     conflicting with "rear"/"back". Map it to "side". Well..
702  *     who cares?
703  */
704 static int snd_chn_to_oss[SND_CHN_T_MAX] = {
705 	[SND_CHN_T_FL] = CHID_L,
706 	[SND_CHN_T_FR] = CHID_R,
707 	[SND_CHN_T_FC] = CHID_C,
708 	[SND_CHN_T_LF] = CHID_LFE,
709 	[SND_CHN_T_SL] = CHID_LS,
710 	[SND_CHN_T_SR] = CHID_RS,
711 	[SND_CHN_T_BL] = CHID_LR,
712 	[SND_CHN_T_BR] = CHID_RR
713 };
714 
715 #define SND_CHN_OSS_VALIDMASK						\
716 			(SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR |	\
717 			 SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF |	\
718 			 SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR |	\
719 			 SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR)
720 
721 #define SND_CHN_OSS_MAX		8
722 #define SND_CHN_OSS_BEGIN	CHID_L
723 #define SND_CHN_OSS_END		CHID_RR
724 
725 static int oss_to_snd_chn[SND_CHN_OSS_END + 1] = {
726 	[CHID_L]   = SND_CHN_T_FL,
727 	[CHID_R]   = SND_CHN_T_FR,
728 	[CHID_C]   = SND_CHN_T_FC,
729 	[CHID_LFE] = SND_CHN_T_LF,
730 	[CHID_LS]  = SND_CHN_T_SL,
731 	[CHID_RS]  = SND_CHN_T_SR,
732 	[CHID_LR]  = SND_CHN_T_BL,
733 	[CHID_RR]  = SND_CHN_T_BR
734 };
735 
736 /*
737  * Used by SNDCTL_DSP_GET_CHNORDER.
738  */
739 int
740 feeder_matrix_oss_get_channel_order(struct pcmchan_matrix *m,
741     unsigned long long *map)
742 {
743 	unsigned long long tmpmap;
744 	uint32_t i;
745 
746 	if (m == NULL || map == NULL || (m->mask & ~SND_CHN_OSS_VALIDMASK) ||
747 	    m->channels > SND_CHN_OSS_MAX)
748 		return (EINVAL);
749 
750 	tmpmap = 0x0000000000000000ULL;
751 
752 	for (i = 0; i < SND_CHN_OSS_MAX && m->map[i].type != SND_CHN_T_MAX;
753 	    i++) {
754 		if ((1 << m->map[i].type) & ~SND_CHN_OSS_VALIDMASK)
755 			return (EINVAL);
756 		tmpmap |=
757 		    (unsigned long long)snd_chn_to_oss[m->map[i].type] <<
758 		    (i * 4);
759 	}
760 
761 	*map = tmpmap;
762 
763 	return (0);
764 }
765 
766 /*
767  * Used by SNDCTL_DSP_SET_CHNORDER.
768  */
769 int
770 feeder_matrix_oss_set_channel_order(struct pcmchan_matrix *m,
771     unsigned long long *map)
772 {
773 	struct pcmchan_matrix tmp;
774 	uint32_t chmask, i;
775 	int ch, cheof;
776 
777 	if (m == NULL || map == NULL || (m->mask & ~SND_CHN_OSS_VALIDMASK) ||
778 	    m->channels > SND_CHN_OSS_MAX || (*map & 0xffffffff00000000ULL))
779 		return (EINVAL);
780 
781 	tmp = *m;
782 	tmp.channels = 0;
783 	tmp.ext = 0;
784 	tmp.mask = 0;
785 	memset(tmp.offset, -1, sizeof(tmp.offset));
786 	cheof = 0;
787 
788 	for (i = 0; i < SND_CHN_OSS_MAX; i++) {
789 		ch = (*map >> (i * 4)) & 0xf;
790 		if (ch < SND_CHN_OSS_BEGIN) {
791 			if (cheof == 0 && m->map[i].type != SND_CHN_T_MAX)
792 				return (EINVAL);
793 			cheof++;
794 			tmp.map[i] = m->map[i];
795 			continue;
796 		} else if (ch > SND_CHN_OSS_END)
797 			return (EINVAL);
798 		else if (cheof != 0)
799 			return (EINVAL);
800 		ch = oss_to_snd_chn[ch];
801 		chmask = 1 << ch;
802 		/* channel not exist in matrix */
803 		if (!(chmask & m->mask))
804 			return (EINVAL);
805 		/* duplicated channel */
806 		if (chmask & tmp.mask)
807 			return (EINVAL);
808 		tmp.map[i] = m->map[m->offset[ch]];
809 		if (tmp.map[i].type != ch)
810 			return (EINVAL);
811 		tmp.offset[ch] = i;
812 		tmp.mask |= chmask;
813 		tmp.channels++;
814 		if (chmask & SND_CHN_T_MASK_LF)
815 			tmp.ext++;
816 	}
817 
818 	if (tmp.channels != m->channels || tmp.ext != m->ext ||
819 	    tmp.mask != m->mask ||
820 	    tmp.map[m->channels].type != SND_CHN_T_MAX)
821 		return (EINVAL);
822 
823 	*m = tmp;
824 
825 	return (0);
826 }
827