Support for Channel Mapping 253

OpusProjection* classes
MixingMatrix class
Projection tests

Change-Id: I98644466abf4ffd36e48bdecad1204d69e1539b9
Signed-off-by: Jean-Marc Valin <jmvalin@jmvalin.ca>
diff --git a/src/mapping_matrix.c b/src/mapping_matrix.c
new file mode 100644
index 0000000..5268bc6
--- /dev/null
+++ b/src/mapping_matrix.c
@@ -0,0 +1,287 @@
+/* Copyright (c) 2017 Google Inc.
+   Written by Andrew Allen */
+/*
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+
+   - Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+   - Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+   OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "arch.h"
+#include "float_cast.h"
+#include "opus_private.h"
+#include "opus_defines.h"
+#include "mapping_matrix.h"
+
+#ifdef ENABLE_EXPERIMENTAL_AMBISONICS
+
+#define MATRIX_INDEX(nb_rows, row, col) (nb_rows * col + row)
+
+int mapping_matrix_get_size(int rows, int cols)
+{
+  return align(sizeof(MappingMatrix)) + rows * cols * sizeof(opus_int16);
+}
+
+opus_int16 *mapping_matrix_get_data(const MappingMatrix *matrix)
+{
+  return (opus_int16*)(void *)(matrix + align(sizeof(MappingMatrix)));
+}
+
+void mapping_matrix_init(MappingMatrix * const matrix,
+  int rows, int cols, int gain, const opus_int16 *data, opus_int32 data_size)
+{
+  int i;
+  opus_int16 *ptr;
+
+#if !defined(ENABLE_ASSERTIONS)
+  (void)data_size;
+#endif
+  celt_assert((opus_uint32)data_size == rows * cols * sizeof(opus_int16));
+
+  matrix->rows = rows;
+  matrix->cols = cols;
+  matrix->gain = gain;
+  ptr = mapping_matrix_get_data(matrix);
+  for (i = 0; i < rows * cols; i++)
+  {
+     ptr[i] = data[i];
+  }
+}
+
+#ifndef DISABLE_FLOAT_API
+void mapping_matrix_multiply_float(const MappingMatrix *matrix,
+                                   const float *input, int input_rows,
+                                   float *output, int output_rows,
+                                   int frame_size)
+{
+  /* Matrix data is ordered col-wise.
+   * Input (x) is [n x k], output (y) is [m x k], matrix (M) is [m x n]:
+   *   y = M x
+   */
+  opus_int16* matrix_data;
+  int i, row, col;
+  float matrix_cell, input_sample;
+
+  celt_assert(input_rows <= matrix->cols && output_rows <= matrix->rows);
+
+  matrix_data = mapping_matrix_get_data(matrix);
+
+  for (i = 0; i < frame_size; i++)
+  {
+    for (row = 0; row < output_rows; row++)
+    {
+      output[MATRIX_INDEX(output_rows, row, i)] = 0;
+      for (col = 0; col < input_rows; col++)
+      {
+        matrix_cell = (0.000030518f)*(float)matrix_data[MATRIX_INDEX(matrix->rows, row, col)];
+        input_sample = input[MATRIX_INDEX(input_rows, col, i)];
+        output[MATRIX_INDEX(output_rows, row, i)] += matrix_cell * input_sample;
+      }
+    }
+  }
+}
+#endif /* DISABLE_FLOAT_API */
+
+void mapping_matrix_multiply_short(const MappingMatrix *matrix,
+                                   const opus_int16 *input, int input_rows,
+                                   opus_int16 *output, int output_rows,
+                                   int frame_size)
+{
+  /* Matrix data is ordered col-wise.
+   * Input (x) is [n x k], output (y) is [m x k], matrix (M) is [m x n]:
+   *   y = M x
+   */
+  opus_int16* matrix_data;
+  int i, row, col;
+
+  celt_assert(input_rows <= matrix->cols && output_rows <= matrix->rows);
+
+  matrix_data = mapping_matrix_get_data(matrix);
+
+  for (i = 0; i < frame_size; i++)
+  {
+    for (row = 0; row < output_rows; row++)
+    {
+      opus_int32 tmp = 0;
+      for (col = 0; col < input_rows; col++)
+      {
+        tmp +=
+          (matrix_data[MATRIX_INDEX(matrix->rows, row, col)] *
+          input[MATRIX_INDEX(input_rows, col, i)]) >> 8;
+      }
+      output[MATRIX_INDEX(output_rows, row, i)] = (tmp + 64)>>7;
+    }
+  }
+}
+
+const MappingMatrix mapping_matrix_foa_mixing = { 6, 6, 0 };
+const opus_int16 mapping_matrix_foa_mixing_data[36] = {
+     16384,      0, -16384,  23170,      0,      0,  16384,  23170,
+     16384,      0,      0,      0,  16384,      0, -16384, -23170,
+         0,      0,  16384, -23170,  16384,      0,      0,      0,
+         0,      0,      0,      0,  32767,      0,      0,      0,
+         0,      0,      0,  32767
+};
+
+const MappingMatrix mapping_matrix_soa_mixing = { 11, 11, 0 };
+const opus_int16 mapping_matrix_soa_mixing_data[121] = {
+     10923,   7723,  13377, -13377,  11585,   9459,   7723, -16384,
+     -6689,      0,      0,  10923,   7723,  13377,  13377, -11585,
+      9459,   7723,  16384,  -6689,      0,      0,  10923, -15447,
+     13377,      0,      0, -18919,   7723,      0,  13377,      0,
+         0,  10923,   7723, -13377, -13377,  11585,  -9459,   7723,
+     16384,  -6689,      0,      0,  10923,  -7723,      0,  13377,
+    -16384,      0, -15447,      0,   9459,      0,      0,  10923,
+     -7723,      0, -13377,  16384,      0, -15447,      0,   9459,
+         0,      0,  10923,  15447,      0,      0,      0,      0,
+    -15447,      0, -18919,      0,      0,  10923,   7723, -13377,
+     13377, -11585,  -9459,   7723, -16384,  -6689,      0,      0,
+     10923, -15447, -13377,      0,      0,  18919,   7723,      0,
+     13377,      0,      0,      0,      0,      0,      0,      0,
+         0,      0,      0,      0,  32767,      0,      0,      0,
+         0,      0,      0,      0,      0,      0,      0,      0,
+     32767
+};
+
+const MappingMatrix mapping_matrix_toa_mixing = { 18, 18, 0 };
+const opus_int16 mapping_matrix_toa_mixing_data[324] = {
+      8208,      0,   -881,  14369,      0,      0,  -8192,  -4163,
+     13218,      0,      0,      0,  11095,  -8836,  -6218,  14833,
+         0,      0,   8208, -10161,    881,  10161, -13218,  -2944,
+     -8192,   2944,      0, -10488,  -6218,   6248, -11095,  -6248,
+         0, -10488,      0,      0,   8208,  10161,    881, -10161,
+    -13218,   2944,  -8192,  -2944,      0,  10488,  -6218,  -6248,
+    -11095,   6248,      0,  10488,      0,      0,   8176,   5566,
+    -11552,   5566,   9681, -11205,   8192, -11205,      0,   4920,
+    -15158,   9756,  -3334,   9756,      0,  -4920,      0,      0,
+      8176,   7871,  11552,      0,      0,  15846,   8192,      0,
+     -9681,  -6958,      0,  13797,   3334,      0, -15158,      0,
+         0,      0,   8176,      0,  11552,   7871,      0,      0,
+      8192,  15846,   9681,      0,      0,      0,   3334,  13797,
+     15158,   6958,      0,      0,   8176,   5566, -11552,  -5566,
+     -9681, -11205,   8192,  11205,      0,   4920,  15158,   9756,
+     -3334,  -9756,      0,   4920,      0,      0,   8208,  14369,
+      -881,      0,      0,  -4163,  -8192,      0, -13218, -14833,
+         0,  -8836,  11095,      0,   6218,      0,      0,      0,
+      8208,  10161,    881,  10161,  13218,   2944,  -8192,   2944,
+         0,  10488,   6218,  -6248, -11095,  -6248,      0, -10488,
+         0,      0,   8208, -14369,   -881,      0,      0,   4163,
+     -8192,      0, -13218,  14833,      0,   8836,  11095,      0,
+      6218,      0,      0,      0,   8208,      0,   -881, -14369,
+         0,      0,  -8192,   4163,  13218,      0,      0,      0,
+     11095,   8836,  -6218, -14833,      0,      0,   8176,  -5566,
+    -11552,   5566,  -9681,  11205,   8192, -11205,      0,  -4920,
+     15158,  -9756,  -3334,   9756,      0,  -4920,      0,      0,
+      8176,      0,  11552,  -7871,      0,      0,   8192, -15846,
+      9681,      0,      0,      0,   3334, -13797,  15158,  -6958,
+         0,      0,   8176,  -7871,  11552,      0,      0, -15846,
+      8192,      0,  -9681,   6958,      0, -13797,   3334,      0,
+    -15158,      0,      0,      0,   8176,  -5566, -11552,  -5566,
+      9681,  11205,   8192,  11205,      0,  -4920, -15158,  -9756,
+     -3334,  -9756,      0,   4920,      0,      0,   8208, -10161,
+       881, -10161,  13218,  -2944,  -8192,  -2944,      0, -10488,
+      6218,   6248, -11095,   6248,      0,  10488,      0,      0,
+         0,      0,      0,      0,      0,      0,      0,      0,
+         0,      0,      0,      0,      0,      0,      0,      0,
+     32767,      0,      0,      0,      0,      0,      0,      0,
+         0,      0,      0,      0,      0,      0,      0,      0,
+         0,      0,      0,  32767
+};
+
+const MappingMatrix mapping_matrix_foa_demixing = { 6, 6, 0 };
+const opus_int16 mapping_matrix_foa_demixing_data[36] = {
+     16384,  16384,  16384,  16384,      0,      0,      0,  23170,
+         0, -23170,      0,      0, -16384,  16384, -16384,  16384,
+         0,      0,  23170,      0, -23170,      0,      0,      0,
+         0,      0,      0,      0,  32767,      0,      0,      0,
+         0,      0,      0,  32767
+};
+
+const MappingMatrix mapping_matrix_soa_demixing = { 11, 11, 3050 };
+const opus_int16 mapping_matrix_soa_demixing_data[121] = {
+      2771,   2771,   2771,   2771,   2771,   2771,   2771,   2771,
+      2771,      0,      0,  10033,  10033, -20066,  10033,  14189,
+     14189, -28378,  10033, -20066,      0,      0,   3393,   3393,
+      3393,  -3393,      0,      0,      0,  -3393,  -3393,      0,
+         0, -17378,  17378,      0, -17378, -24576,  24576,      0,
+     17378,      0,      0,      0, -14189,  14189,      0, -14189,
+    -28378,  28378,      0,  14189,      0,      0,      0,   2399,
+      2399,  -4799,  -2399,      0,      0,      0,  -2399,   4799,
+         0,      0,   1959,   1959,   1959,   1959,  -3918,  -3918,
+     -3918,   1959,   1959,      0,      0,  -4156,   4156,      0,
+      4156,      0,      0,      0,  -4156,      0,      0,      0,
+      8192,   8192, -16384,   8192,  16384,  16384, -32768,   8192,
+    -16384,      0,      0,      0,      0,      0,      0,      0,
+         0,      0,      0,      0,   8312,      0,      0,      0,
+         0,      0,      0,      0,      0,      0,      0,      0,
+      8312
+};
+
+const MappingMatrix mapping_matrix_toa_demixing = { 18, 18, 0 };
+const opus_int16 mapping_matrix_toa_demixing_data[324] = {
+      8192,   8192,   8192,   8192,   8192,   8192,   8192,   8192,
+      8192,   8192,   8192,   8192,   8192,   8192,   8192,   8192,
+         0,      0,      0,  -9779,   9779,   6263,   8857,      0,
+      6263,  13829,   9779, -13829,      0,  -6263,      0,  -8857,
+     -6263,  -9779,      0,      0,  -3413,   3413,   3413, -11359,
+     11359,  11359, -11359,  -3413,   3413,  -3413,  -3413, -11359,
+     11359,  11359, -11359,   3413,      0,      0,  13829,   9779,
+     -9779,   6263,      0,   8857,  -6263,      0,   9779,      0,
+    -13829,   6263,  -8857,      0,  -6263,  -9779,      0,      0,
+         0, -15617, -15617,   6406,      0,      0,  -6406,      0,
+     15617,      0,      0,  -6406,      0,      0,   6406,  15617,
+         0,      0,      0,  -5003,   5003, -10664,  15081,      0,
+    -10664,  -7075,   5003,   7075,      0,  10664,      0, -15081,
+     10664,  -5003,      0,      0,  -8176,  -8176,  -8176,   8208,
+      8208,   8208,   8208,  -8176,  -8176,  -8176,  -8176,   8208,
+      8208,   8208,   8208,  -8176,      0,      0,  -7075,   5003,
+     -5003, -10664,      0,  15081,  10664,      0,   5003,      0,
+      7075, -10664, -15081,      0,  10664,  -5003,      0,      0,
+     15617,      0,      0,      0,  -6406,   6406,      0, -15617,
+         0, -15617,  15617,      0,   6406,  -6406,      0,      0,
+         0,      0,      0, -11393,  11393,   2993,  -4233,      0,
+      2993, -16112,  11393,  16112,      0,  -2993,      0,   4233,
+     -2993, -11393,      0,      0,      0,  -9974,  -9974, -13617,
+         0,      0,  13617,      0,   9974,      0,      0,  13617,
+         0,      0, -13617,   9974,      0,      0,      0,   5579,
+     -5579,  10185,  14403,      0,  10185,  -7890,  -5579,   7890,
+         0, -10185,      0, -14403, -10185,   5579,      0,      0,
+     11826, -11826, -11826,   -901,    901,    901,   -901,  11826,
+    -11826,  11826,  11826,   -901,    901,    901,   -901, -11826,
+         0,      0,  -7890,  -5579,   5579,  10185,      0,  14403,
+    -10185,      0,  -5579,      0,   7890,  10185, -14403,      0,
+    -10185,   5579,      0,      0,  -9974,      0,      0,      0,
+    -13617,  13617,      0,   9974,      0,   9974,  -9974,      0,
+     13617, -13617,      0,      0,      0,      0,  16112, -11393,
+     11393,  -2993,      0,   4233,   2993,      0, -11393,      0,
+    -16112,  -2993,  -4233,      0,   2993,  11393,      0,      0,
+         0,      0,      0,      0,      0,      0,      0,      0,
+         0,      0,      0,      0,      0,      0,      0,      0,
+     32767,      0,      0,      0,      0,      0,      0,      0,
+         0,      0,      0,      0,      0,      0,      0,      0,
+         0,      0,      0,  32767
+};
+
+#endif /* ENABLE_EXPERIMENTAL_AMBISONICS */
diff --git a/src/mapping_matrix.h b/src/mapping_matrix.h
new file mode 100644
index 0000000..8fe82ea
--- /dev/null
+++ b/src/mapping_matrix.h
@@ -0,0 +1,115 @@
+/* Copyright (c) 2017 Google Inc.
+   Written by Andrew Allen */
+/*
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+
+   - Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+   - Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+   OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/**
+ * @file mapping_matrix.h
+ * @brief Opus reference implementation mapping matrix API
+ */
+
+#ifndef MAPPING_MATRIX_H
+#define MAPPING_MATRIX_H
+
+#ifdef ENABLE_EXPERIMENTAL_AMBISONICS
+
+#include "opus_types.h"
+#include "opus_projection.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct MappingMatrix
+{
+    int rows;
+    int cols;
+    int gain; /* in dB. S7.8-format. */
+    /* Matrix cell data goes here using col-wise ordering. */
+} MappingMatrix;
+
+int mapping_matrix_get_size(int rows, int cols);
+
+opus_int16 *mapping_matrix_get_data(const MappingMatrix *matrix);
+
+void mapping_matrix_init(
+    MappingMatrix * const st,
+    int rows,
+    int cols,
+    int gain,
+    const opus_int16 *data,
+    opus_int32 data_size
+);
+
+#ifndef DISABLE_FLOAT_API
+void mapping_matrix_multiply_float(
+    const MappingMatrix *matrix,
+    const float *input,
+    int input_rows,
+    float *output,
+    int output_rows,
+    int frame_size
+);
+#endif /* DISABLE_FLOAT_API */
+
+void mapping_matrix_multiply_short(
+    const MappingMatrix *matrix,
+    const opus_int16 *input,
+    int input_rows,
+    opus_int16 *output,
+    int output_rows,
+    int frame_size
+);
+
+/* Pre-computed mixing and demixing matrices for 1st to 3rd-order ambisonics.
+ *   foa: first-order ambisonics
+ *   soa: second-order ambisonics
+ *   toa: third-order ambisonics
+ */
+extern const MappingMatrix mapping_matrix_foa_mixing;
+extern const opus_int16 mapping_matrix_foa_mixing_data[36];
+
+extern const MappingMatrix mapping_matrix_soa_mixing;
+extern const opus_int16 mapping_matrix_soa_mixing_data[121];
+
+extern const MappingMatrix mapping_matrix_toa_mixing;
+extern const opus_int16 mapping_matrix_toa_mixing_data[324];
+
+extern const MappingMatrix mapping_matrix_foa_demixing;
+extern const opus_int16 mapping_matrix_foa_demixing_data[36];
+
+extern const MappingMatrix mapping_matrix_soa_demixing;
+extern const opus_int16 mapping_matrix_soa_demixing_data[121];
+
+extern const MappingMatrix mapping_matrix_toa_demixing;
+extern const opus_int16 mapping_matrix_toa_demixing_data[324];
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ENABLE_EXPERIMENTAL_AMBISONICS */
+
+#endif /* MAPPING_MATRIX_H */
diff --git a/src/opus_multistream_decoder.c b/src/opus_multistream_decoder.c
index e421726..6ab5d4c 100644
--- a/src/opus_multistream_decoder.c
+++ b/src/opus_multistream_decoder.c
@@ -37,14 +37,6 @@
 #include "float_cast.h"
 #include "os_support.h"
 
