]> code.delx.au - pulseaudio/blobdiff - src/modules/module-ladspa-sink.c
tests: improve resampler test
[pulseaudio] / src / modules / module-ladspa-sink.c
index 3e0babfab894f9b4defde6d503d1932a25bbd8f4..f6430f299fe6c91c933bf77b785146510ce7b4b6 100644 (file)
@@ -5,7 +5,7 @@
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
 
   PulseAudio 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 of the License,
+  by the Free Software Foundation; either version 2.1 of the License,
   or (at your option) any later version.
 
   PulseAudio is distributed in the hope that it will be useful, but
   or (at your option) any later version.
 
   PulseAudio is distributed in the hope that it will be useful, but
@@ -27,6 +27,7 @@
 #endif
 
 #include <pulse/xmalloc.h>
 #endif
 
 #include <pulse/xmalloc.h>
+#include <pulse/i18n.h>
 
 #include <pulsecore/core-error.h>
 #include <pulsecore/namereg.h>
 
 #include <pulsecore/core-error.h>
 #include <pulsecore/namereg.h>
 #include "ladspa.h"
 
 PA_MODULE_AUTHOR("Lennart Poettering");
 #include "ladspa.h"
 
 PA_MODULE_AUTHOR("Lennart Poettering");
-PA_MODULE_DESCRIPTION("Virtual LADSPA sink");
+PA_MODULE_DESCRIPTION(_("Virtual LADSPA sink"));
 PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_LOAD_ONCE(FALSE);
 PA_MODULE_USAGE(
 PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_LOAD_ONCE(FALSE);
 PA_MODULE_USAGE(
-        "sink_name=<name for the sink> "
-        "master=<name of sink to remap> "
-        "format=<sample format> "
-        "channels=<number of channels> "
-        "rate=<sample rate> "
-        "channel_map=<channel map> "
-        "plugin=<ladspa plugin name> "
-        "label=<ladspa plugin label> "
-        "control=<comma seperated list of input control values>");
+    _("sink_name=<name for the sink> "
+      "sink_properties=<properties for the sink> "
+      "master=<name of sink to filter> "
+      "format=<sample format> "
+      "rate=<sample rate> "
+      "channels=<number of channels> "
+      "channel_map=<input channel map> "
+      "plugin=<ladspa plugin name> "
+      "label=<ladspa plugin label> "
+      "control=<comma seperated list of input control values> "
+      "input_ladspaport_map=<comma separated list of input LADSPA port names> "
+      "output_ladspaport_map=<comma separated list of output LADSPA port names> "));
 
 #define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
 
 
 #define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
 
+/* PLEASE NOTICE: The PortAudio ports and the LADSPA ports are two different concepts.
+They are not related and where possible the names of the LADSPA port variables contains "ladspa" to avoid confusion */
+
 struct userdata {
 struct userdata {
-    pa_core *core;
     pa_module *module;
 
     pa_module *module;
 
-    pa_sink *sink, *master;
+    pa_sink *sink;
     pa_sink_input *sink_input;
 
     const LADSPA_Descriptor *descriptor;
     pa_sink_input *sink_input;
 
     const LADSPA_Descriptor *descriptor;
-    unsigned channels;
     LADSPA_Handle handle[PA_CHANNELS_MAX];
     LADSPA_Handle handle[PA_CHANNELS_MAX];
-    LADSPA_Data *input, *output;
+    unsigned long max_ladspaport_count, input_count, output_count, channels;
+    LADSPA_Data **input, **output;
     size_t block_size;
     size_t block_size;
-    unsigned long input_port, output_port;
     LADSPA_Data *control;
 
     /* This is a dummy buffer. Every port must be connected, but we don't care
     LADSPA_Data *control;
 
     /* This is a dummy buffer. Every port must be connected, but we don't care
-       about control out ports. We connect them all to this single buffer. */
+    about control out ports. We connect them all to this single buffer. */
     LADSPA_Data control_out;
 
     pa_memblockq *memblockq;
     LADSPA_Data control_out;
 
     pa_memblockq *memblockq;
+
+    pa_bool_t auto_desc;
 };
 
 static const char* const valid_modargs[] = {
     "sink_name",
 };
 
 static const char* const valid_modargs[] = {
     "sink_name",
+    "sink_properties",
     "master",
     "format",
     "master",
     "format",
-    "channels",
     "rate",
     "rate",
+    "channels",
     "channel_map",
     "plugin",
     "label",
     "control",
     "channel_map",
     "plugin",
     "label",
     "control",
+    "input_ladspaport_map",
+    "output_ladspaport_map",
     NULL
 };
 
 /* Called from I/O thread context */
     NULL
 };
 
 /* Called from I/O thread context */
-static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
     struct userdata *u = PA_SINK(o)->userdata;
 
     switch (code) {
 
     struct userdata *u = PA_SINK(o)->userdata;
 
     switch (code) {
 
-        case PA_SINK_MESSAGE_GET_LATENCY: {
-            pa_usec_t usec = 0;
+    case PA_SINK_MESSAGE_GET_LATENCY:
+
+        /* The sink is _put() before the sink input is, so let's
+         * make sure we don't access it in that time. Also, the
+         * sink input is first shut down, the sink second. */
+        if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+                !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
+            *((pa_usec_t*) data) = 0;
+            return 0;
+        }
+
+        *((pa_usec_t*) data) =
 
             /* Get the latency of the master sink */
 
             /* Get the latency of the master sink */
-            if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
-                usec = 0;
+            pa_sink_get_latency_within_thread(u->sink_input->sink) +
 
             /* Add the latency internal to our sink input on top */
 
             /* Add the latency internal to our sink input on top */
-            usec += pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->master->sample_spec);
+            pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
 
 
-            *((pa_usec_t*) data) = usec;
-            return 0;
-        }
+        return 0;
     }
 
     return pa_sink_process_msg(o, code, data, offset, chunk);
 }
 
 /* Called from main context */
     }
 
     return pa_sink_process_msg(o, code, data, offset, chunk);
 }
 
 /* Called from main context */
