summaryrefslogtreecommitdiffstats
path: root/mix/pcm_vdownmix.c
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2006-03-21 11:55:36 +0000
committerTakashi Iwai <tiwai@suse.de>2006-03-21 11:55:36 +0000
commitb59cf0e288b82fca2e0715fca20ba3ede5dae3dc (patch)
treeca353c9d7bfc50393287bce4d8ca3bf06a3efc0e /mix/pcm_vdownmix.c
parenta86da2532c4072bccaeb380099a90b63262bf14d (diff)
Add upmix and vdownmix plugins
Added PCM upmix and vdownmix plugins. The upmix plugin is for upmixing to 4.0 or 5.1 surrounds by simple copying (and delay line for rear). The vdownmix plugin is a kind of "virtual surround", which downmixes 4.0 or 5.1 input to 2.0 output with some effect.
Diffstat (limited to 'mix/pcm_vdownmix.c')
-rw-r--r--mix/pcm_vdownmix.c341
1 files changed, 341 insertions, 0 deletions
diff --git a/mix/pcm_vdownmix.c b/mix/pcm_vdownmix.c
new file mode 100644
index 0000000..a083ae2
--- /dev/null
+++ b/mix/pcm_vdownmix.c
@@ -0,0 +1,341 @@
+/*
+ * 4/5/6 -> 2 downmix with a simple spacialization
+ *
+ * Copyright (c) 2006 by Takashi Iwai <tiwai@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <alsa/asoundlib.h>
+#include <alsa/pcm_external.h>
+
+/* #define I_AM_POWERFUL */
+
+#ifdef I_AM_POWERFUL
+#define RINGBUF_SIZE (1 << 9)
+#else
+#define RINGBUF_SIZE (1 << 7)
+#endif
+#define RINGBUF_MASK (RINGBUF_SIZE - 1)
+
+struct vdownmix_tap {
+ int delay;
+ int weight;
+};
+
+#define MAX_TAPS 30
+
+struct vdownmix_filter {
+ int taps;
+ struct vdownmix_tap tap[MAX_TAPS];
+};
+
+typedef struct {
+ snd_pcm_extplug_t ext;
+ int channels;
+ unsigned int curpos;
+ short rbuf[RINGBUF_SIZE][5];
+} snd_pcm_vdownmix_t;
+
+static struct vdownmix_filter tap_filters[5] = {
+ {
+#ifdef I_AM_POWERFUL
+ 18,
+#else
+ 14,
+#endif
+ {{ 0, 0xfffffd0a },
+ { 1, 0x41d },
+ { 2, 0xffffe657 },
+ { 3, 0x6eb5 },
+ { 4, 0xffffe657 },
+ { 5, 0x41d },
+ { 6, 0xfffffd0a },
+ { 71, 0xffffff1c },
+ { 72, 0x12e },
+ { 73, 0xfffff81a },
+ { 74, 0x24de },
+ { 75, 0xfffff81a },
+ { 76, 0x12e },
+ { 77, 0xffffff1c },
+ { 265, 0xfffffc65 },
+ { 266, 0xee1 },
+ { 267, 0xfffffc65 },
+ { 395, 0x46a }},
+ },
+
+ {
+#ifdef I_AM_POWERFUL
+ 17,
+#else
+ 10,
+#endif
+ {{ 8, 0xcf },
+ { 9, 0xa7b },
+ { 10, 0xcd7 },
+ { 11, 0x5b3 },
+ { 12, 0x859 },
+ { 13, 0xaf },
+ { 80, 0x38b },
+ { 81, 0x454 },
+ { 82, 0x218 },
+ { 83, 0x2c1 },
+ { 268, 0x58b },
+ { 275, 0xc2 },
+ { 397, 0xbd },
+ { 398, 0x1e8 },
+ { 506, 0xfffffeac },
+ { 507, 0x636 },
+ { 508, 0xfffffeac }},
+ },
+
+ {
+#ifdef I_AM_POWERFUL
+ 11,
+#else
+ 1,
+#endif
+ {{ 3, 0x4000 },
+ { 125, 0x12a },
+ { 126, 0xda1 },
+ { 127, 0x12a },
+ { 193, 0xfffffed3 },
+ { 194, 0xdb9 },
+ { 195, 0xfffffed3 },
+ { 454, 0x10a },
+ { 483, 0xfffffe97 },
+ { 484, 0x698 },
+ { 485, 0xfffffe97 }},
+ },
+
+ {
+#ifdef I_AM_POWERFUL
+ 25,
+#else
+ 10,
+#endif
+ {{ 5, 0x1cb },
+ { 6, 0x9c5 },
+ { 7, 0x117e },
+ { 8, 0x200 },
+ { 9, 0x533 },
+ { 10, 0x1c6 },
+ { 11, 0x167 },
+ { 12, 0x5ff },
+ { 13, 0x425 },
+ { 14, 0xd9 },
+ { 128, 0x247 },
+ { 129, 0x5e1 },
+ { 130, 0xb7 },
+ { 131, 0x122 },
+ { 135, 0x10a },
+ { 200, 0x1b6 },
+ { 201, 0xa7 },
+ { 202, 0x188 },
+ { 203, 0x1d9 },
+ { 445, 0xffffff44 },
+ { 446, 0x5e2 },
+ { 447, 0xffffff44 },
+ { 484, 0xffffff51 },
+ { 485, 0x449 },
+ { 486, 0xffffff51 }},
+ },
+
+ {
+#ifdef I_AM_POWERFUL
+ 21,
+#else
+ 7,
+#endif
+ {{ 0, 0xfffffdee },
+ { 1, 0x28b },
+ { 2, 0xffffed1e },
+ { 3, 0x6336 },
+ { 4, 0xffffed1e },
+ { 5, 0x28b },
+ { 6, 0xfffffdee },
+ { 51, 0xffffff2c },
+ { 52, 0x105 },
+ { 53, 0xfffff86b },
+ { 54, 0x27d9 },
+ { 55, 0xfffff86b },
+ { 56, 0x105 },
+ { 57, 0xffffff2c },
+ { 333, 0xfffffd69 },
+ { 334, 0xb2f },
+ { 335, 0xfffffd69 },
+ { 339, 0xdf },
+ { 340, 0x168 },
+ { 342, 0xa6 },
+ { 343, 0xba }},
+ },
+};
+
+static int tap_index[5][2] = {
+ /* left */
+ { 0, 1 },
+ /* right */
+ { 1, 0 },
+ /* rear left */
+ { 2, 3 },
+ /* rear right */
+ { 3, 2 },
+ /* center */
+ { 4, 4 },
+};
+
+static inline void *area_addr(const snd_pcm_channel_area_t *area, snd_pcm_uframes_t offset)
+{
+ unsigned int bitofs = area->first + area->step * offset;
+ return (char *) area->addr + bitofs / 8;
+}
+
+static inline unsigned int area_step(const snd_pcm_channel_area_t *area)
+{
+ return area->step / 8;
+}
+
+static snd_pcm_sframes_t
+vdownmix_transfer(snd_pcm_extplug_t *ext,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ snd_pcm_uframes_t size)
+{
+ snd_pcm_vdownmix_t *mix = (snd_pcm_vdownmix_t *)ext;
+ short *src[mix->channels], *ptr[2];
+ unsigned int src_step[mix->channels], step[2];
+ int i, ch, curpos, p, idx;
+ int acc[2];
+ int fr;
+
+ ptr[0] = area_addr(dst_areas, dst_offset);
+ step[0] = area_step(dst_areas) / 2;
+ ptr[1] = area_addr(dst_areas + 1, dst_offset);
+ step[1] = area_step(dst_areas + 1) / 2;
+ for (ch = 0; ch < mix->channels; ch++) {
+ const snd_pcm_channel_area_t *src_area = &src_areas[ch];
+ src[ch] = area_addr(src_area, src_offset);
+ src_step[ch] = area_step(src_area) / 2;
+ }
+ curpos = mix->curpos;
+ fr = size;
+ while (fr--) {
+ acc[0] = acc[1] = 0;
+ for (ch = 0; ch < mix->channels; ch++) {
+ mix->rbuf[curpos][ch] = *src[ch];
+ for (idx = 0; idx < 2; idx++) {
+ int f = tap_index[ch][idx];
+ const struct vdownmix_filter *filter;
+ filter = &tap_filters[f];
+ for (i = 0; i < filter->taps; i++) {
+ p = (curpos + RINGBUF_SIZE - filter->tap[i].delay)
+ & RINGBUF_MASK;
+ acc[idx] += mix->rbuf[p][ch] * filter->tap[i].weight;
+ }
+ }
+ src[ch] += src_step[ch];
+ }
+ for (idx = 0; idx < 2; idx++) {
+ acc[idx] >>= 14;
+ if (acc[idx] < -32768)
+ *ptr[idx] = -32768;
+ else if (acc[idx] > 32767)
+ *ptr[idx] = 32767;
+ else
+ *ptr[idx] = acc[idx];
+ ptr[idx] += step[idx];
+ }
+ curpos = (curpos + 1) & RINGBUF_MASK;
+ }
+ mix->curpos = curpos;
+ return size;
+}
+
+
+static int vdownmix_init(snd_pcm_extplug_t *ext)
+{
+ snd_pcm_vdownmix_t *mix = (snd_pcm_vdownmix_t *)ext;
+ mix->channels = ext->channels;
+ if (mix->channels > 5) /* ignore LFE */
+ mix->channels = 5;
+ mix->curpos = 0;
+ memset(mix->rbuf, 0, sizeof(mix->rbuf));
+ return 0;
+}
+
+static snd_pcm_extplug_callback_t vdownmix_callback = {
+ .transfer = vdownmix_transfer,
+ .init = vdownmix_init,
+ /* .dump = filr_dump, */
+};
+
+SND_PCM_PLUGIN_DEFINE_FUNC(vdownmix)
+{
+ snd_config_iterator_t i, next;
+ snd_pcm_vdownmix_t *mix;
+ snd_config_t *sconf = NULL;
+ int err;
+
+ snd_config_for_each(i, next, conf) {
+ snd_config_t *n = snd_config_iterator_entry(i);
+ const char *id;
+ if (snd_config_get_id(n, &id) < 0)
+ continue;
+ if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0)
+ continue;
+ if (strcmp(id, "slave") == 0) {
+ sconf = n;
+ continue;
+ }
+ SNDERR("Unknown field %s", id);
+ return -EINVAL;
+ }
+
+ if (! sconf) {
+ SNDERR("No slave configuration for vdownmix pcm");
+ return -EINVAL;
+ }
+
+ mix = calloc(1, sizeof(*mix));
+ if (mix == NULL)
+ return -ENOMEM;
+
+ mix->ext.version = SND_PCM_EXTPLUG_VERSION;
+ mix->ext.name = "Vdownmix Plugin";
+ mix->ext.callback = &vdownmix_callback;
+ mix->ext.private_data = mix;
+
+ err = snd_pcm_extplug_create(&mix->ext, name, root, sconf, stream, mode);
+ if (err < 0) {
+ free(mix);
+ return err;
+ }
+
+ /* 4/5/6 -> 2 downmix */
+ snd_pcm_extplug_set_param_minmax(&mix->ext, SND_PCM_EXTPLUG_HW_CHANNELS,
+ 4, 6);
+ snd_pcm_extplug_set_slave_param(&mix->ext, SND_PCM_EXTPLUG_HW_CHANNELS, 2);
+ snd_pcm_extplug_set_param(&mix->ext, SND_PCM_EXTPLUG_HW_FORMAT,
+ SND_PCM_FORMAT_S16);
+ snd_pcm_extplug_set_slave_param(&mix->ext, SND_PCM_EXTPLUG_HW_FORMAT,
+ SND_PCM_FORMAT_S16);
+
+ *pcmp = mix->ext.pcm;
+ return 0;
+}
+
+SND_PCM_PLUGIN_SYMBOL(vdownmix);