From e166ddf46f16ca115372bc05a880e47deafc23c1 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Tue, 2 Oct 2012 08:29:52 +0200
Subject: [PATCH] pcm_channels: support floating point samples

---
 src/pcm_channels.c | 71 +++++++++++++++++++++++++++++++++++++++++
 src/pcm_channels.h | 17 ++++++++++
 src/pcm_convert.c  | 79 ++++++++++------------------------------------
 3 files changed, 105 insertions(+), 62 deletions(-)

diff --git a/src/pcm_channels.c b/src/pcm_channels.c
index ec2bd69a5..9d166a437 100644
--- a/src/pcm_channels.c
+++ b/src/pcm_channels.c
@@ -244,3 +244,74 @@ pcm_convert_channels_32(struct pcm_buffer *buffer,
 
 	return dest;
 }
+
+static void
+pcm_convert_channels_float_1_to_2(float *dest, const float *src,
+			       const float *src_end)
+{
+	pcm_convert_channels_24_1_to_2((int32_t *)dest,
+				       (const int32_t *)src,
+				       (const int32_t *)src_end);
+}
+
+static void
+pcm_convert_channels_float_2_to_1(float *restrict dest,
+				  const float *restrict src,
+				  const float *restrict src_end)
+{
+	while (src < src_end) {
+		double a = *src++, b = *src++;
+
+		*dest++ = (a + b) / 2;
+	}
+}
+
+static void
+pcm_convert_channels_float_n_to_2(float *dest,
+				  unsigned src_channels, const float *src,
+				  const float *src_end)
+{
+	unsigned c;
+
+	assert(src_channels > 0);
+
+	while (src < src_end) {
+		double sum = 0;
+		float value;
+
+		for (c = 0; c < src_channels; ++c)
+			sum += *src++;
+		value = sum / (double)src_channels;
+
+		/* XXX this is actually only mono ... */
+		*dest++ = value;
+		*dest++ = value;
+	}
+}
+
+const float *
+pcm_convert_channels_float(struct pcm_buffer *buffer,
+			   unsigned dest_channels,
+			   unsigned src_channels, const float *src,
+			   size_t src_size, size_t *dest_size_r)
+{
+	assert(src_size % (sizeof(*src) * src_channels) == 0);
+
+	size_t dest_size = src_size / src_channels * dest_channels;
+	*dest_size_r = dest_size;
+
+	float *dest = pcm_buffer_get(buffer, dest_size);
+	const float *src_end = pcm_end_pointer(src, src_size);
+
+	if (src_channels == 1 && dest_channels == 2)
+		pcm_convert_channels_float_1_to_2(dest, src, src_end);
+	else if (src_channels == 2 && dest_channels == 1)
+		pcm_convert_channels_float_2_to_1(dest, src, src_end);
+	else if (dest_channels == 2)
+		pcm_convert_channels_float_n_to_2(dest, src_channels, src,
+						  src_end);
+	else
+		return NULL;
+
+	return dest;
+}
diff --git a/src/pcm_channels.h b/src/pcm_channels.h
index 1e4a0991f..6da00316d 100644
--- a/src/pcm_channels.h
+++ b/src/pcm_channels.h
@@ -77,4 +77,21 @@ pcm_convert_channels_32(struct pcm_buffer *buffer,
 			unsigned src_channels, const int32_t *src,
 			size_t src_size, size_t *dest_size_r);
 
+/**
+ * Changes the number of channels in 32 bit float PCM data.
+ *
+ * @param buffer the destination pcm_buffer object
+ * @param dest_channels the number of channels requested
+ * @param src_channels the number of channels in the source buffer
+ * @param src the source PCM buffer
+ * @param src_size the number of bytes in #src
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer
+ */
+const float *
+pcm_convert_channels_float(struct pcm_buffer *buffer,
+			   unsigned dest_channels,
+			   unsigned src_channels, const float *src,
+			   size_t src_size, size_t *dest_size_r);
+
 #endif
diff --git a/src/pcm_convert.c b/src/pcm_convert.c
index 63f9a1b98..32425143a 100644
--- a/src/pcm_convert.c
+++ b/src/pcm_convert.c
@@ -61,55 +61,6 @@ pcm_convert_reset(struct pcm_convert_state *state)
 	pcm_resample_reset(&state->resample);
 }
 
-static const void *
-pcm_convert_channels(struct pcm_buffer *buffer, enum sample_format format,
-		     uint8_t dest_channels,
-		     uint8_t src_channels, const void *src,
-		     size_t src_size, size_t *dest_size_r,
-		     GError **error_r)
-{
-	const void *dest = NULL;
-
-	switch (format) {
-	case SAMPLE_FORMAT_UNDEFINED:
-	case SAMPLE_FORMAT_S8:
-	case SAMPLE_FORMAT_FLOAT:
-	case SAMPLE_FORMAT_DSD:
-		g_set_error(error_r, pcm_convert_quark(), 0,
-			    "Channel conversion not implemented for format '%s'",
-			    sample_format_to_string(format));
-		return NULL;
-
-	case SAMPLE_FORMAT_S16:
-		dest = pcm_convert_channels_16(buffer, dest_channels,
-					       src_channels, src,
-					       src_size, dest_size_r);
-		break;
-
-	case SAMPLE_FORMAT_S24_P32:
-		dest = pcm_convert_channels_24(buffer, dest_channels,
-					       src_channels, src,
-					       src_size, dest_size_r);
-		break;
-
-	case SAMPLE_FORMAT_S32:
-		dest = pcm_convert_channels_32(buffer, dest_channels,
-					       src_channels, src,
-					       src_size, dest_size_r);
-		break;
-	}
-
-	if (dest == NULL) {
-		g_set_error(error_r, pcm_convert_quark(), 0,
-			    "Conversion from %u to %u channels "
-			    "is not implemented",
-			    src_channels, dest_channels);
-		return NULL;
-	}
-
-	return dest;
-}
-
 static const int16_t *
 pcm_convert_16(struct pcm_convert_state *state,
 	       const struct audio_format *src_format,
@@ -273,19 +224,6 @@ pcm_convert_float(struct pcm_convert_state *state,
 
 	assert(dest_format->format == SAMPLE_FORMAT_FLOAT);
 
-	/* convert channels first, hoping the source format is
-	   supported (float is not) */
-
-	if (dest_format->channels != src_format->channels) {
-		buffer = pcm_convert_channels(&state->channels_buffer,
-					      src_format->format,
-					      dest_format->channels,
-					      src_format->channels,
-					      buffer, size, &size, error_r);
-		if (buffer == NULL)
-			return NULL;
-	}
-
 	/* convert to float now */
 
 	buffer = pcm_convert_to_float(&state->format_buffer,
@@ -298,6 +236,23 @@ pcm_convert_float(struct pcm_convert_state *state,
 		return NULL;
 	}
 
+	/* convert channels */
+
+	if (src_format->channels != dest_format->channels) {
+		buffer = pcm_convert_channels_float(&state->channels_buffer,
+						    dest_format->channels,
+						    src_format->channels,
+						    buffer, size, &size);
+		if (buffer == NULL) {
+			g_set_error(error_r, pcm_convert_quark(), 0,
+				    "Conversion from %u to %u channels "
+				    "is not implemented",
+				    src_format->channels,
+				    dest_format->channels);
+			return NULL;
+		}
+	}
+
 	/* resample with float, because this is the best format for
 	   libsamplerate */