Hello,

> One minor thing that could be fixed is the naming (sink name and
> description, and sink input description). Currently they are copied from
> module-virtual-sink, and they could be modified to include the word
> "surround" in some way.

> I don't think this error can happen anymore. Both hrir_map.channels and
> u->hrir_channels seem to originate from pa_sound_file_load(), and that
> function shouldn't return inconsistent sample specs and channel maps
> (otherwise it's buggy).

Both fixed.

> I think this is buggy. I think that even if you successfully find a
> channel for each item in mapping_left, some channels can be left
> uninitialized in mapping_right, if the hrir file doesn't have a
> "symmetrical" channel map. So, it should be checked that a channel has
> been found for both mappings.

You are right. I wrongly assumed that the hrir file is always symmetric. I have 
fixed it and attached an updated patch.

> That was my idea, but I'm not so sure about that anymore. I'm not
> confident that it's safe to remap the hrir contents. But what is the use
> case for having a different channel map for the sink than what is in the
> hrir file? To me it would sound sensible to just use the hrir file
> channel map for the sink, and drop the configurability. I know that I
> recommended earlier to have the channel map configurable...
> 
> Hmm... The main motivation for me to push for matching sink and hrir
> channel maps has been that I'd like to get rid of the mapping_left and
> mapping_right variables, but actually I think it's not possible to
> achieve that, because whatever the channel map, it still has to be
> treated differently for the left and the right ear... So, in conclusion,
> feel free to keep the code as is, if you want to keep the
> configurability.

We either have to keep the mapping (at least for the right ear) or store two 
float arrays, one for the left and one for the right ear.
I do not have a strong preference either way. But if we decide to keep the 
mapping for the right ear we can as well keep the one for the left ear and 
leave the channel map configurable. I neither have a use case for this, though.
What approach do you prefer?

Regards,

Ole
>From 3eccbe849f74fafbeb558632797bfd945d29238d Mon Sep 17 00:00:00 2001
From: Niels Ole Salscheider <niels_...@salscheider-online.de>
Date: Sun, 8 Jan 2012 21:22:35 +0100
Subject: [PATCH] add module-virtual-surround-sink

It provides a virtual surround sound effect

v2: Normalize hrir to avoid clipping, some cleanups
v3: use fabs, not abs
v4: implement changes proposed by Tanu Kaskinen
v5: likewise
v6: use channel map from hrir file
v7: remove hrir_ss and hrir_map form userdata
v8: update naming of sink
---
 src/Makefile.am                                    |    7 +
 ...rtual-sink.c => module-virtual-surround-sink.c} |  323 +++++++++++++++-----
 2 files changed, 258 insertions(+), 72 deletions(-)
 copy src/modules/{module-virtual-sink.c => module-virtual-surround-sink.c} (66%)

diff --git a/src/Makefile.am b/src/Makefile.am
index 603ccc3..9aa31f4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1019,6 +1019,7 @@ modlibexec_LTLIBRARIES += \
 		module-loopback.la \
 		module-virtual-sink.la \
 		module-virtual-source.la \
+		module-virtual-surround-sink.la \
 		module-switch-on-connect.la \
 		module-filter-apply.la \
 		module-filter-heuristics.la
@@ -1319,6 +1320,7 @@ SYMDEF_FILES = \
 		module-loopback-symdef.h \
 		module-virtual-sink-symdef.h \
 		module-virtual-source-symdef.h \
+		module-virtual-surround-sink-symdef.h \
 		module-switch-on-connect-symdef.h \
 		module-filter-apply-symdef.h \
 		module-filter-heuristics-symdef.h
@@ -1535,6 +1537,11 @@ module_virtual_source_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS)
 module_virtual_source_la_LDFLAGS = $(MODULE_LDFLAGS)
 module_virtual_source_la_LIBADD = $(MODULE_LIBADD)
 
+module_virtual_surround_sink_la_SOURCES = modules/module-virtual-surround-sink.c
+module_virtual_surround_sink_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS)
+module_virtual_surround_sink_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_virtual_surround_sink_la_LIBADD = $(MODULE_LIBADD)
+
 # X11
 
 module_x11_bell_la_SOURCES = modules/x11/module-x11-bell.c
