Need a function urgently

Dec 12, 2012 at 1:24 PM

Hi Guys,

I urgently need a function that can do the following:

PlayTracks(string DeviceName, string[] output, string[] filenames)

Output would be an array that specifies the output channel on a sound device.

Filenames would be an array that contains the full paths to file names that need to be played.

So the input arrays might be something like:

Output [0]= "1,3" / Filenames[0]="c:\Soundfiles\File1.wav"

Output[1] = "2,3" / Filenames[1]="c:\Soundfiles\File2.wav"

Given this input, the function should do the following:

Open "file1.wav", and assign the left channel to output 1 on the specified device, and assign the right channel to output 3.

Open "file2.wav", and assign the left channel to channel 2 of the specified device, and assign the right channel to output 3.

Once the channels are assigned, the audio should be played.

Some notes:

Playback must loop.

Note how two input channels are assigned to output 3: This should be allowed, and should do a mixdown in real time.

I have been struggling with this for a long time, and I just can't get it right. Some pointers will be very much appreciate, or alternatively if someone can give me a quotation for a function like that, then perhaps one of the specialists on here can do it for me.

Thanks!

 

Coordinator
Dec 12, 2012 at 3:50 PM

hi, NAudio has some pieces you will need. For example the MultiplexingSampleProvider does quite a bit of this (but no mixing - you'd want MixingSampleProvider). Looping would probably be best implemented with another wrapped layer on top of this.

Also, what output driver model were you hoping to use? Is this aimed at a specific soundcard with multiple outputs?

Mark

Dec 12, 2012 at 5:25 PM

Thank you for your reply Mark,

I looked into MultiplexingWaveProvider and got it working, but then when I tried to do looping, it didn't work. I used the example for looping, which derives a class from WaveStream, but then I can't use the MultiplexingWaveProvider to create an instance of the LoopStream class. (Not the same types...)

I also still don't know: if I assign two inputs to one output, if it will do real time mixdown or not. (I would think it would, but I haven't been able to test it yet). If it does, then I won't need the MixingSampleProvider.

As for the output driver: I don't know which is the most correct, but so far I've tentatively decided on ASIOOut, because I read that it is the best way to access different outputs on a sound device.

I will look at the sample providers instead of the wave providers, and see if that can perhaps sort out my problem...

I guess I can only keep trying. In the mean time, if someone wants to create this function for me, please pm me with quotes...

Thanks!

Coordinator
Dec 12, 2012 at 5:32 PM

For your needs, I'd be tempted to write my own LoopingSampleProvider which takes a delegate that can reset all the WaveFileReaders back to Position 0, and call that when the multiplexer reaches the end. The MultiplexingWaveProvider does not do mixing (it just patches inputs to outputs). It would be possible to make a mixing one though (probably adapting MultiplexingSampleProvider to have a grid of multipliers so every output can be a mix of every input).

AsioOut is probably a good idea for multiple output soundcards. It does mean that your users will need an ASIO driver installed on their system.

I do sometimes do bits of paid NAudio consultancy work, so if you do want to discuss a quote, you can get in touch via the contact link on my CodePlex user profile page.

Mark

Dec 12, 2012 at 6:18 PM

Thank you again Mark, I will try a little bit with the "sample instead of wave" classes, and if I don't come right, I will contact you... (I'd hate to admit defeat though!)

I think this might help someone else as I couldn't find a clear answer on this: If I have two inputs, let's say L and R, and I use one of the Multiplexing classes, and I mapped both of them to "output 0": Will it generate an error? If not: What will happen?

Coordinator
Dec 12, 2012 at 7:19 PM

each output can only be connected to a single input

Dec 13, 2012 at 4:52 AM

Is there a way to get a sound card to perform it's own mixdown? Or is mixdown always performed on the PC?

Coordinator
Dec 13, 2012 at 7:56 AM

some types of output model (e.g. WaveOut) let you open the same device multiple times, and so Windows performs the mixing. But with ASIO, you can only open the device once and must mix yourself.

It also depends if you need perfect syncrhonization. If you opened the soundcard twice, you'd have troubles keeping the two playback streams exactly in sync.

Dec 17, 2012 at 9:34 AM

I think I might have sorted out the mixing while multiplexing thing! Now I am looking for a way to loop the result. You wrote earlier:

Looping would probably be best implemented with another wrapped layer on top of this.

Could you please provide the noob a little bit more information just to get me going?

Thanks!

Coordinator
Dec 17, 2012 at 11:00 AM

you'd implement a waveprovider or sampleprovider that in its Read method, returning from the source provider. However, if the source provider's Read method returns 0, then you need to loop. If the source is a WaveStream, then you can loop by setting its Position to 0. However, if you are working with sample providers, then I'd have a delegate that I called back to reset the sources to the start.