-static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
+static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) {
     struct userdata *u;
 
     pa_sink_assert_ref(s);
     pa_assert_se(u = s->userdata);
 
     struct userdata *u;
 
     pa_sink_assert_ref(s);
     pa_assert_se(u = s->userdata);
 
-    if (PA_SINK_IS_LINKED(state) &&
-        u->sink_input &&
-        PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
-
-        pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
+    if (!PA_SINK_IS_LINKED(state) ||
+            !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+        return 0;
 
 
+    pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
     return 0;
 }
 
 /* Called from I/O thread context */
     return 0;
 }
 
 /* Called from I/O thread context */
-static void sink_request_rewind(pa_sink *s) {
+static void sink_request_rewind_cb(pa_sink *s) {
     struct userdata *u;
 
     pa_sink_assert_ref(s);
     pa_assert_se(u = s->userdata);
 
     struct userdata *u;
 
     pa_sink_assert_ref(s);
     pa_assert_se(u = s->userdata);
 
+    if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+            !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
+        return;
+
     /* Just hand this one over to the master sink */
     /* Just hand this one over to the master sink */
-    pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes + pa_memblockq_get_length(u->memblockq), TRUE, FALSE);
+    pa_sink_input_request_rewind(u->sink_input,
+                                 s->thread_info.rewind_nbytes +
+                                 pa_memblockq_get_length(u->memblockq), TRUE, FALSE, FALSE);
 }
 
 /* Called from I/O thread context */
 }
 
 /* Called from I/O thread context */
-static void sink_update_requested_latency(pa_sink *s) {
+static void sink_update_requested_latency_cb(pa_sink *s) {
     struct userdata *u;
 
     pa_sink_assert_ref(s);
     pa_assert_se(u = s->userdata);
 
     struct userdata *u;
 
     pa_sink_assert_ref(s);
     pa_assert_se(u = s->userdata);
 
+    if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+            !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
+        return;
+
     /* Just hand this one over to the master sink */
     pa_sink_input_set_requested_latency_within_thread(
     /* Just hand this one over to the master sink */
     pa_sink_input_set_requested_latency_within_thread(
-            u->sink_input,
-            pa_sink_get_requested_latency_within_thread(s));
+        u->sink_input,
+        pa_sink_get_requested_latency_within_thread(s));
+}
+
+/* Called from main context */
+static void sink_set_volume_cb(pa_sink *s) {
+    struct userdata *u;
+
+    pa_sink_assert_ref(s);
+    pa_assert_se(u = s->userdata);
+
+    if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
+            !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+        return;
+
+    pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, TRUE);
+}
+
+/* Called from main context */
+static void sink_set_mute_cb(pa_sink *s) {
+    struct userdata *u;
+
+    pa_sink_assert_ref(s);
+    pa_assert_se(u = s->userdata);
+
+    if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
+            !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+        return;
+
+    pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted);
 }
 
 /* Called from I/O thread context */
 }
 
 /* Called from I/O thread context */
@@ -165,15 +219,15 @@ 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;
     struct userdata *u;
     float *src, *dst;
     size_t fs;
-    unsigned n, c;
+    unsigned n, h, c;
     pa_memchunk tchunk;
 
     pa_sink_input_assert_ref(i);
     pa_assert(chunk);
     pa_assert_se(u = i->userdata);
 
     pa_memchunk tchunk;
 
     pa_sink_input_assert_ref(i);
     pa_assert(chunk);
     pa_assert_se(u = i->userdata);
 
-    if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
-        return -1;
+    /* Hmm, process any rewind request that might be queued up */
+    pa_sink_process_rewind(u->sink, 0);
 
     while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) {
         pa_memchunk nchunk;
 
     while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) {
         pa_memchunk nchunk;
@@ -187,7 +241,7 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk
     pa_assert(tchunk.length > 0);
 
     fs = pa_frame_size(&i->sample_spec);
     pa_assert(tchunk.length > 0);
 
     fs = pa_frame_size(&i->sample_spec);
-    n = PA_MIN(tchunk.length, u->block_size) / fs;
+    n = (unsigned) (PA_MIN(tchunk.length, u->block_size) / fs);
 
     pa_assert(n > 0);
 
 
     pa_assert(n > 0);
 
@@ -200,10 +254,12 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk
     src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index);
     dst = (float*) pa_memblock_acquire(chunk->memblock);
 
     src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index);
     dst = (float*) pa_memblock_acquire(chunk->memblock);
 
-    for (c = 0; c < u->channels; c++) {
-        pa_sample_clamp(PA_SAMPLE_FLOAT32NE, u->input, sizeof(float), src+c, u->channels*sizeof(float), n);
-        u->descriptor->run(u->handle[c], n);
-        pa_sample_clamp(PA_SAMPLE_FLOAT32NE, dst+c, u->channels*sizeof(float), u->output, sizeof(float), n);
+    for (h = 0; h < (u->channels / u->max_ladspaport_count); h++) {
+        for (c = 0; c < u->input_count; c++)
+            pa_sample_clamp(PA_SAMPLE_FLOAT32NE, u->input[c], sizeof(float), src+ h*u->max_ladspaport_count + c, u->channels*sizeof(float), n);
+        u->descriptor->run(u->handle[h], n);
+        for (c = 0; c < u->output_count; c++)
+            pa_sample_clamp(PA_SAMPLE_FLOAT32NE, dst + h*u->max_ladspaport_count + c, u->channels*sizeof(float), u->output[c], sizeof(float), n);
     }
 
     pa_memblock_release(tchunk.memblock);
     }
 
     pa_memblock_release(tchunk.memblock);
