using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
#if _WINDOWS && !_X64
using DirectMidi;
#endif
using System.Runtime.InteropServices;
using NAudio.Vorbis;
using FFmpeg.AutoGen;
using System.Diagnostics;
using System.Linq;
namespace FF8
{
#pragma warning disable IDE1006 // Naming Styles
public static class init_debugger_Audio
#pragma warning restore IDE1006 // Naming Styles
{
#if _WINDOWS && !_X64
private static CDirectMusic cdm;
private static CDLSLoader loader;
private static CSegment segment;
private static CAPathPerformance path;
public static CPortPerformance cport; //public explicit
private static COutputPort outport;
private static CCollection ccollection;
private static CInstrument[] instruments;
#endif
private static byte[] getBytes(object aux)
{
int length = Marshal.SizeOf(aux);
IntPtr ptr = Marshal.AllocHGlobal(length);
byte[] myBuffer = new byte[length];
Marshal.StructureToPtr(aux, ptr, true);
Marshal.Copy(ptr, myBuffer, 0, length);
Marshal.FreeHGlobal(ptr);
return myBuffer;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
private struct SoundEntry
{
public UInt32 Size;
public UInt32 Offset;
private UInt32 output_TotalSize => Size + 70; // Total bytes of file -8 because for some reason 8 bytes don't count
private const UInt32 output_HeaderSize = 50; //Total bytes of Header
private UInt32 output_DataSize => Size; //Total bytes of Data Section
//public byte[] UNK; //12
//public WAVEFORMATEX WAVFORMATEX; //18 header starts here
//public ushort SamplesPerBlock; //2
//public ushort ADPCM; //2
//public ADPCMCOEFSET[] ADPCMCoefSets; //array should be of [ADPCM] size //7*4 = 28
public byte[] HeaderData;
public void fillHeader(BinaryReader br)
{
if (HeaderData == null)
{
HeaderData = new byte[output_HeaderSize + 28];
using (MemoryStream ms = new MemoryStream(HeaderData))
{
ms.Write(System.Text.Encoding.ASCII.GetBytes("RIFF"), 0, 4);
ms.Write(getBytes(output_TotalSize), 0, 4);
ms.Write(System.Text.Encoding.ASCII.GetBytes("WAVEfmt "), 0, 8);
ms.Write(getBytes(output_HeaderSize), 0, 4);
ms.Write(br.ReadBytes((int)output_HeaderSize), 0, (int)output_HeaderSize);
ms.Write(System.Text.Encoding.ASCII.GetBytes("data"), 0, 4);
ms.Write(getBytes(output_DataSize), 0, 4);
}
}
}
}
#pragma warning disable CS0649
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct WAVEFORMATEX
{
public ushort wFormatTag;
public ushort nChannels;
public uint nSamplesPerSec;
public uint nAvgBytesPerSec;
public ushort nBlockAlign;
public ushort wBitsPerSample;
public ushort cbSize;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
private struct ADPCMCOEFSET
{
public short iCoef1;
public short iCoef2;
};
#pragma warning restore CS0649
private static SoundEntry[] soundEntries;
public static int soundEntriesCount;
public const int S_OK = 0x00000000;
public const int MaxSoundChannels = 20;
///
/// This is for short lived sound effects. The Larger the array is the more sounds can be
/// played at once. If you want sounds to loop of have volume you'll need to have a
/// SoundEffectInstance added to ffcc, and have those sounds be played like music where they
/// loop in the background till stop.
///
public static Ffcc[] SoundChannels { get; } = new Ffcc[MaxSoundChannels];
public static int CurrentSoundChannel
{
get => _currentSoundChannel;
set
{
if (value >= MaxSoundChannels)
{
value = 0;
}
else if (value < 0)
{
value = MaxSoundChannels - 1;
}
_currentSoundChannel = value;
}
}
public static void Init()
{
string dmusic_pt = "", RaW_ogg_pt = "", music_pt = "", music_wav_pt = "";
//Roses and Wine V07 moves most of the sgt files to dmusic_backup
//it leaves a few files behind. I think because RaW doesn't replace everything.
//ogg files stored in:
RaW_ogg_pt = Extended.GetUnixFullPath(Path.Combine(Memory.FF8DIR, "RaW/GLOBAL/Music"));
if (!Directory.Exists(RaW_ogg_pt))
{
RaW_ogg_pt = null;
}
// From what I gather the OGG files and the sgt files have the same numerical prefix. I
// might try to add the functionality to the debug screen monday.
dmusic_pt = Extended.GetUnixFullPath(Path.Combine(Memory.FF8DIRdata, "Music","dmusic_backup"));
if (!Directory.Exists(dmusic_pt))
{
dmusic_pt = null;
}
music_pt = Extended.GetUnixFullPath(Path.Combine(Memory.FF8DIRdata, "Music","dmusic"));
if (!Directory.Exists(music_pt))
{
music_pt = null;
}
music_wav_pt = Extended.GetUnixFullPath(Path.Combine(Memory.FF8DIRdata, "Music"));
if (!Directory.Exists(music_wav_pt))
{
music_wav_pt = null;
}
// goal of dicmusic is to be able to select a track by prefix. it adds an list of files
// with the same prefix. so you can later on switch out which one you want.
if (RaW_ogg_pt != null)
{
Memory.musices = Directory.GetFiles(RaW_ogg_pt).Where(x=> x.EndsWith(".ogg",StringComparison.OrdinalIgnoreCase)).ToArray();
foreach (string m in Memory.musices)
{
if (ushort.TryParse(Path.GetFileName(m).Substring(0, 3), out ushort key))
{
//mismatched prefix's go here
if (key == 512)
{
key = 0; //loser.ogg and sgt don't match.
}
if (!Memory.dicMusic.ContainsKey(key))
{
Memory.dicMusic.Add(key, new List { m });
}
else
{
Memory.dicMusic[key].Add(m);
}
}
}
}
if (dmusic_pt != null)
{
Memory.musices = Directory.GetFiles(dmusic_pt).Where(x => x.EndsWith(".sgt", StringComparison.OrdinalIgnoreCase)).ToArray();
foreach (string m in Memory.musices)
{
if (ushort.TryParse(Path.GetFileName(m).Substring(0, 3), out ushort key))
{
if (!Memory.dicMusic.ContainsKey(key))
{
Memory.dicMusic.Add(key, new List { m });
}
else
{
Memory.dicMusic[key].Add(m);
}
}
else
{
if (!Memory.dicMusic.ContainsKey(999)) //gets any music w/o prefix
{
Memory.dicMusic.Add(999, new List { m });
}
else
{
Memory.dicMusic[999].Add(m);
}
}
}
}
if (music_pt != null)
{
Memory.musices = Directory.GetFiles(music_pt).Where(x => x.EndsWith(".sgt", StringComparison.OrdinalIgnoreCase)).ToArray();
foreach (string m in Memory.musices)
{
if (ushort.TryParse(Path.GetFileName(m).Substring(0, 3), out ushort key))
{
if (!Memory.dicMusic.ContainsKey(key))
{
Memory.dicMusic.Add(key, new List { m });
}
else
{
Memory.dicMusic[key].Add(m);
}
}
else
{
if (!Memory.dicMusic.ContainsKey(999)) //gets any music w/o prefix
{
Memory.dicMusic.Add(999, new List { m });
}
else
{
Memory.dicMusic[999].Add(m);
}
}
}
}
if (music_wav_pt != null)
{
Memory.musices = Directory.GetFiles(music_wav_pt).Where(x => x.EndsWith(".wav", StringComparison.OrdinalIgnoreCase)).ToArray();
foreach (string m in Memory.musices)
{
if (ushort.TryParse(Path.GetFileName(m).Substring(0, 3), out ushort key))
{
if (!Memory.dicMusic.ContainsKey(key))
{
Memory.dicMusic.Add(key, new List { m });
}
else
{
Memory.dicMusic[key].Add(m);
}
}
else
{
if (!Memory.dicMusic.ContainsKey(999)) //gets any music w/o prefix
{
Memory.dicMusic.Add(999, new List { m });
}
else
{
Memory.dicMusic[999].Add(m);
}
}
}
}
}
//I messed around here as figuring out how things worked probably didn't need to mess with this.
public static void Init_SoundAudio()
{
string path = Path.Combine(Memory.FF8DIRdata, "Sound","audio.fmt");
if(File.Exists(path))
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
using (BinaryReader br = new BinaryReader(fs))
{
soundEntries = new SoundEntry[br.ReadUInt32()];
fs.Seek(36, SeekOrigin.Current);
for (int i = 0; i < soundEntries.Length - 1; i++)
{
UInt32 sz = br.ReadUInt32();
if (sz == 0)
{
fs.Seek(34, SeekOrigin.Current); continue;
}
soundEntries[i] = new SoundEntry
{
Size = sz,
Offset = br.ReadUInt32()
};
fs.Seek(12, SeekOrigin.Current);
soundEntries[i].fillHeader(br);
}
}
soundEntriesCount = soundEntries ==null ? 0: soundEntries.Length;
}
public static void PlaySound(int soundID)
{
if (soundEntries == null || soundEntries[soundID].Size == 0)
{
return;
}
SoundChannels[CurrentSoundChannel] = new Ffcc(
new Ffcc.Buffer_Data { DataSeekLoc = soundEntries[soundID].Offset, DataSize = soundEntries[soundID].Size, HeaderSize = (uint)soundEntries[soundID].HeaderData.Length },
soundEntries[soundID].HeaderData,
Path.Combine(Memory.FF8DIRdata, "Sound","audio.dat"));
SoundChannels[CurrentSoundChannel++].Play();
}
public static void StopSound()
{
//waveout.Stop();
}
public static void Update()
{
//checks to see if music buffer is running low and getframe triggers a refill.
//if (ffccMusic != null && !ffccMusic.Ahead)
//{
// ffccMusic.Next();
//}
//if played in task we don't need to do this.
}
//callable test
public static byte[] ReadFullyByte(Stream stream)
{
// following formula goal is to calculate the number of bytes to make buffer. might be wrong.
long size = stream.Length; // stream.Length should be in bytes. will error later if short.
int start = 0;
byte[] buffer = new byte[size];
int read = 0;
//do
//{
read = stream.Read(buffer, start, buffer.Length);
start++;
//}
//while (read == 0 && start < size);
if (read == 0)
{
return null;
}
if (read < size)
{
Array.Resize(ref buffer, read);
}
return buffer;
}
public static byte[] ReadFullyFloat(VorbisWaveReader stream)
{
// following formula goal is to calculate the number of bytes to make buffer. might be wrong.
long size = (stream.Length / sizeof(float)) + 100; //unsure why but read was > than size so added 100; will error if the size is too small.
float[] buffer = new float[size];
int read = stream.Read(buffer, 0, buffer.Length);
return GetSamplesWaveData(buffer, read);
}
public static byte[] GetSamplesWaveData(byte[] samples, int samplesCount)
{
float[] f = new float[(samplesCount / sizeof(float))];
int i = 0;
for (int n = 0; n < samples.Length; n += sizeof(float))
{
f[i++] = BitConverter.ToSingle(samples, n);
}
return GetSamplesWaveData(f, samplesCount / sizeof(float));
}
public static byte[] GetSamplesWaveData(float[] samples, int samplesCount)
{ // converts 32 bit float samples to 16 bit pcm. I think :P
// https://stackoverflow.com/questions/31957211/how-to-convert-an-array-of-int16-sound-samples-to-a-byte-array-to-use-in-monogam/42151979#42151979
byte[] pcm = new byte[samplesCount * 2];
int sampleIndex = 0,
pcmIndex = 0;
while (sampleIndex < samplesCount)
{
short outsample = (short)(samples[sampleIndex] * short.MaxValue);
pcm[pcmIndex] = (byte)(outsample & 0xff);
pcm[pcmIndex + 1] = (byte)((outsample >> 8) & 0xff);
sampleIndex++;
pcmIndex += 2;
}
return pcm;
}
private static bool musicplaying = false;
private static int lastplayed = -1;
public static void PlayStopMusic()
{
if (!musicplaying || lastplayed != Memory.MusicIndex)
{
PlayMusic();
}
else
{
StopMusic();
}
}
private static Ffcc ffccMusic = null; // testing using class to play music instead of Naudio / Nvorbis
private static int _currentSoundChannel;
public static void PlayMusic()
{
string ext = "";
bool bFakeLinux = false; //set to force linux behaviour on windows; To delete after Linux music playable
if (Memory.dicMusic.Count > 0 && Memory.dicMusic[Memory.MusicIndex].Count > 0)
{
ext = Path.GetExtension(Memory.dicMusic[Memory.MusicIndex][0]).ToLower();
}
else
return;
string pt = Memory.dicMusic[Memory.MusicIndex][0];
StopMusic();
switch (ext)
{
case ".ogg":
//ffccMusic = new Ffcc(@"c:\eyes_on_me.wav", AVMediaType.AVMEDIA_TYPE_AUDIO, Ffcc.FfccMode.STATE_MACH);
if (ffccMusic != null)
ffccMusic.Dispose();
ffccMusic = new Ffcc(pt, AVMediaType.AVMEDIA_TYPE_AUDIO, Ffcc.FfccMode.STATE_MACH);
ffccMusic.PlayInTask(.5f);
break;
case ".sgt":
if(Extended.IsLinux || bFakeLinux)
{
ReadSegmentFileManually(pt);
break;
}
if (!Extended.IsLinux)
{
#if _WINDOWS && !_X64
if (cdm == null)
{
cdm = new CDirectMusic();
cdm.Initialize();
loader = new CDLSLoader();
loader.Initialize();
loader.LoadSegment(pt, out segment);
ccollection = new CCollection();
string pathDLS = Path.Combine(Memory.FF8DIRdata, "Music/dmusic_backup/FF8.dls");
if (!File.Exists(pathDLS))
{
pathDLS = Path.Combine(Memory.FF8DIRdata, "Music/dmusic/FF8.dls");
}
loader.LoadDLS(pathDLS, out ccollection);
uint dwInstrumentIndex = 0;
while (ccollection.EnumInstrument(++dwInstrumentIndex, out INSTRUMENTINFO iInfo) == S_OK)
{
Debug.WriteLine(iInfo.szInstName);
}
instruments = new CInstrument[dwInstrumentIndex];
path = new CAPathPerformance();
path.Initialize(cdm, null, null, DMUS_APATH.DYNAMIC_3D, 128);
cport = new CPortPerformance();
cport.Initialize(cdm, null, null);
outport = new COutputPort();
outport.Initialize(cdm);
uint dwPortCount = 0;
INFOPORT infoport;
do
{
outport.GetPortInfo(++dwPortCount, out infoport);
}
while ((infoport.dwFlags & DMUS_PC.SOFTWARESYNTH) == 0);
outport.SetPortParams(0, 0, 0, DirectMidi.SET.REVERB | DirectMidi.SET.CHORUS, 44100);
outport.ActivatePort(infoport);
cport.AddPort(outport, 0, 1);
for (int i = 0; i < dwInstrumentIndex; i++)
{
ccollection.GetInstrument(out instruments[i], i);
outport.DownloadInstrument(instruments[i]);
}
segment.Download(cport);
cport.PlaySegment(segment);
}
else
{
cport.Stop(segment);
segment.Dispose();
//segment.ConnectToDLS
loader.LoadSegment(pt, out segment);
segment.Download(cport);
cport.PlaySegment(segment);
cdm.Dispose();
}
//GCHandle.Alloc(cdm, GCHandleType.Pinned);
//GCHandle.Alloc(loader, GCHandleType.Pinned);
//GCHandle.Alloc(segment, GCHandleType.Pinned);
//GCHandle.Alloc(path, GCHandleType.Pinned);
//GCHandle.Alloc(cport, GCHandleType.Pinned);
//GCHandle.Alloc(outport, GCHandleType.Pinned);
//GCHandle.Alloc(infoport, GCHandleType.Pinned);
#endif
}
break;
}
musicplaying = true;
lastplayed = Memory.MusicIndex;
}
public static void KillAudio()
{
//if (Sound != null && !Sound.IsDisposed)
//{
// Sound.Dispose();
//}
for (int i = 0; i < MaxSoundChannels; i++)
{
if (SoundChannels[i] != null && !SoundChannels[i].isDisposed)
{
SoundChannels[i].Dispose();
SoundChannels[i] = null;
}
}
try
{
if (Extended.IsLinux)
{
#if _WINDOWS && !_X64
cport.StopAll();
cport.Dispose();
ccollection.Dispose();
loader.Dispose();
outport.Dispose();
path.Dispose();
cdm.Dispose();
#endif
}
}
catch
{
}
}
public static void StopMusic()
{
musicplaying = false;
if (ffccMusic != null)
{
ffccMusic.Dispose();
ffccMusic = null;
}
#if _WINDOWS && !_X64
try
{
if (!Extended.IsLinux)
{
cport.StopAll();
}
}
catch { }
#endif
}
//MUSIC_TIME=LONG->int32; REFERENCE_TIME=LONGLONG->long
[StructLayout(LayoutKind.Sequential, Pack =1, Size =24)]
struct DMUS_IO_SEGMENT_HEADER
{
public uint dwRepeats;
public int mtLength;
public int mtPlayStart;
public int mtLoopStart;
public int mtLoopEnd;
public uint dwResolution;
}
[StructLayout(LayoutKind.Sequential, Pack =1, Size =8)]
struct DMUS_IO_VERSION
{
uint dwVersionMS;
uint dwVersionLS;
}
[StructLayout(LayoutKind.Sequential, Pack =1, Size =32)]
struct DMUS_IO_TRACK_HEADER
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst =16)]
byte[] guidClassID;
uint dwPosition;
uint dwGroup;
[MarshalAs(UnmanagedType.ByValArray, SizeConst =4)]
char[] _ckid;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
char[] _fccType;
public string ckid { get => new string(_ckid); }
public string fccType { get => new string(_fccType); }
}
static DMUS_IO_SEGMENT_HEADER segh = new DMUS_IO_SEGMENT_HEADER();
static DMUS_IO_VERSION vers = new DMUS_IO_VERSION();
static List trkh;
///
/// [LINUX]: This method manually reads DirectMusic Segment files
///
///
private static void ReadSegmentFileManually(string pt)
{
using (FileStream fs = new FileStream(pt, FileMode.Open, FileAccess.Read))
using (BinaryReader br = new BinaryReader(fs))
{
if(ReadFourCc(br) != "RIFF")
{
Console.WriteLine($"init_debugger_Audio::ReadSegmentFileManually: NOT RIFF!");
return;
}
fs.Seek(4, SeekOrigin.Current);
if(ReadFourCc(br) != "DMSG")
{
Console.WriteLine($"init_debugger_Audio::ReadSegmentFileManually: Broken structure. Expected DMSG!");
return;
}
ReadSegmentForm(fs, br);
}
}
private static void ReadSegmentForm(FileStream fs, BinaryReader br)
{
string fourCc;
trkh = new List();
if ((fourCc = ReadFourCc(br)) != "segh")
{ Console.WriteLine($"init_debugger_Audio::ReadSegmentForm: Broken structure. Expected segh, got={fourCc}");return;}
uint chunkSize = br.ReadUInt32();
if (chunkSize != Marshal.SizeOf(segh))
{ Console.WriteLine($"init_debugger_Audio::ReadSegmentForm: chunkSize={chunkSize} is different than DMUS_IO_SEGMENT_HEADER sizeof={Marshal.SizeOf(segh)}");return;}
segh = Extended.ByteArrayToStructure(br.ReadBytes((int)chunkSize));
if((fourCc = ReadFourCc(br)) != "guid")
{Console.WriteLine($"init_debugger_Audio::ReadSegmentForm: expected guid, got={fourCc}");return;}
byte[] guid = br.ReadBytes(br.ReadInt32());
if ((fourCc = ReadFourCc(br)) != "LIST")
{ Console.WriteLine($"init_debugger_Audio::ReadSegmentForm: expected LIST, got={fourCc}");return;}
//let's skip segment data for now, looks like it's not needed, it's not even oficially a part of segh
fs.Seek(br.ReadUInt32(), SeekOrigin.Current);
if ((fourCc = ReadFourCc(br)) != "vers")
{ Console.WriteLine($"init_debugger_Audio::ReadSegmentForm: expected vers, got={fourCc}"); return;}
if ((chunkSize = br.ReadUInt32()) != Marshal.SizeOf(vers))
{ Console.WriteLine($"init_debugger_Audio::ReadSegmentForm: vers expected sizeof={Marshal.SizeOf(vers)}, got={chunkSize}");return;}
vers = Extended.ByteArrayToStructure(br.ReadBytes((int)chunkSize));
if ((fourCc = ReadFourCc(br)) != "LIST")
{ Console.WriteLine($"init_debugger_Audio::ReadSegmentForm: expected LIST, got={fourCc}");return;}
//this list should now contain metadata like name, authors and etc. It's completely useless in this project scope
fs.Seek(br.ReadUInt32(), SeekOrigin.Current); //therefore let's just skip whole UNFO and etc.
if ((fourCc = ReadFourCc(br)) != "LIST")
{ Console.WriteLine($"init_debugger_Audio::ReadSegmentForm: expected LIST, got={fourCc}"); return; }
chunkSize = br.ReadUInt32();
if ((fourCc = ReadFourCc(br)) != "trkl")
{ Console.WriteLine($"init_debugger_Audio::ReadSegmentForm: expected trkl, got={fourCc}"); return; }
//at this point we are free to read the file up to the end by reading all available DMTK RIFFs;
uint eof = (uint)fs.Position + chunkSize-4;
while(fs.Position < eof)
{
if ((fourCc = ReadFourCc(br)) != "RIFF")
{ Console.WriteLine($"init_debugger_Audio::ReadSegmentForm: expected RIFF, got={fourCc}"); return; }
chunkSize = br.ReadUInt32();
long skipTell = fs.Position;
Console.WriteLine($"RIFF entry: {ReadFourCc(br)}/{ReadFourCc(br)}");
trkh.Add(Extended.ByteArrayToStructure(br.ReadBytes((int)br.ReadUInt32())));
//TODO HERE
//this seek below is to ensure that no critical behaviour happens and every RIFF header is read correctly
fs.Seek(skipTell+chunkSize, SeekOrigin.Begin);
}
}
private static string ReadFourCc(BinaryReader br) => new string(br.ReadChars(4));
}
}