This project has moved and is read-only. For the latest updates, please go here.

ASIO input

Dec 27, 2011 at 4:30 PM

Hi,

Is there some reason ASIO input isn't supported?

I've hacked input capability into ASIODriverExt.cs - it's only half a dozen changes and appears to work fine, but I worry that there's  some reason this wasn't done in the first place.

HF

Dec 28, 2011 at 8:04 AM

No huge reason. The original ASIO support was a contribution, and it never worked with my soundcard until recently. There is another patch I have in my in-box to add recording, but it needs a few little tweaks before it can be merged in.

ASIO recording isn't separate from playback like other driver models - you must deal with both outgoing and ingoing buffers in the same callback. It's a model that I really like, but it does mean that it won't quite fit with the other IWaveIn implementations.

Dec 30, 2011 at 12:30 AM

> ASIO recording isn't separate from playback like other driver models - you must deal with both outgoing and ingoing buffers in the same callback.

 

Well, sure, theoretically, but there's no problem with having a callback for each and calling them sequentially, if that's what your interface definition requires. Personally I just hacked it up such that the one callback now gets an extra argument with the input samples, but I can appreciate you wouldn't necessarily want to do that as it'd break everyone's existing code. 

HF

Apr 12, 2012 at 8:29 AM

How is this coming along? any progress in merging this? i would really like to use the ASIO inputs and i also like the idea of a callback with both in and out buffers.

Is there any new patch available?

best

Apr 16, 2012 at 8:37 AM

no I'm afraid you'll need to modify the AsioOut class yourself at the moment to support this. Its a feature I'd like to get into the next public NAudio release

 

Apr 16, 2012 at 8:50 AM

ok, i see... but i can't promise to get it done soon, but i will try this week.

would it be basically like this?:

the AsioOut would get an additional constructor which takes a class or interface which provides a method processing both, in and out buffer. the AsioOut then calls this method with the buffers.

then we would need an adapter class implementing this interface which will take an IWaveProvider and some kind of input interface (is there something already?) to be able to use all the IWaveproviders that already exist...

or what would you propose?

Apr 16, 2012 at 8:53 AM

well the existing interface is IWaveIn, which simply raises an event to notify you whenever audio is available. To fit with the rest of NAudio, that would be good, but the ASIO model is quite different from all other methods of recording.

Apr 16, 2012 at 8:58 AM

yes, so i think the adapter class will just do the following:

- implements a new IAsioProcess (or whatever name) interface

- take IWaveIn and IWaveProvider as constructor arguments

- when the driver does the callback it first raises the event of IWaveIn and then calls the Read method of IWaveProvider

what do you think?

Apr 16, 2012 at 9:04 AM

I'm not quite sure what you have in mind for the adapter. AsioOut has the Init method which takes an IWaveProvider at the moment. To fit with other NAudio classes, the simple thing to do would just be to make AsioOut implement IWaveIn. All you need to do is in the callback raise the event of IWaveIn.

Apr 16, 2012 at 9:12 AM

ok, that would be simple, but it would not allow users to write classes which implement a method which takes both buffers.

for example libpd has an audio processing function like: process_float(int ticks, ref float inBuffer, ref float outBuffer) 

so the in and out buffer needs to be there in the same time. it would be an option to store the in buffer (or a reference to it) when getting the event and providing it to this call in the read method. i am not sure if its flexible enough.... but yea, should also be possible.

Apr 16, 2012 at 9:18 AM

that is a good point. one callback function for in and out can be very useful

May 1, 2012 at 6:02 PM
Hfuy wrote:

Hi,

Is there some reason ASIO input isn't supported?

I've hacked input capability into ASIODriverExt.cs - it's only half a dozen changes and appears to work fine, but I worry that there's  some reason this wasn't done in the first place.

HF

Hi Hfuy,

 

Could you describe exactly which hacks you made to ASIODriverExt.cs to get ASIO input working? I would love to see this feature added to NAudio in the future but for now if I would be happy with a work-around.

Thanks!

May 16, 2012 at 10:28 PM
Edited Jul 5, 2012 at 5:55 PM

Hi,

Based on a recent request:

Sorry it's taken a while to get around to this, but here's what I did. It probably isn't good style with respect to the rest of naudio and it's based on what may now be a pretty old revision of the code, but this is most of what I had to do. It's also been ages since I worked on this so my ability to help out with it may be limited.

