123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432 |
- using System.Runtime.InteropServices;
- using Bmp.Core.Lite.Playback.Outputs.BASS_ASIO;
- using Bmp.Core.Lite.Playback.Outputs.NAudioASIO.Originals;
- namespace Bmp.Core.Lite.Playback.Outputs.NAudioASIO;
- /// <summary>
- /// Callback used by the AsioDriverExt to get wave data
- /// </summary>
- public delegate void AsioFillBufferCallback(nint[] inputChannels, nint[] outputChannels);
- /// <summary>
- /// 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
- /// </summary>
- public class AsioDriverExt
- {
- private readonly string _asioDriverName;
- private AsioDriver _driver;
- private AsioCallbacks _callbacks;
- private AsioBufferInfo[] _bufferInfos;
- public bool IsOutputReadySupported { get; private set; }
- private nint[] _currentOutputBuffers;
- private nint[] _currentInputBuffers;
- private int _numberOfOutputChannels;
- private int _numberOfInputChannels;
- private int _bufferSize;
- private int _outputChannelOffset;
- private int _inputChannelOffset;
- public Action ResetRequestCallback;
- // BmpMod: use driver name for reset
- public AsioDriverExt(string asioDriverName)
- {
- _asioDriverName = asioDriverName;
- Init();
- _callbacks = new AsioCallbacks();
- _callbacks.AsioMessage = AsioMessageCallBack;
- _callbacks.BufferSwitch = BufferSwitchCallBack;
- _callbacks.BufferSwitchTimeInfo = BufferSwitchTimeInfoCallBack;
- _callbacks.SampleRateDidChange = SampleRateDidChangeCallBack;
- BuildCapabilities();
- }
- public bool Future_GetIoFormat_Is_DSD()
- {
- var fmt = new ASIOIoFormat { formatType = -1 };
- Future(ASIOFuture.GetIoFormat, fmt);
- return fmt.formatType == 1;
- }
- public bool Future_GetIoFormat_Is_PCM()
- {
- var fmt = new ASIOIoFormat { formatType = -1 };
- Future(ASIOFuture.GetIoFormat, fmt);
- return fmt.formatType == 0;
- }
- public void Future_SetIoFormat_DSD()
- {
- var fmt = new ASIOIoFormat { formatType = 1 };
- Future(ASIOFuture.SetIoFormat, fmt);
- }
- public void Future_SetIoFormat_PCM()
- {
- var fmt = new ASIOIoFormat { formatType = 0 };
- Future(ASIOFuture.SetIoFormat, fmt);
- }
- private void Future(ASIOFuture selector, [In, Out] object opt)
- {
- var hPinned = GCHandle.Alloc(opt, GCHandleType.Pinned);
- _driver.Future(selector, hPinned.AddrOfPinnedObject());
- }
- private void Init()
- {
- _driver = AsioDriver.GetAsioDriverByName(_asioDriverName);
- if (!_driver.Init(nint.Zero))
- {
- ReleaseDriver();
- throw new InvalidOperationException(_driver.GetErrorMessage());
- }
- }
- /// <summary>
- /// Allows adjustment of which is the first output channel we write to
- /// </summary>
- /// <param name="outputChannelOffset">Output Channel offset</param>
- /// <param name="inputChannelOffset">Input Channel offset</param>
- public void SetChannelOffset(int outputChannelOffset, int inputChannelOffset)
- {
- if (outputChannelOffset + _numberOfOutputChannels <= Capabilities.NbOutputChannels)
- {
- _outputChannelOffset = outputChannelOffset;
- }
- else
- {
- throw new ArgumentException("Invalid channel offset");
- }
- if (inputChannelOffset + _numberOfInputChannels <= Capabilities.NbInputChannels)
- {
- _inputChannelOffset = inputChannelOffset;
- }
- else
- {
- throw new ArgumentException("Invalid channel offset");
- }
- }
- /// <summary>
- /// Gets the driver used.
- /// </summary>
- /// <value>The ASIOdriver.</value>
- public AsioDriver Driver => _driver;
- /// <summary>
- /// Starts playing the buffers.
- /// </summary>
- public void Start() => _driver.Start();
- /// <summary>
- /// Stops playing the buffers.
- /// </summary>
- public void Stop() => _driver.Stop();
- /// <summary>
- /// Shows the control panel.
- /// </summary>
- public void ShowControlPanel()
- {
- _driver.ControlPanel();
- }
- /// <summary>
- /// Releases this instance.
- /// </summary>
- public void ReleaseDriver()
- {
- try
- {
- _driver.DisposeBuffers();
- }
- catch (Exception ex)
- {
- Console.Out.WriteLine(ex.ToString());
- }
- _driver.ReleaseComAsioDriver();
- }
- /// <summary>
- /// Determines whether the specified sample rate is supported.
- /// </summary>
- /// <param name="sampleRate">The sample rate.</param>
- /// <returns>
- /// <c>true</c> if [is sample rate supported]; otherwise, <c>false</c>.
- /// </returns>
- public bool IsSampleRateSupported(double sampleRate) => _driver.CanSampleRate(sampleRate);
- /// <summary>
- /// Sets the sample rate.
- /// </summary>
- /// <param name="sampleRate">The sample rate.</param>
- public void SetSampleRate(double sampleRate, bool resetDriver = true)
- {
- _driver.SetSampleRate(sampleRate);
- if (resetDriver)
- {
- // BmpMod: Reset driver for get latest Capabilities
- ReleaseDriver();
- Init();
- }
- var nowRate = _driver.GetSampleRate();
-
- // ReSharper disable once CompareOfFloatsByEqualityOperator
- var flag = nowRate == sampleRate;
-
- if(!flag) _driver.SetSampleRate(sampleRate); // Error ASE_NoClock ?
- // ReSharper disable once CompareOfFloatsByEqualityOperator
- flag = nowRate == sampleRate;
- // Update Capabilities
- if (flag) BuildCapabilities();
- else throw new NotSupportedException("Sample rate not expected");
- }
- public double GetSampleRate() => _driver.GetSampleRate();
- /// <summary>
- /// Gets or sets the fill buffer callback.
- /// </summary>
- /// <value>The fill buffer callback.</value>
- public AsioFillBufferCallback FillBufferCallback { get; set; }
- /// <summary>
- /// Gets the capabilities of the AsioDriver.
- /// </summary>
- /// <value>The capabilities.</value>
- public AsioDriverCapability Capabilities { get; private set; }
- /// <summary>
- /// Creates the buffers for playing.
- /// </summary>
- /// <param name="numberOfOutputChannels">The number of outputs channels.</param>
- /// <param name="numberOfInputChannels">The number of input channel.</param>
- /// <param name="useMaxBufferSize">if set to <c>true</c> [use max buffer size] else use Prefered size</param>
- public int CreateBuffers(int numberOfOutputChannels, int numberOfInputChannels, bool useMaxBufferSize)
- {
- if (numberOfOutputChannels < 0 || numberOfOutputChannels > Capabilities.NbOutputChannels)
- {
- throw new ArgumentException(
- $"Invalid number of channels {numberOfOutputChannels}, must be in the range [0,{Capabilities.NbOutputChannels}]");
- }
- if (numberOfInputChannels < 0 || numberOfInputChannels > Capabilities.NbInputChannels)
- {
- throw new ArgumentException("numberOfInputChannels",
- $"Invalid number of input channels {numberOfInputChannels}, must be in the range [0,{Capabilities.NbInputChannels}]");
- }
- // each channel needs a buffer info
- _numberOfOutputChannels = numberOfOutputChannels;
- _numberOfInputChannels = numberOfInputChannels;
- // Ask for maximum of output channels even if we use only the nbOutputChannelsArg
- var nbTotalChannels = Capabilities.NbInputChannels + Capabilities.NbOutputChannels;
- _bufferInfos = new AsioBufferInfo[nbTotalChannels];
- _currentOutputBuffers = new nint[numberOfOutputChannels];
- _currentInputBuffers = new nint[numberOfInputChannels];
- // and do the same for output channels
- // ONLY work on output channels (just put isInput = true for InputChannel)
- var totalIndex = 0;
- for (var index = 0; index < Capabilities.NbInputChannels; index++, totalIndex++)
- {
- _bufferInfos[totalIndex].isInput = true;
- _bufferInfos[totalIndex].channelNum = index;
- _bufferInfos[totalIndex].pBuffer0 = nint.Zero;
- _bufferInfos[totalIndex].pBuffer1 = nint.Zero;
- }
- for (var index = 0; index < Capabilities.NbOutputChannels; index++, totalIndex++)
- {
- _bufferInfos[totalIndex].isInput = false;
- _bufferInfos[totalIndex].channelNum = index;
- _bufferInfos[totalIndex].pBuffer0 = nint.Zero;
- _bufferInfos[totalIndex].pBuffer1 = nint.Zero;
- }
- _bufferSize = useMaxBufferSize
- ? Capabilities.BufferMaxSize // use the drivers maximum buffer size
- : Capabilities.BufferPreferredSize; // use the drivers preferred buffer size
-
- unsafe
- {
- fixed (AsioBufferInfo* infos = &_bufferInfos[0])
- {
- var pOutputBufferInfos = new nint(infos);
- // Create the ASIO Buffers with the callbacks
- _driver.CreateBuffers(pOutputBufferInfos, nbTotalChannels, _bufferSize, ref _callbacks);
- }
- }
- // Check if outputReady is supported
- var outputReady = _driver.OutputReady();
- IsOutputReadySupported = outputReady == AsioError.ASE_OK;
- return _bufferSize;
- }
- /// <summary>
- /// Builds the capabilities internally.
- /// </summary>
- public void BuildCapabilities()
- {
- Capabilities = new AsioDriverCapability();
- Capabilities.DriverName = _driver.GetDriverName();
- // Get nb Input/Output channels
- _driver.GetChannels(out Capabilities.NbInputChannels, out Capabilities.NbOutputChannels);
- Capabilities.InputChannelInfos = new AsioChannelInfo[Capabilities.NbInputChannels];
- Capabilities.OutputChannelInfos = new AsioChannelInfo[Capabilities.NbOutputChannels];
- // Get ChannelInfo for Inputs
- for (var i = 0; i < Capabilities.NbInputChannels; i++)
- {
- Capabilities.InputChannelInfos[i] = _driver.GetChannelInfo(i, true);
- }
- // Get ChannelInfo for Output
- for (var i = 0; i < Capabilities.NbOutputChannels; i++)
- {
- Capabilities.OutputChannelInfos[i] = _driver.GetChannelInfo(i, false);
- }
- // Get the current SampleRate
- Capabilities.SampleRate = _driver.GetSampleRate();
- var error = _driver.GetLatencies(out Capabilities.InputLatency, out Capabilities.OutputLatency);
- // focusrite scarlett 2i4 returns ASE_NotPresent here
- if (error != AsioError.ASE_OK && error != AsioError.ASE_NotPresent)
- {
- var ex = new AsioException("ASIOgetLatencies");
- ex.Error = error;
- throw ex;
- }
- // Get BufferSize
- _driver.GetBufferSize(out Capabilities.BufferMinSize, out Capabilities.BufferMaxSize, out Capabilities.BufferPreferredSize, out Capabilities.BufferGranularity);
- }
- /// <summary>
- /// Callback called by the AsioDriver on fill buffer demand. Redirect call to external callback.
- /// </summary>
- /// <param name="doubleBufferIndex">Index of the double buffer.</param>
- /// <param name="directProcess">if set to <c>true</c> [direct process].</param>
- private void BufferSwitchCallBack(int doubleBufferIndex, bool directProcess)
- {
- for (var i = 0; i < _numberOfInputChannels; i++)
- {
- _currentInputBuffers[i] = _bufferInfos[i + _inputChannelOffset].Buffer(doubleBufferIndex);
- }
- for (var i = 0; i < _numberOfOutputChannels; i++)
- {
- _currentOutputBuffers[i] = _bufferInfos[i + _outputChannelOffset + Capabilities.NbInputChannels].Buffer(doubleBufferIndex);
- }
- FillBufferCallback?.Invoke(_currentInputBuffers, _currentOutputBuffers);
- if (IsOutputReadySupported)
- {
- _driver.OutputReady();
- }
- }
- /// <summary>
- /// Callback called by the AsioDriver on event "Samples rate changed".
- /// </summary>
- /// <param name="sRate">The sample rate.</param>
- private void SampleRateDidChangeCallBack(double sRate)
- {
- // Check when this is called?
- Capabilities.SampleRate = sRate;
- }
- /// <summary>
- /// Asio message call back.
- /// </summary>
- /// <param name="selector">The selector.</param>
- /// <param name="value">The value.</param>
- /// <param name="message">The message.</param>
- /// <param name="opt">The opt.</param>
- /// <returns></returns>
- private int AsioMessageCallBack(AsioMessageSelector selector, int value, nint message, nint opt)
- {
- // Check when this is called?
- switch (selector)
- {
- case AsioMessageSelector.kAsioSelectorSupported:
- var subValue = (AsioMessageSelector)Enum.ToObject(typeof(AsioMessageSelector), value);
- switch (subValue)
- {
- case AsioMessageSelector.kAsioEngineVersion:
- return 1;
- case AsioMessageSelector.kAsioResetRequest:
- ResetRequestCallback?.Invoke();
- 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:
- ResetRequestCallback?.Invoke();
- 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;
- }
- /// <summary>
- /// Buffers switch time info call back.
- /// </summary>
- /// <param name="asioTimeParam">The asio time param.</param>
- /// <param name="doubleBufferIndex">Index of the double buffer.</param>
- /// <param name="directProcess">if set to <c>true</c> [direct process].</param>
- /// <returns></returns>
- private nint BufferSwitchTimeInfoCallBack(nint asioTimeParam, int doubleBufferIndex, bool directProcess)
- {
- // Check when this is called?
- return nint.Zero;
- }
- }
|