@@ -217,16 +273,13 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk
 /* Called from I/O thread context */
 static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
     struct userdata *u;
 /* Called from I/O thread context */
 static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
     struct userdata *u;
+    size_t amount = 0;
 
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
-    pa_assert(nbytes > 0);
-
-    if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
-        return;
 
     if (u->sink->thread_info.rewind_nbytes > 0) {
 
     if (u->sink->thread_info.rewind_nbytes > 0) {
-        size_t max_rewrite, amount;
+        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 + pa_memblockq_get_length(u->memblockq);
         amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite);
@@ -235,21 +288,21 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
         if (amount > 0) {
             unsigned c;
 
         if (amount > 0) {
             unsigned c;
 
-            pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE);
-            pa_sink_process_rewind(u->sink, amount);
+            pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE, TRUE);
 
             pa_log_debug("Resetting plugin");
 
             /* Reset the plugin */
             if (u->descriptor->deactivate)
 
             pa_log_debug("Resetting plugin");
 
             /* Reset the plugin */
             if (u->descriptor->deactivate)
-                for (c = 0; c < u->channels; c++)
+                for (c = 0; c < (u->channels / u->max_ladspaport_count); c++)
                     u->descriptor->deactivate(u->handle[c]);
             if (u->descriptor->activate)
                     u->descriptor->deactivate(u->handle[c]);
             if (u->descriptor->activate)
-                for (c = 0; c < u->channels; c++)
+                for (c = 0; c < (u->channels / u->max_ladspaport_count); c++)
                     u->descriptor->activate(u->handle[c]);
         }
     }
 
                     u->descriptor->activate(u->handle[c]);
         }
     }
 
+    pa_sink_process_rewind(u->sink, amount);
     pa_memblockq_rewind(u->memblockq, nbytes);
 }
 
     pa_memblockq_rewind(u->memblockq, nbytes);
 }
 
@@ -260,11 +313,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_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
-    if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
-        return;
-
     pa_memblockq_set_maxrewind(u->memblockq, nbytes);
     pa_memblockq_set_maxrewind(u->memblockq, nbytes);
-    pa_sink_set_max_rewind(u->sink, nbytes);
+    pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
 }
 
 /* Called from I/O thread context */
 }
 
 /* Called from I/O thread context */
@@ -274,10 +324,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);
 
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
-    if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
-        return;
-
-    pa_sink_set_max_request(u->sink, nbytes);
+    pa_sink_set_max_request_within_thread(u->sink, nbytes);
 }
 
 /* Called from I/O thread context */
 }
 
 /* Called from I/O thread context */
@@ -287,10 +334,17 @@ static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
-    if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
-        return;
+    pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
+    struct userdata *u;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert_se(u = i->userdata);
 
 
-    pa_sink_update_latency_range(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
+    pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
 }
 
 /* Called from I/O thread context */
 }
 
 /* Called from I/O thread context */
@@ -300,11 +354,8 @@ static void sink_input_detach_cb(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
-    if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
-        return;
-
     pa_sink_detach_within_thread(u->sink);
     pa_sink_detach_within_thread(u->sink);
-    pa_sink_set_asyncmsgq(u->sink, NULL);
+
     pa_sink_set_rtpoll(u->sink, NULL);
 }
 
     pa_sink_set_rtpoll(u->sink, NULL);
 }
 
@@ -315,14 +366,13 @@ static void sink_input_attach_cb(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
-    if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
-        return;
+    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);
+    pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
+    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));
 
 
-    pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq);
-    pa_sink_set_rtpoll(u->sink, i->sink->rtpoll);
     pa_sink_attach_within_thread(u->sink);
     pa_sink_attach_within_thread(u->sink);
-
-    pa_sink_update_latency_range(u->sink, u->master->thread_info.min_latency, u->master->thread_info.max_latency);
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
@@ -332,15 +382,19 @@ static void sink_input_kill_cb(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
-    pa_sink_unlink(u->sink);
+    /* The order here matters! We first kill the sink input, followed
+     * by the sink. That means the sink callbacks must be protected
+     * against an unconnected sink input! */
     pa_sink_input_unlink(u->sink_input);
     pa_sink_input_unlink(u->sink_input);
+    pa_sink_unlink(u->sink);
 
 
-    pa_sink_unref(u->sink);
-    u->sink = NULL;
     pa_sink_input_unref(u->sink_input);
     u->sink_input = NULL;
 
     pa_sink_input_unref(u->sink_input);
     u->sink_input = NULL;
 
-    pa_module_unload_request(u->module);
+    pa_sink_unref(u->sink);
+    u->sink = NULL;
+
+    pa_module_unload_request(u->module, TRUE);
 }
 
 /* Called from IO thread context */
 }
 
 /* Called from IO thread context */
