/Sound.cs

https://github.com/dzamkov/Bit-Orchestra · C# · 370 lines · 270 code · 41 blank · 59 comment · 23 complexity · 0172c3fd8b043eb427d6cd233e509a83 MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Windows.Forms;
  5. using System.Threading;
  6. using NAudio.Wave;
  7. using NAudio.CoreAudioApi;
  8. using Value = System.Int32;
  9. namespace BitOrchestra
  10. {
  11. /// <summary>
  12. /// Contains options for playing a sound.
  13. /// </summary>
  14. public class SoundOptions
  15. {
  16. /// <summary>
  17. /// The sample rate to play at.
  18. /// </summary>
  19. public int Rate = 8000;
  20. /// <summary>
  21. /// The parameter value to start playing at.
  22. /// </summary>
  23. public int Offset = 0;
  24. /// <summary>
  25. /// The length of the sound in samples, used for exporting.
  26. /// </summary>
  27. public Value Length = 0;
  28. /// <summary>
  29. /// The amount of bits of resolution in the output of the sound.
  30. /// </summary>
  31. public int Resolution = 8;
  32. }
  33. /// <summary>
  34. /// An interface to a sound output for evaluators.
  35. /// </summary>
  36. public class Sound : IDisposable
  37. {
  38. public Sound()
  39. {
  40. }
  41. /// <summary>
  42. /// Tries creating a waveout interface.
  43. /// </summary>
  44. private static bool _CreateWaveout(out IWavePlayer Player)
  45. {
  46. try
  47. {
  48. Player = new WaveOut();
  49. return true;
  50. }
  51. catch
  52. {
  53. Player = null;
  54. return false;
  55. }
  56. }
  57. /// <summary>
  58. /// Tries creating a wasapi interface.
  59. /// </summary>
  60. private static bool _CreateWasapi(out IWavePlayer Player)
  61. {
  62. try
  63. {
  64. Player = new WasapiOut(AudioClientShareMode.Shared, 300);
  65. return true;
  66. }
  67. catch
  68. {
  69. Player = null;
  70. return false;
  71. }
  72. }
  73. /// <summary>
  74. /// Tries creating a directsound interface.
  75. /// </summary>
  76. private static bool _CreateDirectSound(out IWavePlayer Player)
  77. {
  78. try
  79. {
  80. Player = new DirectSoundOut();
  81. return true;
  82. }
  83. catch
  84. {
  85. Player = null;
  86. return false;
  87. }
  88. }
  89. /// <summary>
  90. /// Tries creating an asio interface.
  91. /// </summary>
  92. private static bool _CreateAsio(out IWavePlayer Player)
  93. {
  94. try
  95. {
  96. Player = new AsioOut();
  97. return true;
  98. }
  99. catch
  100. {
  101. Player = null;
  102. return false;
  103. }
  104. }
  105. /// <summary>
  106. /// Tries creating a wave player of some sort.
  107. /// </summary>
  108. private static bool _Create(out IWavePlayer Player)
  109. {
  110. return
  111. _CreateWaveout(out Player) ||
  112. _CreateDirectSound(out Player) ||
  113. _CreateWasapi(out Player) ||
  114. _CreateAsio(out Player);
  115. }
  116. /// <summary>
  117. /// Gets if this sound is currently active.
  118. /// </summary>
  119. public bool IsActive
  120. {
  121. get
  122. {
  123. return this._Player != null;
  124. }
  125. }
  126. /// <summary>
  127. /// Tries playing the sound from the given evaluator stream.
  128. /// </summary>
  129. public bool Play(WaveStream Stream)
  130. {
  131. try
  132. {
  133. if (this._Player == null)
  134. {
  135. if (_Create(out this._Player))
  136. {
  137. this._Player.Init(this._Stream = Stream);
  138. this._Player.Play();
  139. return true;
  140. }
  141. }
  142. return false;
  143. }
  144. catch
  145. {
  146. this._Player = null;
  147. this._Stream = null;
  148. return false;
  149. }
  150. }
  151. /// <summary>
  152. /// Tries exporting the given wavestream as a wave file.
  153. /// </summary>
  154. public static bool Export(string File, WaveStream Stream)
  155. {
  156. try
  157. {
  158. WaveFileWriter.CreateWaveFile(File, Stream);
  159. return true;
  160. }
  161. catch
  162. {
  163. return false;
  164. }
  165. }
  166. /// <summary>
  167. /// Stops playing this sound.
  168. /// </summary>
  169. public void Stop()
  170. {
  171. if (this._Stream != null)
  172. {
  173. // NAudio needs to be persuaded to make sure the stream stays stopped. (Something about threading issues?)
  174. ((IDisposable)(this._Stream)).Dispose();
  175. this._Stream = null;
  176. }
  177. if (this._Player != null)
  178. {
  179. this._Player.Stop();
  180. this._Player.Dispose();
  181. this._Player = null;
  182. }
  183. }
  184. public void Dispose()
  185. {
  186. if (this._Player != null)
  187. {
  188. this._Player.Dispose();
  189. }
  190. }
  191. private IWavePlayer _Player;
  192. private WaveStream _Stream;
  193. }
  194. /// <summary>
  195. /// A sound stream produced by an evaluator.
  196. /// </summary>
  197. public class EvaluatorStream : WaveStream, IDisposable
  198. {
  199. public EvaluatorStream(int BufferSize, Evaluator Evaluator, SoundOptions Options, bool Exporting)
  200. {
  201. this._Exporting = Exporting;
  202. this._Options = Options;
  203. this._Evaluator = Evaluator.GetBuffered(BufferSize);
  204. this._Offset = BufferSize;
  205. this._Parameter = Options.Offset;
  206. // Calculate shift and sample size
  207. int res = Options.Resolution;
  208. int sampsize = (res + 7) / 8;
  209. int shift = sampsize * 8 - res;
  210. this._SampleSize = sampsize;
  211. this._Shift = shift;
  212. this._Advance();
  213. }
  214. public EvaluatorStream(int BufferSize, Expression Expression, SoundOptions Options, bool Exporting)
  215. : this(BufferSize, Expression.GetEvaluator(new Dictionary<Expression,Evaluator>(), BufferSize, Options.Resolution), Options, Exporting)
  216. {
  217. }
  218. public override WaveFormat WaveFormat
  219. {
  220. get
  221. {
  222. return new WaveFormat(this._Options.Rate, this._SampleSize * 8, 1);
  223. }
  224. }
  225. public override long Length
  226. {
  227. get
  228. {
  229. return this._Options.Length * this._SampleSize;
  230. }
  231. }
  232. public override long Position
  233. {
  234. get
  235. {
  236. return (this._Parameter - this._Options.Offset) * this._SampleSize;
  237. }
  238. set
  239. {
  240. this._Parameter = (this._Options.Offset + (int)value) / this._SampleSize;
  241. }
  242. }
  243. public override int Read(byte[] buffer, int offset, int count)
  244. {
  245. if (this._Evaluator == null)
  246. {
  247. return 0;
  248. }
  249. // Its more useful to have a sample count rather than a byte count.
  250. int samplecount = (count / this._SampleSize);
  251. // If exporting, make sure only to write "Options.Length" samples
  252. if (this._Exporting)
  253. samplecount = Math.Min(samplecount, (int)(this._Options.Length - this._Parameter));
  254. // Find sample size and shift amount
  255. int sampsize = this._SampleSize;
  256. int shift = this._Shift;
  257. int ocount = samplecount * sampsize;
  258. while (true)
  259. {
  260. int sampsleft = this._Buffer.Length - this._Offset;
  261. int toread = Math.Min(samplecount, sampsleft);
  262. int ofs = this._Offset;
  263. for (int t = 0; t < toread; t++)
  264. {
  265. Value val = this._Buffer[ofs];
  266. if (this._SampleSize == 1)
  267. {
  268. // Correct byte sign
  269. val += (128 >> shift);
  270. }
  271. if (shift == 0 && this._SampleSize == 1)
  272. {
  273. // Direct byte copying is faster
  274. buffer[offset] = (byte)val;
  275. offset++;
  276. }
  277. else
  278. {
  279. // Copy the first byte manually, since a unique leftshift must be applied.
  280. buffer[offset] = (byte)(val << shift);
  281. offset++;
  282. int sf = 8 - shift;
  283. for (int i = 1; i < sampsize; i++)
  284. {
  285. buffer[offset] = (byte)(val >> sf);
  286. sf += 8;
  287. offset++;
  288. }
  289. }
  290. ofs++;
  291. }
  292. this._Offset = ofs;
  293. samplecount -= toread;
  294. if (this._Offset >= this._Buffer.Length)
  295. {
  296. this._Advance();
  297. continue;
  298. }
  299. else
  300. {
  301. break;
  302. }
  303. }
  304. return ocount;
  305. }
  306. /// <summary>
  307. /// Advances to the next generated buffer.
  308. /// </summary>
  309. private void _Advance()
  310. {
  311. this._Evaluator.Invalidate();
  312. this._Buffer = this._Evaluator.Generate(this._Parameter);
  313. this._Parameter += this._Buffer.Length;
  314. this._Offset = 0;
  315. }
  316. void IDisposable.Dispose()
  317. {
  318. this._Evaluator = null;
  319. }
  320. private bool _Exporting;
  321. private SoundOptions _Options;
  322. private BufferedEvaluator _Evaluator;
  323. private Value _Parameter;
  324. private Value[] _Buffer;
  325. private int _Offset;
  326. private int _SampleSize;
  327. private int _Shift;
  328. }
  329. }