diff --git a/src/modules/module-virtual-sink.c b/src/modules/module-virtual-surround-sink.c
similarity index 66%
copy from src/modules/module-virtual-sink.c
copy to src/modules/module-virtual-surround-sink.c
index cf11ffa..e13d92a 100644
--- a/src/modules/module-virtual-sink.c
+++ b/src/modules/module-virtual-surround-sink.c
@@ -3,6 +3,7 @@
 
     Copyright 2010 Intel Corporation
     Contributor: Pierre-Louis Bossart <pierre-louis.boss...@intel.com>
+    Copyright 2012 Niels Ole Salscheider <niels_...@salscheider-online.de>
 
     PulseAudio is free software; you can redistribute it and/or modify
     it under the terms of the GNU Lesser General Public License as published
@@ -37,11 +38,15 @@
 #include <pulsecore/rtpoll.h>
 #include <pulsecore/sample-util.h>
 #include <pulsecore/ltdl-helper.h>
+#include <pulsecore/sound-file.h>
+#include <pulsecore/resampler.h>
 
-#include "module-virtual-sink-symdef.h"
+#include <math.h>
 
-PA_MODULE_AUTHOR("Pierre-Louis Bossart");
-PA_MODULE_DESCRIPTION(_("Virtual sink"));
+#include "module-virtual-surround-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Niels Ole Salscheider");
+PA_MODULE_DESCRIPTION(_("Virtual surround sink"));
 PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_LOAD_ONCE(FALSE);
 PA_MODULE_USAGE(
@@ -54,6 +59,7 @@ PA_MODULE_USAGE(
           "channel_map=<channel map> "
           "use_volume_sharing=<yes or no> "
           "force_flat_volume=<yes or no> "
+          "hrir=/path/to/left_hrir.wav "
         ));
 
 #define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
@@ -71,6 +77,18 @@ struct userdata {
 
     pa_bool_t auto_desc;
     unsigned channels;
+    unsigned hrir_channels;
+
+    unsigned fs, sink_fs;
+
+    unsigned *mapping_left;
+    unsigned *mapping_right;
+
+    unsigned hrir_samples;
+    float *hrir_data;
+
+    float *input_buffer;
+    int input_buffer_offset;
 };
 
 static const char* const valid_modargs[] = {
@@ -83,6 +101,7 @@ static const char* const valid_modargs[] = {
     "channel_map",
     "use_volume_sharing",
     "force_flat_volume",
+    "hrir",
     NULL
 };
 
@@ -198,10 +217,12 @@ static void sink_set_mute_cb(pa_sink *s) {
 static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
     struct userdata *u;
     float *src, *dst;
-    size_t fs;
-    unsigned n, c;
+    unsigned n;
     pa_memchunk tchunk;
-    pa_usec_t current_latency PA_GCC_UNUSED;
+
+    unsigned j, k, l;
+    float sum_right, sum_left;
+    float current_sample;
 
     pa_sink_input_assert_ref(i);
     pa_assert(chunk);
@@ -210,45 +231,52 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk
     /* Hmm, process any rewind request that might be queued up */
     pa_sink_process_rewind(u->sink, 0);
 
-    /* (1) IF YOU NEED A FIXED BLOCK SIZE USE
-     * pa_memblockq_peek_fixed_size() HERE INSTEAD. NOTE THAT FILTERS
-     * WHICH CAN DEAL WITH DYNAMIC BLOCK SIZES ARE HIGHLY
-     * PREFERRED. */
     while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) {
         pa_memchunk nchunk;
 
-        pa_sink_render(u->sink, nbytes, &nchunk);
+        pa_sink_render(u->sink, nbytes * u->sink_fs / u->fs, &nchunk);
         pa_memblockq_push(u->memblockq, &nchunk);
         pa_memblock_unref(nchunk.memblock);
     }
 
-    /* (2) IF YOU NEED A FIXED BLOCK SIZE, THIS NEXT LINE IS NOT
-     * NECESSARY */
-    tchunk.length = PA_MIN(nbytes, tchunk.length);
+    tchunk.length = PA_MIN(nbytes * u->sink_fs / u->fs, tchunk.length);
     pa_assert(tchunk.length > 0);
 
-    fs = pa_frame_size(&i->sample_spec);
-    n = (unsigned) (tchunk.length / fs);
+    n = (unsigned) (tchunk.length / u->sink_fs);
 
     pa_assert(n > 0);
 
     chunk->index = 0;
-    chunk->length = n*fs;
+    chunk->length = n * u->fs;
     chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length);
 
