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/.gitignore b/.gitignore
index 9bcd28d..eccd0a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,6 +49,7 @@
 tests/test_opus_decode
 tests/test_opus_encode
 tests/test_opus_padding
+tests/test_opus_projection
 celt/arm/armopts.s
 celt/dump_modes/dump_modes
 celt/tests/test_unit_cwrs32
diff --git a/Makefile.am b/Makefile.am
index 5b57b33..f25a950 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -82,7 +82,7 @@
 libopus_la_LIBADD += libarmasm.la
 endif
 
-pkginclude_HEADERS = include/opus.h include/opus_multistream.h include/opus_types.h include/opus_defines.h
+pkginclude_HEADERS = include/opus.h include/opus_multistream.h include/opus_types.h include/opus_defines.h include/opus_projection.h
 
 noinst_HEADERS = $(OPUS_HEAD) $(SILK_HEAD) $(CELT_HEAD)
 
@@ -102,7 +102,8 @@
                   tests/test_opus_api \
                   tests/test_opus_decode \
                   tests/test_opus_encode \
-                  tests/test_opus_padding
+                  tests/test_opus_padding \
+                  tests/test_opus_projection
 
 TESTS = celt/tests/test_unit_cwrs32 \
         celt/tests/test_unit_dft \
@@ -116,7 +117,8 @@
         tests/test_opus_api \
         tests/test_opus_decode \
         tests/test_opus_encode \
-        tests/test_opus_padding
+        tests/test_opus_padding \
+        tests/test_opus_projection
 
 opus_demo_SOURCES = src/opus_demo.c
 
@@ -141,6 +143,9 @@
 tests_test_opus_padding_SOURCES = tests/test_opus_padding.c tests/test_opus_common.h
 tests_test_opus_padding_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
 
+tests_test_opus_projection_SOURCES = tests/test_opus_projection.c tests/test_opus_common.h
+tests_test_opus_projection_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
+
 CELT_OBJ = $(CELT_SOURCES:.c=.lo)
 SILK_OBJ = $(SILK_SOURCES:.c=.lo)
 
