Custom multichannel DSP issue (bit crushed?)

I am currently trying to write a DSP callback which takes a 4 channel input file (ambisonicfile) and outputs stereo. I have implemented all the decoding, but have completely stripped it back so that it just copies one channel to output

The issue is that no matter what I do, the output sounds bit reduced. If I disable the plugin, the output plays back fine.

First off, here are the examples of the sonic output I’m getting:
The clean file, just a single channel from the 4 channel file - https://soundcloud.com/joshkopecek/fmod-pre-dsp-example/s-99g36
and the original WAV file (note I use a .ogg file, which decodes fine when I remove the DSP)
https://www.dropbox.com/s/e2rzmb0kg9a8q84/190805_002.WAV?dl=0
The output version from the DSP (sorry, recorded from iPhone through my laptop, not great quality, but you’ll get the idea)
https://soundcloud.com/joshkopecek/fmod-dsp-bit-crushed-example/s-jBkZB

This is what I’m doing to create the DSP when I set up the channel:

FMOD::DSP *ambisonicDSP;
        FMOD_DSP_DESCRIPTION dspdesc;
        memset(&dspdesc, 0, sizeof(dspdesc));
        FMOD_DSP_PARAMETER_DESC wavedata_desc;
        FMOD_DSP_PARAMETER_DESC channel_index_desc;
        FMOD_DSP_PARAMETER_DESC volume_desc;
        FMOD_DSP_PARAMETER_DESC fAzimuth;
        FMOD_DSP_PARAMETER_DESC fElevation;
        FMOD_DSP_PARAMETER_DESC fDistance;
        FMOD_DSP_PARAMETER_DESC *paramdesc[6] =
        {
            &channel_index_desc,
            &wavedata_desc,
            &volume_desc,
            &fAzimuth,
            &fElevation,
            &fDistance
        };
        
        const char* FMOD_Ambisonic_Channel_Names[maxsounds];
        for (int i = 0; i < maxsounds; i++) {
            FMOD_Ambisonic_Channel_Names[i] = "channel";
        }
        
        // set up the descriptions for the dsp plugin
        FMOD_DSP_INIT_PARAMDESC_DATA(wavedata_desc, "wave data", "", "wave data", FMOD_DSP_PARAMETER_DATA_TYPE_USER);
        FMOD_DSP_INIT_PARAMDESC_INT(
                                    channel_index_desc,
                                    "channel index",
                                    "%",
                                    "the index of the channel",
                                    0,
                                    maxsounds,
                                    channel_index,
                                    false,
                                    FMOD_Ambisonic_Channel_Names
                                    );
        FMOD_DSP_INIT_PARAMDESC_FLOAT(volume_desc, "volume", "%", "linear volume in percent", 0, 1, 1);
        FMOD_DSP_INIT_PARAMDESC_FLOAT(fAzimuth, "Azimuth", "%", "Azimuth parameter", 0, 1, 1);
        FMOD_DSP_INIT_PARAMDESC_FLOAT(fElevation, "Elevation", "%", "Elevation parameter", 0, 1, 1);
        FMOD_DSP_INIT_PARAMDESC_FLOAT(fDistance, "Distance", "%", "Distance parameter", 0, 1, 1);
        
        strncpy(dspdesc.name, "ECHOES Ambisonic Decoder", sizeof(dspdesc.name));
        dspdesc.version = 0x00010000;
        dspdesc.numinputbuffers = 1;
        dspdesc.numoutputbuffers = 1;
        dspdesc.read = ambisonicDSPCallback;
        dspdesc.create = ambisonicDSPCreateCallback;
        dspdesc.release = ambisonicDSPReleaseCallback;
        dspdesc.setparameterfloat = ambisonicDSPSetParameterFloatCallback;
        dspdesc.getparameterfloat = ambisonicDSPGetParameterFloatCallback;
        dspdesc.setparameterint = ambisonicDSPSetParameterIntCallback;
        dspdesc.getparameterint = ambisonicDSPGetParameterIntCallback;
        dspdesc.numparameters = 6;
        dspdesc.paramdesc = paramdesc;
        
        // add the ambisonic decoder
        result = fmodsystem->createDSP(&dspdesc, &ambisonicDSP);
        if (result != FMOD_OK) {
            NSLog(@"fmod:: Error creating ambisonic DSP because: %s", FMOD_ErrorString(result));
            return false;
        }
        
        // set up the params
        int numparams;
        result = ambisonicDSP->getNumParameters(&numparams);
        for (int i = 0; i < numparams; i++)
        {
            FMOD_DSP_PARAMETER_DESC *desc;
            ambisonicDSP->getParameterInfo(i, &desc);
        }
        // pass in the channel index so we can retrieve the right encoders
        result = ambisonicDSP->setParameterInt(0, channel_index);
        if (result != FMOD_OK) {
            NSLog(@"fmod:: Error setting channel index for ambisonic DSP because: %s", FMOD_ErrorString(result));
            return false;
        }
        // set the distance
        result = ambisonicDSP->setParameterFloat(4, 5.0f);
        if (result != FMOD_OK) {
            NSLog(@"fmod:: Error setting distance for ambisonic DSP because: %s", FMOD_ErrorString(result));
            return false;
        }
        // add the dsp so it becomes active
        channel->addDSP(0, ambisonicDSP);