-struct OpusMSDecoder {
-   ChannelLayout layout;
-   /* Decoder states go here */
-};
-
-
-
-
 /* DECODER */
 
 opus_int32 opus_multistream_decoder_get_size(int nb_streams, int nb_coupled_streams)
@@ -408,15 +400,13 @@
 }
 #endif
 
-int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...)
+int opus_multistream_decoder_ctl_va_list(OpusMSDecoder *st, int request,
+                                         va_list ap)
 {
-   va_list ap;
    int coupled_size, mono_size;
    char *ptr;
    int ret = OPUS_OK;
 
-   va_start(ap, request);
-
    coupled_size = opus_decoder_get_size(2);
    mono_size = opus_decoder_get_size(1);
    ptr = (char*)st + align(sizeof(OpusMSDecoder));
@@ -525,14 +515,20 @@
           ret = OPUS_UNIMPLEMENTED;
        break;
    }
-
-   va_end(ap);
    return ret;
 bad_arg:
-   va_end(ap);
    return OPUS_BAD_ARG;
 }
 
+int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...)
+{
+   int ret;
+   va_list ap;
+   va_start(ap, request);
+   ret = opus_multistream_decoder_ctl_va_list(st, request, ap);
+   va_end(ap);
+   return ret;
+}
 
 void opus_multistream_decoder_destroy(OpusMSDecoder *st)
 {
diff --git a/src/opus_multistream_encoder.c b/src/opus_multistream_encoder.c
index 032fc00..07de6cc 100644
--- a/src/opus_multistream_encoder.c
+++ b/src/opus_multistream_encoder.c
@@ -70,28 +70,6 @@
   int frame_size
 );
 
