How to realize a multi channel audio pre-mixer in .net

3.3k views Asked by At

I'd like to use C# to implement an application that can play multiple audio streams at the same time. Peanuts - now the interesting part: assuming every stream is single channel (mono) I want to adjust the volume for every speaker (5.1 or even 7.1) for every stream separately. I can use the windows mixer to do this, but the problem is, that there is only one mixer and I want to adjust this for every stream separately. Any ideas how to implement this?

My first guess was to multiplex the stream eight times (7.1), apply the volume level for every "channel" and then send it to the windows mixer, which is leveled for all channels at 80% for example. Do you know any libraries that might support such use case?

AFAIK bass and fmod cannot do this, but correct me if I'm wrong. As alternative I was thinking of hacking the XNA for this: using a vector that describes the position of the stream related to the listener and using this to apply the volume compensation... just ramblings.

(and please don't point me to some C++/WinAPI ideas on this, this project is not worth to learn another language now.)

1

There are 1 answers

0
Marc Wittke On BEST ANSWER

Finally got it: bass.dll allows to apply a matrix as volume settings for every speaker separately using the method BassMix.BASS_Mixer_ChannelSetMatrix(int streamHandle, float[,] volumeMatrix). You can see a sample here, they're using this to upmix a stereo stream to four speakers. Below the full class I created to solve my problem.

public class SeparateVolumeLevelPlayer : IDisposable
{
    private readonly int outputMixerStream;
    private readonly int inputStream;
    private readonly int numberOfSpeakers;

    public SeparateVolumeLevelPlayer(string fileName, int numberOfSpeakers)
    {
        this.numberOfSpeakers = numberOfSpeakers;
        outputMixerStream = BassMix.BASS_Mixer_StreamCreate(44100, numberOfSpeakers, BASSFlag.BASS_MIXER_MATRIX);
        ThrowOnError();

        // create a stream from the media file
        inputStream = Bass.BASS_StreamCreateFile(fileName, 0L, 0L, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_MIXER_MATRIX | BASSFlag.BASS_SAMPLE_MONO);
        ThrowOnError();

        // add the stream to the mixer
        BassMix.BASS_Mixer_StreamAddChannel(outputMixerStream, inputStream, BASSFlag.BASS_MIXER_MATRIX);
        ThrowOnError();
    }

    public void Play()
    {
        // start playback of the mixed streams
        Bass.BASS_ChannelPlay(outputMixerStream, false);
        ThrowOnError();
    }

    public void SetVolume(float[] volumeValues)
    {
        if (volumeValues == null) 
        {
            throw new ArgumentNullException("volumeValues");
        }

        if (volumeValues.Length != numberOfSpeakers)
        {
            string message =
                string.Format("You must pass a volume level for every speaker. You provided {0} values for {1} speakers",
                                            volumeValues.Length, numberOfSpeakers);
            throw  new ArgumentException(message);
        }

        var volumeMatrix = new float[numberOfSpeakers, 1];

        for (int i = 0; i < numberOfSpeakers; i++)
        {
            volumeMatrix[i, 0] = volumeValues[i];
        }

        // adjust the volume using the matrix
        BassMix.BASS_Mixer_ChannelSetMatrix(inputStream, volumeMatrix);
        ThrowOnError();

    }

    private static void ThrowOnError()
    {
        BASSError err = Bass.BASS_ErrorGetCode();
        if (err != BASSError.BASS_OK)
        {
            throw new ApplicationException(string.Format("bass.dll reported {0}.", err));
        }
    }

    public void Dispose()
    {
        Bass.BASS_StreamFree(inputStream);
        Bass.BASS_StreamFree(outputMixerStream);
    }
}