Audio and MIDI library for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

393 lines
15 KiB

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