init_debugger_Audio.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. using FFmpeg.AutoGen;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Runtime.InteropServices;
  7. namespace OpenVIII
  8. {
  9. #pragma warning disable IDE1006 // Naming Styles
  10. public static class init_debugger_Audio
  11. #pragma warning restore IDE1006 // Naming Styles
  12. {
  13. private static AV.Midi.DirectMedia dm_Midi;
  14. private static AV.Midi.Fluid fluid_Midi;
  15. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
  16. private struct SoundEntry
  17. {
  18. public UInt32 Size;
  19. public UInt32 Offset;
  20. private UInt32 output_TotalSize => Size + 70; // Total bytes of file -8 because for some reason 8 bytes don't count
  21. private const UInt32 output_HeaderSize = 50; //Total bytes of Header
  22. private UInt32 output_DataSize => Size; //Total bytes of Data Section
  23. //public byte[] UNK; //12
  24. //public WAVEFORMATEX WAVFORMATEX; //18 header starts here
  25. //public ushort SamplesPerBlock; //2
  26. //public ushort ADPCM; //2
  27. //public ADPCMCOEFSET[] ADPCMCoefSets; //array should be of [ADPCM] size //7*4 = 28
  28. public byte[] HeaderData;
  29. public void fillHeader(BinaryReader br)
  30. {
  31. if (HeaderData == null)
  32. {
  33. HeaderData = new byte[output_HeaderSize + 28];
  34. using (BinaryWriter bw = new BinaryWriter(new MemoryStream(HeaderData)))
  35. {
  36. bw.Write(System.Text.Encoding.ASCII.GetBytes("RIFF"));
  37. bw.Write(output_TotalSize);
  38. bw.Write(System.Text.Encoding.ASCII.GetBytes("WAVEfmt "));
  39. bw.Write(output_HeaderSize);
  40. bw.Write(br.ReadBytes((int)output_HeaderSize));
  41. bw.Write(System.Text.Encoding.ASCII.GetBytes("data"));
  42. bw.Write(output_DataSize);
  43. }
  44. }
  45. }
  46. }
  47. #pragma warning disable CS0649
  48. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
  49. public struct WAVEFORMATEX
  50. {
  51. public ushort wFormatTag;
  52. public ushort nChannels;
  53. public uint nSamplesPerSec;
  54. public uint nAvgBytesPerSec;
  55. public ushort nBlockAlign;
  56. public ushort wBitsPerSample;
  57. public ushort cbSize;
  58. }
  59. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
  60. private struct ADPCMCOEFSET
  61. {
  62. public short iCoef1;
  63. public short iCoef2;
  64. };
  65. #pragma warning restore CS0649
  66. private static SoundEntry[] soundEntries;
  67. public static int soundEntriesCount;
  68. public const int MaxSoundChannels = 20;
  69. /// <summary>
  70. /// This is for short lived sound effects. The Larger the array is the more sounds can be
  71. /// played at once. If you want sounds to loop of have volume you'll need to have a
  72. /// SoundEffectInstance added to ffcc, and have those sounds be played like music where they
  73. /// loop in the background till stop.
  74. /// </summary>
  75. public static AV.Audio[] SoundChannels { get; } = new AV.Audio[MaxSoundChannels];
  76. public static int CurrentSoundChannel
  77. {
  78. get => _currentSoundChannel;
  79. set
  80. {
  81. if (value >= MaxSoundChannels)
  82. {
  83. value = 0;
  84. }
  85. else if (value < 0)
  86. {
  87. value = MaxSoundChannels - 1;
  88. }
  89. _currentSoundChannel = value;
  90. }
  91. }
  92. public static bool MusicPlaying => musicplaying;
  93. public static void Init()
  94. {
  95. // PC 2000 version has an CD audio track for eyes on me. I don't think we can play that.
  96. const int unkPrefix = 999;
  97. const int altLoserPrefix = 512;
  98. const int loserPrefix = 0;
  99. const int eyesOnMePrefix = 513;
  100. const int altEyesOnMePrefix = 22;
  101. string[] ext = { ".ogg", ".sgt", ".wav", ".mp3" };
  102. //Roses and Wine V07 moves most of the sgt files to dmusic_backup
  103. //it leaves a few files behind. I think because RaW doesn't replace everything.
  104. //ogg files stored in:
  105. string RaW_ogg_pt = Extended.GetUnixFullPath(Path.Combine(Memory.FF8DIR, "RaW", "GLOBAL", "Music"));
  106. // From what I gather the OGG files and the sgt files have the same numerical prefix. I
  107. // might try to add the functionality to the debug screen monday.
  108. string dmusic_pt = Extended.GetUnixFullPath(Path.Combine(Memory.FF8DIRdata, "Music", "dmusic_backup"));
  109. string music_pt = Extended.GetUnixFullPath(Path.Combine(Memory.FF8DIRdata, "Music", "dmusic"));
  110. string music_wav_pt = Extended.GetUnixFullPath(Path.Combine(Memory.FF8DIRdata, "Music"));
  111. // goal of dicmusic is to be able to select a track by prefix. it adds an list of files
  112. // with the same prefix. so you can later on switch out which one you want.
  113. AddMusicPath(RaW_ogg_pt);
  114. AddMusicPath(music_wav_pt);
  115. AddMusicPath(dmusic_pt);
  116. AddMusicPath(music_pt);
  117. if (!Memory.dicMusic.ContainsKey(eyesOnMePrefix) && Memory.dicMusic.ContainsKey(altEyesOnMePrefix))
  118. {
  119. Memory.dicMusic.Add(eyesOnMePrefix, Memory.dicMusic[altEyesOnMePrefix]);
  120. }
  121. void AddMusicPath(string p)
  122. {
  123. if (!string.IsNullOrWhiteSpace(p) && Directory.Exists(p))
  124. {
  125. foreach (string m in Directory.GetFiles(p).Where(x => ext.Any(y => x.EndsWith(y, StringComparison.OrdinalIgnoreCase))))
  126. {
  127. AddMusic(m);
  128. }
  129. }
  130. }
  131. void AddMusic(string m)
  132. {
  133. if (ushort.TryParse(Path.GetFileName(m).Substring(0, 3), out ushort key))
  134. {
  135. //mismatched prefix's go here
  136. if (key == altLoserPrefix)
  137. {
  138. key = loserPrefix; //loser.ogg and sgt don't match.
  139. }
  140. }
  141. else if (m.IndexOf("eyes_on_me", StringComparison.OrdinalIgnoreCase) >= 0)
  142. key = eyesOnMePrefix;
  143. else
  144. key = unkPrefix;
  145. if (!Memory.dicMusic.ContainsKey(key))
  146. {
  147. Memory.dicMusic.Add(key, new List<string> { m });
  148. }
  149. else
  150. {
  151. Memory.dicMusic[key].Add(m);
  152. }
  153. }
  154. }
  155. //I messed around here as figuring out how things worked probably didn't need to mess with this.
  156. public static void Init_SoundAudio()
  157. {
  158. string path = Path.Combine(Memory.FF8DIRdata, "Sound", "audio.fmt");
  159. if (File.Exists(path))
  160. {
  161. FileStream fs = null;
  162. // fs is disposed by binary reader
  163. using (BinaryReader br = new BinaryReader(fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
  164. {
  165. soundEntries = new SoundEntry[br.ReadUInt32()];
  166. fs.Seek(36, SeekOrigin.Current);
  167. for (int i = 0; i < soundEntries.Length - 1; i++)
  168. {
  169. uint sz = br.ReadUInt32();
  170. if (sz == 0)
  171. {
  172. fs.Seek(34, SeekOrigin.Current); continue;
  173. }
  174. soundEntries[i] = new SoundEntry
  175. {
  176. Size = sz,
  177. Offset = br.ReadUInt32()
  178. };
  179. fs.Seek(12, SeekOrigin.Current);
  180. soundEntries[i].fillHeader(br);
  181. }
  182. fs = null;
  183. }
  184. }
  185. soundEntriesCount = soundEntries == null ? 0 : soundEntries.Length;
  186. ////export sounds
  187. //int item = 0;
  188. //using (BinaryReader br = new BinaryReader(File.OpenRead(Path.Combine(Memory.FF8DIRdata, "Sound", "audio.dat"))))
  189. // foreach (SoundEntry s in soundEntries)
  190. // {
  191. // Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "FF8Sounds"));
  192. // if (s.HeaderData == null || s.Size == 0)
  193. // {
  194. // using (FileStream fc = File.Create(Path.Combine(Path.GetTempPath(), "FF8Sounds", $"{item++}.txt")))
  195. // using (BinaryWriter bw = new BinaryWriter(fc))
  196. // {
  197. // bw.Write($"There is no info maybe audio.fmt listed {s.Size} size or there was an issue.");
  198. // }
  199. // continue;
  200. // }
  201. // using (FileStream fc = File.Create(Path.Combine(Path.GetTempPath(), "FF8Sounds",
  202. // $"{item++}.wav"))) using (BinaryWriter bw = new BinaryWriter(fc)) {
  203. // bw.Write(s.HeaderData); br.BaseStream.Seek(s.Offset, SeekOrigin.Begin);
  204. // bw.Write(br.ReadBytes((int)s.Size)); } }
  205. }
  206. /// <summary>
  207. /// Play sound effect
  208. /// </summary>
  209. /// <param name="soundID">
  210. /// ID number of sound
  211. /// <para>The real game uses soundID + 1, so you may need to -1 from any scripts.</para>
  212. /// </param>
  213. /// <param name="persist">
  214. /// <para>If set sound will not be saved to SoundChannels</para>
  215. /// <para>
  216. /// It will be up to the calling object to keep track of the sound object that is returned.
  217. /// </para>
  218. /// </param>
  219. /// <param name="loop">If loop, sound will loop from the set sample number.</param>
  220. public static AV.Audio PlaySound(int soundID, float volume = 1.0f, float pitch = 0.0f, float pan = 0.0f, bool persist = false, bool loop = false)
  221. {
  222. if (soundEntries == null || soundEntries[soundID].Size == 0)
  223. {
  224. return null;
  225. }
  226. AV.Audio ffcc = AV.Audio.Play(
  227. new AV.BufferData {
  228. DataSeekLoc = soundEntries[soundID].Offset,
  229. DataSize = soundEntries[soundID].Size,
  230. HeaderSize = (uint)soundEntries[soundID].HeaderData.Length,},
  231. soundEntries[soundID].HeaderData, loop ? 0 : -1);
  232. if (!persist)
  233. SoundChannels[CurrentSoundChannel++] = ffcc;
  234. ffcc.Play(volume, pitch, pan);
  235. return ffcc;
  236. }
  237. public static void StopSound()
  238. {
  239. //waveout.Stop();
  240. }
  241. public static void Update()
  242. {
  243. //checks to see if music buffer is running low and getframe triggers a refill.
  244. //if (ffccMusic != null && !ffccMusic.Ahead)
  245. //{
  246. // ffccMusic.Next();
  247. //}
  248. //if played in task we don't need to do this.
  249. }
  250. public static byte[] ReadFullyByte(Stream stream)
  251. {
  252. // following formula goal is to calculate the number of bytes to make buffer. might be wrong.
  253. long size = stream.Length; // stream.Length should be in bytes. will error later if short.
  254. int start = 0;
  255. byte[] buffer = new byte[size];
  256. int read = 0;
  257. //do
  258. //{
  259. read = stream.Read(buffer, start, buffer.Length);
  260. start++;
  261. //}
  262. //while (read == 0 && start < size);
  263. if (read == 0)
  264. {
  265. return null;
  266. }
  267. if (read < size)
  268. {
  269. Array.Resize<byte>(ref buffer, read);
  270. }
  271. return buffer;
  272. }
  273. public static byte[] GetSamplesWaveData(float[] samples, int samplesCount)
  274. { // converts 32 bit float samples to 16 bit pcm. I think :P
  275. // https://stackoverflow.com/questions/31957211/how-to-convert-an-array-of-int16-sound-samples-to-a-byte-array-to-use-in-monogam/42151979#42151979
  276. byte[] pcm = new byte[samplesCount * 2];
  277. int sampleIndex = 0,
  278. pcmIndex = 0;
  279. while (sampleIndex < samplesCount)
  280. {
  281. short outsample = (short)(samples[sampleIndex] * short.MaxValue);
  282. pcm[pcmIndex] = (byte)(outsample & 0xff);
  283. pcm[pcmIndex + 1] = (byte)((outsample >> 8) & 0xff);
  284. sampleIndex++;
  285. pcmIndex += 2;
  286. }
  287. return pcm;
  288. }
  289. private static bool musicplaying = false;
  290. private static int lastplayed = -1;
  291. public static void PlayStopMusic(ushort? index = null, float volume = 0.5f, float pitch = 0.0f, float pan = 0.0f)
  292. {
  293. if (!musicplaying || lastplayed != Memory.MusicIndex)
  294. {
  295. PlayMusic(index: index, volume: volume, pitch: pitch, pan: pan);
  296. }
  297. else
  298. {
  299. StopMusic();
  300. }
  301. }
  302. private static AV.Audio ffccMusic = null; // testing using class to play music instead of Naudio / Nvorbis
  303. private static int _currentSoundChannel;
  304. public static void PlayMusic(ushort? index = null, float volume = 0.5f, float pitch = 0.0f, float pan = 0.0f, bool loop = true)
  305. {
  306. Memory.MusicIndex = index ?? Memory.MusicIndex;
  307. if (musicplaying && lastplayed == Memory.MusicIndex) return;
  308. string ext = "";
  309. if (Memory.dicMusic.Count > 0 && Memory.dicMusic[Memory.MusicIndex].Count > 0)
  310. {
  311. ext = Path.GetExtension(Memory.dicMusic[Memory.MusicIndex][0]).ToLower();
  312. }
  313. else
  314. return;
  315. string pt = Memory.dicMusic[Memory.MusicIndex][0];
  316. StopMusic();
  317. switch (ext)
  318. {
  319. case ".ogg":
  320. case ".wav":
  321. default:
  322. //ffccMusic = new Ffcc(@"c:\eyes_on_me.wav", AVMediaType.AVMEDIA_TYPE_AUDIO, Ffcc.FfccMode.STATE_MACH);
  323. if (ffccMusic != null)
  324. ffccMusic.Dispose();
  325. ffccMusic = AV.Audio.Load(pt, loop ? 0 : -1);
  326. if (!loop)
  327. ffccMusic.LOOPSTART = -1;
  328. ffccMusic.PlayInTask(volume, pitch, pan);
  329. break;
  330. case ".sgt":
  331. #if _X64
  332. if (fluid_Midi == null)
  333. fluid_Midi = new AV.Midi.Fluid();
  334. fluid_Midi.ReadSegmentFileManually(pt);
  335. fluid_Midi.Play();
  336. #else
  337. if (Extended.IsLinux)
  338. {
  339. fluid_Midi.ReadSegmentFileManually(pt);
  340. fluid_Midi.Play();
  341. break;
  342. }
  343. else
  344. {
  345. if (dm_Midi == null)
  346. dm_Midi = new AV.Midi.DirectMedia();
  347. dm_Midi.Play(pt,loop);
  348. }
  349. #endif
  350. break;
  351. }
  352. musicplaying = true;
  353. lastplayed = Memory.MusicIndex;
  354. }
  355. public static void KillAudio()
  356. {
  357. SoundChannels.Where(x => x != null).ForEach(action => action.Dispose());
  358. if (dm_Midi != null)
  359. dm_Midi.Dispose();
  360. if (fluid_Midi != null)
  361. fluid_Midi.Dispose();
  362. }
  363. public static void StopMusic()
  364. {
  365. musicplaying = false;
  366. if (ffccMusic != null)
  367. {
  368. ffccMusic.Dispose();
  369. ffccMusic = null;
  370. }
  371. #if !_X64
  372. if (dm_Midi != null)
  373. dm_Midi.Stop();
  374. #else
  375. if (fluid_Midi != null)
  376. fluid_Midi.Stop();
  377. #endif
  378. }
  379. }
  380. }