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