diff --git a/include/opus_projection.h b/include/opus_projection.h
new file mode 100644
index 0000000..1e65610
--- /dev/null
+++ b/include/opus_projection.h
@@ -0,0 +1,568 @@
+/* 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 opus_projection.h
+ * @brief Opus projection reference API
+ */
+
+#ifndef OPUS_PROJECTION_H
+#define OPUS_PROJECTION_H
+
+#include "opus_multistream.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @cond OPUS_INTERNAL_DOC */
+
+/** These are the actual encoder and decoder CTL ID numbers.
+  * They should not be used directly by applications.c
+  * In general, SETs should be even and GETs should be odd.*/
+/**@{*/
+#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST    6001
+#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST    6003
+#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST         6005
+/**@}*/
+
+
+/** @endcond */
+
+/** @defgroup opus_projection_ctls Projection specific encoder and decoder CTLs
+  *
+  * These are convenience macros that are specific to the
+  * opus_projection_encoder_ctl() and opus_projection_decoder_ctl()
+  * interface.
+  * The CTLs from @ref opus_genericctls, @ref opus_encoderctls,
+  * @ref opus_decoderctls, and @ref opus_multistream_ctls may be applied to a
+  * projection encoder or decoder as well.
+  */
+/**@{*/
+
+/** Gets the gain (in dB. S7.8-format) of the demixing matrix from the encoder.
+  * @param[out] x <tt>opus_int32 *</tt>: Returns the gain (in dB. S7.8-format)
+  *                                      of the demixing matrix.
+  * @hideinitializer
+  */
+#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN(x) OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST, __opus_check_int_ptr(x)
+
+
+/** Gets the size in bytes of the demixing matrix from the encoder.
+  * @param[out] x <tt>opus_int32 *</tt>: Returns the size in bytes of the
+  *                                      demixing matrix.
+  * @hideinitializer
+  */
+#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE(x) OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST, __opus_check_int_ptr(x)
+
+
+/** Copies the demixing matrix to the supplied pointer location.
+  * @param[out] x <tt>unsigned char *</tt>: Returns the demixing matrix to the
+  *                                         supplied pointer location.
+  * @param y <tt>opus_in32</tt>: The size in bytes of the reserved memory at the
+  *                              pointer location.
+  * @hideinitializer
+  */
+#define OPUS_PROJECTION_GET_DEMIXING_MATRIX(x,y) OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST, x, __opus_check_int(y)
+
+
+/**@}*/
+
+/** Opus projection encoder state.
+ * This contains the complete state of a projection Opus encoder.
+ * It is position independent and can be freely copied.
+ * @see opus_projection_ambisonics_encoder_create
+ */
+typedef struct OpusProjectionEncoder OpusProjectionEncoder;
+
+
+/** Opus projection decoder state.
+  * This contains the complete state of a projection Opus decoder.
+  * It is position independent and can be freely copied.
+  * @see opus_projection_decoder_create
+  * @see opus_projection_decoder_init
+  */
+typedef struct OpusProjectionDecoder OpusProjectionDecoder;
+
+
+/**\name Projection encoder functions */
+/**@{*/
+
+/** Gets the size of an OpusProjectionEncoder structure.
+  * @param channels <tt>int</tt>: The total number of input channels to encode.
+  *                               This must be no more than 255.
+  * @param mapping_family <tt>int</tt>: The mapping family to use for selecting
+  *                                     the appropriate projection.
+  * @returns The size in bytes on success, or a negative error code
+  *          (see @ref opus_errorcodes) on error.
+  */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_projection_ambisonics_encoder_get_size(
+    int channels,
+    int mapping_family
+);
+
+
+/** Allocates and initializes a projection encoder state.
+  * Call opus_projection_encoder_destroy() to release
+  * this object when finished.
+  * @param Fs <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
+  *                                This must be one of 8000, 12000, 16000,
+  *                                24000, or 48000.
+  * @param channels <tt>int</tt>: Number of channels in the input signal.
+  *                               This must be at most 255.
+  *                               It may be greater than the number of
+  *                               coded channels (<code>streams +
+  *                               coupled_streams</code>).
+  * @param mapping_family <tt>int</tt>: The mapping family to use for selecting
+  *                                     the appropriate projection.
+  * @param[out] streams <tt>int *</tt>: The total number of streams that will
+  *                                     be encoded from the input.
+  * @param[out] coupled_streams <tt>int *</tt>: Number of coupled (2 channel)
+  *                                 streams that will be encoded from the input.
+  * @param application <tt>int</tt>: The target encoder application.
+  *                                  This must be one of the following:
+  * <dl>
+  * <dt>#OPUS_APPLICATION_VOIP</dt>
+  * <dd>Process signal for improved speech intelligibility.</dd>
+  * <dt>#OPUS_APPLICATION_AUDIO</dt>
+  * <dd>Favor faithfulness to the original input.</dd>
+  * <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
+  * <dd>Configure the minimum possible coding delay by disabling certain modes
+  * of operation.</dd>
+  * </dl>
+  * @param[out] error <tt>int *</tt>: Returns #OPUS_OK on success, or an error
+  *                                   code (see @ref opus_errorcodes) on
+  *                                   failure.
+  */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusProjectionEncoder *opus_projection_ambisonics_encoder_create(
+    opus_int32 Fs,
+    int channels,
+    int mapping_family,
+    int *streams,
+    int *coupled_streams,
+    int application,
+    int *error
+) OPUS_ARG_NONNULL(4) OPUS_ARG_NONNULL(5);
+
+
+/** Initialize a previously allocated projection encoder state.
+  * The memory pointed to by \a st must be at least the size returned by
+  * opus_projection_ambisonics_encoder_get_size().
+  * This is intended for applications which use their own allocator instead of
+  * malloc.
+  * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
+  * @see opus_projection_ambisonics_encoder_create
+  * @see opus_projection_ambisonics_encoder_get_size
+  * @param st <tt>OpusProjectionEncoder*</tt>: Projection encoder state to initialize.
+  * @param Fs <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
+  *                                This must be one of 8000, 12000, 16000,
+  *                                24000, or 48000.
+  * @param channels <tt>int</tt>: Number of channels in the input signal.
+  *                               This must be at most 255.
+  *                               It may be greater than the number of
+  *                               coded channels (<code>streams +
+  *                               coupled_streams</code>).
+  * @param streams <tt>int</tt>: The total number of streams to encode from the
+  *                              input.
+  *                              This must be no more than the number of channels.
+  * @param coupled_streams <tt>int</tt>: Number of coupled (2 channel) streams
+  *                                      to encode.
+  *                                      This must be no larger than the total
+  *                                      number of streams.
+  *                                      Additionally, The total number of
+  *                                      encoded channels (<code>streams +
+  *                                      coupled_streams</code>) must be no
+  *                                      more than the number of input channels.
+  * @param application <tt>int</tt>: The target encoder application.
+  *                                  This must be one of the following:
+  * <dl>
+  * <dt>#OPUS_APPLICATION_VOIP</dt>
+  * <dd>Process signal for improved speech intelligibility.</dd>
+  * <dt>#OPUS_APPLICATION_AUDIO</dt>
+  * <dd>Favor faithfulness to the original input.</dd>
+  * <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
+  * <dd>Configure the minimum possible coding delay by disabling certain modes
+  * of operation.</dd>
+  * </dl>
+  * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes)
+  *          on failure.
+  */
+OPUS_EXPORT int opus_projection_ambisonics_encoder_init(
+    OpusProjectionEncoder *st,
+    opus_int32 Fs,
+    int channels,
+    int mapping_family,
+    int *streams,
+    int *coupled_streams,
+    int application
+) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6);
+
+
+/** Encodes a projection Opus frame.
+  * @param st <tt>OpusProjectionEncoder*</tt>: Projection encoder state.
+  * @param[in] pcm <tt>const opus_int16*</tt>: The input signal as interleaved
+  *                                            samples.
+  *                                            This must contain
+  *                                            <code>frame_size*channels</code>
+  *                                            samples.
+  * @param frame_size <tt>int</tt>: Number of samples per channel in the input
+  *                                 signal.
+  *                                 This must be an Opus frame size for the
+  *                                 encoder's sampling rate.
+  *                                 For example, at 48 kHz the permitted values
+  *                                 are 120, 240, 480, 960, 1920, and 2880.
+  *                                 Passing in a duration of less than 10 ms
+  *                                 (480 samples at 48 kHz) will prevent the
+  *                                 encoder from using the LPC or hybrid modes.
+  * @param[out] data <tt>unsigned char*</tt>: Output payload.
+  *                                           This must contain storage for at
+  *                                           least \a max_data_bytes.
+  * @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
+  *                                                 memory for the output
+  *                                                 payload. This may be
+  *                                                 used to impose an upper limit on
+  *                                                 the instant bitrate, but should
+  *                                                 not be used as the only bitrate
+  *                                                 control. Use #OPUS_SET_BITRATE to
+  *                                                 control the bitrate.
+  * @returns The length of the encoded packet (in bytes) on success or a
+  *          negative error code (see @ref opus_errorcodes) on failure.
+  */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_encode(
+    OpusProjectionEncoder *st,
+    const opus_int16 *pcm,
+    int frame_size,
+    unsigned char *data,
+    opus_int32 max_data_bytes
+) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
+
+
+/** Encodes a projection Opus frame from floating point input.
+  * @param st <tt>OpusProjectionEncoder*</tt>: Projection encoder state.
+  * @param[in] pcm <tt>const float*</tt>: The input signal as interleaved
+  *                                       samples with a normal range of
+  *                                       +/-1.0.
+  *                                       Samples with a range beyond +/-1.0
+  *                                       are supported but will be clipped by
+  *                                       decoders using the integer API and
+  *                                       should only be used if it is known
+  *                                       that the far end supports extended
+  *                                       dynamic range.
+  *                                       This must contain
+  *                                       <code>frame_size*channels</code>
+  *                                       samples.
+  * @param frame_size <tt>int</tt>: Number of samples per channel in the input
+  *                                 signal.
+  *                                 This must be an Opus frame size for the
+  *                                 encoder's sampling rate.
+  *                                 For example, at 48 kHz the permitted values
+  *                                 are 120, 240, 480, 960, 1920, and 2880.
+  *                                 Passing in a duration of less than 10 ms
+  *                                 (480 samples at 48 kHz) will prevent the
+  *                                 encoder from using the LPC or hybrid modes.
+  * @param[out] data <tt>unsigned char*</tt>: Output payload.
+  *                                           This must contain storage for at
+  *                                           least \a max_data_bytes.
+  * @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
+  *                                                 memory for the output
+  *                                                 payload. This may be
+  *                                                 used to impose an upper limit on
+  *                                                 the instant bitrate, but should
+  *                                                 not be used as the only bitrate
+  *                                                 control. Use #OPUS_SET_BITRATE to
+  *                                                 control the bitrate.
+  * @returns The length of the encoded packet (in bytes) on success or a
+  *          negative error code (see @ref opus_errorcodes) on failure.
+  */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_encode_float(
+    OpusProjectionEncoder *st,
+    const float *pcm,
+    int frame_size,
+    unsigned char *data,
+    opus_int32 max_data_bytes
+) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
+
+
+/** Frees an <code>OpusProjectionEncoder</code> allocated by
+  * opus_projection_ambisonics_encoder_create().
+  * @param st <tt>OpusProjectionEncoder*</tt>: Projection encoder state to be freed.
+  */
+OPUS_EXPORT void opus_projection_encoder_destroy(OpusProjectionEncoder *st);
+
+
+/** Perform a CTL function on a projection Opus encoder.
+  *
+  * Generally the request and subsequent arguments are generated by a
+  * convenience macro.
+  * @param st <tt>OpusProjectionEncoder*</tt>: Projection encoder state.
+  * @param request This and all remaining parameters should be replaced by one
+  *                of the convenience macros in @ref opus_genericctls,
+  *                @ref opus_encoderctls, @ref opus_multistream_ctls, or
+  *                @ref opus_projection_ctls
+  * @see opus_genericctls
+  * @see opus_encoderctls
+  * @see opus_multistream_ctls
+  * @see opus_projection_ctls
+  */
+OPUS_EXPORT int opus_projection_encoder_ctl(OpusProjectionEncoder *st, int request, ...) OPUS_ARG_NONNULL(1);
+
+
+/**@}*/
+
+/**\name Projection decoder functions */
+/**@{*/
+
+/** Gets the size of an <code>OpusProjectionDecoder</code> structure.
+  * @param channels <tt>int</tt>: The total number of output channels.
+  *                               This must be no more than 255.
+  * @param streams <tt>int</tt>: The total number of streams coded in the
+  *                              input.
+  *                              This must be no more than 255.
+  * @param coupled_streams <tt>int</tt>: Number streams to decode as coupled
+  *                                      (2 channel) streams.
+  *                                      This must be no larger than the total
+  *                                      number of streams.
+  *                                      Additionally, The total number of
+  *                                      coded channels (<code>streams +
+  *                                      coupled_streams</code>) must be no
+  *                                      more than 255.
+  * @returns The size in bytes on success, or a negative error code
+  *          (see @ref opus_errorcodes) on error.
+  */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_projection_decoder_get_size(
+    int channels,
+    int streams,
+    int coupled_streams
+);
+
+
+/** Allocates and initializes a projection decoder state.
+  * Call opus_projection_decoder_destroy() to release
+  * this object when finished.
+  * @param Fs <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
+  *                                This must be one of 8000, 12000, 16000,
+  *                                24000, or 48000.
+  * @param channels <tt>int</tt>: Number of channels to output.
+  *                               This must be at most 255.
+  *                               It may be different from the number of coded
+  *                               channels (<code>streams +
+  *                               coupled_streams</code>).
+  * @param streams <tt>int</tt>: The total number of streams coded in the
+  *                              input.
+  *                              This must be no more than 255.
+  * @param coupled_streams <tt>int</tt>: Number of streams to decode as coupled
+  *                                      (2 channel) streams.
+  *                                      This must be no larger than the total
+  *                                      number of streams.
+  *                                      Additionally, The total number of
+  *                                      coded channels (<code>streams +
+  *                                      coupled_streams</code>) must be no
+  *                                      more than 255.
+  * @param[in] demixing_matrix <tt>const unsigned char[demixing_matrix_size]</tt>: Demixing matrix
+  *                         that mapping from coded channels to output channels,
+  *                         as described in @ref opus_projection and
+  *                         @ref opus_projection_ctls.
+  * @param demixing_matrix_size <tt>opus_int32</tt>: The size in bytes of the
+  *                                                  demixing matrix, as
+  *                                                  described in @ref
+  *                                                  opus_projection_ctls.
+  * @param[out] error <tt>int *</tt>: Returns #OPUS_OK on success, or an error
+  *                                   code (see @ref opus_errorcodes) on
+  *                                   failure.
+  */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT 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
+) OPUS_ARG_NONNULL(5);
+
+
+/** Intialize a previously allocated projection decoder state object.
+  * The memory pointed to by \a st must be at least the size returned by
+  * opus_projection_decoder_get_size().
+  * This is intended for applications which use their own allocator instead of
+  * malloc.
+  * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
+  * @see opus_projection_decoder_create
+  * @see opus_projection_deocder_get_size
+  * @param st <tt>OpusProjectionDecoder*</tt>: Projection encoder state to initialize.
+  * @param Fs <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
+  *                                This must be one of 8000, 12000, 16000,
+  *                                24000, or 48000.
+  * @param channels <tt>int</tt>: Number of channels to output.
+  *                               This must be at most 255.
+  *                               It may be different from the number of coded
+  *                               channels (<code>streams +
+  *                               coupled_streams</code>).
+  * @param streams <tt>int</tt>: The total number of streams coded in the
+  *                              input.
+  *                              This must be no more than 255.
+  * @param coupled_streams <tt>int</tt>: Number of streams to decode as coupled
+  *                                      (2 channel) streams.
+  *                                      This must be no larger than the total
+  *                                      number of streams.
+  *                                      Additionally, The total number of
+  *                                      coded channels (<code>streams +
+  *                                      coupled_streams</code>) must be no
+  *                                      more than 255.
+  * @param[in] demixing_matrix <tt>const unsigned char[demixing_matrix_size]</tt>: Demixing matrix
+  *                         that mapping from coded channels to output channels,
+  *                         as described in @ref opus_projection and
+  *                         @ref opus_projection_ctls.
+  * @param demixing_matrix_size <tt>opus_int32</tt>: The size in bytes of the
+  *                                                  demixing matrix, as
+  *                                                  described in @ref
+  *                                                  opus_projection_ctls.
+  * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes)
+  *          on failure.
+  */
+OPUS_EXPORT 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
+) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6);
+
+
+/** Decode a projection Opus packet.
+  * @param st <tt>OpusProjectionDecoder*</tt>: Projection decoder state.
+  * @param[in] data <tt>const unsigned char*</tt>: Input payload.
+  *                                                Use a <code>NULL</code>
+  *                                                pointer to indicate packet
+  *                                                loss.
+  * @param len <tt>opus_int32</tt>: Number of bytes in payload.
+  * @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
+  *                                       samples.
+  *                                       This must contain room for
+  *                                       <code>frame_size*channels</code>
+  *                                       samples.
+  * @param frame_size <tt>int</tt>: The number of samples per channel of
+  *                                 available space in \a pcm.
+  *                                 If this is less than the maximum packet duration
+  *                                 (120 ms; 5760 for 48kHz), this function will not be capable
+  *                                 of decoding some packets. In the case of PLC (data==NULL)
+  *                                 or FEC (decode_fec=1), then frame_size needs to be exactly
+  *                                 the duration of audio that is missing, otherwise the
+  *                                 decoder will not be in the optimal state to decode the
+  *                                 next incoming packet. For the PLC and FEC cases, frame_size
+  *                                 <b>must</b> be a multiple of 2.5 ms.
+  * @param decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band
+  *                                 forward error correction data be decoded.
+  *                                 If no such data is available, the frame is
+  *                                 decoded as if it were lost.
+  * @returns Number of samples decoded on success or a negative error code
+  *          (see @ref opus_errorcodes) on failure.
+  */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_decode(
+    OpusProjectionDecoder *st,
+    const unsigned char *data,
+    opus_int32 len,
+    opus_int16 *pcm,
+    int frame_size,
+    int decode_fec
+) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
+
+
+/** Decode a projection Opus packet with floating point output.
+  * @param st <tt>OpusProjectionDecoder*</tt>: Projection decoder state.
+  * @param[in] data <tt>const unsigned char*</tt>: Input payload.
+  *                                                Use a <code>NULL</code>
+  *                                                pointer to indicate packet
+  *                                                loss.
+  * @param len <tt>opus_int32</tt>: Number of bytes in payload.
+  * @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
+  *                                       samples.
+  *                                       This must contain room for
+  *                                       <code>frame_size*channels</code>
+  *                                       samples.
+  * @param frame_size <tt>int</tt>: The number of samples per channel of
+  *                                 available space in \a pcm.
+  *                                 If this is less than the maximum packet duration
+  *                                 (120 ms; 5760 for 48kHz), this function will not be capable
+  *                                 of decoding some packets. In the case of PLC (data==NULL)
+  *                                 or FEC (decode_fec=1), then frame_size needs to be exactly
+  *                                 the duration of audio that is missing, otherwise the
+  *                                 decoder will not be in the optimal state to decode the
+  *                                 next incoming packet. For the PLC and FEC cases, frame_size
+  *                                 <b>must</b> be a multiple of 2.5 ms.
+  * @param decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band
+  *                                 forward error correction data be decoded.
+  *                                 If no such data is available, the frame is
+  *                                 decoded as if it were lost.
+  * @returns Number of samples decoded on success or a negative error code
+  *          (see @ref opus_errorcodes) on failure.
+  */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_decode_float(
+    OpusProjectionDecoder *st,
+    const unsigned char *data,
+    opus_int32 len,
+    float *pcm,
+    int frame_size,
+    int decode_fec
+) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
+
+
+/** Perform a CTL function on a projection Opus decoder.
+  *
+  * Generally the request and subsequent arguments are generated by a
+  * convenience macro.
+  * @param st <tt>OpusProjectionDecoder*</tt>: Projection decoder state.
+  * @param request This and all remaining parameters should be replaced by one
+  *                of the convenience macros in @ref opus_genericctls,
+  *                @ref opus_decoderctls, @ref opus_multistream_ctls, or
+  *                @ref opus_projection_ctls.
+  * @see opus_genericctls
+  * @see opus_decoderctls
+  * @see opus_multistream_ctls
+  * @see opus_projection_ctls
+  */
+OPUS_EXPORT int opus_projection_decoder_ctl(OpusProjectionDecoder *st, int request, ...) OPUS_ARG_NONNULL(1);
+
+
+/** Frees an <code>OpusProjectionDecoder</code> allocated by
+  * opus_projection_decoder_create().
+  * @param st <tt>OpusProjectionDecoder</tt>: Projection decoder state to be freed.
+  */
+OPUS_EXPORT void opus_projection_decoder_destroy(OpusProjectionDecoder *st);
+
+
+/**@}*/
+
+/**@}*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* OPUS_PROJECTION_H */
diff --git a/opus_sources.mk b/opus_sources.mk
index e4eeb91..b0763f9 100644
--- a/opus_sources.mk
+++ b/opus_sources.mk
@@ -4,7 +4,10 @@
 src/opus_multistream.c \
 src/opus_multistream_encoder.c \
 src/opus_multistream_decoder.c \