-    pa_memblockq_drop(u->memblockq, chunk->length);
+    pa_memblockq_drop(u->memblockq, n * u->sink_fs);
 
     src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index);
     dst = (float*) pa_memblock_acquire(chunk->memblock);
 
-    /* (3) PUT YOUR CODE HERE TO DO SOMETHING WITH THE DATA */
+    for (l = 0; l < n; l++) {
+        memcpy(((char*) u->input_buffer) + u->input_buffer_offset * u->sink_fs, ((char *) src) + l * u->sink_fs, u->sink_fs);
+
+        sum_right = 0;
+        sum_left = 0;
+
+        /* fold the input buffer with the impulse response */
+        for (j = 0; j < u->hrir_samples; j++) {
+            for (k = 0; k < u->channels; k++) {
+                current_sample = u->input_buffer[((u->input_buffer_offset + j) % u->hrir_samples) * u->channels + k];
+
+                sum_left += current_sample * u->hrir_data[j * u->hrir_channels + u->mapping_left[k]];
+                sum_right += current_sample * u->hrir_data[j * u->hrir_channels + u->mapping_right[k]];
+            }
+        }
+
+        dst[2 * l] = PA_CLAMP_UNLIKELY(sum_left, -1.0f, 1.0f);
+        dst[2 * l + 1] = PA_CLAMP_UNLIKELY(sum_right, -1.0f, 1.0f);
 
-    /* As an example, copy input to output */
-    for (c = 0; c < u->channels; c++) {
-        pa_sample_clamp(PA_SAMPLE_FLOAT32NE,
-                        dst+c, u->channels * sizeof(float),
-                        src+c, u->channels * sizeof(float),
-                        n);
+        u->input_buffer_offset--;
+        if (u->input_buffer_offset < 0)
+            u->input_buffer_offset += u->hrir_samples;
     }
 
     pa_memblock_release(tchunk.memblock);
@@ -256,14 +284,6 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk
 
     pa_memblock_unref(tchunk.memblock);
 
-    /* (4) IF YOU NEED THE LATENCY FOR SOMETHING ACQUIRE IT LIKE THIS: */
-    current_latency =
-        /* Get the latency of the master sink */
-        pa_sink_get_latency_within_thread(i->sink) +
-
-        /* Add the latency internal to our sink input on top */
-        pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec);
-
     return 0;
 }
 
@@ -278,19 +298,21 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
     if (u->sink->thread_info.rewind_nbytes > 0) {
         size_t max_rewrite;
 
-        max_rewrite = nbytes + pa_memblockq_get_length(u->memblockq);
-        amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite);
+        max_rewrite = nbytes * u->sink_fs / u->fs + pa_memblockq_get_length(u->memblockq);
+        amount = PA_MIN(u->sink->thread_info.rewind_nbytes * u->sink_fs / u->fs, max_rewrite);
         u->sink->thread_info.rewind_nbytes = 0;
 
         if (amount > 0) {
             pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE, TRUE);
 
-            /* (5) PUT YOUR CODE HERE TO RESET YOUR FILTER  */
+            /* Reset the input buffer */
+            memset(u->input_buffer, 0, u->hrir_samples * u->sink_fs);
+            u->input_buffer_offset = 0;
         }
     }
 
     pa_sink_process_rewind(u->sink, amount);
-    pa_memblockq_rewind(u->memblockq, nbytes);
+    pa_memblockq_rewind(u->memblockq, nbytes * u->sink_fs / u->fs);
 }
 
 /* Called from I/O thread context */
@@ -300,8 +322,8 @@ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
-    pa_memblockq_set_maxrewind(u->memblockq, nbytes);
-    pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
+    pa_memblockq_set_maxrewind(u->memblockq, nbytes * u->sink_fs / u->fs);
+    pa_sink_set_max_rewind_within_thread(u->sink, nbytes * u->sink_fs / u->fs);
 }
 
 /* Called from I/O thread context */
@@ -311,10 +333,7 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
-    /* (6) IF YOU NEED A FIXED BLOCK SIZE ROUND nbytes UP TO MULTIPLES
-     * OF IT HERE. THE PA_ROUND_UP MACRO IS USEFUL FOR THAT. */
-
-    pa_sink_set_max_request_within_thread(u->sink, nbytes);
+    pa_sink_set_max_request_within_thread(u->sink, nbytes * u->sink_fs / u->fs);
 }
 
 /* Called from I/O thread context */