a very quick and dirty example, with a lambda being passed to the looper so it can request reposition to start

var input1 = new WaveFileReader(...);
var input2 = new WaveFileReader(...);
var mixer = new YourMixingAndMultiplexingSamplePovider(input1, input2);
var looper = new Looper(mixer, () => { input1.Position=0; input2.Position=0; });

Dec 23, 2012 at 9:43 AM

Hi Mark,

Thanks for taking the time to help me out. I am very confused about this (sorry)

My structure is like this:

I start out with a list of file names. From the filenames, I create an array of meteringsamplesproviders like so: (Note, this is just pseudo code, just to show what I am trying to do)

MeteringSampleProvider[] SamplesBeforeMixdown  = new MeteringSampleProvider(new AudioFileReader([FileNames.Count]));

for(int n = 0; n<FileNames.count;n++)

{

MeteringSampleProvider input = new MeteringSampleProvider(new AudioFileReader(FileNames[n]));

SamplesBeforeMixdown[n] = input;

}

Then I pass the array to my MixingMultiplexingSampleProvider, like this:

MixingMultiplexingSampleProvider sampleprovider = new MixingMultiplexingSampleProvider(SamplesBeforeMixdown, WaveOut.GetCapabilities(playbackDeviceNumber).Channels);

Also, I made changes to the MeteringSampleProvide class in order to store offset / channels, so that I can link each sample provider in the array to a waveformpainter control. (I catch the StreamVolume event in order to draw the wave in the waveformpainter. So I till need to access the UI controls as well.

I am completely confused now as to where/how I need to implement looping. I'm slightly new to this, so sorry for all the questions...

Thanks!

Dec 23, 2012 at 11:10 AM

I created the following class: 

    public class MyLoopingSampleProvider : ISampleProvider 
    {
        AudioFileReader audiofilereader;

        /// <summary>
        /// Initializes a new instance of MyLoopingSampleProvider
        /// </summary>
        /// <param name="filename">the path to a sound file</param>

        public MyLoopingSampleProvider(string filename)
        {
            audiofilereader = new AudioFileReader(filename);
        }

        /// <summary>
        /// Reads samples from this sample provider
        /// </summary>
        /// <param name="buffer">Sample buffer</param>
        /// <param name="offset">Offset into sample buffer</param>
        /// <param name="sampleCount">Number of samples desired</param>
        /// <returns>Number of samples read</returns>
        public int Read(float[] buffer, int offset, int count)
        {
            int bytesread = 0;

            bytesread = audiofilereader.Read(buffer, offset, count);

            if (bytesread == 0)
            {
                audiofilereader.Position = 0;
                bytesread = audiofilereader.Read(buffer, offset, count);
            }

            return bytesread; 
        }

        /// <summary>
        /// WaveFormat
        /// </summary>
        public WaveFormat WaveFormat
        {
            get { return audiofilereader.WaveFormat; }
        }

    }
 

Not the Read function: When no bytes were returned, it will set the audiofilereader to 0, and just read again.

Then I do like this: For each file in my input array, I'll create the MyLoopingSampleProvider. With the output, I create a meteringsampleprovider (For painting the input waves).

With the array of meteringsampleproviders from above, I then create the mixingmultiplexingsampleprovider.

The output from the mixingmultiplexingsampleprovider is then in turn used to create a meteringsampleprovicer (for painting the output waves).

Finally tha last meteringsampleprovider is used for playing as a waveout.

When I choose only one stereo wavefile as input, it plays twice, then stops. When I choose 2 stereo inputs, it plays 1 and a half times, then stops. When I choose 3 inputs, it plays once, but never repeats.

When I play the MyLoopingSampleProvider directly, (iow without creating the metering and/or MixingMultiplexingSampleProvider it will loop correctly, with no problems.

Am I at least on the right track with what I am doing?

Will I be able to use this method for multiple files with different durations? What it needs to do is to keep playing until the longest sound has finished, then loop all of them at the same time. In other words, it should not loop a shorter file while a longer file is still playing.

Any help will be very much appreciated!

Thanks!

Coordinator
Dec 23, 2012 at 7:36 PM

Sort of, although if you look at the example code I showed earlier, the looper comes after the mixingmultiplexingsampleprovider. You have to do that if you want it to only loop after the longest file has played

Mark

Dec 24, 2012 at 6:09 AM
Edited Dec 24, 2012 at 9:18 AM

I did that first, like you said, but the question then is, is how will I access the position property, since the metering sample provider doesn't have that.. (and the mixing sample provider is created with an array of metering sample providers..)

Coordinator
Dec 27, 2012 at 8:25 AM

the example I showed above passes a delegate which has a closure around the two readers, allowing it to set their Position property to 0. That's how I would do it, but there are other ways to give your Looper access to the mixer inputs so it can reset their positions.