and this is the processing callback (without my ambisonic decoding)

int numstereochannels = 2;

FMOD_RESULT F_CALLBACK ambisonicDSPCallback(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels)
{
    ambisonicdsp_data_t *data = (ambisonicdsp_data_t *)dsp_state->plugindata;
    
    outchannels = &numstereochannels;
for (unsigned int samp = 0; samp < length; samp++)
    {
        for (unsigned chan = 0; chan < *outchannels; chan++)
        {
            data->buffer[(samp * numstereochannels) + chan] = outbuffer[(samp * numstereochannels) + chan] = inbuffer[(samp * inchannels) + 0] * data->volume_linear;
        }
    }
    
    data->channels = numstereochannels;
    
    return FMOD_OK;
}

and the creation callback for the DSP in case you need that

FMOD_RESULT F_CALLBACK ambisonicDSPCreateCallback(FMOD_DSP_STATE *dsp_state)
{
    unsigned int blocksize;
    FMOD_SPEAKERMODE speakermode;
    FMOD_SPEAKERMODE speakermodeoutput;
    unsigned int numspeakers = 2;
    FMOD_RESULT result;
    
    result = dsp_state->functions->getspeakermode(dsp_state, &speakermode, &speakermodeoutput);
    if (result != FMOD_OK) {
        NSLog(@"fmod:: Error getting speaker mode because: %s", FMOD_ErrorString(result));
    }
    switch (speakermodeoutput) {
        case FMOD_SPEAKERMODE_STEREO:
            numspeakers = 2;
            break;
        case FMOD_SPEAKERMODE_MONO:
            numspeakers = 1;
            break;
        case FMOD_SPEAKERMODE_QUAD:
            numspeakers = 4;
            break;
        default:
            numspeakers = 2;
            break;
    }
    
    result = dsp_state->functions->getblocksize(dsp_state, &blocksize);
    if (result != FMOD_OK) {
        NSLog(@"fmod:: Error getting ambisonic blocksize because: %s", FMOD_ErrorString(result));
    }
    
    ambisonicdsp_data_t *data = (ambisonicdsp_data_t *)calloc(sizeof(ambisonicdsp_data_t), 1);
    if (!data)
    {
        return FMOD_ERR_MEMORY;
    }
    
    int samplerate;
    result = dsp_state->functions->getsamplerate(dsp_state, &samplerate);
    if (result != FMOD_OK) {
        NSLog(@"fmod:: Error getting ambisonic sample rate because: %s", FMOD_ErrorString(result));
    }
    
    dsp_state->plugindata = data;
    data->volume_linear = 0.5f;
    data->length_samples = blocksize;
    data->sample_rate = samplerate;
    data->scratch_outbuffer = new float *[numstereochannels];
    
    // scratch input is just for one channel at a time
    data->scratch_inbuffer.resize(512);
    
    // output is stereo
    for (unsigned i = 0; i < 2; i++) {
        data->scratch_outbuffer[i] = new float[blocksize * sizeof(float)];
        memset(data->scratch_outbuffer[i], 0, blocksize * sizeof(float));
    }
    
    data->scratch_outinterleaved_buffer.resize(blocksize * 2);
    
    data->buffer = (float *)malloc(blocksize * numspeakers * sizeof(float));
    if (!data->buffer)
    {
        return FMOD_ERR_MEMORY;
    }
    
    return FMOD_OK;
}

As you can see, I’m deinterleaving the first channel from the input and copying it into 1 and 2 of the output. I set the outputchannels to 2.

I’m at a loss as to why something so simple might be generating the result that it is, especially when removing the DSP results in clean playback.

Could you let me know if I’m doing something wrong here?

So I discovered here that the outbuffer expects the same number of interleaved channels as the inchannels, regardless of the number of output channels you specify.

So the line

outbuffer[(samp * numstereochannels) + chan] = inbuffer[(samp * inchannels) + 0] * data->volume_linear;

meant that I was ending up with short frames, i.e.
[1,2][1,2] …
and it was expecting
[1,2,3,4],[1,2,3,4]
and would discard (?) the other channels

So doing this

outbuffer[(samp * inchannels) + chan] = inbuffer[(samp * inchannels) + 0];

i.e. copying the first mono channel into each stereo channel and leaving 3 & 4 blank gives a clean signal.