@@ -334,10 +353,6 @@ static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
-    /* (7) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE
-     * BLOCK MINUS ONE SAMPLE HERE. pa_usec_to_bytes_round_up() IS
-     * USEFUL FOR THAT. */
-
     pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
 }
 
@@ -363,13 +378,8 @@ static void sink_input_attach_cb(pa_sink_input *i) {
     pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll);
     pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
 
-    /* (8.1) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE
-     * BLOCK MINUS ONE SAMPLE HERE. SEE (7) */
     pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
 
-    /* (8.2) IF YOU NEED A FIXED BLOCK SIZE ROUND
-     * pa_sink_input_get_max_request(i) UP TO MULTIPLES OF IT
-     * HERE. SEE (6) */
     pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i));
     pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i));
 
@@ -443,8 +453,8 @@ static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
 
         pl = pa_proplist_new();
         z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
-        pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Virtual Sink %s on %s",
-                         pa_proplist_gets(u->sink->proplist, "device.vsink.name"), z ? z : dest->name);
+        pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Virtual Surround Sink %s on %s",
+                         pa_proplist_gets(u->sink->proplist, "device.vsurroundsink.name"), z ? z : dest->name);
 
         pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl);
         pa_proplist_free(pl);
@@ -471,10 +481,53 @@ static void sink_input_mute_changed_cb(pa_sink_input *i) {
     pa_sink_mute_changed(u->sink, i->muted);
 }
 