@@ -353,40 +407,96 @@ static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t s
     /* If we are added for the first time, ask for a rewinding so that
      * we are heard right-away. */
     if (PA_SINK_INPUT_IS_LINKED(state) &&
     /* If we are added for the first time, ask for a rewinding so that
      * we are heard right-away. */
     if (PA_SINK_INPUT_IS_LINKED(state) &&
-        i->thread_info.state == PA_SINK_INPUT_INIT) {
+            i->thread_info.state == PA_SINK_INPUT_INIT) {
         pa_log_debug("Requesting rewind due to state change.");
         pa_log_debug("Requesting rewind due to state change.");
-        pa_sink_input_request_rewind(i, 0, FALSE, TRUE);
+        pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE);
+    }
+}
+
+/* Called from main context */
+static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) {
+    struct userdata *u;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert_se(u = i->userdata);
+
+    return u->sink != dest;
+}
+
+/* Called from main context */
+static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
+    struct userdata *u;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert_se(u = i->userdata);
+
+    if (dest) {
+        pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
+        pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
+    } else
+        pa_sink_set_asyncmsgq(u->sink, NULL);
+
+    if (u->auto_desc && dest) {
+        const char *z;
+        pa_proplist *pl;
+
+        pl = pa_proplist_new();
+        z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
+        pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s",
+                         pa_proplist_gets(u->sink->proplist, "device.ladspa.name"), z ? z : dest->name);
+
+        pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl);
+        pa_proplist_free(pl);
     }
 }
 
     }
 }
 
+/* Called from main context */
+static void sink_input_volume_changed_cb(pa_sink_input *i) {
+    struct userdata *u;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert_se(u = i->userdata);
+
+    pa_sink_volume_changed(u->sink, &i->volume);
+}
+
+/* Called from main context */
+static void sink_input_mute_changed_cb(pa_sink_input *i) {
+    struct userdata *u;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert_se(u = i->userdata);
+
+    pa_sink_mute_changed(u->sink, i->muted);
+}
+
 int pa__init(pa_module*m) {
     struct userdata *u;
     pa_sample_spec ss;
     pa_channel_map map;
     pa_modargs *ma;
     char *t;
 int pa__init(pa_module*m) {
     struct userdata *u;
     pa_sample_spec ss;
     pa_channel_map map;
     pa_modargs *ma;
     char *t;
-    const char *z;
     pa_sink *master;
     pa_sink_input_new_data sink_input_data;
     pa_sink_new_data sink_data;
     pa_sink *master;
     pa_sink_input_new_data sink_input_data;
     pa_sink_new_data sink_data;
-    const char *plugin, *label;
+    const char *plugin, *label, *input_ladspaport_map, *output_ladspaport_map;
     LADSPA_Descriptor_Function descriptor_func;
     LADSPA_Descriptor_Function descriptor_func;
+    unsigned long input_ladspaport[PA_CHANNELS_MAX], output_ladspaport[PA_CHANNELS_MAX];
     const char *e, *cdata;
     const LADSPA_Descriptor *d;
     const char *e, *cdata;
     const LADSPA_Descriptor *d;
-    unsigned long input_port, output_port, p, j, n_control;
-    unsigned c;
+    unsigned long p, h, j, n_control, c;
     pa_bool_t *use_default = NULL;
 
     pa_assert(m);
 
     pa_bool_t *use_default = NULL;
 
     pa_assert(m);
 
-    pa_assert(sizeof(LADSPA_Data) == sizeof(float));
+    pa_assert_cc(sizeof(LADSPA_Data) == sizeof(float));
 
     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
         pa_log("Failed to parse module arguments.");
         goto fail;
     }
 
 
     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
         pa_log("Failed to parse module arguments.");
         goto fail;
     }
 
-    if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "master", NULL), PA_NAMEREG_SINK, 1))) {
+    if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "master", NULL), PA_NAMEREG_SINK))) {
         pa_log("Master sink not found");
         goto fail;
     }
         pa_log("Master sink not found");
         goto fail;
     }
@@ -409,16 +519,22 @@ int pa__init(pa_module*m) {
         goto fail;
     }
 
         goto fail;
     }
 
+    if (!(input_ladspaport_map = pa_modargs_get_value(ma, "input_ladspaport_map", NULL)))
+        pa_log_debug("Using default input ladspa port mapping");
+
+    if (!(output_ladspaport_map = pa_modargs_get_value(ma, "output_ladspaport_map", NULL)))
+        pa_log_debug("Using default output ladspa port mapping");
+
     cdata = pa_modargs_get_value(ma, "control", NULL);
 
     u = pa_xnew0(struct userdata, 1);
     cdata = pa_modargs_get_value(ma, "control", NULL);
 
     u = pa_xnew0(struct userdata, 1);
-    u->core = m->core;
     u->module = m;
     m->userdata = u;
     u->module = m;
     m->userdata = u;
-    u->master = master;
-    u->sink = NULL;
-    u->sink_input = NULL;
     u->memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, pa_frame_size(&ss), 1, 1, 0, NULL);
     u->memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, pa_frame_size(&ss), 1, 1, 0, NULL);
+    u->max_ladspaport_count = 1; /*to avoid division by zero etc. in pa__done when failing before this value has been set*/
+    u->channels = 0;
+    u->input = NULL;
+    u->output = NULL;
 
     if (!(e = getenv("LADSPA_PATH")))
         e = LADSPA_PATH;
 
     if (!(e = getenv("LADSPA_PATH")))
         e = LADSPA_PATH;