-src/repacketizer.c
+src/repacketizer.c \
+src/opus_projection_encoder.c \
+src/opus_projection_decoder.c \
+src/mapping_matrix.c
 
 OPUS_SOURCES_FLOAT = \
 src/analysis.c \
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 */
diff --git a/tests/test_opus_projection.c b/tests/test_opus_projection.c
new file mode 100644
index 0000000..4b11c12
--- /dev/null
+++ b/tests/test_opus_projection.c
@@ -0,0 +1,431 @@
+/* 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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include "float_cast.h"
+#include "opus.h"
+#include "test_opus_common.h"
+#include "opus_projection.h"
+#include "mathops.h"
+#include "../src/mapping_matrix.c"
+#include "mathops.c"
+
+#ifdef ENABLE_EXPERIMENTAL_AMBISONICS
+
+#define BUFFER_SIZE 960
+#define MAX_DATA_BYTES 32768
+#define MAX_FRAME_SAMPLES 5760
+
+#define INT16_TO_FLOAT(x) ((1/32768.f)*(float)x)
+
+void print_matrix_short(const opus_int16 *data, int rows, int cols)
+{
+  int i, j;
+  for (i = 0; i < rows; i++)
+  {
+    for (j = 0; j < cols; j++)
+    {
+      fprintf(stderr, "%8.5f  ", (float)INT16_TO_FLOAT(data[j * rows + i]));
+    }
+    fprintf(stderr, "\n");
+  }
+  fprintf(stderr, "\n");
+}
+
+void print_matrix_float(const float *data, int rows, int cols)
+{
+  int i, j;
+  for (i = 0; i < rows; i++)
+  {
+    for (j = 0; j < cols; j++)
+    {
+      fprintf(stderr, "%8.5f ", data[j * rows + i]);
+    }
+    fprintf(stderr, "\n");
+  }
+  fprintf(stderr, "\n");
+}
+
+void print_matrix(MappingMatrix *matrix)
+{
+  opus_int16 *data;
+
+  fprintf(stderr, "%d x %d, gain: %d\n", matrix->rows, matrix->cols,
+    matrix->gain);
+
+  data = mapping_matrix_get_data(matrix);
+  print_matrix_short(data, matrix->rows, matrix->cols);
+}
+
+int assert_transform_short(
+  const opus_int16 *a, const opus_int16 *b, int size, opus_int16 tolerance)
+{
+  int i;
+  for (i = 0; i < size; i++)
+  {
+    if (abs(a[i] - b[i]) > tolerance)
+    {
+      return 0;
+    }
+  }
+  return 1;
+}
+
+int assert_transform_float(
+  const float *a, const float *b, int size, float tolerance)
+{
+  int i;
+  for (i = 0; i < size; i++)
+  {
+    if (fabsf(a[i] - b[i]) > tolerance)
+    {
+      return 0;
+    }
+  }
+  return 1;
+}
+
+void test_matrix_transform(void)
+{
+  /* Create testing mixing matrix (4 x 3), gain 0dB:
+  *   [ 0 1 0 ]
+  *   [ 1 0 0 ]
+  *   [ 0 0 0 ]
+  *   [ 0 0 1 ]
+  */
+  opus_int32 matrix_size;
+  MappingMatrix *testing_matrix;
+  const opus_int16 testing_matrix_data[12] = {
+    0, 32767, 0, 0, 32767, 0, 0, 0, 0, 0, 0, 32767 };
+
+  const int frame_size = 10;
+  const opus_int16 input[30] = {
+    32767, 0, -32768, 29491, -3277, -29491, 26214, -6554, -26214, 22938, -9830,
+    -22938, 19661, -13107, -19661, 16384, -16384, -16384, 13107, -19661, -13107,
+    9830, -22938, -9830, 6554, -26214, -6554, 3277, -29491, -3277};
+  const opus_int16 expected_output[40] = {
+    0, 32767, 0, -32768, -3277, 29491, 0, -29491, -6554, 26214, 0, -26214,
+    -9830, 22938, 0, -22938, -13107, 19661, 0, -19661, -16384, 16384, 0, -16384,
+    -19661, 13107, 0, -13107, -22938, 9830, 0, -9830, -26214, 6554, 0, -6554,
+    -29491, 3277, 0, -3277};
+  opus_int16 output[40] = {0};
+
+#ifndef DISABLE_FLOAT_API
+  int i;
+  /* Sample-accurate to -93.9794 dB */
+  float flt_tolerance = 2e-5f;
+  float input32[30] = {0};
+  float output32[40] = {0};
+  float expected_output32[40] = {0};
+
+  /* Convert short to float representations. */
+  for (i = 0; i < 30; i++)
+  {
+    input32[i] = INT16_TO_FLOAT(input[i]);
+  }
+  for (i = 0; i < 40; i++)
+  {
+    expected_output32[i] = INT16_TO_FLOAT(expected_output[i]);
+  }
+#endif /* DISABLE_FLOAT_API */
+
+  /* Create the matrix. */
+  matrix_size = mapping_matrix_get_size(4, 3);
+  testing_matrix = (MappingMatrix *)opus_alloc(matrix_size);
+  mapping_matrix_init(testing_matrix, 4, 3, 0, testing_matrix_data,
+    12 * sizeof(opus_int16));
+
+  mapping_matrix_multiply_short(testing_matrix, input, testing_matrix->cols,
+    output, testing_matrix->rows, frame_size);
+  if (!assert_transform_short(output, expected_output, 40, 1))
+  {
+    fprintf(stderr, "Matrix:\n");
+    print_matrix(testing_matrix);
+
+    fprintf(stderr, "Input (short):\n");
+    print_matrix_short(input, testing_matrix->cols, frame_size);
+
+    fprintf(stderr, "Expected Output (short):\n");
+    print_matrix_short(expected_output, testing_matrix->rows, frame_size);
+
+    fprintf(stderr, "Output (short):\n");
+    print_matrix_short(output, testing_matrix->rows, frame_size);
+
+    goto bad_cleanup;
+  }
+
+#ifndef DISABLE_FLOAT_API
+  mapping_matrix_multiply_float(testing_matrix, input32, testing_matrix->cols,
+    output32, testing_matrix->rows, frame_size);
+  if (!assert_transform_float(output32, expected_output32, 40, flt_tolerance))
+  {
+    fprintf(stderr, "Matrix:\n");
+    print_matrix(testing_matrix);
+
+    fprintf(stderr, "Input (float):\n");
+    print_matrix_float(input32, testing_matrix->cols, frame_size);
+
+    fprintf(stderr, "Expected Output (float):\n");
+    print_matrix_float(expected_output32, testing_matrix->rows, frame_size);
+
+    fprintf(stderr, "Output (float):\n");
+    print_matrix_float(output32, testing_matrix->rows, frame_size);
+
+    goto bad_cleanup;
+  }
+#endif
+  opus_free(testing_matrix);
+  return;
+bad_cleanup:
+  opus_free(testing_matrix);
+  test_failed();
+}
+
+void test_creation_arguments(const int channels, const int mapping_family)
+{
+  int streams;
+  int coupled_streams;
+  int enc_error;
+  int dec_error;
+  int ret;
+  OpusProjectionEncoder *st_enc = NULL;
+  OpusProjectionDecoder *st_dec = NULL;
+
+  const opus_int32 Fs = 48000;
+  const int application = OPUS_APPLICATION_AUDIO;
+
+  int order_plus_one = (int)floor(sqrt((float)channels));
+  int nondiegetic_channels = channels - order_plus_one * order_plus_one;
+
+  int is_channels_valid = 0;
+  int is_projection_valid = 0;
+
+  st_enc = opus_projection_ambisonics_encoder_create(Fs, channels,
+    mapping_family, &streams, &coupled_streams, application, &enc_error);
+  if (st_enc != NULL)
+  {
+    opus_int32 matrix_size;
+    unsigned char *matrix;
+
+    ret = opus_projection_encoder_ctl(st_enc,
+      OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST, &matrix_size);
+    if (ret != OPUS_OK || !matrix_size)
+      test_failed();
+
+    matrix = (unsigned char *)opus_alloc(matrix_size);
+    ret = opus_projection_encoder_ctl(st_enc,
+      OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST, matrix, matrix_size);
+
+    opus_projection_encoder_destroy(st_enc);
+
+    st_dec = opus_projection_decoder_create(Fs, channels, streams,
+      coupled_streams, matrix, matrix_size, &dec_error);
+    if (st_dec != NULL)
+    {
+      opus_projection_decoder_destroy(st_dec);
+    }
+    opus_free(matrix);
+  }
+
+  is_channels_valid = (order_plus_one >= 2 && order_plus_one <= 4) &&
+    (nondiegetic_channels == 0 || nondiegetic_channels == 2);
+  is_projection_valid = (enc_error == OPUS_OK && dec_error == OPUS_OK);
+  if (is_channels_valid ^ is_projection_valid)
+  {
+    fprintf(stderr, "Channels: %d, Family: %d\n", channels, mapping_family);
+    fprintf(stderr, "Order+1: %d, Non-diegetic Channels: %d\n",
+      order_plus_one, nondiegetic_channels);
+    fprintf(stderr, "Streams: %d, Coupled Streams: %d\n",
+      streams, coupled_streams);
+    test_failed();
+  }
+}
+
+void generate_music(short *buf, opus_int32 len, opus_int32 channels)
+{
+   opus_int32 i,j,k;
+   opus_int32 *a,*b,*c,*d;
+   a = (opus_int32 *)malloc(sizeof(opus_int32) * channels);
+   b = (opus_int32 *)malloc(sizeof(opus_int32) * channels);
+   c = (opus_int32 *)malloc(sizeof(opus_int32) * channels);
+   d = (opus_int32 *)malloc(sizeof(opus_int32) * channels);
+   memset(a, 0, sizeof(opus_int32) * channels);
+   memset(b, 0, sizeof(opus_int32) * channels);
+   memset(c, 0, sizeof(opus_int32) * channels);
+   memset(d, 0, sizeof(opus_int32) * channels);
+   j=0;
+
+   for(i=0;i<len;i++)
+   {
+     for(k=0;k<channels;k++)
+     {
+      opus_uint32 r;
+      opus_int32 v;
+      v=(((j*((j>>12)^((j>>10|j>>12)&26&j>>7)))&128)+128)<<15;
+      r=fast_rand();v+=r&65535;v-=r>>16;
+      b[k]=v-a[k]+((b[k]*61+32)>>6);a[k]=v;
+      c[k]=(30*(c[k]+b[k]+d[k])+32)>>6;d[k]=b[k];
+      v=(c[k]+128)>>8;
+      buf[i*channels+k]=v>32767?32767:(v<-32768?-32768:v);
+      if(i%6==0)j++;
+     }
+   }
+
+   free(a);
+   free(b);
+   free(c);
+   free(d);
+}
+
+void test_encode_decode(opus_int32 bitrate, opus_int32 channels,
+                        const int mapping_family)
+{
+  const opus_int32 Fs = 48000;
+  const int application = OPUS_APPLICATION_AUDIO;
+
+  OpusProjectionEncoder *st_enc;
+  OpusProjectionDecoder *st_dec;
+  int streams;
+  int coupled;
+  int error;
+  short *buffer_in;
+  short *buffer_out;
+  unsigned char data[MAX_DATA_BYTES] = { 0 };
+  int len;
+  int out_samples;
+  opus_int32 matrix_size = 0;
+  unsigned char *matrix = NULL;
+
+  buffer_in = (short *)malloc(sizeof(short) * BUFFER_SIZE * channels);
+  buffer_out = (short *)malloc(sizeof(short) * BUFFER_SIZE * channels);
+
+  st_enc = opus_projection_ambisonics_encoder_create(Fs, channels,
+    mapping_family, &streams, &coupled, application, &error);
+  if (error != OPUS_OK) {
+    fprintf(stderr,
+      "Couldn\'t create encoder with %d channels and mapping family %d.\n",
+      channels, mapping_family);
+    free(buffer_in);
+    free(buffer_out);
+    test_failed();
+  }
+
+  error = opus_projection_encoder_ctl(st_enc,
+    OPUS_SET_BITRATE(bitrate * 1000 * (streams + coupled)));
+  if (error != OPUS_OK)
+  {
+    goto bad_cleanup;
+  }
+
+  error = opus_projection_encoder_ctl(st_enc,
+    OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST, &matrix_size);
+  if (error != OPUS_OK || !matrix_size)
+  {
+    goto bad_cleanup;
+  }
+
+  matrix = (unsigned char *)opus_alloc(matrix_size);
+  error = opus_projection_encoder_ctl(st_enc,
+    OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST, matrix, matrix_size);
+
+  st_dec = opus_projection_decoder_create(Fs, channels, streams, coupled,
+    matrix, matrix_size, &error);
+  opus_free(matrix);
+
+  if (error != OPUS_OK) {
+    fprintf(stderr,
+      "Couldn\'t create decoder with %d channels, %d streams "
+      "and %d coupled streams.\n", channels, streams, coupled);
+    goto bad_cleanup;
+  }
+
+  generate_music(buffer_in, BUFFER_SIZE, channels);
+
+  len = opus_projection_encode(
+    st_enc, buffer_in, BUFFER_SIZE, data, MAX_DATA_BYTES);
+  if(len<0 || len>MAX_DATA_BYTES) {
+    fprintf(stderr,"opus_encode() returned %d\n", len);
+    goto bad_cleanup;
+  }
+
+  out_samples = opus_projection_decode(
+    st_dec, data, len, buffer_out, MAX_FRAME_SAMPLES, 0);
+  if(out_samples!=BUFFER_SIZE) {
+    fprintf(stderr,"opus_decode() returned %d\n", out_samples);
+    goto bad_cleanup;
+  }
+
+  free(buffer_in);
+  free(buffer_out);
+  return;
+bad_cleanup:
+  free(buffer_in);
+  free(buffer_out);
+  test_failed();
+}
+
+int main(int _argc, char **_argv)
+{
+  unsigned int i;
+
+  (void)_argc;
+  (void)_argv;
+
+  /* Test matrix creation/multiplication. */
+  test_matrix_transform();
+
+  /* Test full range of channels in creation arguments. */
+  for (i = 0; i < 255; i++)
+    test_creation_arguments(i, 253);
+
+  /* Test encode/decode pipeline. */
+  test_encode_decode(64 * 16, 16, 253);
+
+  fprintf(stderr, "All projection tests passed.\n");
+  return 0;
+}
+
+#else
+
+int main(int _argc, char **_argv)
+{
+  (void)_argc;
+  (void)_argv;
+  fprintf(stderr, "Projection tests are disabled. "
+          "Configure with --enable-ambisonics for support.\n");
+  return 0;
+}
+
+#endif /* ENABLE_EXPERIMENTAL_AMBISONICS */