feeder_volume.c revision 330897
1130803Smarcel/*-
2130803Smarcel * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3130803Smarcel *
4130803Smarcel * Copyright (c) 2005-2009 Ariff Abdullah <ariff@FreeBSD.org>
5130803Smarcel * All rights reserved.
6130803Smarcel *
7130803Smarcel * Redistribution and use in source and binary forms, with or without
8130803Smarcel * modification, are permitted provided that the following conditions
9130803Smarcel * are met:
10130803Smarcel * 1. Redistributions of source code must retain the above copyright
11130803Smarcel *    notice, this list of conditions and the following disclaimer.
12130803Smarcel * 2. Redistributions in binary form must reproduce the above copyright
13130803Smarcel *    notice, this list of conditions and the following disclaimer in the
14130803Smarcel *    documentation and/or other materials provided with the distribution.
15130803Smarcel *
16130803Smarcel * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17130803Smarcel * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18130803Smarcel * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19130803Smarcel * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20130803Smarcel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21130803Smarcel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22130803Smarcel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23130803Smarcel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24130803Smarcel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25130803Smarcel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26130803Smarcel * SUCH DAMAGE.
27130803Smarcel */
28130803Smarcel
29130803Smarcel/* feeder_volume, a long 'Lost Technology' rather than a new feature. */
30130803Smarcel
31130803Smarcel#ifdef _KERNEL
32130803Smarcel#ifdef HAVE_KERNEL_OPTION_HEADERS
33130803Smarcel#include "opt_snd.h"
34130803Smarcel#endif
35130803Smarcel#include <dev/sound/pcm/sound.h>
36130803Smarcel#include <dev/sound/pcm/pcm.h>
37130803Smarcel#include "feeder_if.h"
38130803Smarcel
39130803Smarcel#define SND_USE_FXDIV
40130803Smarcel#include "snd_fxdiv_gen.h"
41130803Smarcel
42130803SmarcelSND_DECLARE_FILE("$FreeBSD: stable/11/sys/dev/sound/pcm/feeder_volume.c 330897 2018-03-14 03:19:51Z eadler $");
43130803Smarcel#endif
44130803Smarcel
45130803Smarceltypedef void (*feed_volume_t)(int *, int *, uint32_t, uint8_t *, uint32_t);
46130803Smarcel
47130803Smarcel#define FEEDVOLUME_CALC8(s, v)	(SND_VOL_CALC_SAMPLE((intpcm_t)		\
48130803Smarcel				 (s) << 8, v) >> 8)
49130803Smarcel#define FEEDVOLUME_CALC16(s, v)	SND_VOL_CALC_SAMPLE((intpcm_t)(s), v)
50130803Smarcel#define FEEDVOLUME_CALC24(s, v)	SND_VOL_CALC_SAMPLE((intpcm64_t)(s), v)
51130803Smarcel#define FEEDVOLUME_CALC32(s, v)	SND_VOL_CALC_SAMPLE((intpcm64_t)(s), v)
52130803Smarcel
53130803Smarcel#define FEEDVOLUME_DECLARE(SIGN, BIT, ENDIAN)				\
54130803Smarcelstatic void								\
55130803Smarcelfeed_volume_##SIGN##BIT##ENDIAN(int *vol, int *matrix,			\
56130803Smarcel    uint32_t channels, uint8_t *dst, uint32_t count)			\
57130803Smarcel{									\
58130803Smarcel	intpcm##BIT##_t v;						\
59130803Smarcel	intpcm_t x;							\
60130803Smarcel	uint32_t i;							\
61130803Smarcel									\
62130803Smarcel	dst += count * PCM_##BIT##_BPS * channels;			\
63130803Smarcel	do {								\
64130803Smarcel		i = channels;						\
65130803Smarcel		do {							\
66130803Smarcel			dst -= PCM_##BIT##_BPS;				\
67130803Smarcel			i--;						\
68130803Smarcel			x = PCM_READ_##SIGN##BIT##_##ENDIAN(dst);	\
69130803Smarcel			v = FEEDVOLUME_CALC##BIT(x, vol[matrix[i]]);	\
70130803Smarcel			x = PCM_CLAMP_##SIGN##BIT(v);			\
71130803Smarcel			_PCM_WRITE_##SIGN##BIT##_##ENDIAN(dst, x);	\
72130803Smarcel		} while (i != 0);					\
73130803Smarcel	} while (--count != 0);						\
74130803Smarcel}
75130803Smarcel
76130803Smarcel#if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
77130803SmarcelFEEDVOLUME_DECLARE(S, 16, LE)
78130803SmarcelFEEDVOLUME_DECLARE(S, 32, LE)
79130803Smarcel#endif
80130803Smarcel#if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
81130803SmarcelFEEDVOLUME_DECLARE(S, 16, BE)
82130803SmarcelFEEDVOLUME_DECLARE(S, 32, BE)
83130803Smarcel#endif
84130803Smarcel#ifdef SND_FEEDER_MULTIFORMAT
85130803SmarcelFEEDVOLUME_DECLARE(S,  8, NE)
86130803SmarcelFEEDVOLUME_DECLARE(S, 24, LE)
87130803SmarcelFEEDVOLUME_DECLARE(S, 24, BE)
88130803SmarcelFEEDVOLUME_DECLARE(U,  8, NE)
89130803SmarcelFEEDVOLUME_DECLARE(U, 16, LE)
90130803SmarcelFEEDVOLUME_DECLARE(U, 24, LE)
91130803SmarcelFEEDVOLUME_DECLARE(U, 32, LE)
92130803SmarcelFEEDVOLUME_DECLARE(U, 16, BE)
93130803SmarcelFEEDVOLUME_DECLARE(U, 24, BE)
94130803SmarcelFEEDVOLUME_DECLARE(U, 32, BE)
95130803Smarcel#endif
96130803Smarcel
97130803Smarcelstruct feed_volume_info {
98130803Smarcel	uint32_t bps, channels;
99130803Smarcel	feed_volume_t apply;
100130803Smarcel	int volume_class;
101130803Smarcel	int state;
102130803Smarcel	int matrix[SND_CHN_MAX];
103130803Smarcel};
104130803Smarcel
105130803Smarcel#define FEEDVOLUME_ENTRY(SIGN, BIT, ENDIAN)				\
106130803Smarcel	{								\
107130803Smarcel		AFMT_##SIGN##BIT##_##ENDIAN,				\
108130803Smarcel		feed_volume_##SIGN##BIT##ENDIAN				\
109130803Smarcel	}
110130803Smarcel
111130803Smarcelstatic const struct {
112130803Smarcel	uint32_t format;
113130803Smarcel	feed_volume_t apply;
114130803Smarcel} feed_volume_info_tab[] = {
115130803Smarcel#if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
116130803Smarcel	FEEDVOLUME_ENTRY(S, 16, LE),
117130803Smarcel	FEEDVOLUME_ENTRY(S, 32, LE),
118130803Smarcel#endif
119130803Smarcel#if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
120130803Smarcel	FEEDVOLUME_ENTRY(S, 16, BE),
121130803Smarcel	FEEDVOLUME_ENTRY(S, 32, BE),
122130803Smarcel#endif
123130803Smarcel#ifdef SND_FEEDER_MULTIFORMAT
124130803Smarcel	FEEDVOLUME_ENTRY(S,  8, NE),
125130803Smarcel	FEEDVOLUME_ENTRY(S, 24, LE),
126130803Smarcel	FEEDVOLUME_ENTRY(S, 24, BE),
127130803Smarcel	FEEDVOLUME_ENTRY(U,  8, NE),
128130803Smarcel	FEEDVOLUME_ENTRY(U, 16, LE),
129130803Smarcel	FEEDVOLUME_ENTRY(U, 24, LE),
130130803Smarcel	FEEDVOLUME_ENTRY(U, 32, LE),
131130803Smarcel	FEEDVOLUME_ENTRY(U, 16, BE),
132130803Smarcel	FEEDVOLUME_ENTRY(U, 24, BE),
133130803Smarcel	FEEDVOLUME_ENTRY(U, 32, BE)
134130803Smarcel#endif
135130803Smarcel};
136130803Smarcel
137130803Smarcel#define FEEDVOLUME_TAB_SIZE	((int32_t)				\
138130803Smarcel				 (sizeof(feed_volume_info_tab) /	\
139130803Smarcel				  sizeof(feed_volume_info_tab[0])))
140130803Smarcel
141130803Smarcelstatic int
142130803Smarcelfeed_volume_init(struct pcm_feeder *f)
143130803Smarcel{
144130803Smarcel	struct feed_volume_info *info;
145130803Smarcel	struct pcmchan_matrix *m;
146130803Smarcel	uint32_t i;
147130803Smarcel	int ret;
148130803Smarcel
149130803Smarcel	if (f->desc->in != f->desc->out ||
150130803Smarcel	    AFMT_CHANNEL(f->desc->in) > SND_CHN_MAX)
151130803Smarcel		return (EINVAL);
152130803Smarcel
153130803Smarcel	for (i = 0; i < FEEDVOLUME_TAB_SIZE; i++) {
154130803Smarcel		if (AFMT_ENCODING(f->desc->in) ==
155130803Smarcel		    feed_volume_info_tab[i].format) {
156130803Smarcel			info = malloc(sizeof(*info), M_DEVBUF,
157130803Smarcel			    M_NOWAIT | M_ZERO);
158130803Smarcel			if (info == NULL)
159130803Smarcel				return (ENOMEM);
160130803Smarcel
161130803Smarcel			info->bps = AFMT_BPS(f->desc->in);
162130803Smarcel			info->channels = AFMT_CHANNEL(f->desc->in);
163130803Smarcel			info->apply = feed_volume_info_tab[i].apply;
164130803Smarcel			info->volume_class = SND_VOL_C_PCM;
165130803Smarcel			info->state = FEEDVOLUME_ENABLE;
166130803Smarcel
167130803Smarcel			f->data = info;
168130803Smarcel			m = feeder_matrix_default_channel_map(info->channels);
169130803Smarcel			if (m == NULL) {
170130803Smarcel				free(info, M_DEVBUF);
171130803Smarcel				return (EINVAL);
172130803Smarcel			}
173130803Smarcel
174130803Smarcel			ret = feeder_volume_apply_matrix(f, m);
175130803Smarcel			if (ret != 0)
176130803Smarcel				free(info, M_DEVBUF);
177130803Smarcel
178130803Smarcel			return (ret);
179130803Smarcel		}
180130803Smarcel	}
181130803Smarcel
182130803Smarcel	return (EINVAL);
183130803Smarcel}
184130803Smarcel
185130803Smarcelstatic int
186130803Smarcelfeed_volume_free(struct pcm_feeder *f)
187130803Smarcel{
188130803Smarcel	struct feed_volume_info *info;
189130803Smarcel
190130803Smarcel	info = f->data;
191130803Smarcel	if (info != NULL)
192130803Smarcel		free(info, M_DEVBUF);
193130803Smarcel
194130803Smarcel	f->data = NULL;
195130803Smarcel
196130803Smarcel	return (0);
197130803Smarcel}
198130803Smarcel
199130803Smarcelstatic int
200130803Smarcelfeed_volume_set(struct pcm_feeder *f, int what, int value)
201130803Smarcel{
202130803Smarcel	struct feed_volume_info *info;
203130803Smarcel	struct pcmchan_matrix *m;
204130803Smarcel	int ret;
205130803Smarcel
206130803Smarcel	info = f->data;
207130803Smarcel	ret = 0;
208130803Smarcel
209130803Smarcel	switch (what) {
210130803Smarcel	case FEEDVOLUME_CLASS:
211130803Smarcel		if (value < SND_VOL_C_BEGIN || value > SND_VOL_C_END)
212130803Smarcel			return (EINVAL);
213130803Smarcel		info->volume_class = value;
214130803Smarcel		break;
215130803Smarcel	case FEEDVOLUME_CHANNELS:
216130803Smarcel		if (value < SND_CHN_MIN || value > SND_CHN_MAX)
217130803Smarcel			return (EINVAL);
218130803Smarcel		m = feeder_matrix_default_channel_map(value);
219130803Smarcel		if (m == NULL)
220130803Smarcel			return (EINVAL);
221130803Smarcel		ret = feeder_volume_apply_matrix(f, m);
222130803Smarcel		break;
223130803Smarcel	case FEEDVOLUME_STATE:
224130803Smarcel		if (!(value == FEEDVOLUME_ENABLE || value == FEEDVOLUME_BYPASS))
225130803Smarcel			return (EINVAL);
226130803Smarcel		info->state = value;
227130803Smarcel		break;
228130803Smarcel	default:
229130803Smarcel		return (EINVAL);
230130803Smarcel		break;
231130803Smarcel	}
232130803Smarcel
233130803Smarcel	return (ret);
234130803Smarcel}
235130803Smarcel
236130803Smarcelstatic int
237130803Smarcelfeed_volume_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
238130803Smarcel    uint32_t count, void *source)
239130803Smarcel{
240130803Smarcel	struct feed_volume_info *info;
241130803Smarcel	uint32_t j, align;
242130803Smarcel	int i, *vol, *matrix;
243130803Smarcel	uint8_t *dst;
244130803Smarcel
245130803Smarcel	/*
246130803Smarcel	 * Fetch filter data operation.
247130803Smarcel	 */
248130803Smarcel	info = f->data;
249130803Smarcel
250130803Smarcel	if (info->state == FEEDVOLUME_BYPASS)
251130803Smarcel		return (FEEDER_FEED(f->source, c, b, count, source));
252130803Smarcel
253130803Smarcel	vol = c->volume[SND_VOL_C_VAL(info->volume_class)];
254130803Smarcel	matrix = info->matrix;
255130803Smarcel
256130803Smarcel	/*
257130803Smarcel	 * First, let see if we really need to apply gain at all.
258130803Smarcel	 */
259130803Smarcel	j = 0;
260130803Smarcel	i = info->channels;
261130803Smarcel	do {
262130803Smarcel		if (vol[matrix[--i]] != SND_VOL_FLAT) {
263130803Smarcel			j = 1;
264130803Smarcel			break;
265130803Smarcel		}
266130803Smarcel	} while (i != 0);
267130803Smarcel
268130803Smarcel	/* Nope, just bypass entirely. */
269130803Smarcel	if (j == 0)
270130803Smarcel		return (FEEDER_FEED(f->source, c, b, count, source));
271130803Smarcel
272130803Smarcel	dst = b;
273130803Smarcel	align = info->bps * info->channels;
274130803Smarcel
275130803Smarcel	do {
276130803Smarcel		if (count < align)
277130803Smarcel			break;
278130803Smarcel
279130803Smarcel		j = SND_FXDIV(FEEDER_FEED(f->source, c, dst, count, source),
280130803Smarcel		    align);
281130803Smarcel		if (j == 0)
282130803Smarcel			break;
283130803Smarcel
284130803Smarcel		info->apply(vol, matrix, info->channels, dst, j);
285130803Smarcel
286130803Smarcel		j *= align;
287130803Smarcel		dst += j;
288130803Smarcel		count -= j;
289130803Smarcel
290130803Smarcel	} while (count != 0);
291130803Smarcel
292130803Smarcel	return (dst - b);
293130803Smarcel}
294130803Smarcel
295130803Smarcelstatic struct pcm_feederdesc feeder_volume_desc[] = {
296130803Smarcel	{ FEEDER_VOLUME, 0, 0, 0, 0 },
297130803Smarcel	{ 0, 0, 0, 0, 0 }
298130803Smarcel};
299130803Smarcel
300130803Smarcelstatic kobj_method_t feeder_volume_methods[] = {
301130803Smarcel	KOBJMETHOD(feeder_init,		feed_volume_init),
302130803Smarcel	KOBJMETHOD(feeder_free,		feed_volume_free),
303130803Smarcel	KOBJMETHOD(feeder_set,		feed_volume_set),
304130803Smarcel	KOBJMETHOD(feeder_feed,		feed_volume_feed),
305130803Smarcel	KOBJMETHOD_END
306130803Smarcel};
307130803Smarcel
308130803SmarcelFEEDER_DECLARE(feeder_volume, NULL);
309130803Smarcel
310130803Smarcel/* Extern */
311130803Smarcel
312130803Smarcel/*
313130803Smarcel * feeder_volume_apply_matrix(): For given matrix map, apply its configuration
314130803Smarcel *                               to feeder_volume matrix structure. There are
315130803Smarcel *                               possibilites that feeder_volume be inserted
316130803Smarcel *                               before or after feeder_matrix, which in this
317130803Smarcel *                               case feeder_volume must be in a good terms
318130803Smarcel *                               with _current_ matrix.
319130803Smarcel */
320130803Smarcelint
321130803Smarcelfeeder_volume_apply_matrix(struct pcm_feeder *f, struct pcmchan_matrix *m)
322130803Smarcel{
323130803Smarcel	struct feed_volume_info *info;
324130803Smarcel	uint32_t i;
325130803Smarcel
326130803Smarcel	if (f == NULL || f->desc == NULL || f->desc->type != FEEDER_VOLUME ||
327130803Smarcel	    f->data == NULL || m == NULL || m->channels < SND_CHN_MIN ||
328130803Smarcel	    m->channels > SND_CHN_MAX)
329130803Smarcel		return (EINVAL);
330130803Smarcel
331130803Smarcel	info = f->data;
332130803Smarcel
333130803Smarcel	for (i = 0; i < (sizeof(info->matrix) / sizeof(info->matrix[0])); i++) {
334130803Smarcel		if (i < m->channels)
335130803Smarcel			info->matrix[i] = m->map[i].type;
336130803Smarcel		else
337130803Smarcel			info->matrix[i] = SND_CHN_T_FL;
338130803Smarcel	}
339130803Smarcel
340130803Smarcel	info->channels = m->channels;
341130803Smarcel
342130803Smarcel	return (0);
343130803Smarcel}
344130803Smarcel