@@ -460,64 +576,127 @@ int pa__init(pa_module*m) {
     pa_log_debug("Maker: %s", d->Maker);
     pa_log_debug("Copyright: %s", d->Copyright);
 
     pa_log_debug("Maker: %s", d->Maker);
     pa_log_debug("Copyright: %s", d->Copyright);
 
-    input_port = output_port = (unsigned long) -1;
     n_control = 0;
     n_control = 0;
+    u->channels = ss.channels;
 
 
+    /*
+    * Enumerate ladspa ports
+    * Default mapping is in order given by the plugin
+    */
     for (p = 0; p < d->PortCount; p++) {
     for (p = 0; p < d->PortCount; p++) {
+        if (LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) {
+            if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p])) {
+                pa_log_debug("Port %lu is input: %s", p, d->PortNames[p]);
+                input_ladspaport[u->input_count] = p;
+                u->input_count++;
+            } else if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p])) {
+                pa_log_debug("Port %lu is output: %s", p, d->PortNames[p]);
+                output_ladspaport[u->output_count] = p;
+                u->output_count++;
+            }
+        } else if (LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p]) && LADSPA_IS_PORT_INPUT(d->PortDescriptors[p])) {
+            pa_log_debug("Port %lu is control: %s", p, d->PortNames[p]);
+            n_control++;
+        } else
+            pa_log_debug("Ignored port %s", d->PortNames[p]);
+        /* XXX: Has anyone ever seen an in-place plugin with non-equal number of input and output ports? */
+        /* Could be if the plugin is for up-mixing stereo to 5.1 channels */
+        /* Or if the plugin is down-mixing 5.1 to two channel stereo or binaural encoded signal */
+        if (u->input_count > u->max_ladspaport_count)
+            u->max_ladspaport_count = u->input_count;
+        else
+            u->max_ladspaport_count = u->output_count;
+    }
 
 
-        if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) {
+    if (u->channels % u->max_ladspaport_count) {
+        pa_log("Cannot handle non-integral number of plugins required for given number of channels");
+        goto fail;
+    }
 
 
-            if (strcmp(d->PortNames[p], "Input") == 0) {
-                pa_assert(input_port == (unsigned long) -1);
-                input_port = p;
-            } else {
-                pa_log("Found audio input port on plugin we cannot handle: %s", d->PortNames[p]);
+    pa_log_debug("Will run %lu plugin instances", u->channels / u->max_ladspaport_count);
+
+    /* Parse data for input ladspa port map */
+    if (input_ladspaport_map) {
+        const char *state = NULL;
+        char *pname;
+        c = 0;
+        while ((pname = pa_split(input_ladspaport_map, ",", &state))) {
+            if (c == u->input_count) {
+                pa_log("Too many ports in input ladspa port map");
                 goto fail;
             }
 
                 goto fail;
             }
 
-        } else if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) {
 
 
-            if (strcmp(d->PortNames[p], "Output") == 0) {
-                pa_assert(output_port == (unsigned long) -1);
-                output_port = p;
-            } else {
-                pa_log("Found audio output port on plugin we cannot handle: %s", d->PortNames[p]);
-                goto fail;
+            for (p = 0; p < d->PortCount; p++) {
+                if (strcmp(d->PortNames[p], pname) == 0) {
+                    if (LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p]) && LADSPA_IS_PORT_INPUT(d->PortDescriptors[p])) {
+                        input_ladspaport[c] = p;
+                    } else {
+                        pa_log("Port %s is not an audio input ladspa port", pname);
+                        pa_xfree(pname);
+                        goto fail;
+                    }
+                }
             }
             }
-
-        } else if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p]))
-            n_control++;
-        else {
-            pa_assert(LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p]));
-            pa_log_debug("Ignored control output port \"%s\".", d->PortNames[p]);
+            c++;
+            pa_xfree(pname);
         }
     }
 
         }
     }
 
-    if ((input_port == (unsigned long) -1) || (output_port == (unsigned long) -1)) {
-        pa_log("Failed to identify input and output ports. "
-               "Right now this module can only deal with plugins which provide an 'Input' and an 'Output' audio port. "
-               "Patches welcome!");
-        goto fail;
+    /* Parse data for output port map */
+    if (output_ladspaport_map) {
+        const char *state = NULL;
+        char *pname;
+        c = 0;
+        while ((pname = pa_split(output_ladspaport_map, ",", &state))) {
+            if (c == u->output_count) {
+                pa_log("Too many ports in output ladspa port map");
+                goto fail;
+            }
+            for (p = 0; p < d->PortCount; p++) {
+                if (strcmp(d->PortNames[p], pname) == 0) {
+                    if (LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p]) && LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p])) {
+                        output_ladspaport[c] = p;
+                    } else {
+                        pa_log("Port %s is not an output ladspa port", pname);
+                        pa_xfree(pname);
+                        goto fail;
+                    }
+                }
+            }
+            c++;
+            pa_xfree(pname);
+        }
     }
 
     }
 
+
     u->block_size = pa_frame_align(pa_mempool_block_size_max(m->core->mempool), &ss);
 
     u->block_size = pa_frame_align(pa_mempool_block_size_max(m->core->mempool), &ss);
 
