0
votes

I have been using NAudio with the "Fire and Forget Audio Playback with NAudio" tutorial (thank you Mark for this awesome utility!) as written here: http://mark-dot-net.blogspot.nl/2014/02/fire-and-forget-audio-playback-with.html

I managed to add a VolumeSampleProvider to it, using the MixingSampleProvider as input. However, when I now play two sounds right after each other, the first sound always gets the volume of the second as well, even though the first is already playing.

So my question is: How do I add sounds with an individual volume per sound?

This is what I used:

        mixer = new MixingSampleProvider(waveformat);
        mixer.ReadFully = true;
        volumeProvider = new VolumeSampleProvider(mixer);
        panProvider = new PanningSampleProvider(volumeProvider);
        outputDevice.Init(panProvider);
        outputDevice.Play();
2

2 Answers

1
votes

I'm not 100% certain of what you are asking and I don't know if you solved this already but here's my take on this.

ISampleProvider objects play the "pass the buck" game to their source ISampleProvider via the Read() method. Eventually, someone does some actual reading of audio bytes. Individual ISampleProvider classes do whatever they do to the bytes.

MixingSampleProvider, for instance, takes N audio sources... those get mixed. When Read() is called, it iterates the audio sources and reads count bytes from each.

Passing it to a VolumeSampleProvider handles all the bytes (from those various sources) as a group... it says:

buffer[offset+n] *= volume;

That's going to adjust the bytes across the board... so every byte gets adjusted in the buffer by the volume multiplier;

The PanningSampleProvider just provides a multiplier to the stereo audio and adjusts the bytes accordingly, doing the same sort of thing as the VolumeSampleProvider.

If you want to individually handle audio source volumes, you need to handle that upstream of the MixingSampleProvider. Essentially, the things that you pass to the MixingSampleProvider need to be able to have their volume adjusted independently.

If you passed a bunch of SampleChannel objects to your MixingSampleProvider... you could accomplish independent volume adjustment. The Samplechannel class incorporates a VolumeSampleProvider object and provides a Volume property that allows one to set the volume on that VolumeSampleProvider object.

SampleChannel also incorporates a MeteringSampleProvider that provides reporting of the maximum sample value during a given period. It raises an event that gives you an array of those values, one per channel.

1
votes

I realized (thanks to itsmatt) that the only way to make this work, is to leave the mixer alone and adjust the panning and volume of each CachedSound individually, before adding it to the mixer. Therefore I needed to rewrite the CachedSoundSampleProvider, using a pan and volume as extra input parameters.

This is the new constructor:

    public CachedSoundSampleProvider(CachedSound cachedSound, float volume = 1, float pan = 0)
    {
        this.cachedSound = cachedSound;
        LeftVolume = volume * (0.5f - pan / 2);
        RightVolume = volume * (0.5f + pan / 2);
    }

And this is the new Read() function:

    public int Read(float[] buffer, int offset, int count)
    {
        long availableSamples = cachedSound.AudioData.Length - position;
        long samplesToCopy = Math.Min(availableSamples, count);

        int destOffset = offset;
        for (int sourceSample = 0; sourceSample < samplesToCopy; sourceSample += 2)
        {
            float outL = cachedSound.AudioData[position + sourceSample + 0];
            float outR = cachedSound.AudioData[position + sourceSample + 1];

            buffer[destOffset + 0] = outL * LeftVolume;
            buffer[destOffset + 1] = outR * RightVolume;
            destOffset += 2;
        }

        position += samplesToCopy;
        return (int)samplesToCopy;
    }