Music.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Runtime.InteropServices;
  6. using System.Threading;
  7. namespace OpenVIII.AV
  8. {
  9. public static class Music
  10. {
  11. public static void Init()
  12. {
  13. Memory.Log.WriteLine($"{nameof(Music)} :: {nameof(Init)}");
  14. // PC 2000 version has an CD audio track for eyes on me. I don't think we can play that.
  15. const MusicId unkPrefix = (MusicId)999;
  16. const MusicId altLoserPrefix = (MusicId)512;
  17. const MusicId loserPrefix = (MusicId)0;
  18. const MusicId eyesOnMePrefix = (MusicId)513;
  19. const MusicId altEyesOnMePrefix = (MusicId)22;
  20. string[] ext = { ".ogg", ".sgt", ".wav", ".mp3" };
  21. //Roses and Wine V07 moves most of the sgt files to dmusic_backup
  22. //it leaves a few files behind. I think because RaW doesn't replace everything.
  23. //ogg files stored in:
  24. var RaW_ogg_pt = Extended.GetUnixFullPath(Path.Combine(Memory.FF8Dir, "RaW", "GLOBAL", "Music"));
  25. // From what I gather the OGG files and the sgt files have the same numerical prefix. I
  26. // might try to add the functionality to the debug screen monday.
  27. var dmusic_pt = Extended.GetUnixFullPath(Path.Combine(Memory.FF8DirData, "Music", "dmusic_backup"));
  28. var music_pt = Extended.GetUnixFullPath(Path.Combine(Memory.FF8DirData, "Music", "dmusic"));
  29. var music_wav_pt = Extended.GetUnixFullPath(Path.Combine(Memory.FF8DirData, "Music"));
  30. // goal of dicmusic is to be able to select a track by prefix. it adds an list of files
  31. // with the same prefix. so you can later on switch out which one you want.
  32. AddMusicPath(RaW_ogg_pt);
  33. AddMusicPath(music_wav_pt);
  34. AddMusicPath(dmusic_pt);
  35. AddMusicPath(music_pt);
  36. if (!Memory.DicMusic.ContainsKey(eyesOnMePrefix) && Memory.DicMusic.ContainsKey(altEyesOnMePrefix))
  37. {
  38. Memory.DicMusic.Add(eyesOnMePrefix, Memory.DicMusic[altEyesOnMePrefix]);
  39. }
  40. var a = ArchiveZzz.Load(Memory.Archives.ZZZ_OTHER);
  41. var list = a?.GetListOfFiles();
  42. if (list != null && list.Length > 0)
  43. {
  44. ZZZ = true;
  45. foreach (var m in list.Where(x => ext.Any(y => x.EndsWith(y, StringComparison.OrdinalIgnoreCase))))
  46. {
  47. AddMusic(m);
  48. }
  49. }
  50. void AddMusicPath(string p)
  51. {
  52. if (!string.IsNullOrWhiteSpace(p) && Directory.Exists(p))
  53. {
  54. foreach (var m in Directory.GetFiles(p).Where(x => ext.Any(y => x.EndsWith(y, StringComparison.OrdinalIgnoreCase))))
  55. {
  56. AddMusic(m);
  57. }
  58. }
  59. }
  60. void AddMusic(string m)
  61. {
  62. if (ushort.TryParse(Path.GetFileName(m).Substring(0, 3), out var key))
  63. {
  64. //mismatched prefix's go here
  65. if ((MusicId)key == altLoserPrefix)
  66. {
  67. key = (ushort)loserPrefix; //loser.ogg and sgt don't match.
  68. }
  69. }
  70. else if (m.IndexOf("eyes_on_me", StringComparison.OrdinalIgnoreCase) >= 0)
  71. key = (ushort)eyesOnMePrefix;
  72. else
  73. key = (ushort)unkPrefix;
  74. if (!Memory.DicMusic.ContainsKey((MusicId)key))
  75. {
  76. Memory.DicMusic.Add((MusicId)key, new List<string> { m });
  77. }
  78. else
  79. {
  80. Memory.DicMusic[(MusicId)key].Add(m);
  81. }
  82. }
  83. }
  84. public static bool Playing => musicplaying;
  85. /// <summary>
  86. /// contains files from other.zzz
  87. /// </summary>
  88. public static bool ZZZ { get; set; } = false;
  89. public static object MusicTask { get; }
  90. /// <summary>
  91. /// <para>checks to see if music buffer is running low and getframe triggers a refill.</para>
  92. /// <para>if played in task we don't need to do this.</para>
  93. /// </summary>
  94. public static void Update()
  95. {
  96. if (!Memory.Threaded)
  97. ffccMusic?.NextLoop();
  98. }
  99. //public static byte[] ReadFullyByte(Stream stream)
  100. //{
  101. // // following formula goal is to calculate the number of bytes to make buffer. might be wrong.
  102. // long size = stream.Length; // stream.Length should be in bytes. will error later if short.
  103. // int start = 0;
  104. // byte[] buffer = new byte[size];
  105. // int read = 0;
  106. // //do
  107. // //{
  108. // read = stream.Read(buffer, start, buffer.Length);
  109. // start++;
  110. // //}
  111. // //while (read == 0 && start < size);
  112. // if (read == 0)
  113. // {
  114. // return null;
  115. // }
  116. // if (read < size)
  117. // {
  118. // Array.Resize<byte>(ref buffer, read);
  119. // }
  120. // return buffer;
  121. //}
  122. //public static byte[] GetSamplesWaveData(float[] samples, int samplesCount)
  123. //{ // converts 32 bit float samples to 16 bit pcm. I think :P
  124. // // https://stackoverflow.com/questions/31957211/how-to-convert-an-array-of-int16-sound-samples-to-a-byte-array-to-use-in-monogam/42151979#42151979
  125. // byte[] pcm = new byte[samplesCount * 2];
  126. // int sampleIndex = 0,
  127. // pcmIndex = 0;
  128. // while (sampleIndex < samplesCount)
  129. // {
  130. // short outsample = (short)(samples[sampleIndex] * short.MaxValue);
  131. // pcm[pcmIndex] = (byte)(outsample & 0xff);
  132. // pcm[pcmIndex + 1] = (byte)((outsample >> 8) & 0xff);
  133. // sampleIndex++;
  134. // pcmIndex += 2;
  135. // }
  136. // return pcm;
  137. //}
  138. private static bool musicplaying = false;
  139. private static int lastplayed = -1;
  140. public static void PlayStop(ushort? index = null, float volume = 0.5f, float pitch = 0.0f, float pan = 0.0f)
  141. {
  142. if (!musicplaying || lastplayed != Memory.MusicIndex)
  143. {
  144. Play(index: index, volume: volume, pitch: pitch, pan: pan);
  145. }
  146. else
  147. {
  148. Stop();
  149. }
  150. }
  151. private static AV.Audio ffccMusic = null; // testing using class to play music instead of Naudio / Nvorbis
  152. public static void Play(ushort? index = null, float volume = 0.5f, float pitch = 0.0f, float pan = 0.0f, bool loop = true)
  153. {
  154. Memory.MusicIndex = index ?? Memory.MusicIndex;
  155. if (musicplaying && lastplayed == Memory.MusicIndex) return;
  156. var ext = "";
  157. if (Memory.DicMusic.Count > 0 && Memory.DicMusic[(MusicId)Memory.MusicIndex].Count > 0)
  158. {
  159. ext = Path.GetExtension(Memory.DicMusic[(MusicId)Memory.MusicIndex][0]).ToLower();
  160. }
  161. else
  162. return;
  163. var filename = Memory.DicMusic[(MusicId)Memory.MusicIndex][0];
  164. Stop();
  165. switch (ext)
  166. {
  167. case ".ogg":
  168. case ".wav":
  169. case ".mp3":
  170. default:
  171. //ffccMusic = new Ffcc(@"c:\eyes_on_me.wav", AVMediaType.AVMEDIA_TYPE_AUDIO, Ffcc.FfccMode.STATE_MACH);
  172. if (ffccMusic != null)
  173. {
  174. ffccMusic?.Dispose();
  175. ffccMusic = null;
  176. }
  177. if (!ZZZ)
  178. {
  179. ffccMusic = AV.Audio.Load(filename, loop ? 0 : -1);
  180. if (!loop)
  181. ffccMusic.LoopStart = -1;
  182. if (Memory.Threaded)
  183. ffccMusic.PlayInTask(volume, pitch, pan);
  184. else
  185. ffccMusic.Play(volume, pitch, pan);
  186. }
  187. else
  188. {
  189. return;
  190. //cancelTokenSource = new CancellationTokenSource();
  191. //cancelToken = cancelTokenSource.Token;
  192. //MusicTask = Task.Run(()=>PlayInTask(ref ffccMusic,volume, pitch, pan, loop, filename,cancelToken), cancelToken);
  193. }
  194. break;
  195. case ".sgt":
  196. #if _X64 || !_WINDOWS
  197. if (fluid_Midi == null)
  198. fluid_Midi = new AV.Midi.Fluid();
  199. fluid_Midi.ReadSegmentFileManually(filename);
  200. fluid_Midi.Play();
  201. #else
  202. if (dm_Midi == null)
  203. dm_Midi = new AV.Midi.DirectMedia();
  204. dm_Midi.Play(filename,loop);
  205. #endif
  206. break;
  207. }
  208. musicplaying = true;
  209. lastplayed = Memory.MusicIndex;
  210. }
  211. private static unsafe void PlayInTask(ref Audio ffAudio, float volume, float pitch, float pan, bool loop, string filename, CancellationToken cancelToken)
  212. {
  213. return;
  214. //ArchiveZzz a = (ArchiveZzz)ArchiveZzz.Load(Memory.Archives.ZZZ_OTHER);
  215. //var fd = a.ArchiveMap.GetFileData(filename);
  216. //BufferData buffer_Data = new AV.BufferData
  217. //{
  218. // DataSeekLoc = fd.Value.Offset,
  219. // DataSize = fd.Value.UncompressedSize,
  220. // HeaderSize = 0,
  221. // Target = BufferData.TargetFile.other_zzz
  222. //};
  223. //GCHandle gch = GCHandle.Alloc(buffer_Data, GCHandleType.Pinned);
  224. //ffAudio = AV.Audio.Load(
  225. // &buffer_Data,
  226. // null, loop ? 0 : -1, Ffcc.FfccMode.STATE_MACH);
  227. //ffAudio.PlayInTask(volume, pitch, pan);
  228. //while (!cancelToken.IsCancellationRequested)
  229. // Thread.Sleep(1000);
  230. //gch.Free();
  231. }
  232. public static void KillAudio()
  233. {
  234. dm_Midi?.Dispose();
  235. fluid_Midi?.Dispose();
  236. }
  237. public static void Stop()
  238. {
  239. musicplaying = false;
  240. if (ffccMusic != null)
  241. {
  242. ffccMusic.Dispose();
  243. ffccMusic = null;
  244. }
  245. cancelTokenSource?.Cancel();
  246. #if !_X64
  247. if (dm_Midi != null)
  248. dm_Midi.Stop();
  249. #else
  250. fluid_Midi?.Stop();
  251. #endif
  252. }
  253. private static AV.Midi.DirectMedia dm_Midi;
  254. private static AV.Midi.Fluid fluid_Midi;
  255. private static CancellationTokenSource cancelTokenSource;
  256. private static CancellationToken cancelToken;
  257. }
  258. }