-    u->input = (LADSPA_Data*) pa_xnew(uint8_t, u->block_size);
-    if (LADSPA_IS_INPLACE_BROKEN(d->Properties))
-        u->output = (LADSPA_Data*) pa_xnew(uint8_t, u->block_size);
-    else
+    /* Create buffers */
+    if (LADSPA_IS_INPLACE_BROKEN(d->Properties)) {
+        u->input = (LADSPA_Data**) pa_xnew(LADSPA_Data*, (unsigned) u->input_count);
+        for (c = 0; c < u->input_count; c++)
+            u->input[c] = (LADSPA_Data*) pa_xnew(uint8_t, (unsigned) u->block_size);
+        u->output = (LADSPA_Data**) pa_xnew(LADSPA_Data*, (unsigned) u->output_count);
+        for (c = 0; c < u->output_count; c++)
+            u->output[c] = (LADSPA_Data*) pa_xnew(uint8_t, (unsigned) u->block_size);
+    } else {
+        u->input = (LADSPA_Data**) pa_xnew(LADSPA_Data*, (unsigned) u->max_ladspaport_count);
+        for (c = 0; c < u->max_ladspaport_count; c++)
+            u->input[c] = (LADSPA_Data*) pa_xnew(uint8_t, (unsigned) u->block_size);
         u->output = u->input;
         u->output = u->input;
-
-    u->channels = ss.channels;
-
-    for (c = 0; c < ss.channels; c++) {
-        if (!(u->handle[c] = d->instantiate(d, ss.rate))) {
-            pa_log("Failed to instantiate plugin %s with label %s for channel %i", plugin, d->Label, c);
+    }
+    /* Initialize plugin instances */
+    for (h = 0; h < (u->channels / u->max_ladspaport_count); h++) {
+        if (!(u->handle[h] = d->instantiate(d, ss.rate))) {
+            pa_log("Failed to instantiate plugin %s with label %s", plugin, d->Label);
             goto fail;
         }
 
             goto fail;
         }
 
-        d->connect_port(u->handle[c], input_port, u->input);
-        d->connect_port(u->handle[c], output_port, u->output);
+        for (c = 0; c < u->input_count; c++)
+            d->connect_port(u->handle[h], input_ladspaport[c], u->input[c]);
+        for (c = 0; c < u->output_count; c++)
+            d->connect_port(u->handle[h], output_ladspaport[c], u->output[c]);
     }
 
     if (!cdata && n_control > 0) {
     }
 
     if (!cdata && n_control > 0) {
@@ -528,10 +707,9 @@ int pa__init(pa_module*m) {
     if (n_control > 0) {
         const char *state = NULL;
         char *k;
     if (n_control > 0) {
         const char *state = NULL;
         char *k;
-        unsigned long h;
 
 
-        u->control = pa_xnew(LADSPA_Data, n_control);
-        use_default = pa_xnew(pa_bool_t, n_control);
+        u->control = pa_xnew(LADSPA_Data, (unsigned) n_control);
+        use_default = pa_xnew(pa_bool_t, (unsigned) n_control);
         p = 0;
 
         while ((k = pa_split(cdata, ",", &state)) && p < n_control) {
         p = 0;
 
         while ((k = pa_split(cdata, ",", &state)) && p < n_control) {
@@ -552,11 +730,11 @@ int pa__init(pa_module*m) {
             pa_xfree(k);
 
             use_default[p] = FALSE;
             pa_xfree(k);
 
             use_default[p] = FALSE;
-            u->control[p++] = f;
+            u->control[p++] = (LADSPA_Data) f;
         }
 
         /* The previous loop doesn't take the last control value into account
         }
 
         /* The previous loop doesn't take the last control value into account
-           if it is left empty, so we do it here. */
+        if it is left empty, so we do it here. */
         if (*cdata == 0 || cdata[strlen(cdata) - 1] == ',') {
             if (p < n_control)
                 use_default[p] = TRUE;
         if (*cdata == 0 || cdata[strlen(cdata) - 1] == ',') {
             if (p < n_control)
                 use_default[p] = TRUE;
@@ -582,7 +760,7 @@ int pa__init(pa_module*m) {
                 continue;
 
             if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p])) {
                 continue;
 
             if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p])) {
-                for (c = 0; c < ss.channels; c++)
+                for (c = 0; c < (u->channels / u->max_ladspaport_count); c++)
                     d->connect_port(u->handle[c], p, &u->control_out);
                 continue;
             }
                     d->connect_port(u->handle[c], p, &u->control_out);
                 continue;
             }
@@ -601,59 +779,59 @@ int pa__init(pa_module*m) {
                 upper = d->PortRangeHints[p].UpperBound;
 
                 if (LADSPA_IS_HINT_SAMPLE_RATE(hint)) {
                 upper = d->PortRangeHints[p].UpperBound;
 
                 if (LADSPA_IS_HINT_SAMPLE_RATE(hint)) {
-                    lower *= ss.rate;
-                    upper *= ss.rate;
+                    lower *= (LADSPA_Data) ss.rate;
+                    upper *= (LADSPA_Data) ss.rate;
                 }
 
                 switch (hint & LADSPA_HINT_DEFAULT_MASK) {
 
                 }
 
                 switch (hint & LADSPA_HINT_DEFAULT_MASK) {
 
-                    case LADSPA_HINT_DEFAULT_MINIMUM:
-                        u->control[h] = lower;
-                        break;
-
-                    case LADSPA_HINT_DEFAULT_MAXIMUM:
-                        u->control[h] = upper;
-                        break;
-
-                    case LADSPA_HINT_DEFAULT_LOW:
-                        if (LADSPA_IS_HINT_LOGARITHMIC(hint))
-                            u->control[h] = exp(log(lower) * 0.75 + log(upper) * 0.25);
-                        else
-                            u->control[h] = lower * 0.75 + upper * 0.25;
-                        break;
-
-                    case LADSPA_HINT_DEFAULT_MIDDLE:
-                        if (LADSPA_IS_HINT_LOGARITHMIC(hint))
-                            u->control[h] = exp(log(lower) * 0.5 + log(upper) * 0.5);
-                        else
-                            u->control[h] = lower * 0.5 + upper * 0.5;
-                        break;
-
-                    case LADSPA_HINT_DEFAULT_HIGH:
-                        if (LADSPA_IS_HINT_LOGARITHMIC(hint))
-                            u->control[h] = exp(log(lower) * 0.25 + log(upper) * 0.75);
-                        else
-                            u->control[h] = lower * 0.25 + upper * 0.75;
-                        break;
-
-                    case LADSPA_HINT_DEFAULT_0:
-                        u->control[h] = 0;
-                        break;
-
-                    case LADSPA_HINT_DEFAULT_1:
-                        u->control[h] = 1;
-                        break;
-
-                    case LADSPA_HINT_DEFAULT_100:
-                        u->control[h] = 100;
-                        break;
-
-                    case LADSPA_HINT_DEFAULT_440:
-                        u->control[h] = 440;
-                        break;
-
-                    default:
-                        pa_assert_not_reached();
+                case LADSPA_HINT_DEFAULT_MINIMUM:
+                    u->control[h] = lower;
+                    break;
+
+                case LADSPA_HINT_DEFAULT_MAXIMUM:
+                    u->control[h] = upper;
+                    break;
+
+                case LADSPA_HINT_DEFAULT_LOW:
+                    if (LADSPA_IS_HINT_LOGARITHMIC(hint))
+                        u->control[h] = (LADSPA_Data) exp(log(lower) * 0.75 + log(upper) * 0.25);
+                    else
+                        u->control[h] = (LADSPA_Data) (lower * 0.75 + upper * 0.25);
+                    break;
+
+                case LADSPA_HINT_DEFAULT_MIDDLE:
+                    if (LADSPA_IS_HINT_LOGARITHMIC(hint))
+                        u->control[h] = (LADSPA_Data) exp(log(lower) * 0.5 + log(upper) * 0.5);
+                    else
+                        u->control[h] = (LADSPA_Data) (lower * 0.5 + upper * 0.5);
+                    break;
+
+                case LADSPA_HINT_DEFAULT_HIGH:
+                    if (LADSPA_IS_HINT_LOGARITHMIC(hint))
+                        u->control[h] = (LADSPA_Data) exp(log(lower) * 0.25 + log(upper) * 0.75);
+                    else
+                        u->control[h] = (LADSPA_Data) (lower * 0.25 + upper * 0.75);
+                    break;
+
+                case LADSPA_HINT_DEFAULT_0:
+                    u->control[h] = 0;
+                    break;
+
+                case LADSPA_HINT_DEFAULT_1:
+                    u->control[h] = 1;
+                    break;
+
+                case LADSPA_HINT_DEFAULT_100:
+                    u->control[h] = 100;
+                    break;
+
+                case LADSPA_HINT_DEFAULT_440:
+                    u->control[h] = 440;
+                    break;
+
+                default:
+                    pa_assert_not_reached();
                 }
             }
 
                 }
             }
 
@@ -662,7 +840,7 @@ int pa__init(pa_module*m) {
 
             pa_log_debug("Binding %f to port %s", u->control[h], d->PortNames[p]);
 
 
             pa_log_debug("Binding %f to port %s", u->control[h], d->PortNames[p]);
 
-            for (c = 0; c < ss.channels; c++)
+            for (c = 0; c < (u->channels / u->max_ladspaport_count); c++)
                 d->connect_port(u->handle[c], p, &u->control[h]);
 
             h++;
                 d->connect_port(u->handle[c], p, &u->control[h]);
 
             h++;
@@ -672,7 +850,7 @@ int pa__init(pa_module*m) {
     }
 
     if (d->activate)
     }
 
     if (d->activate)
-        for (c = 0; c < u->channels; c++)
+        for (c = 0; c < (u->channels / u->max_ladspaport_count); c++)
             d->activate(u->handle[c]);
 
     /* Create sink */
             d->activate(u->handle[c]);
 
     /* Create sink */
@@ -681,11 +859,8 @@ int pa__init(pa_module*m) {
     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.ladspa", master->name);
     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.ladspa", master->name);
-    sink_data.namereg_fail = FALSE;
     pa_sink_new_data_set_sample_spec(&sink_data, &ss);
     pa_sink_new_data_set_channel_map(&sink_data, &map);
     pa_sink_new_data_set_sample_spec(&sink_data, &ss);
     pa_sink_new_data_set_channel_map(&sink_data, &map);
-    z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
-    pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", label, z ? z : master->name);
     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.ladspa.module", plugin);
     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.ladspa.module", plugin);
@@ -695,7 +870,22 @@ int pa__init(pa_module*m) {
     pa_proplist_sets(sink_data.proplist, "device.ladspa.copyright", d->Copyright);
     pa_proplist_setf(sink_data.proplist, "device.ladspa.unique_id", "%lu", (unsigned long) d->UniqueID);
 
     pa_proplist_sets(sink_data.proplist, "device.ladspa.copyright", d->Copyright);
     pa_proplist_setf(sink_data.proplist, "device.ladspa.unique_id", "%lu", (unsigned long) d->UniqueID);
 
-    u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY);
+    if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) {
+        pa_log("Invalid properties");
+        pa_sink_new_data_done(&sink_data);
+        goto fail;
+    }
+
+    if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
+        const char *z;
+
+        z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
+        pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", d->Name, z ? z : master->name);
+    }
+
+    u->sink = pa_sink_new(m->core, &sink_data,
+                          PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME|
+                          (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)));
     pa_sink_new_data_done(&sink_data);
 
     if (!u->sink) {
     pa_sink_new_data_done(&sink_data);
 
     if (!u->sink) {
@@ -703,26 +893,28 @@ int pa__init(pa_module*m) {
         goto fail;
     }
 
         goto fail;
     }
 
-    u->sink->parent.process_msg = sink_process_msg;
-    u->sink->set_state = sink_set_state;
-    u->sink->update_requested_latency = sink_update_requested_latency;
-    u->sink->request_rewind = sink_request_rewind;
+    u->sink->parent.process_msg = sink_process_msg_cb;
+    u->sink->set_state = sink_set_state_cb;
+    u->sink->update_requested_latency = sink_update_requested_latency_cb;
+    u->sink->request_rewind = sink_request_rewind_cb;
+    u->sink->set_volume = sink_set_volume_cb;
+    u->sink->set_mute = sink_set_mute_cb;
     u->sink->userdata = u;
 
     pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
     u->sink->userdata = u;
 
     pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
-    pa_sink_set_rtpoll(u->sink, master->rtpoll);
 
     /* Create sink input */
     pa_sink_input_new_data_init(&sink_input_data);
     sink_input_data.driver = __FILE__;
     sink_input_data.module = m;
 
     /* Create sink input */
     pa_sink_input_new_data_init(&sink_input_data);
     sink_input_data.driver = __FILE__;
     sink_input_data.module = m;
-    sink_input_data.sink = u->master;
+    sink_input_data.sink = master;
+    sink_input_data.origin_sink = u->sink;
     pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "LADSPA Stream");
     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_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "LADSPA Stream");
     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);
 