-typedef enum {
-  MAPPING_TYPE_NONE,
-  MAPPING_TYPE_SURROUND
-#ifdef ENABLE_EXPERIMENTAL_AMBISONICS
-  ,  /* Do not include comma at end of enumerator list */
-  MAPPING_TYPE_AMBISONICS
-#endif
-} MappingType;
-
-struct OpusMSEncoder {
-   ChannelLayout layout;
-   int arch;
-   int lfe_stream;
-   int application;
-   int variable_duration;
-   MappingType mapping_type;
-   opus_int32 bitrate_bps;
-   /* Encoder states go here */
-   /* then opus_val32 window_mem[channels*120]; */
-   /* then opus_val32 preemph_mem[channels]; */
-};
-
 static opus_val32 *ms_get_preemph_mem(OpusMSEncoder *st)
 {
    int s;
@@ -1196,15 +1174,13 @@
 }
 #endif
 
-int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...)
+int opus_multistream_encoder_ctl_va_list(OpusMSEncoder *st, int request,
+                                         va_list ap)
 {
-   va_list ap;
    int coupled_size, mono_size;
    char *ptr;
    int ret = OPUS_OK;
 
-   va_start(ap, request);
-
    coupled_size = opus_encoder_get_size(2);
    mono_size = opus_encoder_get_size(1);
    ptr = (char*)st + align(sizeof(OpusMSEncoder));
@@ -1392,14 +1368,21 @@
       ret = OPUS_UNIMPLEMENTED;
       break;
    }
