/Source/Toolkit/SharpDX.Toolkit.Audio/AudioManager.cs

https://github.com/KarimLUCCIN/SharpDX · C# · 701 lines · 502 code · 79 blank · 120 comment · 65 complexity · 9be4d4b0ef3b61d65bf98789bc87c82c MD5 · raw file

  1. using SharpDX.Toolkit.Content;
  2. // Copyright (c) 2010-2014 SharpDX - SharpDX Team
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. namespace SharpDX.Toolkit.Audio
  22. {
  23. using System;
  24. using Multimedia;
  25. using X3DAudio;
  26. using XAPO.Fx;
  27. using XAudio2;
  28. using XAudio2.Fx;
  29. using Reverb = XAudio2.Fx.Reverb;
  30. using ReverbParameters = XAudio2.Fx.ReverbParameters;
  31. /// <summary>
  32. /// This manages the XAudio2 audio graph, device, and mastering voice. This manager also allows loading of <see cref="SoundEffect"/> using
  33. /// the <see cref="IContentManager"/>
  34. /// </summary>
  35. public class AudioManager : GameSystem
  36. {
  37. private const float FLT_MIN = 1.175494351e-38F;
  38. /// <summary>
  39. /// Result of a device not found.
  40. /// </summary>
  41. /// <unmanaged>ERROR_NOT_FOUND</unmanaged>
  42. private static readonly ResultDescriptor NotFound = new ResultDescriptor(unchecked((int)0x80070490), "Windows Portable Devices", "ERROR_NOT_FOUND", "NotFound");
  43. private static readonly ReverbI3DL2Parameters[] reverbPresets =
  44. {
  45. ReverbI3DL2Parameters.Presets.Default,
  46. ReverbI3DL2Parameters.Presets.Generic,
  47. ReverbI3DL2Parameters.Presets.PaddedCell,
  48. ReverbI3DL2Parameters.Presets.Room,
  49. ReverbI3DL2Parameters.Presets.BathRoom,
  50. ReverbI3DL2Parameters.Presets.LivingRoom,
  51. ReverbI3DL2Parameters.Presets.StoneRoom,
  52. ReverbI3DL2Parameters.Presets.Auditorium,
  53. ReverbI3DL2Parameters.Presets.ConcertHall,
  54. ReverbI3DL2Parameters.Presets.Cave,
  55. ReverbI3DL2Parameters.Presets.Arena,
  56. ReverbI3DL2Parameters.Presets.Hangar,
  57. ReverbI3DL2Parameters.Presets.CarpetedHallway,
  58. ReverbI3DL2Parameters.Presets.Hallway,
  59. ReverbI3DL2Parameters.Presets.StoneCorridor,
  60. ReverbI3DL2Parameters.Presets.Alley,
  61. ReverbI3DL2Parameters.Presets.Forest,
  62. ReverbI3DL2Parameters.Presets.City,
  63. ReverbI3DL2Parameters.Presets.Mountains,
  64. ReverbI3DL2Parameters.Presets.Quarry,
  65. ReverbI3DL2Parameters.Presets.Plain,
  66. ReverbI3DL2Parameters.Presets.ParkingLot,
  67. ReverbI3DL2Parameters.Presets.SewerPipe,
  68. ReverbI3DL2Parameters.Presets.UnderWater,
  69. ReverbI3DL2Parameters.Presets.SmallRoom,
  70. ReverbI3DL2Parameters.Presets.MediumRoom,
  71. ReverbI3DL2Parameters.Presets.LargeRoom,
  72. ReverbI3DL2Parameters.Presets.MediumHall,
  73. ReverbI3DL2Parameters.Presets.LargeHall,
  74. ReverbI3DL2Parameters.Presets.Plate,
  75. };
  76. private ContentManager contentManager;
  77. private MasteringLimiter masteringLimiter;
  78. private MasteringLimiterParameters masteringLimiterParameters;
  79. private float masterVolume;
  80. private Reverb reverb;
  81. private ReverbParameters reverbParameters;
  82. private float speedOfSound;
  83. private X3DAudio x3DAudio;
  84. private bool isMasteringLimiterEnabled;
  85. private bool isReverbEffectEnabled;
  86. private bool isReverbFilterEnabled;
  87. private bool isSpatialAudioEnabled;
  88. private XAudio2 device;
  89. private SoundEffectInstancePool instancePool;
  90. private MasteringVoice masteringVoice;
  91. private SubmixVoice reverbVoice;
  92. private Speakers speakers;
  93. /// <summary>
  94. /// Initializes a new instance of the <see cref="AudioManager" /> class.
  95. /// </summary>
  96. /// <param name="game">The game.</param>
  97. public AudioManager(Game game)
  98. : base(game)
  99. {
  100. Services.AddService(this);
  101. masterVolume = 1.0f;
  102. Speakers = Speakers.None;
  103. masteringLimiterParameters = new MasteringLimiterParameters { Loudness = MasteringLimiter.DefaultLoudness, Release = MasteringLimiter.DefaultRelease };
  104. reverbParameters = (ReverbParameters)ReverbI3DL2Parameters.Presets.Default;
  105. InstancePool = new SoundEffectInstancePool(this);
  106. // register the audio manager as game system
  107. game.GameSystems.Add(this);
  108. }
  109. /// <summary>
  110. /// Gets a value indicating whether mastering limiter is enabled.
  111. /// </summary>
  112. public bool IsMasteringLimiterEnabled
  113. {
  114. get
  115. {
  116. DisposeGuard();
  117. return isMasteringLimiterEnabled;
  118. }
  119. private set
  120. {
  121. DisposeGuard();
  122. isMasteringLimiterEnabled = value;
  123. }
  124. }
  125. /// <summary>
  126. /// Gets a value indicating whether reverb effect is enabled.
  127. /// </summary>
  128. public bool IsReverbEffectEnabled
  129. {
  130. get
  131. {
  132. DisposeGuard();
  133. return isReverbEffectEnabled;
  134. }
  135. private set
  136. {
  137. DisposeGuard();
  138. isReverbEffectEnabled = value;
  139. }
  140. }
  141. /// <summary>
  142. /// Gets a value indicating whether reverb filter is enabled.
  143. /// </summary>
  144. public bool IsReverbFilterEnabled
  145. {
  146. get
  147. {
  148. DisposeGuard();
  149. return isReverbFilterEnabled;
  150. }
  151. private set
  152. {
  153. DisposeGuard();
  154. isReverbFilterEnabled = value;
  155. }
  156. }
  157. /// <summary>
  158. /// Gets a value indicating whether spatial audio is enabled.
  159. /// </summary>
  160. public bool IsSpatialAudioEnabled
  161. {
  162. get
  163. {
  164. DisposeGuard();
  165. return isSpatialAudioEnabled;
  166. }
  167. private set
  168. {
  169. DisposeGuard();
  170. isSpatialAudioEnabled = value;
  171. }
  172. }
  173. /// <summary>
  174. /// Sets and gets the volume of the Mastering voice.
  175. /// </summary>
  176. public float MasterVolume
  177. {
  178. get
  179. {
  180. DisposeGuard();
  181. return masterVolume;
  182. }
  183. set
  184. {
  185. DisposeGuard();
  186. if (MathUtil.NearEqual(value, masterVolume))
  187. return;
  188. masterVolume = MathUtil.Clamp(value, 0.0f, 1.0f);
  189. if (MasteringVoice != null)
  190. MasteringVoice.SetVolume(masterVolume);
  191. }
  192. }
  193. /// <summary>
  194. /// Gets the <see cref="XAudio2"/> device associated with the current <see cref="AudioManager"/> instance.
  195. /// </summary>
  196. internal XAudio2 Device
  197. {
  198. get
  199. {
  200. DisposeGuard();
  201. return device;
  202. }
  203. private set
  204. {
  205. DisposeGuard();
  206. device = value;
  207. }
  208. }
  209. /// <summary>
  210. /// Gets the pool of <see cref="SoundEffectInstance"/> associated with the current <see cref="AudioManager"/> instance.
  211. /// </summary>
  212. internal SoundEffectInstancePool InstancePool
  213. {
  214. get
  215. {
  216. DisposeGuard();
  217. return instancePool;
  218. }
  219. private set
  220. {
  221. DisposeGuard();
  222. instancePool = value;
  223. }
  224. }
  225. /// <summary>
  226. /// Gets a reference to the <see cref="MasteringVoice"/> associated with the current <see cref="AudioManager"/> instance.
  227. /// </summary>
  228. internal MasteringVoice MasteringVoice
  229. {
  230. get
  231. {
  232. DisposeGuard();
  233. return masteringVoice;
  234. }
  235. private set
  236. {
  237. DisposeGuard();
  238. masteringVoice = value;
  239. }
  240. }
  241. /// <summary>
  242. /// Gets the <see cref="SubmixVoice"/> associated with the current <see cref="AudioManager"/> instance (for reverb effect).
  243. /// </summary>
  244. internal SubmixVoice ReverbVoice
  245. {
  246. get
  247. {
  248. DisposeGuard();
  249. return reverbVoice;
  250. }
  251. private set
  252. {
  253. DisposeGuard();
  254. reverbVoice = value;
  255. }
  256. }
  257. /// <summary>
  258. /// Gets the speaker configuration.
  259. /// </summary>
  260. internal Speakers Speakers
  261. {
  262. get
  263. {
  264. DisposeGuard();
  265. return speakers;
  266. }
  267. private set
  268. {
  269. DisposeGuard();
  270. speakers = value;
  271. }
  272. }
  273. /// <summary>
  274. /// Disables the master volume limiter.
  275. /// </summary>
  276. public void DisableMasterVolumeLimiter()
  277. {
  278. DisposeGuard();
  279. if (!IsMasteringLimiterEnabled)
  280. return;
  281. if(MasteringVoice != null && masteringLimiter != null)
  282. MasteringVoice.DisableEffect(0);
  283. IsMasteringLimiterEnabled = false;
  284. }
  285. /// <summary>
  286. /// Disables the reverb effect.
  287. /// </summary>
  288. public void DisableReverbEffect()
  289. {
  290. DisposeGuard();
  291. if (!IsReverbEffectEnabled)
  292. return;
  293. if(ReverbVoice != null && reverb != null)
  294. ReverbVoice.DisableEffect(0);
  295. IsReverbEffectEnabled = false;
  296. }
  297. /// <summary>
  298. /// Enables the master volume limiter.
  299. /// </summary>
  300. public void EnableMasterVolumeLimiter()
  301. {
  302. DisposeGuard();
  303. if (IsMasteringLimiterEnabled)
  304. return;
  305. if (MasteringVoice != null)
  306. {
  307. if(masteringLimiter == null)
  308. CreateMasteringLimitier();
  309. else
  310. MasteringVoice.EnableEffect(0);
  311. }
  312. IsMasteringLimiterEnabled = true;
  313. }
  314. /// <summary>
  315. /// Enables the reverb effect.
  316. /// </summary>
  317. public void EnableReverbEffect()
  318. {
  319. DisposeGuard();
  320. if (IsReverbEffectEnabled)
  321. return;
  322. if (MasteringVoice != null)
  323. {
  324. if(ReverbVoice == null)
  325. CreateReverbSubmixVoice();
  326. else
  327. ReverbVoice.EnableEffect(0);
  328. }
  329. IsReverbEffectEnabled = true;
  330. }
  331. /// <summary>
  332. /// Enables the reverb filter.
  333. /// </summary>
  334. public void EnableReverbFilter()
  335. {
  336. DisposeGuard();
  337. IsReverbFilterEnabled = true;
  338. }
  339. /// <summary>
  340. /// Enables the spatial audio effect.
  341. /// </summary>
  342. /// <param name="speedOfSound">The speed of the sound in the medium. Should be greater than or equal to 1.175494351e-38F.</param>
  343. public void EnableSpatialAudio(float speedOfSound)
  344. {
  345. DisposeGuard();
  346. if (speedOfSound < FLT_MIN)
  347. throw new ArgumentOutOfRangeException("speedOfSound", "Speed of sound must be greater than or equal to FLT_MIN (1.175494351e-38F).");
  348. IsSpatialAudioEnabled = true;
  349. this.speedOfSound = speedOfSound;
  350. if (MasteringVoice != null)
  351. {
  352. x3DAudio = new X3DAudio(Speakers, speedOfSound);
  353. }
  354. }
  355. /// <summary>
  356. /// Enables the spatial audio effect with the default speed of sound equal to <see cref="X3DAudio.SpeedOfSound"/>.
  357. /// </summary>
  358. public void EnableSpatialAudio()
  359. {
  360. EnableSpatialAudio(X3DAudio.SpeedOfSound);
  361. }
  362. /// <summary>
  363. /// Initializes XAudio2 and MasteringVoice. And registers itself as an <see cref="IContentReaderFactory"/>
  364. /// </summary>
  365. /// <exception cref="InvalidOperationException">Is thrown when the IContentManager is not an instance of <see cref="ContentManager"/>.</exception>
  366. /// <exception cref="AudioException">Is thrown when the <see cref="AudioManager"/> instance could not be initialized (either due to unsupported features or missing audio-device).</exception>
  367. public override void Initialize()
  368. {
  369. base.Initialize();
  370. contentManager = Content as ContentManager;
  371. if (contentManager == null)
  372. {
  373. throw new InvalidOperationException("Unable to initialize AudioManager. Expecting IContentManager to be an instance of ContentManager");
  374. }
  375. try
  376. {
  377. #if DEBUG && !WIN8METRO && !WP8 && !DIRECTX11_1
  378. try
  379. {
  380. // "XAudio2Flags.DebugEngine" is supported only in XAudio 2.7, but not in newer versions
  381. // msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.xaudio2.xaudio2create(v=vs.85).aspx
  382. Device = new XAudio2(XAudio2Flags.DebugEngine, ProcessorSpecifier.DefaultProcessor);
  383. Device.StartEngine();
  384. }
  385. catch (Exception)
  386. #endif
  387. {
  388. Device = new XAudio2(XAudio2Flags.None, ProcessorSpecifier.DefaultProcessor);
  389. Device.StartEngine();
  390. }
  391. }
  392. catch (SharpDXException ex)
  393. {
  394. DisposeCore();
  395. throw new AudioException("Error creating XAudio device.", ex);
  396. }
  397. #if !W8CORE && !DIRECTX11_1
  398. if (Device.DeviceCount == 0)
  399. {
  400. DisposeCore();
  401. throw new AudioException("No default audio devices detected.");
  402. }
  403. #endif
  404. #if W8CORE || DIRECTX11_1
  405. string deviceId = null;
  406. #else
  407. const int deviceId = 0;
  408. #endif
  409. try
  410. {
  411. MasteringVoice = new MasteringVoice(Device, XAudio2.DefaultChannels, XAudio2.DefaultSampleRate, deviceId);
  412. }
  413. catch (SharpDXException ex)
  414. {
  415. DisposeCore();
  416. #if W8CORE
  417. if (ex.ResultCode == AudioManager.NotFound)
  418. {
  419. throw new AudioException("No default audio devices detected.");
  420. }
  421. else
  422. #endif
  423. {
  424. throw new AudioException("Error creating mastering voice.", ex);
  425. }
  426. }
  427. MasteringVoice.SetVolume(masterVolume);
  428. #if W8CORE || DIRECTX11_1
  429. Speakers = (Speakers)MasteringVoice.ChannelMask;
  430. #else
  431. var deviceDetails = Device.GetDeviceDetails(deviceId);
  432. Speakers = deviceDetails.OutputFormat.ChannelMask;
  433. #endif
  434. if (IsMasteringLimiterEnabled)
  435. {
  436. try
  437. {
  438. CreateMasteringLimitier();
  439. }
  440. catch (Exception)
  441. {
  442. DisposeCore();
  443. throw;
  444. }
  445. }
  446. if (IsSpatialAudioEnabled)
  447. {
  448. try
  449. {
  450. x3DAudio = new X3DAudio(Speakers, speedOfSound);
  451. }
  452. catch (Exception)
  453. {
  454. DisposeCore();
  455. throw;
  456. }
  457. }
  458. if (IsReverbEffectEnabled)
  459. {
  460. try
  461. {
  462. CreateReverbSubmixVoice();
  463. }
  464. catch (Exception)
  465. {
  466. DisposeCore();
  467. throw;
  468. }
  469. }
  470. contentManager.ReaderFactories.Add(new AudioContentReaderFactory());
  471. }
  472. /// <summary>
  473. /// Sets the mastering limiter parameters.
  474. /// </summary>
  475. /// <param name="release">Speed at which the limiter stops affecting audio once it drops below the limiter's threshold.</param>
  476. /// <param name="loudness">Threshold of the limiter.</param>
  477. /// <exception cref="ObjectDisposedException">Is thrown when this instance was already disposed.</exception>
  478. /// <exception cref="ArgumentOutOfRangeException">Is thrown when either <paramref name="release"/> or <paramref name="loudness"/> are outside of allowed ranges
  479. /// (<see cref="MasteringLimiter.MinimumRelease"/>/<see cref="MasteringLimiter.MaximumRelease"/>
  480. /// and <see cref="MasteringLimiter.MinimumLoudness"/>/<see cref="MasteringLimiter.MaximumLoudness"/> respectively).</exception>
  481. public void SetMasteringLimit(int release, int loudness)
  482. {
  483. DisposeGuard();
  484. if (release < MasteringLimiter.MinimumRelease || release > MasteringLimiter.MaximumRelease)
  485. throw new ArgumentOutOfRangeException("release");
  486. if (loudness < MasteringLimiter.MinimumLoudness || loudness > MasteringLimiter.MaximumLoudness)
  487. throw new ArgumentOutOfRangeException("loudness");
  488. masteringLimiterParameters = new MasteringLimiterParameters { Loudness = loudness, Release = release };
  489. if (MasteringVoice != null && masteringLimiter != null)
  490. {
  491. MasteringVoice.SetEffectParameters(0, masteringLimiterParameters);
  492. }
  493. }
  494. /// <summary>
  495. /// Sets the Reverb effect parameters.
  496. /// </summary>
  497. /// <param name="parameters">The reverb effect parameters.</param>
  498. /// /// <exception cref="ObjectDisposedException">Is thrown when this instance was already disposed.</exception>
  499. public void SetReverbEffectParameters(ReverbParameters parameters)
  500. {
  501. DisposeGuard();
  502. reverbParameters = parameters;
  503. if (ReverbVoice != null && reverb != null)
  504. {
  505. ReverbVoice.SetEffectParameters(0, parameters);
  506. }
  507. }
  508. /// <summary>
  509. /// Sets the Reverb effect parameters from an existing preset.
  510. /// </summary>
  511. /// <param name="preset">The existing Reverb preset.</param>
  512. public void SetReverbEffectParameters(ReverbPresets preset)
  513. {
  514. DisposeGuard();
  515. SetReverbEffectParameters((ReverbParameters)reverbPresets[(int)preset]);
  516. }
  517. /// <summary>
  518. /// Calculate 3D Audio parameters.
  519. /// </summary>
  520. /// <param name="listener">The 3D audio listener definition.</param>
  521. /// <param name="emitter">The 3D audio emitter definition.</param>
  522. /// <param name="flags">The 3D audio calculate flags.</param>
  523. /// <param name="dspSettings">The DSP settings.</param>
  524. internal void Calculate3D(Listener listener, Emitter emitter, CalculateFlags flags, DspSettings dspSettings)
  525. {
  526. DisposeGuard();
  527. x3DAudio.Calculate(listener, emitter, flags, dspSettings);
  528. }
  529. /// <summary>
  530. /// Adds a disposable audio asset to the list of the objects to dispose.
  531. /// </summary>
  532. /// <param name="audioAsset">To dispose.</param>
  533. internal T ToDisposeAudioAsset<T>(T audioAsset) where T : IDisposable
  534. {
  535. return ToDispose(audioAsset);
  536. }
  537. protected override void Dispose(bool disposeManagedResources)
  538. {
  539. if (disposeManagedResources)
  540. {
  541. InstancePool.Dispose();
  542. base.Dispose(true);
  543. DisposeCore();
  544. }
  545. }
  546. private void CreateMasteringLimitier()
  547. {
  548. try
  549. {
  550. masteringLimiter = new MasteringLimiter();
  551. masteringLimiter.Parameter = masteringLimiterParameters;
  552. MasteringVoice.SetEffectChain(new EffectDescriptor(masteringLimiter));
  553. }
  554. catch (SharpDXException ex)
  555. {
  556. throw new AudioException("Error creating mastering limiter.", ex);
  557. }
  558. }
  559. private void CreateReverbSubmixVoice()
  560. {
  561. try
  562. {
  563. VoiceDetails masterDetails = MasteringVoice.VoiceDetails;
  564. SubmixVoiceFlags sendFlags = IsReverbFilterEnabled ? SubmixVoiceFlags.UseFilter : SubmixVoiceFlags.None;
  565. ReverbVoice = new SubmixVoice(Device, 1, masterDetails.InputSampleRate, sendFlags, 0);
  566. }
  567. catch (SharpDXException ex)
  568. {
  569. throw new AudioException("Error creating reverb submix voice.", ex);
  570. }
  571. try
  572. {
  573. reverb = new Reverb();
  574. ReverbVoice.SetEffectChain(new EffectDescriptor(reverb, 1));
  575. ReverbVoice.SetEffectParameters(0, reverbParameters);
  576. }
  577. catch (SharpDXException ex)
  578. {
  579. throw new AudioException("Error setting reverb effect.", ex);
  580. }
  581. }
  582. private void DisposeCore()
  583. {
  584. if (x3DAudio != null)
  585. {
  586. x3DAudio = null;
  587. }
  588. if (ReverbVoice != null)
  589. {
  590. ReverbVoice.DestroyVoice();
  591. ReverbVoice.Dispose();
  592. ReverbVoice = null;
  593. reverb.Dispose();
  594. reverb = null;
  595. }
  596. IsReverbEffectEnabled = false;
  597. if (MasteringVoice != null)
  598. {
  599. MasteringVoice.DestroyVoice();
  600. MasteringVoice.Dispose();
  601. MasteringVoice = null;
  602. }
  603. if (masteringLimiter != null)
  604. {
  605. masteringLimiter.Dispose();
  606. masteringLimiter = null;
  607. }
  608. IsMasteringLimiterEnabled = false;
  609. if (Device != null)
  610. {
  611. Device.StopEngine();
  612. Device.Dispose();
  613. Device = null;
  614. }
  615. }
  616. private void DisposeGuard()
  617. {
  618. if (IsDisposed)
  619. throw new ObjectDisposedException(GetType().FullName);
  620. }
  621. }
  622. }