-    u->sink_input = pa_sink_input_new(m->core, &sink_input_data, PA_SINK_INPUT_DONT_MOVE);
+    pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
     pa_sink_input_new_data_done(&sink_input_data);
 
     if (!u->sink_input)
     pa_sink_input_new_data_done(&sink_input_data);
 
     if (!u->sink_input)
@@ -733,17 +925,23 @@ int pa__init(pa_module*m) {
     u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
     u->sink_input->update_max_request = sink_input_update_max_request_cb;
     u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
     u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
     u->sink_input->update_max_request = sink_input_update_max_request_cb;
     u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
+    u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb;
     u->sink_input->kill = sink_input_kill_cb;
     u->sink_input->attach = sink_input_attach_cb;
     u->sink_input->detach = sink_input_detach_cb;
     u->sink_input->state_change = sink_input_state_change_cb;
     u->sink_input->kill = sink_input_kill_cb;
     u->sink_input->attach = sink_input_attach_cb;
     u->sink_input->detach = sink_input_detach_cb;
     u->sink_input->state_change = sink_input_state_change_cb;
+    u->sink_input->may_move_to = sink_input_may_move_to_cb;
+    u->sink_input->moving = sink_input_moving_cb;
+    u->sink_input->volume_changed = sink_input_volume_changed_cb;
+    u->sink_input->mute_changed = sink_input_mute_changed_cb;
     u->sink_input->userdata = u;
 
     u->sink_input->userdata = u;
 
+    u->sink->input_to_master = u->sink_input;
+
     pa_sink_put(u->sink);
     pa_sink_input_put(u->sink_input);
 
     pa_modargs_free(ma);
     pa_sink_put(u->sink);
     pa_sink_input_put(u->sink_input);
 
     pa_modargs_free(ma);
-
     pa_xfree(use_default);
 
     return 0;
     pa_xfree(use_default);
 
     return 0;
@@ -759,6 +957,15 @@ fail:
     return -1;
 }
 
     return -1;
 }
 