-
-   va_end(ap);
    return ret;
 bad_arg:
-   va_end(ap);
    return OPUS_BAD_ARG;
 }
 
+int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...)
+{
+   int ret;
+   va_list ap;
+   va_start(ap, request);
+   ret = opus_multistream_encoder_ctl_va_list(st, request, ap);
+   va_end(ap);
+   return ret;
+}
+
 void opus_multistream_encoder_destroy(OpusMSEncoder *st)
 {
     opus_free(st);
diff --git a/src/opus_private.h b/src/opus_private.h
index a731cc5..acbb0ae 100644
--- a/src/opus_private.h
+++ b/src/opus_private.h
@@ -33,6 +33,7 @@
 #include "opus.h"
 #include "celt.h"
 
+#include <stdarg.h> /* va_list */
 #include <stddef.h> /* offsetof */
 
 struct OpusRepacketizer {
@@ -50,6 +51,38 @@
    unsigned char mapping[256];
 } ChannelLayout;
 
+typedef enum {
+  MAPPING_TYPE_NONE,
+  MAPPING_TYPE_SURROUND
+#ifdef ENABLE_EXPERIMENTAL_AMBISONICS
+  ,  /* Do not include comma at end of enumerator list */
+  MAPPING_TYPE_AMBISONICS
+#endif /* ENABLE_EXPERIMENTAL_AMBISONICS */
+} MappingType;
+
+struct OpusMSEncoder {
+   ChannelLayout layout;
+   int arch;
+   int lfe_stream;
+   int application;
+   int variable_duration;
+   MappingType mapping_type;
+   opus_int32 bitrate_bps;
+   /* Encoder states go here */
+   /* then opus_val32 window_mem[channels*120]; */
+   /* then opus_val32 preemph_mem[channels]; */
+};
+
+struct OpusMSDecoder {
+   ChannelLayout layout;
+   /* Decoder states go here */
+};
+
+int opus_multistream_encoder_ctl_va_list(struct OpusMSEncoder *st, int request,
+  va_list ap);
+int opus_multistream_decoder_ctl_va_list(struct OpusMSDecoder *st, int request,
+  va_list ap);
+
 int validate_layout(const ChannelLayout *layout);
 int get_left_channel(const ChannelLayout *layout, int stream_id, int prev);
 int get_right_channel(const ChannelLayout *layout, int stream_id, int prev);
diff --git a/src/opus_projection_decoder.c b/src/opus_projection_decoder.c
new file mode 100644
index 0000000..a9b1d65
--- /dev/null
+++ b/src/opus_projection_decoder.c
@@ -0,0 +1,234 @@
+/* Copyright (c) 2017 Google Inc.
+   Written by Andrew Allen */
+/*
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+
+   - Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+   - Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+   OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "mathops.h"
+#include "os_support.h"
+#include "opus_private.h"
+#include "opus_defines.h"
+#include "opus_projection.h"
+#include "opus_multistream.h"
+#include "mapping_matrix.h"
+#include "stack_alloc.h"
+
+#ifdef ENABLE_EXPERIMENTAL_AMBISONICS
+
+struct OpusProjectionDecoder
+{
+  int demixing_matrix_size_in_bytes;
+  /* Encoder states go here */
+};
+
+static MappingMatrix *get_demixing_matrix(OpusProjectionDecoder *st)
+{
+  return (MappingMatrix *)((char*)st + align(sizeof(OpusProjectionDecoder)));
+}
+
+static OpusMSDecoder *get_multistream_decoder(OpusProjectionDecoder *st)
+{
+  return (OpusMSDecoder *)((char*)st + align(sizeof(OpusProjectionDecoder) +
+    st->demixing_matrix_size_in_bytes));
+}
+
+opus_int32 opus_projection_decoder_get_size(int channels, int streams,
+                                            int coupled_streams)
+{
+  opus_int32 matrix_size;
+  opus_int32 decoder_size;
+
+  matrix_size =
+    mapping_matrix_get_size(streams + coupled_streams, channels);
+  decoder_size = opus_multistream_decoder_get_size(streams, coupled_streams);
+  if (!decoder_size)
+    return 0;
+
+  return align(sizeof(OpusProjectionDecoder) + matrix_size + decoder_size);
+}
+
+int opus_projection_decoder_init(OpusProjectionDecoder *st, opus_int32 Fs,
+  int channels, int streams, int coupled_streams,
+  unsigned char *demixing_matrix, opus_int32 demixing_matrix_size)
+{
+  int nb_input_streams;
+  opus_int32 expected_matrix_size;
+  int i, ret;
+  unsigned char mapping[255];
+  VARDECL(opus_int16, buf);
+  ALLOC_STACK;
+
+  /* Verify supplied matrix size. */
+  nb_input_streams = streams + coupled_streams;
+  expected_matrix_size = nb_input_streams * channels * sizeof(opus_int16);
+  if (expected_matrix_size != demixing_matrix_size)
+  {
+    RESTORE_STACK;
+    return OPUS_BAD_ARG;
+  }
+
+  /* Convert demixing matrix input into internal format. */
+  ALLOC(buf, demixing_matrix_size, opus_int16);
+  for (i = 0; i < nb_input_streams * channels; i++)
+  {
+    int s = demixing_matrix[2*i + 1] << 8 | demixing_matrix[2*i];
+    s = ((s & 0xFFFF) ^ 0x8000) - 0x8000;
+    buf[i] = (opus_int16)s;
+  }
+
+  /* Assign demixing matrix. */
+  st->demixing_matrix_size_in_bytes = expected_matrix_size;
+  mapping_matrix_init(get_demixing_matrix(st), nb_input_streams, channels, 0,
+    buf, demixing_matrix_size);
+
+  /* Set trivial mapping so each input channel pairs with a matrix column. */
+  for (i = 0; i < channels; i++)
+  {
+    mapping[i] = i;
+  }
+
+  ret = opus_multistream_decoder_init(
+    get_multistream_decoder(st), Fs, channels, streams, coupled_streams, mapping);
+  RESTORE_STACK;
+  return ret;
+}
+
+OpusProjectionDecoder *opus_projection_decoder_create(
+  opus_int32 Fs, int channels, int streams, int coupled_streams,
+  unsigned char *demixing_matrix, opus_int32 demixing_matrix_size, int *error)
+{
+  int size;
+  int ret;
+  OpusProjectionDecoder *st;
+
+  /* Allocate space for the projection decoder. */
+  size = opus_projection_decoder_get_size(channels, streams, coupled_streams);
+  if (!size) {
+    if (error)
+      *error = OPUS_ALLOC_FAIL;
+    return NULL;
+  }
+  st = (OpusProjectionDecoder *)opus_alloc(size);
+  if (!st)
+  {
+    if (error)
+      *error = OPUS_ALLOC_FAIL;
+    return NULL;
+  }
+
+  /* Initialize projection decoder with provided settings. */
+  ret = opus_projection_decoder_init(st, Fs, channels, streams, coupled_streams,
+                                     demixing_matrix, demixing_matrix_size);
+  if (ret != OPUS_OK)
+  {
+    opus_free(st);
+    st = NULL;
+  }
+  if (error)
+    *error = ret;
+  return st;
+}
+
+int opus_projection_decode(OpusProjectionDecoder *st, const unsigned char *data,
+                           opus_int32 len, opus_int16 *pcm, int frame_size,
+                           int decode_fec)
+{
+#ifdef NONTHREADSAFE_PSEUDOSTACK
+  celt_fatal("Unable to use opus_projection_decode() when NONTHREADSAFE_PSEUDOSTACK is defined.");
+#endif
+  MappingMatrix *matrix;
+  OpusMSDecoder *ms_decoder;
+  int ret;
+  VARDECL(opus_int16, buf);
+  ALLOC_STACK;
+
+  ms_decoder = get_multistream_decoder(st);
+  ALLOC(buf, (ms_decoder->layout.nb_streams + ms_decoder->layout.nb_coupled_streams) *
+    frame_size, opus_int16);
+  ret = opus_multistream_decode(ms_decoder, data, len, buf, frame_size,
+                                        decode_fec);
+  if (ret <= 0)
+    return ret;
+  frame_size = ret;
+  matrix = get_demixing_matrix(st);
+  mapping_matrix_multiply_short(matrix, buf,
+    ms_decoder->layout.nb_streams + ms_decoder->layout.nb_coupled_streams,
+    pcm, ms_decoder->layout.nb_channels, frame_size);
+  RESTORE_STACK;
+  return frame_size;
+}
+
+#ifndef DISABLE_FLOAT_API
+int opus_projection_decode_float(OpusProjectionDecoder *st, const unsigned char *data,
+                                 opus_int32 len, float *pcm,
+                                 int frame_size, int decode_fec)
+{
+#ifdef NONTHREADSAFE_PSEUDOSTACK
+  celt_fatal("Unable to use opus_projection_decode_float() when NONTHREADSAFE_PSEUDOSTACK is defined.");
+#endif
+  MappingMatrix *matrix;
+  OpusMSDecoder *ms_decoder;
+  int ret;
+  VARDECL(float, buf);
+  ALLOC_STACK;
+
+  ms_decoder = get_multistream_decoder(st);
+  ALLOC(buf, (ms_decoder->layout.nb_streams + ms_decoder->layout.nb_coupled_streams) *
+    frame_size, float);
+  ret = opus_multistream_decode_float(ms_decoder, data, len, buf,
+                                      frame_size, decode_fec);
+  if (ret <= 0)
+    return ret;
+  frame_size = ret;
+  matrix = get_demixing_matrix(st);
+  mapping_matrix_multiply_float(matrix, buf,
+    ms_decoder->layout.nb_streams + ms_decoder->layout.nb_coupled_streams,
+    pcm, ms_decoder->layout.nb_channels, frame_size);
+  RESTORE_STACK;
+  return frame_size;
+}
+#endif
+
+int opus_projection_decoder_ctl(OpusProjectionDecoder *st, int request, ...)
+{
+  va_list ap;
+  int ret = OPUS_OK;
+
+  va_start(ap, request);
+  ret = opus_multistream_decoder_ctl_va_list(get_multistream_decoder(st),
+    request, ap);
+  va_end(ap);
+  return ret;
+}
+
+void opus_projection_decoder_destroy(OpusProjectionDecoder *st)
+{
+  opus_free(st);
+}
+
+#endif /* ENABLE_EXPERIMENTAL_AMBISONICS */
diff --git a/src/opus_projection_encoder.c b/src/opus_projection_encoder.c
new file mode 100644
index 0000000..e34b0e5
--- /dev/null
+++ b/src/opus_projection_encoder.c
@@ -0,0 +1,412 @@
+/* Copyright (c) 2017 Google Inc.
+   Written by Andrew Allen */
+/*
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+
+   - Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+   - Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+   OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "mathops.h"
+#include "os_support.h"
+#include "opus_private.h"
+#include "opus_defines.h"
+#include "opus_projection.h"
+#include "opus_multistream.h"
+#include "stack_alloc.h"
+#include "mapping_matrix.h"
+
+#ifdef ENABLE_EXPERIMENTAL_AMBISONICS
+
+struct OpusProjectionEncoder
+{
+  int mixing_matrix_size_in_bytes;
+  int demixing_matrix_size_in_bytes;
+  /* Encoder states go here */
+};
+
+static int get_order_plus_one_from_channels(int channels, int *order_plus_one)
+{
+  int order_plus_one_;
+  int acn_channels;
+  int nondiegetic_channels;
+
+  /* Allowed numbers of channels:
+   * (1 + n)^2 + 2j, for n = 0...14 and j = 0 or 1.
+   */
+  order_plus_one_ = isqrt32(channels);
+  acn_channels = order_plus_one_ * order_plus_one_;
+  nondiegetic_channels = channels - acn_channels;
+  if (order_plus_one)
+    *order_plus_one = order_plus_one_;
+
+  if (order_plus_one_ < 1 || order_plus_one_ > 15 ||
+      (nondiegetic_channels != 0 && nondiegetic_channels != 2))
+    return OPUS_BAD_ARG;
+  return OPUS_OK;
+}
+
+static int get_streams_from_channels(int channels, int mapping_family,
+                                     int *streams, int *coupled_streams,
+                                     int *order_plus_one)
+{
+  if (mapping_family == 253)
+  {
+    if (get_order_plus_one_from_channels(channels, order_plus_one) != OPUS_OK)
+      return OPUS_BAD_ARG;
+    if (streams)
+      *streams = (channels + 1) / 2;
+    if (coupled_streams)
+      *coupled_streams = channels / 2;
+    return OPUS_OK;
+  }
+  return OPUS_BAD_ARG;
+}
+
+static MappingMatrix *get_mixing_matrix(OpusProjectionEncoder *st)
+{
+  return (MappingMatrix *)((char*)st + align(sizeof(OpusProjectionEncoder)));
+}
+
+static MappingMatrix *get_demixing_matrix(OpusProjectionEncoder *st)
+{
+  return (MappingMatrix *)((char*)st + align(sizeof(OpusProjectionEncoder) +
+    st->mixing_matrix_size_in_bytes));
+}
+
+static OpusMSEncoder *get_multistream_encoder(OpusProjectionEncoder *st)
+{
+  return (OpusMSEncoder *)((char*)st + align(sizeof(OpusProjectionEncoder) +
+    st->mixing_matrix_size_in_bytes + st->demixing_matrix_size_in_bytes));
+}
+
+opus_int32 opus_projection_ambisonics_encoder_get_size(int channels,
+                                                       int mapping_family)
+{
+  int nb_streams;
+  int nb_coupled_streams;
+  int order_plus_one;
+  int matrix_rows;
+  opus_int32 matrix_size;
+  opus_int32 encoder_size;
+  int ret;
+
+  ret = get_streams_from_channels(channels, mapping_family, &nb_streams,
+                                  &nb_coupled_streams, &order_plus_one);
+  if (ret != OPUS_OK)
+  {
+    return 0;
+  }
+
+  matrix_rows = order_plus_one * order_plus_one + 2;
+  matrix_size = mapping_matrix_get_size(matrix_rows, matrix_rows);
+  encoder_size =
+      opus_multistream_encoder_get_size(nb_streams, nb_coupled_streams);
+  if (!encoder_size)
+    return 0;
+  return align(sizeof(OpusProjectionEncoder) + matrix_size + matrix_size + encoder_size);
+}
+
+int opus_projection_ambisonics_encoder_init(OpusProjectionEncoder *st, opus_int32 Fs,
+                                            int channels, int mapping_family,
+                                            int *streams, int *coupled_streams,
+                                            int application)
+{
+  MappingMatrix *mixing_matrix;
+  MappingMatrix *demixing_matrix;
+  OpusMSEncoder *ms_encoder;
+  int nb_streams;
+  int nb_coupled_streams;
+  int i;
+  int ret;
+  unsigned char mapping[255];
+
+  if (get_streams_from_channels(channels, mapping_family,
+                                &nb_streams, &nb_coupled_streams, NULL)
+      != OPUS_OK)
+    return OPUS_BAD_ARG;
+
+  if (streams == NULL || coupled_streams == NULL) {
+    return OPUS_BAD_ARG;
+  }
+  *streams = nb_streams;
+  *coupled_streams = nb_coupled_streams;
+
+  if (mapping_family == 253)
+  {
+    int order_plus_one;
+    if (get_order_plus_one_from_channels(channels, &order_plus_one) != OPUS_OK)
+      return OPUS_BAD_ARG;
+
+    /* Assign mixing matrix based on available pre-computed matrices. */
+    mixing_matrix = get_mixing_matrix(st);
+    if (order_plus_one == 2)
+    {
+      mapping_matrix_init(mixing_matrix, mapping_matrix_foa_mixing.rows,
+        mapping_matrix_foa_mixing.cols, mapping_matrix_foa_mixing.gain,
+        mapping_matrix_foa_mixing_data, 36 * sizeof(opus_int16));
+    }
+    else if (order_plus_one == 3)
+    {
+      mapping_matrix_init(mixing_matrix, mapping_matrix_soa_mixing.rows,
+        mapping_matrix_soa_mixing.cols, mapping_matrix_soa_mixing.gain,
+        mapping_matrix_soa_mixing_data, 121 * sizeof(opus_int16));
+    }
+    else if (order_plus_one == 4)
+    {
+      mapping_matrix_init(mixing_matrix, mapping_matrix_toa_mixing.rows,
+        mapping_matrix_toa_mixing.cols, mapping_matrix_toa_mixing.gain,
+        mapping_matrix_toa_mixing_data, 324 * sizeof(opus_int16));
+    }
+    st->mixing_matrix_size_in_bytes = mapping_matrix_get_size(
+      mixing_matrix->rows, mixing_matrix->cols);
+
+    /* Assign demixing matrix based on available pre-computed matrices. */
+    demixing_matrix = get_demixing_matrix(st);
+    if (order_plus_one == 2)
+    {
+      mapping_matrix_init(demixing_matrix, mapping_matrix_foa_demixing.rows,
+        mapping_matrix_foa_demixing.cols, mapping_matrix_foa_demixing.gain,
+        mapping_matrix_foa_demixing_data, 36 * sizeof(opus_int16));
+    }
+    else if (order_plus_one == 3)
+    {
+      mapping_matrix_init(demixing_matrix, mapping_matrix_soa_demixing.rows,
+        mapping_matrix_soa_demixing.cols, mapping_matrix_soa_demixing.gain,
+        mapping_matrix_soa_demixing_data, 121 * sizeof(opus_int16));
+    }
+    else if (order_plus_one == 4)
+    {
+      mapping_matrix_init(demixing_matrix, mapping_matrix_toa_demixing.rows,
+        mapping_matrix_toa_demixing.cols, mapping_matrix_toa_demixing.gain,
+        mapping_matrix_toa_demixing_data, 324 * sizeof(opus_int16));
+    }
+    st->demixing_matrix_size_in_bytes = mapping_matrix_get_size(
+      demixing_matrix->rows, demixing_matrix->cols);
+  }
+  else
+    return OPUS_UNIMPLEMENTED;
+
+  /* Ensure matrices are large enough for desired coding scheme. */
+  if (nb_streams + nb_coupled_streams > mixing_matrix->rows ||
+      channels > mixing_matrix->cols ||
+      channels > demixing_matrix->rows ||
+      nb_streams + nb_coupled_streams > demixing_matrix->cols)
+    return OPUS_BAD_ARG;
+
+  /* Set trivial mapping so each input channel pairs with a matrix column. */
+  for (i = 0; i < channels; i++)
+  {
+    mapping[i] = i;
+  }
+
+  /* Initialize multistream encoder with provided settings. */
+  ms_encoder = get_multistream_encoder(st);
+  ret = opus_multistream_encoder_init(ms_encoder, Fs, channels, nb_streams,
+                                      nb_coupled_streams, mapping, application);
+  return ret;
+}
+
+OpusProjectionEncoder *opus_projection_ambisonics_encoder_create(
+    opus_int32 Fs, int channels, int mapping_family, int *streams,
+    int *coupled_streams, int application, int *error)
+{
+  int size;
+  int ret;
+  OpusProjectionEncoder *st;
+
+  /* Allocate space for the projection encoder. */
+  size = opus_projection_ambisonics_encoder_get_size(channels, mapping_family);
+  if (!size) {
+    if (error)
+      *error = OPUS_ALLOC_FAIL;
+    return NULL;
+  }
+  st = (OpusProjectionEncoder *)opus_alloc(size);
+  if (!st)
+  {
+    if (error)
+      *error = OPUS_ALLOC_FAIL;
+    return NULL;
+  }
+
+  /* Initialize projection encoder with provided settings. */
+  ret = opus_projection_ambisonics_encoder_init(st, Fs, channels,
+     mapping_family, streams, coupled_streams, application);
+  if (ret != OPUS_OK)
+  {
+    opus_free(st);
+    st = NULL;
+  }
+  if (error)
+    *error = ret;
+  return st;
+}
+
+int opus_projection_encode(OpusProjectionEncoder *st, const opus_int16 *pcm,
+                           int frame_size, unsigned char *data,
+                           opus_int32 max_data_bytes)
+{
+#ifdef NONTHREADSAFE_PSEUDOSTACK
+  celt_fatal("Unable to use opus_projection_encode() when NONTHREADSAFE_PSEUDOSTACK is defined.");
+#endif
+  MappingMatrix *matrix;
+  OpusMSEncoder *ms_encoder;
+  int ret;
+  VARDECL(opus_int16, buf);
+  ALLOC_STACK;
+
+  matrix = get_mixing_matrix(st);
+  ms_encoder = get_multistream_encoder(st);
+  ALLOC(buf, (ms_encoder->layout.nb_streams + ms_encoder->layout.nb_coupled_streams) *
+    frame_size, opus_int16);
+  mapping_matrix_multiply_short(matrix, pcm,
+    ms_encoder->layout.nb_channels, buf,
+    ms_encoder->layout.nb_streams + ms_encoder->layout.nb_coupled_streams,
+    frame_size);
+  ret = opus_multistream_encode(ms_encoder, buf, frame_size, data, max_data_bytes);
+  RESTORE_STACK;
+  return ret;
+}
+
+#ifndef DISABLE_FLOAT_API
+int opus_projection_encode_float(OpusProjectionEncoder *st, const float *pcm,
+                                 int frame_size, unsigned char *data,
+                                 opus_int32 max_data_bytes)
+{
+#ifdef NONTHREADSAFE_PSEUDOSTACK
+  celt_fatal("Unable to use opus_projection_encode_float() when NONTHREADSAFE_PSEUDOSTACK is defined.");
+#endif
+  MappingMatrix *matrix;
+  OpusMSEncoder *ms_encoder;
+  int ret;
+  VARDECL(float, buf);
+  ALLOC_STACK;
+
+  matrix = get_mixing_matrix(st);
+  ms_encoder = get_multistream_encoder(st);
+  ALLOC(buf, (ms_encoder->layout.nb_streams + ms_encoder->layout.nb_coupled_streams) *
+    frame_size, float);
+  mapping_matrix_multiply_float(matrix, pcm,
+    ms_encoder->layout.nb_channels, buf,
+    ms_encoder->layout.nb_streams + ms_encoder->layout.nb_coupled_streams,
+    frame_size);
+  ret = opus_multistream_encode_float(ms_encoder, buf, frame_size, data, max_data_bytes);
+  RESTORE_STACK;
+  return ret;
+}
+#endif
+
+void opus_projection_encoder_destroy(OpusProjectionEncoder *st)
+{
+  opus_free(st);
+}
+
+int opus_projection_encoder_ctl(OpusProjectionEncoder *st, int request, ...)
+{
+  MappingMatrix *demixing_matrix;
+  OpusMSEncoder *ms_encoder;
+  int ret = OPUS_OK;
+
+  ms_encoder = get_multistream_encoder(st);
+  demixing_matrix = get_demixing_matrix(st);
+
+  va_list ap;
+  va_start(ap, request);
+  switch(request)
+  {
+  case OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST:
+  {
+    opus_int32 *value = va_arg(ap, opus_int32*);
+    if (!value)
+    {
+      goto bad_arg;
+    }
+    *value =
+      ms_encoder->layout.nb_channels * (ms_encoder->layout.nb_streams
+      + ms_encoder->layout.nb_coupled_streams) * sizeof(opus_int16);
+  }
+  break;
+  case OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST:
+  {
+    opus_int32 *value = va_arg(ap, opus_int32*);
+    if (!value)
+    {
+      goto bad_arg;
+    }
+    *value = demixing_matrix->gain;
+  }
+  break;
+  case OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST:
+  {
+    int i;
+    int nb_input_streams;
+    int nb_output_streams;
+    unsigned char *external_char;
+    opus_int16 *internal_short;
+    opus_int32 external_size;
+    opus_int32 internal_size;
+
+    /* (I/O is in relation to the decoder's perspective). */
+    nb_input_streams = ms_encoder->layout.nb_streams +
+      ms_encoder->layout.nb_coupled_streams;
+    nb_output_streams = ms_encoder->layout.nb_channels;
+
+    external_char = va_arg(ap, unsigned char *);
+    external_size = va_arg(ap, opus_uint32);
+    if (!external_char)
+    {
+      goto bad_arg;
+    }
+    internal_short = mapping_matrix_get_data(demixing_matrix);
+    internal_size = nb_input_streams * nb_output_streams * sizeof(opus_int16);
+    if (external_size != internal_size)
+    {
+      goto bad_arg;
+    }
+
+    /* Copy demixing matrix subset to output destination. */
+    for (i = 0; i < nb_input_streams * nb_output_streams; i++)
+    {
+      external_char[2*i] = (unsigned char)internal_short[i];
+      external_char[2*i+1] = (unsigned char)(internal_short[i] >> 8);
+    }
+  }
+  break;
+  default:
+  {
+    ret = opus_multistream_encoder_ctl_va_list(ms_encoder, request, ap);
+  }
+  break;
+  }
+  va_end(ap);
+  return ret;
+
+bad_arg:
+  va_end(ap);
+  return OPUS_BAD_ARG;
+}
+
+#endif /* ENABLE_EXPERIMENTAL_AMBISONICS */