Reputation: 71
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();
Upvotes: 3
Views: 4892
Reputation: 71
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;
}
Upvotes: 4
Reputation: 31406
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.
Upvotes: 2