It expands outputBufferInfos with additional entries for input. outputBufferInfos is now misnamed, but it shouldn't be a breaking change as the output buffers are still at the same indices in the array; the appropriate solution is probably to create a new bufferInfos array and deprecate outputBufferInfos (but assign it to bufferInfos for back compat). Then it'll need wrapping up to support all the appropriate interfaces, but that's beyond me. Otherwise, just listen for ASIOFillBufferCallback, observe the buffer index and read the latter half of the outputBufferInfos array.

In short, it's a terrible hack job. Anyway, here it is.

PS - I think this is all you need. I did make changes to other files, but only so as to be make the ASIO driver standalone from the rest of naudio (it didn't take much). I honestly can't remember if this file alone is enough, but I can make the rest of it available if need be.

[Incorrect code redacted - see post below for code]


May 18, 2012 at 9:38 AM

thanks for this, I'll refer back to this when I get round to adding ASIO in support which is long overdue

May 31, 2012 at 5:32 PM

Hi Hfuy

 

First of all, thanks very much for the response! I was a bit confused though. I have an unchanged version of NAudio 1.5 and when I compare the code you posted with my ASIODriverExt.cs in araxis merge I see that they are 100 percent identical. Did you perhaps grab the un-hacked file by accident?

 

Thanks for your time!

Jul 5, 2012 at 1:40 PM

Hi,

  If you don't mind posting the Hacked ASIODriverExt.cs with your changes for ASIO recording.

 

Thanks in advance

 

Jul 5, 2012 at 5:54 PM
Edited Jul 5, 2012 at 5:56 PM

> Did you perhaps grab the un-hacked file by accident?

What d'you think I am, some sort of idi...

...oh yeah, I am an idiot. Sorry, folks, my bad.

Anyway, here it is. I did just test this out and it is working, at least in the context of my own code. The purpose of this change, by the way, was to write an SMPTE timecode decoder. This is also working, and if there's any interest in naudio being able to decode SMPTE timecode, I might be persuaded to donate it.

using System;

namespace NAudio.Wave.Asio
{

    /// 

    /// ASIODriverCapability holds all the information from the ASIODriver.
    /// Use ASIODriverExt to get the Capabilities
    /// 

    internal class ASIODriverCapability
    {
        public String DriverName;  
      
        public int NbInputChannels;
        public int NbOutputChannels;

        public int InputLatency;
        public int OutputLatency;

        public int BufferMinSize;
        public int BufferMaxSize;
        public int BufferPreferredSize;
        public int BufferGranularity;

        public double SampleRate;

        public ASIOChannelInfo[] InputChannelInfos;
        public ASIOChannelInfo[] OutputChannelInfos;
    }

    /// 

    /// Callback used by the ASIODriverExt to get wave data
    /// 

    internal delegate void ASIOFillBufferCallback(IntPtr[] InputBufferChannels, IntPtr[] OutputBufferChannels);

    /// 

    /// ASIODriverExt is a simplified version of the ASIODriver. It provides an easier
    /// way to access the capabilities of the Driver and implement the callbacks necessary 
    /// for feeding the driver.
    /// Implementation inspired from Rob Philpot's with a managed C++ ASIO wrapper BlueWave.Interop.Asio
    /// http://www.codeproject.com/KB/mcpp/Asio.Net.aspx
    /// 
    /// Contributor: Alexandre Mutel - email: alexandre_mutel at yahoo.fr
    /// 
    /// ASIO input hacked in by: Phil Rhodes
    /// This is currently extremely basic and breaks the previous API in several
    /// places. See http://naudio.codeplex.com/discussions/284258
    /// 
    /// Changes are particularly in CreateBuffers and BufferSwitchCallback.
    /// 
    /// The underlying driver in ASIODriver.cs does not seem to need changes to
    /// support input.
    /// 
    /// 

    internal class ASIODriverExt
    {
        private ASIODriver driver;
        private ASIOCallbacks callbacks;
        private ASIODriverCapability capability;
        public ASIOBufferInfo[] BufferInfos;
        private bool isOutputReadySupport;
        private IntPtr[] CurrentOutputBuffers;
        private IntPtr[] CurrentInputBuffers;
        private int nbOutputChannels;
        private ASIOFillBufferCallback BuffersReadyCallback;
        private int bufferSize;
        private int channelOffset;

        /// 

        /// Initializes a new instance of the  class based on an already
        /// instantiated ASIODriver instance.
        /// 

        /// A ASIODriver already instantiated.
        public ASIODriverExt(ASIODriver driver)
        {
            this.driver = driver;

            if (!driver.init(IntPtr.Zero))
            {
                throw new ApplicationException(driver.getErrorMessage());
            }

            callbacks = new ASIOCallbacks();
            callbacks.pasioMessage = AsioMessageCallBack;
            callbacks.pbufferSwitch = BufferSwitchCallBack;
            callbacks.pbufferSwitchTimeInfo = BufferSwitchTimeInfoCallBack;
            callbacks.psampleRateDidChange = SampleRateDidChangeCallBack;

            BuildCapabilities();
        }

        /// 

        /// Allows adjustment of which is the first output channel we write to
        /// 

        /// Channel offset
        public void SetChannelOffset(int channelOffset)
        {
            if (channelOffset + nbOutputChannels <= Capabilities.NbOutputChannels)
            {
                this.channelOffset = channelOffset;
            }
            else
            {
                throw new ArgumentException("Invalid channel offset");
            }
       }

        /// 

        /// Gets the driver used.
        /// 

        /// The ASIOdriver.
        public ASIODriver Driver
        {
            get { return driver; }
        }

        /// 

        /// Starts playing the buffers.
        /// 

        public void Start()
        {
            driver.start();
        }

        /// 

        /// Stops playing the buffers.
        /// 

        public void Stop()
        {
            driver.stop();
        }

        /// 

        /// Shows the control panel.
        /// 

        public void ShowControlPanel()
        {
            driver.controlPanel();
        }

        /// 

        /// Releases this instance.
        /// 

        public void ReleaseDriver()
        {
            try
            {
                driver.disposeBuffers();
            } catch (Exception ex)
            {
                Console.Out.WriteLine(ex.ToString());
            }
            driver.ReleaseComASIODriver();
        }

        /// 

        /// Determines whether the specified sample rate is supported.
        /// 

        /// The sample rate.
        /// 
        /// 	true if [is sample rate supported]; otherwise, false.
        /// 
        public bool IsSampleRateSupported(double sampleRate)
        {
            return driver.canSampleRate(sampleRate);
        }

        /// 

        /// Sets the sample rate.
        /// 

        /// The sample rate.
        public void SetSampleRate(double sampleRate)
        {
            driver.setSampleRate(sampleRate);
            // Update Capabilities
            BuildCapabilities();
        }

        /// 

        /// Gets or sets the fill buffer callback.
        /// 

        /// The fill buffer callback.
        public ASIOFillBufferCallback FillBufferCallback
        {
            get { return BuffersReadyCallback; }
            set { BuffersReadyCallback = value; }
        }

        /// 

        /// Gets the capabilities of the ASIODriver.
        /// 

        /// The capabilities.
        public ASIODriverCapability Capabilities
        {
            get { return capability; }
        }

        /// 

        /// Creates the buffers for playing.
        /// 

        /// The number of outputs channels.
        /// if set to true [use max buffer size] else use Prefered size
        public int CreateBuffers(int nbInputChannelsArg, int nbOutputChannelsArg, bool useMaxBufferSize)
        {
            if (nbOutputChannelsArg <= 0 || nbOutputChannelsArg > capability.NbOutputChannels)
            {
                throw new ArgumentException(String.Format(
                                                "Invalid number of channels {0}, must be in the range [1,{1}]",
                                                nbOutputChannelsArg, capability.NbOutputChannels));
            }

            // each channel needs a buffer info
            nbOutputChannels = nbOutputChannelsArg;
            // Ask for maximum of output channels even if we use only the nbOutputChannelsArg
            int nbTotalChannels = capability.NbInputChannels + capability.NbOutputChannels;
            BufferInfos = new ASIOBufferInfo[nbTotalChannels];
            CurrentOutputBuffers = new IntPtr[nbOutputChannelsArg];
            CurrentInputBuffers = new IntPtr[nbInputChannelsArg];
            // and do the same for output channels
            // ONLY work on output channels (just put isInput = true for InputChannel)
            int totalIndex = 0;
            for (int index = 0; index < capability.NbInputChannels; index++, totalIndex++)
            {
                BufferInfos[totalIndex].isInput = true;
                BufferInfos[totalIndex].channelNum = index;
                BufferInfos[totalIndex].pBuffer0 = IntPtr.Zero;
                BufferInfos[totalIndex].pBuffer1 = IntPtr.Zero;
            }

            for (int index = 0; index < capability.NbOutputChannels; index++, totalIndex++)
            {
                BufferInfos[totalIndex].isInput = false;
                BufferInfos[totalIndex].channelNum = index;
                BufferInfos[totalIndex].pBuffer0 = IntPtr.Zero;
                BufferInfos[totalIndex].pBuffer1 = IntPtr.Zero;
            }

            if (useMaxBufferSize)
            {
                // use the drivers maximum buffer size
                bufferSize = capability.BufferMaxSize;
            }
            else
            {
                // use the drivers preferred buffer size
                bufferSize = capability.BufferPreferredSize;
            }

            unsafe
            {
                fixed (ASIOBufferInfo* infos = &BufferInfos[0])
                {
                    IntPtr pOutputBufferInfos = new IntPtr(infos);

                    // Create the ASIO Buffers with the callbacks

                    driver.createBuffers(pOutputBufferInfos, nbTotalChannels, bufferSize, ref callbacks);
                }
            }

            // Check if outputReady is supported
            isOutputReadySupport = (driver.outputReady() == ASIOError.ASE_OK);
            
            return bufferSize;
        }

        /// 

        /// Builds the capabilities internally.
        /// 

        private void BuildCapabilities()
        {
            capability = new ASIODriverCapability();

            capability.DriverName = driver.getDriverName();

            // Get nb Input/Output channels
            driver.getChannels(out capability.NbInputChannels, out capability.NbOutputChannels);

            capability.InputChannelInfos = new ASIOChannelInfo[capability.NbInputChannels];
            capability.OutputChannelInfos = new ASIOChannelInfo[capability.NbOutputChannels];

            // Get ChannelInfo for Inputs
            for (int i = 0; i < capability.NbInputChannels; i++)
            {
                capability.InputChannelInfos[i] = driver.getChannelInfo(i, true);
            }

            // Get ChannelInfo for Output
            for (int i = 0; i < capability.NbOutputChannels; i++)
            {
                capability.OutputChannelInfos[i] = driver.getChannelInfo(i, false);
            }

            // Get the current SampleRate
            capability.SampleRate = driver.getSampleRate();


            // Get Latencies
            driver.getLatencies(out capability.InputLatency, out capability.OutputLatency);

            // Get BufferSize
            driver.getBufferSize(out capability.BufferMinSize, out capability.BufferMaxSize, out capability.BufferPreferredSize, out capability.BufferGranularity);
        }

        /// 

        /// Callback called by the ASIODriver on fill buffer demand. Redirect call to external callback.
        /// 

        /// Index of the double buffer.
        /// if set to true [direct process].
        private void BufferSwitchCallBack(int doubleBufferIndex, bool directProcess)
        {

            for (int i = 0; i < capability.NbInputChannels; i++)
            {
                CurrentInputBuffers[i] = BufferInfos[i + channelOffset].Buffer(doubleBufferIndex);
            }

            for (int i = 0; i < nbOutputChannels; i++)
            {
                CurrentOutputBuffers[i] = BufferInfos[i + channelOffset + capability.NbInputChannels].Buffer(doubleBufferIndex);
            }

            if (BuffersReadyCallback != null)

                BuffersReadyCallback(CurrentInputBuffers, CurrentOutputBuffers);

            if (isOutputReadySupport)
                driver.outputReady();            
        }

        /// 

        /// Callback called by the ASIODriver on event "Samples rate changed".
        /// 

        /// The sample rate.
        private void SampleRateDidChangeCallBack(double sRate)
        {
            // Check when this is called?
            capability.SampleRate = sRate;
        }

        /// 

        /// Asio message call back.
        /// 

        /// The selector.
        /// The value.
        /// The message.
        /// The opt.
        /// 
        private int AsioMessageCallBack(ASIOMessageSelector selector, int value, IntPtr message, IntPtr opt)
        {
            // Check when this is called?
            switch (selector)
            {
                case ASIOMessageSelector.kAsioSelectorSupported:
                    ASIOMessageSelector subValue = (ASIOMessageSelector)Enum.ToObject(typeof(ASIOMessageSelector), value);
                    switch (subValue)
                    {
                        case ASIOMessageSelector.kAsioEngineVersion:
                            return 1;
                        case ASIOMessageSelector.kAsioResetRequest:
                            return 0;
                        case ASIOMessageSelector.kAsioBufferSizeChange:
                            return 0;
                        case ASIOMessageSelector.kAsioResyncRequest:
                            return 0;
                        case ASIOMessageSelector.kAsioLatenciesChanged:
                            return 0;
                        case ASIOMessageSelector.kAsioSupportsTimeInfo:
//                            return 1; DON'T SUPPORT FOR NOW. NEED MORE TESTING.
                            return 0;
                        case ASIOMessageSelector.kAsioSupportsTimeCode:
//                            return 1; DON'T SUPPORT FOR NOW. NEED MORE TESTING.
                            return 0;
                    }
                    break;
                case ASIOMessageSelector.kAsioEngineVersion:
                    return 2;
                case ASIOMessageSelector.kAsioResetRequest:
                    return 1;
                case ASIOMessageSelector.kAsioBufferSizeChange:
                    return 0;
                case ASIOMessageSelector.kAsioResyncRequest:
                    return 0;
                case ASIOMessageSelector.kAsioLatenciesChanged:
                    return 0;
                case ASIOMessageSelector.kAsioSupportsTimeInfo:
                    return 0;
                case ASIOMessageSelector.kAsioSupportsTimeCode:
                    return 0;
            }
            return 0;            
        }

        /// 

        /// Buffers switch time info call back.
        /// 

        /// The asio time param.
        /// Index of the double buffer.
        /// if set to true [direct process].
        /// 
        private IntPtr BufferSwitchTimeInfoCallBack(IntPtr asioTimeParam, int doubleBufferIndex, bool directProcess)
        {
            // Check when this is called?
            return IntPtr.Zero;   
        }
    }
}

 

Jul 5, 2012 at 10:06 PM

Thanks for the update Hfuy! You're right there's not too much added here, I will try your changes in my code and see if I can't get the ASIO input working.

 

Cheers!

Mark

Jul 6, 2012 at 5:09 AM

Hi Hfuy,

  Thank you for the update, how to implement this function BuffersReadyCallback(CurrentInputBuffers, CurrentOutputBuffers). It would be really helpful if you post the all the dependent function implementations. How to give a specific Input channel number for recording , I am not clear with this things any ideas would be helpful.

 

Thanks in advance

Jul 6, 2012 at 11:19 AM
Edited Jul 6, 2012 at 11:24 AM

Sorry about the formatting in this, this forum software is an absolute nightmare!

Anyway, there are a couple of stages to this.

First, enumerate the available ASIO devices:

string[] names = ASIODriver.GetASIODriverNames();
foreach(string name in names){
	ASIODriver d = ASIODriver.GetASIODriverByName(name);
	// Make visible names of ASIO drivers and figure out which one you want
}

 

Pick the one you want (assuming you want the first listed):

int WhichAsioDevice = 0;

ASIODriver drv = ASIODriver.GetASIODriverByName(ASIODriver.GetASIODriverNames()[WhichAsioDevice]);

 

Get an ASIODriverExt instance for it:

 

ASIODriverExt drvx = new ASIODriverExt(drv);

 



Set up and start:

 

ASIOFillBufferCallback cbk = new ASIOFillBufferCallback(BufFeeder); // See below

drvx.FillBufferCallback = cbk;

drvx.CreateBuffers(drvx.Capabilities.NbInputChannels, drvx.Capabilities.NbOutputChannels, false);
drvx.Start();


Here's the buffer feeder (and, now, reader) callback:

 

private static void BufFeeder(IntPtr[] InputBufferChannels, IntPtr[] OutputBufferChannels)
	int InputChannel = 3; // This happens to be the back panel line input on my PC
	unsafe
	 {
		Int32* InBuf = (Int32*)InputBufferChannels[InputChannel];
		// Do something with 512 bytes of *InBuf
	}
}

 

That should be enough to get you going!


HF

Jul 10, 2012 at 1:08 PM

Hi Hfuy,

   Thank you for the posting, is there any easy way to convert that InBuf to .wav file. If I have given the filename the inBuf should be saved to the filename.wav file. Please help me in this regard. Any sample code would be helpful.

 

Thanks in advance.


Jul 10, 2012 at 1:18 PM

Sorry, I can't really help you - I used the data in other parts of my software, and never needed to send it to a file. Presumably there is a way to do this in naudio; I found a few promising hits with a simple google search:

 

http://www.google.co.uk/#hl=en&sa=X&ei=eh38T_eHJYWL8gOqlcmRBw&ved=0CEIQvwUoAQ&q=naudio+save+wav+file&spell=1&bav=on.2,or.r_gc.r_pw.r_qf.,cf.osb&fp=42bc37e02999c64c&biw=960&bih=569

Sep 9, 2012 at 5:21 PM

Just wanted to say that I've checked in ASIO recording, basing a lot of the code off Hfuy's implementation, so thanks very much for sharing