+int pa__get_n_used(pa_module *m) {
+    struct userdata *u;
+
+    pa_assert(m);
+    pa_assert_se(u = m->userdata);
+
+    return pa_sink_linked_by(u->sink);
+}
+
 void pa__done(pa_module*m) {
     struct userdata *u;
     unsigned c;
 void pa__done(pa_module*m) {
     struct userdata *u;
     unsigned c;
@@ -768,32 +975,51 @@ void pa__done(pa_module*m) {
     if (!(u = m->userdata))
         return;
 
     if (!(u = m->userdata))
         return;
 
-    if (u->sink) {
-        pa_sink_unlink(u->sink);
-        pa_sink_unref(u->sink);
-    }
+    /* See comments in sink_input_kill_cb() above regarding
+    * destruction order! */
 
 
-    if (u->sink_input) {
+    if (u->sink_input)
         pa_sink_input_unlink(u->sink_input);
         pa_sink_input_unlink(u->sink_input);
+
+    if (u->sink)
+        pa_sink_unlink(u->sink);
+
+    if (u->sink_input)
         pa_sink_input_unref(u->sink_input);
         pa_sink_input_unref(u->sink_input);
-    }
 
 
-    for (c = 0; c < u->channels; c++)
+    if (u->sink)
+        pa_sink_unref(u->sink);
+
+    for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) {
         if (u->handle[c]) {
             if (u->descriptor->deactivate)
                 u->descriptor->deactivate(u->handle[c]);
             u->descriptor->cleanup(u->handle[c]);
         }
         if (u->handle[c]) {
             if (u->descriptor->deactivate)
                 u->descriptor->deactivate(u->handle[c]);
             u->descriptor->cleanup(u->handle[c]);
         }
+    }
 
 
-    if (u->output != u->input)
-        pa_xfree(u->output);
+    if (u->output == u->input) {
+        if (u->input != NULL) {
+            for (c = 0; c < u->max_ladspaport_count; c++)
+                pa_xfree(u->input[c]);
+            pa_xfree(u->input);
+        }
+    } else {
+        if (u->input != NULL) {
+            for (c = 0; c < u->input_count; c++)
+                pa_xfree(u->input[c]);
+            pa_xfree(u->input);
+        }
+        if (u->output != NULL) {
+            for (c = 0; c < u->output_count; c++)
+                pa_xfree(u->output[c]);
+            pa_xfree(u->output);
+        }
+    }
 
     if (u->memblockq)
         pa_memblockq_free(u->memblockq);
 
 
     if (u->memblockq)
         pa_memblockq_free(u->memblockq);
 
-    pa_xfree(u->input);
-
     pa_xfree(u->control);
     pa_xfree(u->control);
-
     pa_xfree(u);
 }
     pa_xfree(u);
 }