ASIODriverExt.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. using System.Runtime.InteropServices;
  2. using Bmp.Core.Lite.Playback.Outputs.BASS_ASIO;
  3. using Bmp.Core.Lite.Playback.Outputs.NAudioASIO.Originals;
  4. namespace Bmp.Core.Lite.Playback.Outputs.NAudioASIO;
  5. /// <summary>
  6. /// Callback used by the AsioDriverExt to get wave data
  7. /// </summary>
  8. public delegate void AsioFillBufferCallback(nint[] inputChannels, nint[] outputChannels);
  9. /// <summary>
  10. /// AsioDriverExt is a simplified version of the AsioDriver. It provides an easier
  11. /// way to access the capabilities of the Driver and implement the callbacks necessary
  12. /// for feeding the driver.
  13. /// Implementation inspired from Rob Philpot's with a managed C++ ASIO wrapper BlueWave.Interop.Asio
  14. /// http://www.codeproject.com/KB/mcpp/Asio.Net.aspx
  15. ///
  16. /// Contributor: Alexandre Mutel - email: alexandre_mutel at yahoo.fr
  17. /// </summary>
  18. public class AsioDriverExt
  19. {
  20. private readonly string _asioDriverName;
  21. private AsioDriver _driver;
  22. private AsioCallbacks _callbacks;
  23. private AsioBufferInfo[] _bufferInfos;
  24. public bool IsOutputReadySupported { get; private set; }
  25. private nint[] _currentOutputBuffers;
  26. private nint[] _currentInputBuffers;
  27. private int _numberOfOutputChannels;
  28. private int _numberOfInputChannels;
  29. private int _bufferSize;
  30. private int _outputChannelOffset;
  31. private int _inputChannelOffset;
  32. public Action ResetRequestCallback;
  33. // BmpMod: use driver name for reset
  34. public AsioDriverExt(string asioDriverName)
  35. {
  36. _asioDriverName = asioDriverName;
  37. Init();
  38. _callbacks = new AsioCallbacks();
  39. _callbacks.AsioMessage = AsioMessageCallBack;
  40. _callbacks.BufferSwitch = BufferSwitchCallBack;
  41. _callbacks.BufferSwitchTimeInfo = BufferSwitchTimeInfoCallBack;
  42. _callbacks.SampleRateDidChange = SampleRateDidChangeCallBack;
  43. BuildCapabilities();
  44. }
  45. public bool Future_GetIoFormat_Is_DSD()
  46. {
  47. var fmt = new ASIOIoFormat { formatType = -1 };
  48. Future(ASIOFuture.GetIoFormat, fmt);
  49. return fmt.formatType == 1;
  50. }
  51. public bool Future_GetIoFormat_Is_PCM()
  52. {
  53. var fmt = new ASIOIoFormat { formatType = -1 };
  54. Future(ASIOFuture.GetIoFormat, fmt);
  55. return fmt.formatType == 0;
  56. }
  57. public void Future_SetIoFormat_DSD()
  58. {
  59. var fmt = new ASIOIoFormat { formatType = 1 };
  60. Future(ASIOFuture.SetIoFormat, fmt);
  61. }
  62. public void Future_SetIoFormat_PCM()
  63. {
  64. var fmt = new ASIOIoFormat { formatType = 0 };
  65. Future(ASIOFuture.SetIoFormat, fmt);
  66. }
  67. private void Future(ASIOFuture selector, [In, Out] object opt)
  68. {
  69. var hPinned = GCHandle.Alloc(opt, GCHandleType.Pinned);
  70. _driver.Future(selector, hPinned.AddrOfPinnedObject());
  71. }
  72. private void Init()
  73. {
  74. _driver = AsioDriver.GetAsioDriverByName(_asioDriverName);
  75. if (!_driver.Init(nint.Zero))
  76. {
  77. ReleaseDriver();
  78. throw new InvalidOperationException(_driver.GetErrorMessage());
  79. }
  80. }
  81. /// <summary>
  82. /// Allows adjustment of which is the first output channel we write to
  83. /// </summary>
  84. /// <param name="outputChannelOffset">Output Channel offset</param>
  85. /// <param name="inputChannelOffset">Input Channel offset</param>
  86. public void SetChannelOffset(int outputChannelOffset, int inputChannelOffset)
  87. {
  88. if (outputChannelOffset + _numberOfOutputChannels <= Capabilities.NbOutputChannels)
  89. {
  90. _outputChannelOffset = outputChannelOffset;
  91. }
  92. else
  93. {
  94. throw new ArgumentException("Invalid channel offset");
  95. }
  96. if (inputChannelOffset + _numberOfInputChannels <= Capabilities.NbInputChannels)
  97. {
  98. _inputChannelOffset = inputChannelOffset;
  99. }
  100. else
  101. {
  102. throw new ArgumentException("Invalid channel offset");
  103. }
  104. }
  105. /// <summary>
  106. /// Gets the driver used.
  107. /// </summary>
  108. /// <value>The ASIOdriver.</value>
  109. public AsioDriver Driver => _driver;
  110. /// <summary>
  111. /// Starts playing the buffers.
  112. /// </summary>
  113. public void Start() => _driver.Start();
  114. /// <summary>
  115. /// Stops playing the buffers.
  116. /// </summary>
  117. public void Stop() => _driver.Stop();
  118. /// <summary>
  119. /// Shows the control panel.
  120. /// </summary>
  121. public void ShowControlPanel()
  122. {
  123. _driver.ControlPanel();
  124. }
  125. /// <summary>
  126. /// Releases this instance.
  127. /// </summary>
  128. public void ReleaseDriver()
  129. {
  130. try
  131. {
  132. _driver.DisposeBuffers();
  133. }
  134. catch (Exception ex)
  135. {
  136. Console.Out.WriteLine(ex.ToString());
  137. }
  138. _driver.ReleaseComAsioDriver();
  139. }
  140. /// <summary>
  141. /// Determines whether the specified sample rate is supported.
  142. /// </summary>
  143. /// <param name="sampleRate">The sample rate.</param>
  144. /// <returns>
  145. /// <c>true</c> if [is sample rate supported]; otherwise, <c>false</c>.
  146. /// </returns>
  147. public bool IsSampleRateSupported(double sampleRate) => _driver.CanSampleRate(sampleRate);
  148. /// <summary>
  149. /// Sets the sample rate.
  150. /// </summary>
  151. /// <param name="sampleRate">The sample rate.</param>
  152. public void SetSampleRate(double sampleRate, bool resetDriver = true)
  153. {
  154. _driver.SetSampleRate(sampleRate);
  155. if (resetDriver)
  156. {
  157. // BmpMod: Reset driver for get latest Capabilities
  158. ReleaseDriver();
  159. Init();
  160. }
  161. var nowRate = _driver.GetSampleRate();
  162. // ReSharper disable once CompareOfFloatsByEqualityOperator
  163. var flag = nowRate == sampleRate;
  164. if(!flag) _driver.SetSampleRate(sampleRate); // Error ASE_NoClock ?
  165. // ReSharper disable once CompareOfFloatsByEqualityOperator
  166. flag = nowRate == sampleRate;
  167. // Update Capabilities
  168. if (flag) BuildCapabilities();
  169. else throw new NotSupportedException("Sample rate not expected");
  170. }
  171. public double GetSampleRate() => _driver.GetSampleRate();
  172. /// <summary>
  173. /// Gets or sets the fill buffer callback.
  174. /// </summary>
  175. /// <value>The fill buffer callback.</value>
  176. public AsioFillBufferCallback FillBufferCallback { get; set; }
  177. /// <summary>
  178. /// Gets the capabilities of the AsioDriver.
  179. /// </summary>
  180. /// <value>The capabilities.</value>
  181. public AsioDriverCapability Capabilities { get; private set; }
  182. /// <summary>
  183. /// Creates the buffers for playing.
  184. /// </summary>
  185. /// <param name="numberOfOutputChannels">The number of outputs channels.</param>
  186. /// <param name="numberOfInputChannels">The number of input channel.</param>
  187. /// <param name="useMaxBufferSize">if set to <c>true</c> [use max buffer size] else use Prefered size</param>
  188. public int CreateBuffers(int numberOfOutputChannels, int numberOfInputChannels, bool useMaxBufferSize)
  189. {
  190. if (numberOfOutputChannels < 0 || numberOfOutputChannels > Capabilities.NbOutputChannels)
  191. {
  192. throw new ArgumentException(
  193. $"Invalid number of channels {numberOfOutputChannels}, must be in the range [0,{Capabilities.NbOutputChannels}]");
  194. }
  195. if (numberOfInputChannels < 0 || numberOfInputChannels > Capabilities.NbInputChannels)
  196. {
  197. throw new ArgumentException("numberOfInputChannels",
  198. $"Invalid number of input channels {numberOfInputChannels}, must be in the range [0,{Capabilities.NbInputChannels}]");
  199. }
  200. // each channel needs a buffer info
  201. _numberOfOutputChannels = numberOfOutputChannels;
  202. _numberOfInputChannels = numberOfInputChannels;
  203. // Ask for maximum of output channels even if we use only the nbOutputChannelsArg
  204. var nbTotalChannels = Capabilities.NbInputChannels + Capabilities.NbOutputChannels;
  205. _bufferInfos = new AsioBufferInfo[nbTotalChannels];
  206. _currentOutputBuffers = new nint[numberOfOutputChannels];
  207. _currentInputBuffers = new nint[numberOfInputChannels];
  208. // and do the same for output channels
  209. // ONLY work on output channels (just put isInput = true for InputChannel)
  210. var totalIndex = 0;
  211. for (var index = 0; index < Capabilities.NbInputChannels; index++, totalIndex++)
  212. {
  213. _bufferInfos[totalIndex].isInput = true;
  214. _bufferInfos[totalIndex].channelNum = index;
  215. _bufferInfos[totalIndex].pBuffer0 = nint.Zero;
  216. _bufferInfos[totalIndex].pBuffer1 = nint.Zero;
  217. }
  218. for (var index = 0; index < Capabilities.NbOutputChannels; index++, totalIndex++)
  219. {
  220. _bufferInfos[totalIndex].isInput = false;
  221. _bufferInfos[totalIndex].channelNum = index;
  222. _bufferInfos[totalIndex].pBuffer0 = nint.Zero;
  223. _bufferInfos[totalIndex].pBuffer1 = nint.Zero;
  224. }
  225. _bufferSize = useMaxBufferSize
  226. ? Capabilities.BufferMaxSize // use the drivers maximum buffer size
  227. : Capabilities.BufferPreferredSize; // use the drivers preferred buffer size
  228. unsafe
  229. {
  230. fixed (AsioBufferInfo* infos = &_bufferInfos[0])
  231. {
  232. var pOutputBufferInfos = new nint(infos);
  233. // Create the ASIO Buffers with the callbacks
  234. _driver.CreateBuffers(pOutputBufferInfos, nbTotalChannels, _bufferSize, ref _callbacks);
  235. }
  236. }
  237. // Check if outputReady is supported
  238. var outputReady = _driver.OutputReady();
  239. IsOutputReadySupported = outputReady == AsioError.ASE_OK;
  240. return _bufferSize;
  241. }
  242. /// <summary>
  243. /// Builds the capabilities internally.
  244. /// </summary>
  245. public void BuildCapabilities()
  246. {
  247. Capabilities = new AsioDriverCapability();
  248. Capabilities.DriverName = _driver.GetDriverName();
  249. // Get nb Input/Output channels
  250. _driver.GetChannels(out Capabilities.NbInputChannels, out Capabilities.NbOutputChannels);
  251. Capabilities.InputChannelInfos = new AsioChannelInfo[Capabilities.NbInputChannels];
  252. Capabilities.OutputChannelInfos = new AsioChannelInfo[Capabilities.NbOutputChannels];
  253. // Get ChannelInfo for Inputs
  254. for (var i = 0; i < Capabilities.NbInputChannels; i++)
  255. {
  256. Capabilities.InputChannelInfos[i] = _driver.GetChannelInfo(i, true);
  257. }
  258. // Get ChannelInfo for Output
  259. for (var i = 0; i < Capabilities.NbOutputChannels; i++)
  260. {
  261. Capabilities.OutputChannelInfos[i] = _driver.GetChannelInfo(i, false);
  262. }
  263. // Get the current SampleRate
  264. Capabilities.SampleRate = _driver.GetSampleRate();
  265. var error = _driver.GetLatencies(out Capabilities.InputLatency, out Capabilities.OutputLatency);
  266. // focusrite scarlett 2i4 returns ASE_NotPresent here
  267. if (error != AsioError.ASE_OK && error != AsioError.ASE_NotPresent)
  268. {
  269. var ex = new AsioException("ASIOgetLatencies");
  270. ex.Error = error;
  271. throw ex;
  272. }
  273. // Get BufferSize
  274. _driver.GetBufferSize(out Capabilities.BufferMinSize, out Capabilities.BufferMaxSize, out Capabilities.BufferPreferredSize, out Capabilities.BufferGranularity);
  275. }
  276. /// <summary>
  277. /// Callback called by the AsioDriver on fill buffer demand. Redirect call to external callback.
  278. /// </summary>
  279. /// <param name="doubleBufferIndex">Index of the double buffer.</param>
  280. /// <param name="directProcess">if set to <c>true</c> [direct process].</param>
  281. private void BufferSwitchCallBack(int doubleBufferIndex, bool directProcess)
  282. {
  283. for (var i = 0; i < _numberOfInputChannels; i++)
  284. {
  285. _currentInputBuffers[i] = _bufferInfos[i + _inputChannelOffset].Buffer(doubleBufferIndex);
  286. }
  287. for (var i = 0; i < _numberOfOutputChannels; i++)
  288. {
  289. _currentOutputBuffers[i] = _bufferInfos[i + _outputChannelOffset + Capabilities.NbInputChannels].Buffer(doubleBufferIndex);
  290. }
  291. FillBufferCallback?.Invoke(_currentInputBuffers, _currentOutputBuffers);
  292. if (IsOutputReadySupported)
  293. {
  294. _driver.OutputReady();
  295. }
  296. }
  297. /// <summary>
  298. /// Callback called by the AsioDriver on event "Samples rate changed".
  299. /// </summary>
  300. /// <param name="sRate">The sample rate.</param>
  301. private void SampleRateDidChangeCallBack(double sRate)
  302. {
  303. // Check when this is called?
  304. Capabilities.SampleRate = sRate;
  305. }
  306. /// <summary>
  307. /// Asio message call back.
  308. /// </summary>
  309. /// <param name="selector">The selector.</param>
  310. /// <param name="value">The value.</param>
  311. /// <param name="message">The message.</param>
  312. /// <param name="opt">The opt.</param>
  313. /// <returns></returns>
  314. private int AsioMessageCallBack(AsioMessageSelector selector, int value, nint message, nint opt)
  315. {
  316. // Check when this is called?
  317. switch (selector)
  318. {
  319. case AsioMessageSelector.kAsioSelectorSupported:
  320. var subValue = (AsioMessageSelector)Enum.ToObject(typeof(AsioMessageSelector), value);
  321. switch (subValue)
  322. {
  323. case AsioMessageSelector.kAsioEngineVersion:
  324. return 1;
  325. case AsioMessageSelector.kAsioResetRequest:
  326. ResetRequestCallback?.Invoke();
  327. return 0;
  328. case AsioMessageSelector.kAsioBufferSizeChange:
  329. return 0;
  330. case AsioMessageSelector.kAsioResyncRequest:
  331. return 0;
  332. case AsioMessageSelector.kAsioLatenciesChanged:
  333. return 0;
  334. case AsioMessageSelector.kAsioSupportsTimeInfo:
  335. // return 1; DON'T SUPPORT FOR NOW. NEED MORE TESTING.
  336. return 0;
  337. case AsioMessageSelector.kAsioSupportsTimeCode:
  338. // return 1; DON'T SUPPORT FOR NOW. NEED MORE TESTING.
  339. return 0;
  340. }
  341. break;
  342. case AsioMessageSelector.kAsioEngineVersion:
  343. return 2;
  344. case AsioMessageSelector.kAsioResetRequest:
  345. ResetRequestCallback?.Invoke();
  346. return 1;
  347. case AsioMessageSelector.kAsioBufferSizeChange:
  348. return 0;
  349. case AsioMessageSelector.kAsioResyncRequest:
  350. return 0;
  351. case AsioMessageSelector.kAsioLatenciesChanged:
  352. return 0;
  353. case AsioMessageSelector.kAsioSupportsTimeInfo:
  354. return 0;
  355. case AsioMessageSelector.kAsioSupportsTimeCode:
  356. return 0;
  357. }
  358. return 0;
  359. }
  360. /// <summary>
  361. /// Buffers switch time info call back.
  362. /// </summary>
  363. /// <param name="asioTimeParam">The asio time param.</param>
  364. /// <param name="doubleBufferIndex">Index of the double buffer.</param>
  365. /// <param name="directProcess">if set to <c>true</c> [direct process].</param>
  366. /// <returns></returns>
  367. private nint BufferSwitchTimeInfoCallBack(nint asioTimeParam, int doubleBufferIndex, bool directProcess)
  368. {
  369. // Check when this is called?
  370. return nint.Zero;
  371. }
  372. }