+static pa_channel_position_t mirror_channel(pa_channel_position_t channel) {
+    switch (channel) {
+        case PA_CHANNEL_POSITION_FRONT_LEFT:
+            return PA_CHANNEL_POSITION_FRONT_RIGHT;
+
+        case PA_CHANNEL_POSITION_FRONT_RIGHT:
+            return PA_CHANNEL_POSITION_FRONT_LEFT;
+
+        case PA_CHANNEL_POSITION_REAR_LEFT:
+            return PA_CHANNEL_POSITION_REAR_RIGHT;
+
+        case PA_CHANNEL_POSITION_REAR_RIGHT:
+            return PA_CHANNEL_POSITION_REAR_LEFT;
+
+        case PA_CHANNEL_POSITION_SIDE_LEFT:
+            return PA_CHANNEL_POSITION_SIDE_RIGHT;
+
+        case PA_CHANNEL_POSITION_SIDE_RIGHT:
+            return PA_CHANNEL_POSITION_SIDE_LEFT;
+
+        case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER:
+            return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
+
+        case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER:
+            return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
+
+        case PA_CHANNEL_POSITION_TOP_FRONT_LEFT:
+            return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT;
+
+        case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT:
+            return PA_CHANNEL_POSITION_TOP_FRONT_LEFT;
+
+        case PA_CHANNEL_POSITION_TOP_REAR_LEFT:
+            return PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
+
+        case PA_CHANNEL_POSITION_TOP_REAR_RIGHT:
+            return PA_CHANNEL_POSITION_TOP_REAR_LEFT;
+
+        default:
+            return channel;
+    }
+}
+
 int pa__init(pa_module*m) {
     struct userdata *u;
-    pa_sample_spec ss;
-    pa_channel_map map;
+    pa_sample_spec ss, sink_input_ss;
+    pa_channel_map map, sink_input_map;
     pa_modargs *ma;
     pa_sink *master=NULL;
     pa_sink_input_new_data sink_input_data;
@@ -483,6 +536,20 @@ int pa__init(pa_module*m) {
     pa_bool_t force_flat_volume = FALSE;
     pa_memchunk silence;
 
+    const char *hrir_file;
+    unsigned i, j, found_channel_left, found_channel_right;
+    float hrir_sum, hrir_max;
+    float *hrir_data;
+
+    pa_sample_spec hrir_ss;
+    pa_channel_map hrir_map;
+
+    pa_sample_spec hrir_temp_ss;
+    pa_memchunk hrir_temp_chunk;
+    pa_resampler *resampler;
+
+    hrir_temp_chunk.memblock = NULL;
+
     pa_assert(m);
 
     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
@@ -497,13 +564,39 @@ int pa__init(pa_module*m) {
 
     pa_assert(master);
 
-    ss = master->sample_spec;
-    ss.format = PA_SAMPLE_FLOAT32;
-    map = master->channel_map;
+    u = pa_xnew0(struct userdata, 1);
+    u->module = m;
+    m->userdata = u;
+
+    /* Initialize hrir and input buffer */
+    /* this is the hrir file for the left ear! */
+    hrir_file = pa_modargs_get_value(ma, "hrir", NULL);
+
+    if (!(hrir_file = pa_modargs_get_value(ma, "hrir", NULL))) {
+        pa_log("The mandatory 'hrir' module argument is missing.");
+        goto fail;
+    }
+
+    if (pa_sound_file_load(master->core->mempool, hrir_file, &hrir_temp_ss, &hrir_map, &hrir_temp_chunk, NULL) < 0) {
+        pa_log("Cannot load hrir file.");
+        goto fail;
+    }
+
+    /* sample spec / map of hrir */
+    hrir_ss.format = PA_SAMPLE_FLOAT32;
+    hrir_ss.rate = master->sample_spec.rate;
+    hrir_ss.channels = hrir_temp_ss.channels;
+
+    /* sample spec of sink */
+    ss = hrir_ss;
+    map = hrir_map;
     if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
         pa_log("Invalid sample format specification or channel map");
         goto fail;
     }
+    ss.format = PA_SAMPLE_FLOAT32;
+    hrir_ss.rate = ss.rate;
+    u->channels = ss.channels;
 
     if (pa_modargs_get_value_boolean(ma, "use_volume_sharing", &use_volume_sharing) < 0) {
         pa_log("use_volume_sharing= expects a boolean argument");
@@ -520,22 +613,26 @@ int pa__init(pa_module*m) {
         goto fail;
     }
 
-    u = pa_xnew0(struct userdata, 1);
-    u->module = m;
-    m->userdata = u;
-    u->channels = ss.channels;
+    /* sample spec / map of sink input */
+    pa_channel_map_init_stereo(&sink_input_map);
+    sink_input_ss.channels = 2;
+    sink_input_ss.format = PA_SAMPLE_FLOAT32;
+    sink_input_ss.rate = ss.rate;
+
+    u->sink_fs = pa_frame_size(&ss);
+    u->fs = pa_frame_size(&sink_input_ss);
 
     /* Create sink */
     pa_sink_new_data_init(&sink_data);
     sink_data.driver = __FILE__;
     sink_data.module = m;
     if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
-        sink_data.name = pa_sprintf_malloc("%s.vsink", master->name);
+        sink_data.name = pa_sprintf_malloc("%s.vsurroundsink", master->name);
     pa_sink_new_data_set_sample_spec(&sink_data, &ss);
     pa_sink_new_data_set_channel_map(&sink_data, &map);
     pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
     pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
-    pa_proplist_sets(sink_data.proplist, "device.vsink.name", sink_data.name);
+    pa_proplist_sets(sink_data.proplist, "device.vsurroundsink.name", sink_data.name);
 
     if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) {
         pa_log("Invalid properties");
@@ -547,7 +644,7 @@ int pa__init(pa_module*m) {
         const char *z;
 
         z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
-        pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Virtual Sink %s on %s", sink_data.name, z ? z : master->name);
+        pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Virtual Surround Sink %s on %s", sink_data.name, z ? z : master->name);
     }
 
     u->sink = pa_sink_new(m->core, &sink_data, (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY))
@@ -581,10 +678,10 @@ int pa__init(pa_module*m) {
     sink_input_data.module = m;
     pa_sink_input_new_data_set_sink(&sink_input_data, master, FALSE);
     sink_input_data.origin_sink = u->sink;
-    pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Virtual Sink Stream from %s", pa_proplist_gets(u->sink->proplist, PA_PROP_DEVICE_DESCRIPTION));
+    pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Virtual Surround Sink Stream from %s", pa_proplist_gets(u->sink->proplist, PA_PROP_DEVICE_DESCRIPTION));
     pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
-    pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
-    pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
+    pa_sink_input_new_data_set_sample_spec(&sink_input_data, &sink_input_ss);
+    pa_sink_input_new_data_set_channel_map(&sink_input_data, &sink_input_map);
 
     pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
     pa_sink_input_new_data_done(&sink_input_data);
@@ -611,19 +708,90 @@ int pa__init(pa_module*m) {
     u->sink->input_to_master = u->sink_input;
 
     pa_sink_input_get_silence(u->sink_input, &silence);
-    u->memblockq = pa_memblockq_new("module-virtual-sink memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &silence);
+    u->memblockq = pa_memblockq_new("module-virtual-surround-sink memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &sink_input_ss, 1, 1, 0, &silence);
     pa_memblock_unref(silence.memblock);
 
-    /* (9) INITIALIZE ANYTHING ELSE YOU NEED HERE */
+    /* resample hrir */
+    resampler = pa_resampler_new(u->sink->core->mempool, &hrir_temp_ss, &hrir_map, &hrir_ss, &hrir_map,
+                                 PA_RESAMPLER_SRC_SINC_BEST_QUALITY, PA_RESAMPLER_NO_REMAP);
+    pa_resampler_run(resampler, &hrir_temp_chunk, &hrir_temp_chunk);
+    pa_resampler_free(resampler);
+
+    u->hrir_samples =  hrir_temp_chunk.length / pa_frame_size(&hrir_ss);
+    u->hrir_channels = hrir_ss.channels;
+
+    /* copy hrir data */
+    hrir_data = (float *) pa_memblock_acquire(hrir_temp_chunk.memblock);
+    u->hrir_data = (float *) pa_xmalloc(hrir_temp_chunk.length);
+    memcpy(u->hrir_data, hrir_data, hrir_temp_chunk.length);
+    pa_memblock_release(hrir_temp_chunk.memblock);
+    pa_memblock_unref(hrir_temp_chunk.memblock);
+    hrir_temp_chunk.memblock = NULL;
+
+    if (hrir_map.channels < map.channels) {
+        pa_log("hrir file does not have enough channels!");
+        goto fail;
+    }
+
+    /* normalize hrir to avoid clipping */
+    hrir_max = 0;
+    for (i = 0; i < u->hrir_samples; i++) {
+        hrir_sum = 0;
+        for (j = 0; j < u->hrir_channels; j++)
+            hrir_sum += fabs(u->hrir_data[i * u->hrir_channels + j]);
+
+        if (hrir_sum > hrir_max)
+            hrir_max = hrir_sum;
+    }
+    if (hrir_max > 1) {
+        for (i = 0; i < u->hrir_samples; i++) {
+            for (j = 0; j < u->hrir_channels; j++)
+                u->hrir_data[i * u->hrir_channels + j] /= hrir_max * 1.2;
+        }
+    }
+
+    /* create mapping between hrir and input */
+    u->mapping_left = (unsigned *) pa_xnew0(unsigned, u->channels);
+    u->mapping_right = (unsigned *) pa_xnew0(unsigned, u->channels);
+    for (i = 0; i < map.channels; i++) {
+        found_channel_left = 0;
+        found_channel_right = 0;
+
+        for (j = 0; j < hrir_map.channels; j++) {
+            if (hrir_map.map[j] == map.map[i]) {
+                u->mapping_left[i] = j;
+                found_channel_left = 1;
+            }
+
+            if (hrir_map.map[j] == mirror_channel(map.map[i])) {
+                u->mapping_right[i] = j;
+                found_channel_right = 1;
+            }
+        }
+
+        if (!found_channel_left) {
+            pa_log("Cannot find mapping for channel %s", pa_channel_position_to_string(map.map[i]));
+            goto fail;
+        }
+        if (!found_channel_right) {
+            pa_log("Cannot find mapping for channel %s", pa_channel_position_to_string(mirror_channel(map.map[i])));
+            goto fail;
+        }
+    }
+
+    u->input_buffer = pa_xmalloc0(u->hrir_samples * u->sink_fs);
+    u->input_buffer_offset = 0;
 
     pa_sink_put(u->sink);
     pa_sink_input_put(u->sink_input);
 
     pa_modargs_free(ma);
-
     return 0;
 
 fail:
+    if (hrir_temp_chunk.memblock)
+        pa_memblock_unref(hrir_temp_chunk.memblock);
+
     if (ma)
         pa_modargs_free(ma);
 
@@ -667,5 +835,16 @@ void pa__done(pa_module*m) {
     if (u->memblockq)
         pa_memblockq_free(u->memblockq);
 
+    if (u->hrir_data)
+        pa_xfree(u->hrir_data);
+
+    if (u->input_buffer)
+        pa_xfree(u->input_buffer);
+
+    if (u->mapping_left)
+        pa_xfree(u->mapping_left);
+    if (u->mapping_right)
+        pa_xfree(u->mapping_right);
+
     pa_xfree(u);
 }
-- 
1.7.8.5

Attachment: signature.asc
Description: This is a digitally signed message part.

_______________________________________________
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss

Reply via email to