diff --git a/.gitignore b/.gitignore index 80921d3..b8718c9 100644 --- a/.gitignore +++ b/.gitignore @@ -259,4 +259,11 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ -*.pyc \ No newline at end of file +*.pyc +/ObjectListView +/.vscode +/VG Music Studio - Core/GBA/AlphaDream/AlphaDreamChannel.cs +/VG Music Studio - Core/GBA/MP2K/MP2KChannel.cs +/VG Music Studio - WinForms/ObjectListView +/VG Music Studio - WinForms/API +/VG Music Studio - MIDI diff --git a/VG Music Studio - Core/Codec/CodecEnums.cs b/VG Music Studio - Core/Codec/CodecEnums.cs new file mode 100644 index 0000000..1e916a8 --- /dev/null +++ b/VG Music Studio - Core/Codec/CodecEnums.cs @@ -0,0 +1,180 @@ +namespace Kermalis.VGMusicStudio.Core.Codec; + +/* This code has been copied directly from vgmstream.h in VGMStream's repository * + * and modified into C# code to work with VGMS. Link to its repository can be * + * found here: https://github.com/vgmstream/vgmstream */ +public enum CodecType +{ + codec_SILENCE, /* generates silence */ + + /* PCM */ + codec_PCM16LE, /* little endian 16-bit PCM */ + codec_PCM16BE, /* big endian 16-bit PCM */ + codec_PCM16_int, /* 16-bit PCM with sample-level interleave (for blocks) */ + + codec_PCM8, /* 8-bit PCM */ + codec_PCM8_int, /* 8-bit PCM with sample-level interleave (for blocks) */ + codec_PCM8_U, /* 8-bit PCM, unsigned (0x80 = 0) */ + codec_PCM8_U_int, /* 8-bit PCM, unsigned (0x80 = 0) with sample-level interleave (for blocks) */ + codec_PCM8_SB, /* 8-bit PCM, sign bit (others are 2's complement) */ + codec_PCM4, /* 4-bit PCM, signed */ + codec_PCM4_U, /* 4-bit PCM, unsigned */ + + codec_ULAW, /* 8-bit u-Law (non-linear PCM) */ + codec_ULAW_int, /* 8-bit u-Law (non-linear PCM) with sample-level interleave (for blocks) */ + codec_ALAW, /* 8-bit a-Law (non-linear PCM) */ + + codec_PCMFLOAT, /* 32-bit float PCM */ + codec_PCM24LE, /* little endian 24-bit PCM */ + codec_PCM24BE, /* big endian 24-bit PCM */ + + /* ADPCM */ + codec_CRI_ADX, /* CRI ADX */ + codec_CRI_ADX_fixed, /* CRI ADX, encoding type 2 with fixed coefficients */ + codec_CRI_ADX_exp, /* CRI ADX, encoding type 4 with exponential scale */ + codec_CRI_ADX_enc_8, /* CRI ADX, type 8 encryption (God Hand) */ + codec_CRI_ADX_enc_9, /* CRI ADX, type 9 encryption (PSO2) */ + + codec_NGC_DSP, /* Nintendo DSP ADPCM */ + codec_NGC_DSP_subint, /* Nintendo DSP ADPCM with frame subinterframe */ + codec_NGC_DTK, /* Nintendo DTK ADPCM (hardware disc), also called TRK or ADP */ + codec_NGC_AFC, /* Nintendo AFC ADPCM */ + codec_VADPCM, /* Silicon Graphics VADPCM */ + + codec_G721, /* CCITT G.721 */ + + codec_XA, /* CD-ROM XA 4-bit */ + codec_XA8, /* CD-ROM XA 8-bit */ + codec_XA_EA, /* EA's Saturn XA (not to be confused with EA-XA) */ + codec_PSX, /* Sony PS ADPCM (VAG) */ + codec_PSX_badflags, /* Sony PS ADPCM with custom flag byte */ + codec_PSX_cfg, /* Sony PS ADPCM with configurable frame size (int math) */ + codec_PSX_pivotal, /* Sony PS ADPCM with configurable frame size (float math) */ + codec_HEVAG, /* Sony PSVita ADPCM */ + + codec_EA_XA, /* Electronic Arts EA-XA ADPCM v1 (stereo) aka "EA ADPCM" */ + codec_EA_XA_int, /* Electronic Arts EA-XA ADPCM v1 (mono/interleave) */ + codec_EA_XA_V2, /* Electronic Arts EA-XA ADPCM v2 */ + codec_MAXIS_XA, /* Maxis EA-XA ADPCM */ + codec_EA_XAS_V0, /* Electronic Arts EA-XAS ADPCM v0 */ + codec_EA_XAS_V1, /* Electronic Arts EA-XAS ADPCM v1 */ + + codec_IMA, /* IMA ADPCM (stereo or mono, low nibble first) */ + codec_IMA_int, /* IMA ADPCM (mono/interleave, low nibble first) */ + codec_DVI_IMA, /* DVI IMA ADPCM (stereo or mono, high nibble first) */ + codec_DVI_IMA_int, /* DVI IMA ADPCM (mono/interleave, high nibble first) */ + codec_NW_IMA, + codec_SNDS_IMA, /* Heavy Iron Studios .snds IMA ADPCM */ + codec_QD_IMA, + codec_WV6_IMA, /* Gorilla Systems WV6 4-bit IMA ADPCM */ + codec_HV_IMA, /* High Voltage 4-bit IMA ADPCM */ + codec_FFTA2_IMA, /* Final Fantasy Tactics A2 4-bit IMA ADPCM */ + codec_BLITZ_IMA, /* Blitz Games 4-bit IMA ADPCM */ + + codec_MS_IMA, /* Microsoft IMA ADPCM */ + codec_MS_IMA_mono, /* Microsoft IMA ADPCM (mono/interleave) */ + codec_XBOX_IMA, /* XBOX IMA ADPCM */ + codec_XBOX_IMA_mch, /* XBOX IMA ADPCM (multichannel) */ + codec_XBOX_IMA_int, /* XBOX IMA ADPCM (mono/interleave) */ + codec_NDS_IMA, /* IMA ADPCM w/ NDS layout */ + codec_DAT4_IMA, /* Eurocom 'DAT4' IMA ADPCM */ + codec_RAD_IMA, /* Radical IMA ADPCM */ + codec_RAD_IMA_mono, /* Radical IMA ADPCM (mono/interleave) */ + codec_APPLE_IMA4, /* Apple Quicktime IMA4 */ + codec_FSB_IMA, /* FMOD's FSB multichannel IMA ADPCM */ + codec_WWISE_IMA, /* Audiokinetic Wwise IMA ADPCM */ + codec_REF_IMA, /* Reflections IMA ADPCM */ + codec_AWC_IMA, /* Rockstar AWC IMA ADPCM */ + codec_UBI_IMA, /* Ubisoft IMA ADPCM */ + codec_UBI_SCE_IMA, /* Ubisoft SCE IMA ADPCM */ + codec_H4M_IMA, /* H4M IMA ADPCM (stereo or mono, high nibble first) */ + codec_MTF_IMA, /* Capcom MT Framework IMA ADPCM */ + codec_CD_IMA, /* Crystal Dynamics IMA ADPCM */ + + codec_MSADPCM, /* Microsoft ADPCM (stereo/mono) */ + codec_MSADPCM_int, /* Microsoft ADPCM (mono) */ + codec_MSADPCM_ck, /* Microsoft ADPCM (Cricket Audio variation) */ + codec_WS, /* Westwood Studios VBR ADPCM */ + + codec_AICA, /* Yamaha AICA ADPCM (stereo) */ + codec_AICA_int, /* Yamaha AICA ADPCM (mono/interleave) */ + codec_CP_YM, /* Capcom's Yamaha ADPCM (stereo/mono) */ + codec_ASKA, /* Aska ADPCM */ + codec_NXAP, /* NXAP ADPCM */ + + codec_TGC, /* Tiger Game.com 4-bit ADPCM */ + + codec_PSX_DSE_SQUARESOFT, /* SquareSoft Digital Sound Elements 16-bit PCM (For PSX) */ + codec_PS2_DSE_PROCYON, /* Procyon Studio Digital Sound Elements ADPCM (PS2 Version, encoded with VAG-ADPCM) */ + codec_NDS_DSE_PROCYON, /* Procyon Studio Digital Sound Elements ADPCM (NDS Version, encoded with IMA-ADPCM) */ + codec_WII_DSE_PROCYON, /* Procyon Studio Digital Sound Elements ADPCM (Wii Version, encoded with DSP-ADPCM) */ + codec_L5_555, /* Level-5 0x555 ADPCM */ + codec_LSF, /* lsf ADPCM (Fastlane Street Racing iPhone)*/ + codec_MTAF, /* Konami MTAF ADPCM */ + codec_MTA2, /* Konami MTA2 ADPCM */ + codec_MC3, /* Paradigm MC3 3-bit ADPCM */ + codec_FADPCM, /* FMOD FADPCM 4-bit ADPCM */ + codec_ASF, /* Argonaut ASF 4-bit ADPCM */ + codec_DSA, /* Ocean DSA 4-bit ADPCM */ + codec_XMD, /* Konami XMD 4-bit ADPCM */ + codec_TANTALUS, /* Tantalus 4-bit ADPCM */ + codec_PCFX, /* PC-FX 4-bit ADPCM */ + codec_OKI16, /* OKI 4-bit ADPCM with 16-bit output and modified expand */ + codec_OKI4S, /* OKI 4-bit ADPCM with 16-bit output and cuadruple step */ + codec_PTADPCM, /* Platinum 4-bit ADPCM */ + codec_IMUSE, /* LucasArts iMUSE Variable ADPCM */ + codec_COMPRESSWAVE, /* CompressWave Huffman ADPCM */ + + /* others */ + codec_SDX2, /* SDX2 2:1 Squareroot-Delta-Exact compression DPCM */ + codec_SDX2_int, /* SDX2 2:1 Squareroot-Delta-Exact compression with sample-level interleave */ + codec_CBD2, /* CBD2 2:1 Cuberoot-Delta-Exact compression DPCM */ + codec_CBD2_int, /* CBD2 2:1 Cuberoot-Delta-Exact compression, with sample-level interleave */ + codec_SASSC, /* Activision EXAKT SASSC 8-bit DPCM */ + codec_DERF, /* DERF 8-bit DPCM */ + codec_WADY, /* WADY 8-bit DPCM */ + codec_NWA, /* VisualArt's NWA DPCM */ + codec_ACM, /* InterPlay ACM */ + codec_CIRCUS_ADPCM, /* Circus 8-bit ADPCM */ + codec_UBI_ADPCM, /* Ubisoft 4/6-bit ADPCM */ + + codec_EA_MT, /* Electronic Arts MicroTalk (linear-predictive speech codec) */ + codec_CIRCUS_VQ, /* Circus VQ */ + codec_RELIC, /* Relic Codec (DCT-based) */ + codec_CRI_HCA, /* CRI High Compression Audio (MDCT-based) */ + codec_TAC, /* tri-Ace Codec (MDCT-based) */ + codec_ICE_RANGE, /* Inti Creates "range" codec */ + codec_ICE_DCT, /* Inti Creates "DCT" codec */ + + + codec_OGG_VORBIS, /* Xiph Vorbis with Ogg layer (MDCT-based) */ + codec_VORBIS_custom, /* Xiph Vorbis with custom layer (MDCT-based) */ + + + codec_MPEG_custom, /* MPEG audio with custom features (MDCT-based) */ + codec_MPEG_ealayer3, /* EALayer3, custom MPEG frames */ + codec_MPEG_layer1, /* MP1 MPEG audio (MDCT-based) */ + codec_MPEG_layer2, /* MP2 MPEG audio (MDCT-based) */ + codec_MPEG_layer3, /* MP3 MPEG audio (MDCT-based) */ + + + codec_G7221C, /* ITU G.722.1 annex C (Polycom Siren 14) */ + + + codec_G719, /* ITU G.719 annex B (Polycom Siren 22) */ + + + codec_MP4_AAC, /* AAC (MDCT-based) */ + + + codec_ATRAC9, /* Sony ATRAC9 (MDCT-based) */ + + + codec_CELT_FSB, /* Custom Xiph CELT (MDCT-based) */ + + + codec_SPEEX, /* Custom Speex (CELP-based) */ + + + codec_FFmpeg, /* Formats handled by FFmpeg (ATRAC3, XMA, AC3, etc) */ +} \ No newline at end of file diff --git a/VG Music Studio - Core/Codec/DSPADPCM.cs b/VG Music Studio - Core/Codec/DSPADPCM.cs new file mode 100644 index 0000000..b18acb8 --- /dev/null +++ b/VG Music Studio - Core/Codec/DSPADPCM.cs @@ -0,0 +1,1081 @@ +using Kermalis.EndianBinaryIO; +using Kermalis.VGMusicStudio.Core.Util; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace Kermalis.VGMusicStudio.Core.Codec; + +internal struct DSPADPCM +{ + public static short Min = short.MinValue; + public static short Max = short.MaxValue; + + public static double[] Tvec = new double[3]; + + public DSPADPCMInfo[] Info; + private readonly ushort NumChannels; + private ushort Channel; + public byte[] Data; + public short[]? DataOutput; + + private int DataOffset; + private int SamplePos; + public int Scale; + public byte ByteValue; + public int FrameOffset; + public int[]? Coef1; + public int[]? Coef2; + public short[]? Hist1; + public short[]? Hist2; + private int Nibble; + + public static class DSPADPCMConstants + { + public const int BytesPerFrame = 8; + public const int SamplesPerFrame = 14; + public const int NibblesPerFrame = 16; + } + + public DSPADPCM(EndianBinaryReader r, ushort? numChannels) + { + _ = numChannels == null ? NumChannels = 1 : NumChannels = (ushort)numChannels; // The number of waveform channels + Info = new DSPADPCMInfo[NumChannels]; + for (int i = 0; i < NumChannels; i++) + { + Info[i] = new DSPADPCMInfo(r); // First, the 96 byte-long DSP-ADPCM header table is read and each variable is assigned with a value + } + Data = new byte[DataUtils.RoundUp((int)Info[0].NumAdpcmNibbles / 2, 16) * NumChannels]; // Next, this allocates the full size of the data, based on NumAdpcmNibbles divided by 2 and rounded up to the 16th byte + DataOutput = new short[Info[0].NumSamples * NumChannels]; // This will allocate the size of the DataOutput array based on the value in NumSamples + + r.ReadBytes(Data); // This reads the compressed sample data based on the size allocated + r.Stream.Align(16); // This will align the EndianBinaryReader stream offset to the 16th byte, since all sample data ends at every 16th byte + return; + } + + #region DSP-ADPCM Info + public interface IDSPADPCMInfo + { + public short[] Coef { get; } + public ushort Gain { get; } + public ushort PredScale { get; } + public short Yn1 { get; } + public short Yn2 { get; } + + public ushort LoopPredScale { get; } + public short LoopYn1 { get; } + public short LoopYn2 { get; } + } + + public class DSPADPCMInfo : IDSPADPCMInfo + { + public uint NumSamples { get; set; } + public uint NumAdpcmNibbles { get; set; } + public uint SampleRate { get; set; } + public ushort LoopFlag { get; set; } + public ushort Format { get; set; } + public uint Sa { get; set; } + public uint Ea { get; set; } + public uint Ca { get; set; } + public short[] Coef { get; set; } + public ushort Gain { get; set; } + public ushort PredScale { get; set; } + public short Yn1 { get; set; } + public short Yn2 { get; set; } + + public ushort LoopPredScale { get; set; } + public short LoopYn1 { get; set; } + public short LoopYn2 { get; set; } + public ushort[] Padding { get; set; } + + public DSPADPCMInfo(EndianBinaryReader r) + { + NumSamples = r.ReadUInt32(); + + NumAdpcmNibbles = r.ReadUInt32(); + + SampleRate = r.ReadUInt32(); + + LoopFlag = r.ReadUInt16(); + + Format = r.ReadUInt16(); + + Sa = r.ReadUInt32(); + + Ea = r.ReadUInt32(); + + Ca = r.ReadUInt32(); + + Coef = new short[16]; + r.ReadInt16s(Coef); + + Gain = r.ReadUInt16(); + + PredScale = r.ReadUInt16(); + + Yn1 = r.ReadInt16(); + + Yn2 = r.ReadInt16(); + + LoopPredScale = r.ReadUInt16(); + + LoopYn1 = r.ReadInt16(); + + LoopYn2 = r.ReadInt16(); + + Padding = new ushort[11]; + r.ReadUInt16s(Padding); + } + + public byte[] ToBytes() + { + _ = new byte[96]; + var numSamples = new byte[4]; + var numAdpcmNibbles = new byte[4]; + var sampleRate = new byte[4]; + var loopFlag = new byte[2]; + var format = new byte[2]; + var sa = new byte[4]; + var ea = new byte[4]; + var ca = new byte[4]; + var coef = new byte[32]; + var gain = new byte[2]; + var predScale = new byte[2]; + var yn1 = new byte[2]; + var yn2 = new byte[2]; + var loopPredScale = new byte[2]; + var loopYn1 = new byte[2]; + var loopYn2 = new byte[2]; + var padding = new byte[22]; + + BinaryPrimitives.WriteUInt32BigEndian(numSamples, NumSamples); + BinaryPrimitives.WriteUInt32BigEndian(numAdpcmNibbles, NumAdpcmNibbles); + BinaryPrimitives.WriteUInt32BigEndian(sampleRate, SampleRate); + BinaryPrimitives.WriteUInt16BigEndian(loopFlag, LoopFlag); + BinaryPrimitives.WriteUInt16BigEndian(format, Format); + BinaryPrimitives.WriteUInt32BigEndian(sa, Sa); + BinaryPrimitives.WriteUInt32BigEndian(ea, Ea); + BinaryPrimitives.WriteUInt32BigEndian(ca, Ca); + int index = 0; + for (int i = 0; i < 16; i++) + { + coef[index++] = (byte)(Coef[i] >> 8); + coef[index++] = (byte)(Coef[i] & 0xff); + } + BinaryPrimitives.WriteUInt16BigEndian(gain, Gain); + BinaryPrimitives.WriteUInt16BigEndian(predScale, PredScale); + BinaryPrimitives.WriteInt16BigEndian(yn1, Yn1); + BinaryPrimitives.WriteInt16BigEndian(yn2, Yn2); + BinaryPrimitives.WriteUInt16BigEndian(loopPredScale, LoopPredScale); + BinaryPrimitives.WriteInt16BigEndian(loopYn1, LoopYn1); + BinaryPrimitives.WriteInt16BigEndian(loopYn2, LoopYn2); + + // A collection expression is used for combining all byte arrays into one, instead of using the Concat() function + byte[]? bytes = [.. numSamples, .. numAdpcmNibbles, .. sampleRate, + .. loopFlag, .. format, .. sa, .. ea, .. ca, .. coef, .. gain, + .. predScale, .. yn1, .. yn2, .. loopPredScale, .. loopYn1, .. loopYn2, .. padding]; + + return bytes; + } + } + #endregion + + #region DSP-ADPCM Convert + + public static int NibblesToSamples(int nibbles) + { + var fullFrames = nibbles / 16; + var remainder = nibbles % 16; + + return remainder > 0 ? (fullFrames * 14) + remainder - 2 : fullFrames * 14; + } + + public static int BytesToSamples(int bytes, int channels) + { + return channels <= 0 ? 0 : (bytes / channels) / (8 * 14); + } + + public readonly byte[] InfoToBytes() + { + var info = new DSPADPCMInfo[NumChannels]; + var infoData = new byte[96 * NumChannels]; + for (int i = 0; i < NumChannels; i++) + { + Array.Copy(info[i].ToBytes(), infoData, 96 * (i + 1)); + } + return infoData; + } + + public readonly byte[] DataOutputToBytes() + { + int index = 0; + var data = new byte[DataOutput!.Length * 2]; + for (int i = 0; i < DataOutput.Length; i++) + { + data[index++] = (byte)(DataOutput[i] >> 8); + data[index++] = (byte)DataOutput[i]; + } + return data; + } + + public readonly byte[] ConvertToWav() + { + // Creating the RIFF Wave header + string fileID = "RIFF"; + uint fileSize = (uint)((DataOutput!.Length * 2) + 44); // File size must match the size of the samples and header size + string waveID = "WAVE"; + string formatID = "fmt "; + uint formatLength = 16; // Always a length 16 + ushort formatType = 1; // Always PCM16 + // Number of channels is already manually defined + uint sampleRate = Info[0].SampleRate; // Sample Rate is read directly from the Info context + ushort bitsPerSample = 16; // bitsPerSample must be written to AFTER numNibbles + uint numNibbles = sampleRate * bitsPerSample * Channel / 8; // numNibbles must be written BEFORE bitsPerSample is written + ushort bitRate = (ushort)((bitsPerSample * Channel) / 8); + string dataID = "data"; + uint dataSize = (uint)(DataOutput!.Length * 2); + + var convertedData = new byte[dataSize]; + int index = 0; + for (int i = 0; i < DataOutput!.Length; i++) + { + convertedData[index++] = (byte)(DataOutput![i] & 0xff); + convertedData[index++] = (byte)(DataOutput![i] >> 8); + } + + _ = new byte[4]; + var header2 = new byte[4]; + _ = new byte[4]; + _ = new byte[4]; + var header5 = new byte[4]; + var header6 = new byte[2]; + var header7 = new byte[2]; + var header8 = new byte[4]; + var header9 = new byte[4]; + var header10 = new byte[2]; + var header11 = new byte[2]; + _ = new byte[4]; + var header13 = new byte[4]; + + byte[]? header1 = Encoding.ASCII.GetBytes(fileID); + BinaryPrimitives.WriteUInt32LittleEndian(header2, fileSize); + byte[]? header3 = Encoding.ASCII.GetBytes(waveID); + byte[]? header4 = Encoding.ASCII.GetBytes(formatID); + BinaryPrimitives.WriteUInt32LittleEndian(header5, formatLength); + BinaryPrimitives.WriteUInt16LittleEndian(header6, formatType); + BinaryPrimitives.WriteUInt16LittleEndian(header7, NumChannels); + BinaryPrimitives.WriteUInt32LittleEndian(header8, sampleRate); + BinaryPrimitives.WriteUInt32LittleEndian(header9, numNibbles); + BinaryPrimitives.WriteUInt16LittleEndian(header10, bitRate); + BinaryPrimitives.WriteUInt16LittleEndian(header11, bitsPerSample); + byte[]? header12 = Encoding.ASCII.GetBytes(dataID); + BinaryPrimitives.WriteUInt32LittleEndian(header13, dataSize); + + _ = new byte[44]; + byte[]? header = [ // Using this instead of the Concat() function, which does the exact same task of adding data into the array + .. header1, .. header2, .. header3, + .. header4, .. header5, .. header6, + .. header7, .. header8, .. header9, + .. header10, .. header11, .. header12, .. header13]; + _ = new byte[fileSize]; + byte[]? waveData = [.. header, .. convertedData]; + + + return waveData; + } + + #endregion + + #region DSP-ADPCM Encode + + public static void Encode(Span src, Span dst, DSPADPCMInfo cxt, uint samples) + { + Span coefs = cxt.Coef; + CorrelateCoefs(src, samples, coefs); + + int frameCount = (int)((samples / DSPADPCMConstants.SamplesPerFrame) + (samples % DSPADPCMConstants.SamplesPerFrame)); + + Span pcm = src; + Span adpcm = dst; + Span pcmFrame = new short[DSPADPCMConstants.SamplesPerFrame + 2]; + Span adpcmFrame = new byte[DSPADPCMConstants.BytesPerFrame]; + + short srcIndex = 0; + short dstIndex = 0; + + for (int i = 0; i < frameCount; ++i, pcm[srcIndex] += DSPADPCMConstants.SamplesPerFrame, adpcm[srcIndex] += DSPADPCMConstants.BytesPerFrame) + { + coefs = new short[2 + 0]; + + DSPEncodeFrame(pcmFrame, DSPADPCMConstants.SamplesPerFrame, adpcmFrame, coefs); + + pcmFrame[0] = pcmFrame[14]; + pcmFrame[1] = pcmFrame[15]; + } + + cxt.Gain = 0; + cxt.PredScale = dst[dstIndex++]; + cxt.Yn1 = 0; + cxt.Yn2 = 0; + } + + public static void InnerProductMerge(Span vecOut, Span pcmBuf) + { + pcmBuf = new short[14].AsSpan(); + vecOut = Tvec; + + for (int i = 0; i <= 2; i++) + { + vecOut[i] = 0.0f; + for (int x = 0; x < 14; x++) + vecOut[i] -= pcmBuf[x - i] * pcmBuf[x]; + + } + } + + public static void OuterProductMerge(Span mtxOut, Span pcmBuf) + { + pcmBuf = new short[14].AsSpan(); + mtxOut[3] = Tvec[3]; + + for (int x = 1; x <= 2; x++) + for (int y = 1; y <= 2; y++) + { + mtxOut[x] = 0.0; + mtxOut[y] = 0.0; + for (int z = 0; z < 14; z++) + mtxOut[x + y] += pcmBuf[z - x] * pcmBuf[z - y]; + } + } + + public static bool AnalyzeRanges(Span mtx, Span vecIdxsOut) + { + mtx[3] = Tvec[3]; + Span recips = new double[3].AsSpan(); + double val, tmp, min, max; + + /* Get greatest distance from zero */ + for (int x = 1; x <= 2; x++) + { + val = Math.Max(Math.Abs(mtx[x] + mtx[1]), Math.Abs(mtx[x] + mtx[2])); + if (val < double.Epsilon) + return true; + + recips[x] = 1.0 / val; + } + + int maxIndex = 0; + for (int i = 1; i <= 2; i++) + { + for (int x = 1; x < i; x++) + { + tmp = mtx[x] + mtx[i]; + for (int y = 1; y < x; y++) + tmp -= (mtx[x] + mtx[y]) * (mtx[y] + mtx[i]); + mtx[x + i] = tmp; + } + + val = 0.0; + for (int x = i; x <= 2; x++) + { + tmp = mtx[x] + mtx[i]; + for (int y = 1; y < i; y++) + tmp -= (mtx[x] + mtx[y]) * (mtx[y] + mtx[i]); + + mtx[x + i] = tmp; + tmp = Math.Abs(tmp) * recips[x]; + if (tmp >= val) + { + val = tmp; + maxIndex = x; + } + } + + if (maxIndex != i) + { + for (int y = 1; y <= 2; y++) + { + tmp = mtx[maxIndex] + mtx[y]; + mtx[maxIndex + y] = mtx[i] + mtx[y]; + mtx[i + y] = tmp; + } + recips[maxIndex] = recips[i]; + } + + vecIdxsOut[i] = maxIndex; + + if (mtx[i] + mtx[i] == 0.0) + return true; + + if (i != 2) + { + tmp = 1.0 / mtx[i] + mtx[i]; + for (int x = i + 1; x <= 2; x++) + mtx[x + i] *= tmp; + } + } + + /* Get range */ + min = 1.0e10; + max = 0.0; + for (int i = 1; i <= 2; i++) + { + tmp = Math.Abs(mtx[i] + mtx[i]); + if (tmp < min) + min = tmp; + if (tmp > max) + max = tmp; + } + + if (min / max < 1.0e-10) + return true; + + return false; + } + + public static void BidirectionalFilter(Span mtx, Span vecIdxs, Span vecOut) + { + mtx[3] = Tvec[3]; + vecOut = Tvec; + double tmp; + + for (int i = 1, x = 0; i <= 2; i++) + { + int index = vecIdxs[i]; + tmp = vecOut[index]; + vecOut[index] = vecOut[i]; + if (x != 0) + for (int y = x; y <= i - 1; y++) + tmp -= vecOut[y] * mtx[i] + mtx[y]; + else if (tmp != 0.0) + x = i; + vecOut[i] = tmp; + } + + for (int i = 2; i > 0; i--) + { + tmp = vecOut[i]; + for (int y = i + 1; y <= 2; y++) + tmp -= vecOut[y] * mtx[i] + mtx[y]; + vecOut[i] = tmp / mtx[i] + mtx[i]; + } + + vecOut[0] = 1.0; + } + + public static bool QuadraticMerge(Span inOutVec) + { + inOutVec = Tvec; + + double v0, v1, v2 = inOutVec[2]; + double tmp = 1.0 - (v2 * v2); + + if (tmp == 0.0) + return true; + + v0 = (inOutVec[0] - (v2 * v2)) / tmp; + v1 = (inOutVec[1] - (inOutVec[1] * v2)) / tmp; + + inOutVec[0] = v0; + inOutVec[1] = v1; + + return Math.Abs(v1) > 1.0; + } + + public static void FinishRecord(Span vIn, Span vOut) + { + vIn = Tvec; + vOut = Tvec; + for (int z = 1; z <= 2; z++) + { + if (vIn[z] >= 1.0) + vIn[z] = 0.9999999999; + + else if (vIn[z] <= -1.0) + vIn[z] = -0.9999999999; + } + vOut[0] = 1.0; + vOut[1] = (vIn[2] * vIn[1]) + vIn[1]; + vOut[2] = vIn[2]; + } + + public static void MatrixFilter(Span src, Span dst) + { + src = Tvec; + dst = Tvec; + double[] mtx = new double[3]; + Tvec = mtx; + + mtx[2 + 0] = 1.0; + for (int i = 1; i <= 2; i++) + mtx[2 + i] = -src[i]; + + for (int i = 2; i > 0; i--) + { + double val = 1.0 - ((mtx[i] + mtx[i]) * (mtx[i] + mtx[i])); + for (int y = 1; y <= i; y++) + mtx[i - 1 + y] = (((mtx[i] + mtx[i]) * (mtx[i] + mtx[y])) + mtx[i] + mtx[y]) / val; + } + + dst[0] = 1.0; + for (int i = 1; i <= 2; i++) + { + dst[i] = 0.0; + for (int y = 1; y <= i; y++) + dst[i] += (mtx[i] + mtx[y]) * dst[i - y]; + } + } + + public static void MergeFinishRecord(Span src, Span dst) + { + src = Tvec; + dst = Tvec; + int dstIndex = 0; + Span tmp = new double[dstIndex].AsSpan(); + double val = src[0]; + + dst[0] = 1.0; + for (int i = 1; i <= 2; i++) + { + double v2 = 0.0; + for (int y = 1; y < i; y++) + v2 += dst[y] * src[i - y]; + + if (val > 0.0) + dst[i] = -(v2 + src[i]) / val; + else + dst[i] = 0.0; + + tmp[i] = dst[i]; + + for (int y = 1; y < i; y++) + dst[y] += dst[i] * dst[i - y]; + + val *= 1.0 - (dst[i] * dst[i]); + } + + FinishRecord(tmp, dst); + } + + public static double ContrastVectors(Span source1, Span source2) + { + source1 = Tvec; + source2 = Tvec; + double val = (source2[2] * source2[1] + -source2[1]) / (1.0 - source2[2] * source2[2]); + double val1 = (source1[0] * source1[0]) + (source1[1] * source1[1]) + (source1[2] * source1[2]); + double val2 = (source1[0] * source1[1]) + (source1[1] * source1[2]); + double val3 = source1[0] * source1[2]; + return val1 + (2.0 * val * val2) + (2.0 * (-source2[1] * val + -source2[2]) * val3); + } + + public static void FilterRecords(Span vecBest, int exp, Span records, int recordCount) + { + vecBest[8] = Tvec[8]; + records = Tvec; + Span bufferList = new double[8].AsSpan(); + bufferList[8] = Tvec[8]; + + Span buffer1 = new int[8].AsSpan(); + Span buffer2 = Tvec; + + int index; + double value, tempVal = 0; + + for (int x = 0; x < 2; x++) + { + for (int y = 0; y < exp; y++) + { + buffer1[y] = 0; + for (int i = 0; i <= 2; i++) + bufferList[y + i] = 0.0; + } + for (int z = 0; z < recordCount; z++) + { + index = 0; + value = 1.0e30; + for (int i = 0; i < exp; i++) + { + vecBest = new double[i].AsSpan(); + records = new double[z].AsSpan(); + tempVal = ContrastVectors(vecBest, records); + if (tempVal < value) + { + value = tempVal; + index = i; + } + } + buffer1[index]++; + MatrixFilter(records, buffer2); + for (int i = 0; i <= 2; i++) + bufferList[index + i] += buffer2[i]; + } + + for (int i = 0; i < exp; i++) + if (buffer1[i] > 0) + for (int y = 0; y <= 2; y++) + bufferList[i + y] /= buffer1[i]; + + for (int i = 0; i < exp; i++) + bufferList = new double[i]; + MergeFinishRecord(bufferList, vecBest); + } + } + + public static void CorrelateCoefs(Span source, uint samples, Span coefsOut) + { + int numFrames = (int)((samples + 13) / 14); + int frameSamples; + + Span blockBuffer = new short[0x3800].AsSpan(); + Span pcmHistBuffer = new short[2 + 14].AsSpan(); + + Span vec1 = Tvec; + Span vec2 = Tvec; + + Span mtx = Tvec; + mtx[3] = Tvec[3]; + Span vecIdxs = new int[3].AsSpan(); + + Span records = new double[numFrames * 2].AsSpan(); + records = Tvec; + int recordCount = 0; + + Span vecBest = new double[8].AsSpan(); + vecBest[8] = Tvec[8]; + + int sourceIndex = 0; + + /* Iterate though 1024-block frames */ + for (int x = (int)samples; x > 0;) + { + if (x > 0x3800) /* Full 1024-block frame */ + { + frameSamples = 0x3800; + x -= 0x3800; + } + else /* Partial frame */ + { + /* Zero lingering block samples */ + frameSamples = x; + for (int z = 0; z < 14 && z + frameSamples < 0x3800; z++) + blockBuffer[frameSamples + z] = 0; + x = 0; + } + + /* Copy (potentially non-frame-aligned PCM samples into aligned buffer) */ + source[sourceIndex] += (short)frameSamples; + + + for (int i = 0; i < frameSamples;) + { + for (int z = 0; z < 14; z++) + pcmHistBuffer[0 + z] = pcmHistBuffer[1 + z]; + for (int z = 0; z < 14; z++) + pcmHistBuffer[1 + z] = blockBuffer[i++]; + + pcmHistBuffer = new short[1].AsSpan(); + + InnerProductMerge(vec1, pcmHistBuffer); + if (Math.Abs(vec1[0]) > 10.0) + { + OuterProductMerge(mtx, pcmHistBuffer); + if (!AnalyzeRanges(mtx, vecIdxs)) + { + BidirectionalFilter(mtx, vecIdxs, vec1); + if (!QuadraticMerge(vec1)) + { + records = new double[recordCount].AsSpan(); + FinishRecord(vec1, records); + recordCount++; + } + } + } + } + } + + vec1[0] = 1.0; + vec1[1] = 0.0; + vec1[2] = 0.0; + + for (int z = 0; z < recordCount; z++) + { + records = new double[z].AsSpan(); + vecBest = new double[0].AsSpan(); + MatrixFilter(records, vecBest); + for (int y = 1; y <= 2; y++) + vec1[y] += vecBest[0] + vecBest[y]; + } + for (int y = 1; y <= 2; y++) + vec1[y] /= recordCount; + + MergeFinishRecord(vec1, vecBest); + + + int exp = 1; + for (int w = 0; w < 3;) + { + vec2[0] = 0.0; + vec2[1] = -1.0; + vec2[2] = 0.0; + for (int i = 0; i < exp; i++) + for (int y = 0; y <= 2; y++) + vecBest[exp + i + y] = (0.01 * vec2[y]) + vecBest[i] + vecBest[y]; + ++w; + exp = 1 << w; + FilterRecords(vecBest, exp, records, recordCount); + } + + /* Write output */ + for (int z = 0; z < 8; z++) + { + double d; + d = -vecBest[z] + vecBest[1] * 2048.0; + if (d > 0.0) + coefsOut[z * 2] = (d > 32767.0) ? (short)32767 : (short)Math.Round(d); + else + coefsOut[z * 2] = (d < -32768.0) ? (short)-32768 : (short)Math.Round(d); + + d = -vecBest[z] + vecBest[2] * 2048.0; + if (d > 0.0) + coefsOut[z * 2 + 1] = (d > 32767.0) ? (short)32767 : (short)Math.Round(d); + else + coefsOut[z * 2 + 1] = (d < -32768.0) ? (short)-32768 : (short)Math.Round(d); + } + } + + /* Make sure source includes the yn values (16 samples total) */ + public static void DSPEncodeFrame(Span pcmInOut, int sampleCount, Span adpcmOut, Span coefsIn) + { + pcmInOut = new short[16].AsSpan(); + adpcmOut = new byte[8].AsSpan(); + coefsIn = new short[8].AsSpan(); + coefsIn = new short[2].AsSpan(); + + Span inSamples = new int[8].AsSpan(); + inSamples = new int[16].AsSpan(); + Span outSamples = new int[8].AsSpan(); + outSamples = new int[14].AsSpan(); + + int bestIndex = 0; + + Span scale = new int[8].AsSpan(); + Span distAccum = new double[8].AsSpan(); + + /* Iterate through each coef set, finding the set with the smallest error */ + for (int i = 0; i < 8; i++) + { + int v1, v2, v3; + int distance, index; + + /* Set yn values */ + inSamples[i + 0] = pcmInOut[0]; + inSamples[i + 1] = pcmInOut[1]; + + /* Round and clamp samples for this coef set */ + distance = 0; + for (int s = 0; s < sampleCount; s++) + { + /* Multiply previous samples by coefs */ + inSamples[i + (s + 2)] = v1 = ((pcmInOut[s] * (coefsIn[i] + coefsIn[1])) + (pcmInOut[s + 1] * (coefsIn[i] + coefsIn[0]))) / 2048; + /* Subtract from current sample */ + v2 = pcmInOut[s + 2] - v1; + /* Clamp */ + v3 = (v2 >= 32767) ? 32767 : (v2 <= -32768) ? -32768 : v2; + /* Compare distance */ + if (Math.Abs(v3) > Math.Abs(distance)) + distance = v3; + } + + /* Set initial scale */ + for (scale[i] = 0; (scale[i] <= 12) && ((distance > 7) || (distance < -8)); scale[i]++, distance /= 2) + { + } + scale[i] = (scale[i] <= 1) ? -1 : scale[i] - 2; + + do + { + scale[i]++; + distAccum[i] = 0; + index = 0; + + for (int s = 0; s < sampleCount; s++) + { + /* Multiply previous */ + v1 = (((inSamples[i] + inSamples[s]) * (coefsIn[i] + coefsIn[1])) + ((inSamples[i] + inSamples[s + 1]) * (coefsIn[i] + coefsIn[0]))); + /* Evaluate from real sample */ + v2 = (pcmInOut[s + 2] << 11) - v1; + /* Round to nearest sample */ + v3 = (v2 > 0) ? (int)((double)v2 / (1 << scale[i]) / 2048 + 0.4999999f) : (int)((double)v2 / (1 << scale[i]) / 2048 - 0.4999999f); + + /* Clamp sample and set index */ + if (v3 < -8) + { + if (index < (v3 = -8 - v3)) + index = v3; + v3 = -8; + } + else if (v3 > 7) + { + if (index < (v3 -= 7)) + index = v3; + v3 = 7; + } + + /* Store result */ + outSamples[i + s] = v3; + + /* Round and expand */ + v1 = (v1 + ((v3 * (1 << scale[i])) << 11) + 1024) >> 11; + /* Clamp and store */ + inSamples[i + (s + 2)] = v2 = (v1 >= 32767) ? 32767 : (v1 <= -32768) ? -32768 : v1; + /* Accumulate distance */ + v3 = pcmInOut[s + 2] - v2; + distAccum[i] += v3 * (double)v3; + } + + for (int x = index + 8; x > 256; x >>= 1) + if (++scale[i] >= 12) + scale[i] = 11; + } while ((scale[i] < 12) && (index > 1)); + } + + double min = double.MaxValue; + for (int i = 0; i < 8; i++) + { + if (distAccum[i] < min) + { + min = distAccum[i]; + bestIndex = i; + } + } + + /* Write converted samples */ + for (int s = 0; s < sampleCount; s++) + pcmInOut[s + 2] = (short)(inSamples[bestIndex] + inSamples[s + 2]); + + /* Write ps */ + adpcmOut[0] = (byte)((bestIndex << 4) | (scale[bestIndex] & 0xF)); + + /* Zero remaining samples */ + for (int s = sampleCount; s < 14; s++) + outSamples[bestIndex + s] = 0; + + /* Write output samples */ + for (int y = 0; y < 7; y++) + { + adpcmOut[y + 1] = (byte)((outSamples[bestIndex] + outSamples[y * 2] << 4) | (outSamples[bestIndex] + outSamples[y * 2 + 1] & 0xF)); + } + } + + public static void EncodeFrame(Span src, Span dst, Span coefs, byte one) + { + coefs = new short[0 + 2]; + DSPEncodeFrame(src, 14, dst, coefs); + } + + #endregion + + #region DSP-ADPCM Decode + + #region Method 1 + + private static readonly sbyte[] NibbleToSbyte = [0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1]; + + public static int DivideByRoundUp(int dividend, int divisor) + { + return (dividend + divisor - 1) / divisor; + } + + private static sbyte GetHighNibble(byte value) + { + return NibbleToSbyte[(value >> 4) & 0xF]; + } + + private static sbyte GetLowNibble(byte value) + { + return NibbleToSbyte[value & 0xF]; + } + + public static short Clamp16(int value) + { + if (value > Max) + return Max; + else if (value < Min) + return Min; + else return (short)value; + } + + #region Current code + public void Init(byte[] data, DSPADPCMInfo[] info) + { + Info = info; + Data = data; + DataOffset = 0; + SamplePos = 0; + FrameOffset = 0; + + Hist1 = new short[NumChannels]; + Hist2 = new short[NumChannels]; + Coef1 = new int[NumChannels]; + Coef2 = new int[NumChannels]; + } + + public void Decode() + { + Init(Data, Info); // Sets up the field variables + + // Each DSP-ADPCM frame is 8 bytes long: 1 byte for header, 7 bytes for sample data + // This loop reads every 8 bytes and decodes them until the samplePos reaches NumSamples + while (SamplePos < Info[0].NumSamples) + { + // This function will decode one frame at a time + DecodeFrame(); + + // The dataOffset is incremented by 8, and samplePos is incremented by 14 to prepare for the next frame + DataOffset += DSPADPCMConstants.BytesPerFrame; + SamplePos += DSPADPCMConstants.SamplesPerFrame; + } + + } + + public void DecodeFrame() + { + // It will decode 1 single DSP frame of size 0x08 (src) into 14 samples in a PCM buffer (dst) + for (int i = 0; i < NumChannels; i++) + { + Hist1![i] = Info[i].Yn1; + Hist2![i] = Info[i].Yn2; + } + + // Parsing the frame's header byte + Scale = 1 << ((Data[DataOffset]) & 0xf); + int coefIndex = ((Data[DataOffset] >> 4) & 0xf) * 2; + + // Parsing the coefficient pairs, based on the nibble's value + for (int i = 0; i < NumChannels; i++) + { + Coef1![i] = Info[i].Coef[coefIndex + 0]; + Coef2![i] = Info[i].Coef[coefIndex + 1]; + } + + // This loop decodes the frame's nibbles, each of which are 4-bits long (half a byte in length) + for (FrameOffset = 0; FrameOffset < DSPADPCMConstants.SamplesPerFrame * NumChannels; FrameOffset += NumChannels) + { + // This ensures multi-channel DSP-ADPCM data is decoded as well + for (Channel = 0; Channel < NumChannels; Channel++) + { + // Stores the value of the entire byte based on the frame's offset + ByteValue = Data[DataOffset + 0x01 + FrameOffset / 2]; + + // This function decodes one nibble within a frame into a sample + short sample = GetSample(); + + // The DSP-ADPCM frame may have bytes that go beyond the DataOutput length, if this happens, this will safely finish the DecodeFrame function's task as is + if ((SamplePos + FrameOffset) * (Channel + 1) >= DataOutput!.Length) { return; } + + // The PCM16 sample is stored into the array entry, based on the sample offset and frame offset, multiplied by which wave channel is being used + DataOutput[(SamplePos + FrameOffset) * (Channel + 1)] = sample; + + // History values are stored, hist1 is copied into hist2 and the PCM16 sample is copied into hist1, before moving onto the next byte in the frame + Hist2![Channel] = Hist1![Channel]; + Hist1[Channel] = sample; + } + } + + // After the frame is decoded, the values in hist1 and hist2 are copied into Yn1 and Yn2 to prepare for the next frame + for (int i = 0; i < NumChannels; i++) + { + Info[i].Yn1 = Hist1![i]; + Info[i].Yn2 = Hist2![i]; + } + } + + public short GetSample() + { + Nibble = (FrameOffset & 1) != 0 ? // This conditional operator will store the value of the nibble + GetLowNibble(ByteValue) : // If the byte is not 0, it will obtain the least significant nibble (4-bits) + GetHighNibble(ByteValue); // Otherwise, if the byte is 0, it will obtain the most significant nibble (4-bits) + int largerVal = (Nibble * Scale) << 11; // The nibble's value is multiplied by scale's value, then 11 bits are shifted left, making the value larger + int newVal = (largerVal + 1024 + (Coef1![Channel] * Hist1![Channel]) + (Coef2![Channel] * Hist2![Channel])) >> 11; // Coefficients are multiplied by the value stored in hist1 and hist2 respectively, then the values are added together to make a new value + short sample = Clamp16(newVal); // The new value is then clamped into a 16-bit value, which makes a PCM16 sample + + return sample; + } + + #endregion + + + #endregion + + + #endregion + + #region DSP-ADPCM Math + public static uint GetBytesForADPCMBuffer(uint samples) + { + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + if ((samples % DSPADPCMConstants.SamplesPerFrame) == frames) + frames++; + + return frames * DSPADPCMConstants.BytesPerFrame; + } + + public static uint GetBytesForADPCMSamples(uint samples) + { + uint extraBytes = 0; + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + uint extraSamples = (samples % DSPADPCMConstants.SamplesPerFrame); + + if (extraSamples == frames) + { + extraBytes = (extraSamples / 2) + (extraSamples % 2) + 1; + } + + return DSPADPCMConstants.BytesPerFrame * frames + extraBytes; + } + + public static uint GetBytesForPCMBuffer(uint samples) + { + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + if ((samples % DSPADPCMConstants.SamplesPerFrame) == frames) + frames++; + + return frames * DSPADPCMConstants.SamplesPerFrame * sizeof(int); + } + + public static uint GetBytesForPCMSamples(uint samples) + { + return samples * sizeof(int); + } + + public static uint GetNibbleAddress(uint samples) + { + int frames = (int)(samples / DSPADPCMConstants.SamplesPerFrame); + int extraSamples = (int)(samples % DSPADPCMConstants.SamplesPerFrame); + + return (uint)(DSPADPCMConstants.NibblesPerFrame * frames + extraSamples + 2); + } + + public static uint GetNibblesForNSamples(uint samples) + { + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + uint extraSamples = (samples % DSPADPCMConstants.SamplesPerFrame); + uint extraNibbles = extraSamples == 0 ? 0 : extraSamples + 2; + + return DSPADPCMConstants.NibblesPerFrame * frames + extraNibbles; + } + + public static uint GetSampleForADPCMNibble(uint nibble) + { + uint frames = nibble / DSPADPCMConstants.NibblesPerFrame; + uint extraNibbles = (nibble % DSPADPCMConstants.NibblesPerFrame); + uint samples = DSPADPCMConstants.SamplesPerFrame * frames; + + return samples + extraNibbles - 2; + } + #endregion +} diff --git a/VG Music Studio - Core/ADPCMDecoder.cs b/VG Music Studio - Core/Codec/IMAADPCM.cs similarity index 90% rename from VG Music Studio - Core/ADPCMDecoder.cs rename to VG Music Studio - Core/Codec/IMAADPCM.cs index 894ea76..df6840d 100644 --- a/VG Music Studio - Core/ADPCMDecoder.cs +++ b/VG Music Studio - Core/Codec/IMAADPCM.cs @@ -1,9 +1,10 @@ using System; -namespace Kermalis.VGMusicStudio.Core; +namespace Kermalis.VGMusicStudio.Core.Codec; -internal struct ADPCMDecoder +internal struct IMAADPCM { + // TODO: Add encoding functionality and PCM16 to IMA-ADPCM conversion private static ReadOnlySpan IndexTable => new short[8] { -1, -1, -1, -1, 2, 4, 6, 8, @@ -39,9 +40,9 @@ public void Init(byte[] data) OnSecondNibble = false; } - public static short[] ADPCMToPCM16(byte[] data) + public static short[] IMAADPCMToPCM16(byte[] data) { - var decoder = new ADPCMDecoder(); + var decoder = new IMAADPCM(); decoder.Init(data); short[] buffer = new short[(data.Length - 4) * 2]; diff --git a/VG Music Studio - Core/Codec/LayoutEnums.cs b/VG Music Studio - Core/Codec/LayoutEnums.cs new file mode 100644 index 0000000..f241948 --- /dev/null +++ b/VG Music Studio - Core/Codec/LayoutEnums.cs @@ -0,0 +1,59 @@ +namespace Kermalis.VGMusicStudio.Core.Codec; + +/* This code has been copied directly from vgmstream.h in VGMStream's repository * + * and modified into C# code to work with VGMS. Link to its repository can be * + * found here: https://github.com/vgmstream/vgmstream */ +public enum LayoutType +{ + /* generic */ + layout_none, /* straight data */ + + /* interleave */ + layout_interleave, /* equal interleave throughout the stream */ + + /* headered blocks */ + layout_blocked_ast, + layout_blocked_halpst, + layout_blocked_xa, + layout_blocked_ea_schl, + layout_blocked_ea_1snh, + layout_blocked_caf, + layout_blocked_wsi, + layout_blocked_str_snds, + layout_blocked_ws_aud, + layout_blocked_matx, + layout_blocked_dec, + layout_blocked_xvas, + layout_blocked_vs, + layout_blocked_mul, + layout_blocked_gsb, + layout_blocked_thp, + layout_blocked_filp, + layout_blocked_ea_swvr, + layout_blocked_adm, + layout_blocked_bdsp, + layout_blocked_mxch, + layout_blocked_ivaud, /* GTA IV .ivaud blocks */ + layout_blocked_ps2_iab, + layout_blocked_vs_str, + layout_blocked_rws, + layout_blocked_hwas, + layout_blocked_ea_sns, /* newest Electronic Arts blocks, found in SNS/SNU/SPS/etc formats */ + layout_blocked_awc, /* Rockstar AWC */ + layout_blocked_vgs, /* Guitar Hero II (PS2) */ + layout_blocked_xwav, + layout_blocked_xvag_subsong, /* XVAG subsongs [God of War III (PS4)] */ + layout_blocked_ea_wve_au00, /* EA WVE au00 blocks */ + layout_blocked_ea_wve_ad10, /* EA WVE Ad10 blocks */ + layout_blocked_sthd, /* Dream Factory STHD */ + layout_blocked_h4m, /* H4M video */ + layout_blocked_xa_aiff, /* XA in AIFF files [Crusader: No Remorse (SAT), Road Rash (3DO)] */ + layout_blocked_vs_square, + layout_blocked_vid1, + layout_blocked_ubi_sce, + layout_blocked_tt_ad, + + /* otherwise odd */ + layout_segmented, /* song divided in segments (song sections) */ + layout_layered, /* song divided in layers (song channels) */ +} \ No newline at end of file diff --git a/VG Music Studio - Core/Codec/MetaEnums.cs b/VG Music Studio - Core/Codec/MetaEnums.cs new file mode 100644 index 0000000..e4cb65a --- /dev/null +++ b/VG Music Studio - Core/Codec/MetaEnums.cs @@ -0,0 +1,479 @@ +namespace Kermalis.VGMusicStudio.Core.Codec; + +/* This code has been copied directly from vgmstream.h in VGMStream's repository * + * and modified into C# code to work with VGMS. Link to its repository can be * + * found here: https://github.com/vgmstream/vgmstream */ +public enum MetaType +{ + meta_SILENCE, + + meta_DSP_STD, /* Nintendo standard GC ADPCM (DSP) header */ + meta_DSP_CSTR, /* Star Fox Assault "Cstr" */ + meta_DSP_RS03, /* Retro: Metroid Prime 2 "RS03" */ + meta_DSP_STM, /* Paper Mario 2 STM */ + meta_AGSC, /* Retro: Metroid Prime 2 title */ + meta_CSMP, /* Retro: Metroid Prime 3 (Wii), Donkey Kong Country Returns (Wii) */ + meta_RFRM, /* Retro: Donkey Kong Country Tropical Freeze (Wii U) */ + meta_DSP_MPDSP, /* Monopoly Party single header stereo */ + meta_DSP_JETTERS, /* Bomberman Jetters .dsp */ + meta_DSP_MSS, /* Free Radical GC games */ + meta_DSP_GCM, /* some of Traveller's Tales games */ + meta_DSP_STR, /* Conan .str files */ + meta_DSP_SADB, /* Procyon Studio Digtial Sound Elements DSP-ADPCM (Wii) .sad */ + meta_DSP_WSI, /* .wsi */ + meta_IDSP_TT, /* Traveller's Tales games */ + meta_DSP_WII_MUS, /* .mus */ + meta_DSP_WII_WSD, /* Phantom Brave (Wii) */ + meta_WII_NDP, /* Vertigo (Wii) */ + meta_DSP_YGO, /* Konami: Yu-Gi-Oh! The Falsebound Kingdom (NGC), Hikaru no Go 3 (NGC) */ + + meta_STRM, /* Nintendo/HAL Labs Nitro Soundmaker STRM */ + meta_RSTM, /* Nintendo/HAL Labs NW4R Soundmaker RSTM (Revolution Stream, similar to STRM) */ + meta_AFC, /* AFC */ + meta_AST, /* AST */ + meta_RWSD, /* Nintendo/HAL Labs NW4R Soundmaker single-stream RWSD */ + meta_RWAR, /* Nintendo/HAL Labs NW4R Soundmaker single-stream RWAR */ + meta_RWAV, /* Nintendo/HAL Labs NW4R Soundmaker contents of RWAR */ + meta_CWAV, /* Nintendo/HAL Labs NW4C Soundmaker contents of CWAR */ + meta_FWAV, /* Nintendo/HAL Labs NW4F Soundmaker contents of FWAR */ + meta_THP, /* THP movie files */ + meta_SWAV, + meta_NDS_RRDS, /* Ridge Racer DS */ + meta_WII_BNS, /* Wii BNS Banner Sound (similar to RSTM) */ + meta_WIIU_BTSND, /* Wii U Boot Sound */ + + meta_ADX_03, /* CRI ADX "type 03" */ + meta_ADX_04, /* CRI ADX "type 04" */ + meta_ADX_05, /* CRI ADX "type 05" */ + meta_AIX, /* CRI AIX */ + meta_AAX, /* CRI AAX */ + meta_UTF_DSP, /* CRI ADPCM_WII, like AAX with DSP */ + + meta_DTK, + meta_RSF, + meta_HALPST, /* HAL Labs HALPST */ + meta_GCSW, /* GCSW (PCM) */ + meta_CAF, /* tri-Crescendo CAF */ + meta_MYSPD, /* U-Sing .myspd */ + meta_HIS, /* Her Ineractive .his */ + meta_BNSF, /* Bandai Namco Sound Format */ + + meta_XA, /* CD-ROM XA */ + meta_ADS, + meta_NPS, + meta_RXWS, + meta_RAW_INT, + meta_EXST, + meta_SVAG_KCET, + meta_PS_HEADERLESS, /* headerless PS-ADPCM */ + meta_MIB_MIH, + meta_PS2_MIC, /* KOEI MIC File */ + meta_PS2_VAGi, /* VAGi Interleaved File */ + meta_PS2_VAGp, /* VAGp Mono File */ + meta_PS2_pGAV, /* VAGp with Little Endian Header */ + meta_PS2_VAGp_AAAP, /* Acclaim Austin Audio VAG header */ + meta_SEB, + meta_STR_WAV, /* Blitz Games STR+WAV files */ + meta_ILD, + meta_PS2_PNB, /* PsychoNauts Bgm File */ + meta_VPK, /* VPK Audio File */ + meta_PS2_BMDX, /* Beatmania thing */ + meta_PS2_IVB, /* Langrisser 3 IVB */ + meta_PS2_SND, /* some Might & Magics SSND header */ + meta_SVS, /* Square SVS */ + meta_XSS, /* Dino Crisis 3 */ + meta_SL3, /* Test Drive Unlimited */ + meta_HGC1, /* Knights of the Temple 2 */ + meta_AUS, /* Various Capcom games */ + meta_RWS, /* RenderWare games (only when using RW Audio middleware) */ + meta_FSB1, /* FMOD Sample Bank, version 1 */ + meta_FSB2, /* FMOD Sample Bank, version 2 */ + meta_FSB3, /* FMOD Sample Bank, version 3.0/3.1 */ + meta_FSB4, /* FMOD Sample Bank, version 4 */ + meta_FSB5, /* FMOD Sample Bank, version 5 */ + meta_RWX, /* Air Force Delta Storm (XBOX) */ + meta_XWB, /* Microsoft XACT framework (Xbox, X360, Windows) */ + meta_PS2_XA30, /* Driver - Parallel Lines (PS2) */ + meta_MUSC, /* Krome PS2 games */ + meta_MUSX, + meta_LEG, /* Legaia 2 [no header_id] */ + meta_FILP, /* Resident Evil - Dead Aim */ + meta_IKM, + meta_STER, + meta_BG00, /* Ibara, Mushihimesama */ + meta_PS2_RSTM, /* Midnight Club 3 */ + meta_PS2_KCES, /* Dance Dance Revolution */ + meta_HXD, + meta_VSV, + meta_SCD_PCM, /* Lunar - Eternal Blue */ + meta_PS2_PCM, /* Konami KCEJ East: Ephemeral Fantasia, Yu-Gi-Oh! The Duelists of the Roses, 7 Blades */ + meta_PS2_RKV, /* Legacy of Kain - Blood Omen 2 (PS2) */ + meta_PS2_VAS, /* Pro Baseball Spirits 5 */ + meta_PS2_ENTH, /* Enthusia */ + meta_SDT, /* Baldur's Gate - Dark Alliance */ + meta_NGC_TYDSP, /* Ty - The Tasmanian Tiger */ + meta_DC_STR, /* SEGA Stream Asset Builder */ + meta_DC_STR_V2, /* variant of SEGA Stream Asset Builder */ + meta_NGC_BH2PCM, /* Bio Hazard 2 */ + meta_SAP, + meta_DC_IDVI, /* Eldorado Gate */ + meta_KRAW, /* Geometry Wars - Galaxies */ + meta_PS2_OMU, /* PS2 Int file with Header */ + meta_PS2_XA2, /* XG3 Extreme-G Racing */ + meta_NUB, + meta_IDSP_NL, /* Mario Strikers Charged (Wii) */ + meta_IDSP_IE, /* Defencer (GC) */ + meta_SPT_SPD, /* Various (SPT+SPT DSP) */ + meta_ISH_ISD, /* Various (ISH+ISD DSP) */ + meta_GSP_GSB, /* Tecmo games (Super Swing Golf 1 & 2, Quamtum Theory) */ + meta_YDSP, /* WWE Day of Reckoning */ + meta_FFCC_STR, /* Final Fantasy: Crystal Chronicles */ + meta_UBI_JADE, /* Beyond Good & Evil, Rayman Raving Rabbids */ + meta_GCA, /* Metal Slug Anthology */ + meta_NGC_SSM, /* Golden Gashbell Full Power */ + meta_PS2_JOE, /* Wall-E / Pixar games */ + meta_NGC_YMF, /* WWE WrestleMania X8 */ + meta_SADL, + meta_PS2_CCC, /* Tokyo Xtreme Racer DRIFT 2 */ + meta_FAG, /* Jackie Chan - Stuntmaster */ + meta_PS2_MIHB, /* Merged MIH+MIB */ + meta_NGC_PDT, /* Mario Party 6 */ + meta_DC_ASD, /* Miss Moonligh */ + meta_NAOMI_SPSD, /* Guilty Gear X */ + meta_RSD, + meta_PS2_ASS, /* ASS */ + meta_SEG, /* Eragon */ + meta_NDS_STRM_FFTA2, /* Final Fantasy Tactics A2 */ + meta_KNON, + meta_ZWDSP, /* Zack and Wiki */ + meta_VGS, /* Guitar Hero Encore - Rocks the 80s */ + meta_DCS_WAV, + meta_SMP, + meta_WII_SNG, /* Excite Trucks */ + meta_MUL, + meta_SAT_BAKA, /* Crypt Killer */ + meta_VSF, + meta_PS2_VSF_TTA, /* Tiny Toon Adventures: Defenders of the Universe */ + meta_ADS_MIDWAY, + meta_PS2_SPS, /* Ape Escape 2 */ + meta_PS2_XA2_RRP, /* RC Revenge Pro */ + meta_NGC_DSP_KONAMI, /* Konami DSP header, found in various games */ + meta_UBI_CKD, /* Ubisoft CKD RIFF header (Rayman Origins Wii) */ + meta_RAW_WAVM, + meta_WVS, + meta_XBOX_MATX, /* XBOX MATX */ + meta_XMU, + meta_XVAS, + meta_EA_SCHL, /* Electronic Arts SCHl with variable header */ + meta_EA_SCHL_fixed, /* Electronic Arts SCHl with fixed header */ + meta_EA_BNK, /* Electronic Arts BNK */ + meta_EA_1SNH, /* Electronic Arts 1SNh/EACS */ + meta_EA_EACS, + meta_RAW_PCM, + meta_GENH, /* generic header */ + meta_AIFC, /* Audio Interchange File Format AIFF-C */ + meta_AIFF, /* Audio Interchange File Format */ + meta_STR_SNDS, /* .str with SNDS blocks and SHDR header */ + meta_WS_AUD, /* Westwood Studios .aud */ + meta_WS_AUD_old, /* Westwood Studios .aud, old style */ + meta_RIFF_WAVE, /* RIFF, for WAVs */ + meta_RIFF_WAVE_POS, /* .wav + .pos for looping (Ys Complete PC) */ + meta_RIFF_WAVE_labl, /* RIFF w/ loop Markers in LIST-adtl-labl */ + meta_RIFF_WAVE_smpl, /* RIFF w/ loop data in smpl chunk */ + meta_RIFF_WAVE_wsmp, /* RIFF w/ loop data in wsmp chunk */ + meta_RIFF_WAVE_MWV, /* .mwv RIFF w/ loop data in ctrl chunk pflt */ + meta_RIFX_WAVE, /* RIFX, for big-endian WAVs */ + meta_RIFX_WAVE_smpl, /* RIFX w/ loop data in smpl chunk */ + meta_XNB, /* XNA Game Studio 4.0 */ + meta_PC_MXST, /* Lego Island MxSt */ + meta_SAB, /* Worms 4 Mayhem SAB+SOB file */ + meta_NWA, /* Visual Art's NWA */ + meta_NWA_NWAINFOINI, /* Visual Art's NWA w/ NWAINFO.INI for looping */ + meta_NWA_GAMEEXEINI, /* Visual Art's NWA w/ Gameexe.ini for looping */ + meta_SAT_DVI, /* Konami KCE Nagoya DVI (SAT games) */ + meta_DC_KCEY, /* Konami KCE Yokohama KCEYCOMP (DC games) */ + meta_ACM, /* InterPlay ACM header */ + meta_MUS_ACM, /* MUS playlist of InterPlay ACM files */ + meta_DEC, /* Falcom PC games (Xanadu Next, Gurumin) */ + meta_VS, /* Men in Black .vs */ + meta_FFXI_BGW, /* FFXI (PC) BGW */ + meta_FFXI_SPW, /* FFXI (PC) SPW */ + meta_STS, + meta_PS2_P2BT, /* Pop'n'Music 7 Audio File */ + meta_PS2_GBTS, /* Pop'n'Music 9 Audio File */ + meta_NGC_DSP_IADP, /* Gamecube Interleave DSP */ + meta_PS2_TK5, /* Tekken 5 Stream Files */ + meta_PS2_MCG, /* Gunvari MCG Files (was name .GCM on disk) */ + meta_ZSD, /* Dragon Booster ZSD */ + meta_REDSPARK, /* "RedSpark" RSD (MadWorld) */ + meta_IVAUD, /* .ivaud GTA IV */ + meta_NDS_HWAS, /* Spider-Man 3, Tony Hawk's Downhill Jam, possibly more... */ + meta_NGC_LPS, /* Rave Master (Groove Adventure Rave)(GC) */ + meta_NAOMI_ADPCM, /* NAOMI/NAOMI2 ARcade games */ + meta_SD9, /* beatmaniaIIDX16 - EMPRESS (Arcade) */ + meta_2DX9, /* beatmaniaIIDX16 - EMPRESS (Arcade) */ + meta_PS2_VGV, /* Rune: Viking Warlord */ + meta_GCUB, + meta_MAXIS_XA, /* Sim City 3000 (PC) */ + meta_NGC_SCK_DSP, /* Scorpion King (NGC) */ + meta_CAFF, /* iPhone .caf */ + meta_EXAKT_SC, /* Activision EXAKT .SC (PS2) */ + meta_WII_WAS, /* DiRT 2 (WII) */ + meta_PONA_3DO, /* Policenauts (3DO) */ + meta_PONA_PSX, /* Policenauts (PSX) */ + meta_XBOX_HLWAV, /* Half Life 2 (XBOX) */ + meta_AST_MV, + meta_AST_MMV, + meta_DMSG, /* Nightcaster II - Equinox (XBOX) */ + meta_NGC_DSP_AAAP, /* Turok: Evolution (NGC), Vexx (NGC) */ + meta_PS2_WB, /* Shooting Love. ~TRIZEAL~ */ + meta_S14, /* raw Siren 14, 24kbit mono */ + meta_SSS, /* raw Siren 14, 48kbit stereo */ + meta_PS2_GCM, /* NamCollection */ + meta_PS2_SMPL, /* Homura */ + meta_PS2_MSA, /* Psyvariar -Complete Edition- */ + meta_PS2_VOI, /* RAW Danger (Zettaizetsumei Toshi 2 - Itetsuita Kiokutachi) [PS2] */ + meta_P3D, /* Prototype P3D */ + meta_PS2_TK1, /* Tekken (NamCollection) */ + meta_NGC_RKV, /* Legacy of Kain - Blood Omen 2 (GC) */ + meta_DSP_DDSP, /* Various (2 dsp files stuck together */ + meta_NGC_DSP_MPDS, /* Big Air Freestyle, Terminator 3 */ + meta_DSP_STR_IG, /* Micro Machines, Superman Superman: Shadow of Apokolis */ + meta_EA_SWVR, /* Future Cop L.A.P.D., Freekstyle */ + meta_PS2_B1S, /* 7 Wonders of the ancient world */ + meta_PS2_WAD, /* The golden Compass */ + meta_DSP_XIII, /* XIII, possibly more (Ubisoft header???) */ + meta_DSP_CABELAS, /* Cabelas games */ + meta_PS2_ADM, /* Dragon Quest V (PS2) */ + meta_LPCM_SHADE, + meta_DSP_BDSP, /* Ah! My Goddess */ + meta_PS2_VMS, /* Autobahn Raser - Police Madness */ + meta_XAU, /* XPEC Entertainment (Beat Down (PS2 Xbox), Spectral Force Chronicle (PS2)) */ + meta_GH3_BAR, /* Guitar Hero III Mobile .bar */ + meta_FFW, /* Freedom Fighters [NGC] */ + meta_DSP_DSPW, /* Sengoku Basara 3 [WII] */ + meta_PS2_JSTM, /* Tantei Jinguji Saburo - Kind of Blue (PS2) */ + meta_SQEX_SCD, /* Square-Enix SCD */ + meta_NGC_NST_DSP, /* Animaniacs [NGC] */ + meta_BAF, /* Bizarre Creations (Blur, James Bond) */ + meta_XVAG, /* Ratchet & Clank Future: Quest for Booty (PS3) */ + meta_CPS, + meta_MSF, + meta_PS3_PAST, /* Bakugan Battle Brawlers (PS3) */ + meta_SGXD, /* Sony: Folklore, Genji, Tokyo Jungle (PS3), Brave Story, Kurohyo (PSP) */ + meta_WII_RAS, /* Donkey Kong Country Returns (Wii) */ + meta_SPM, + meta_VGS_PS, + meta_PS2_IAB, /* Ueki no Housoku - Taosu ze Robert Juudan!! (PS2) */ + meta_VS_STR, /* The Bouncer */ + meta_LSF_N1NJ4N, /* .lsf n1nj4n Fastlane Street Racing (iPhone) */ + meta_XWAV, + meta_RAW_SNDS, + meta_PS2_WMUS, /* The Warriors (PS2) */ + meta_HYPERSCAN_KVAG, /* Hyperscan KVAG/BVG */ + meta_IOS_PSND, /* Crash Bandicoot Nitro Kart 2 (iOS) */ + meta_BOS_ADP, + meta_QD_ADP, + meta_EB_SFX, /* Excitebots .sfx */ + meta_EB_SF0, /* Excitebots .sf0 */ + meta_MTAF, + meta_PS2_VAG1, /* Metal Gear Solid 3 VAG1 */ + meta_PS2_VAG2, /* Metal Gear Solid 3 VAG2 */ + meta_ALP, + meta_WPD, /* Shuffle! (PC) */ + meta_MN_STR, /* Mini Ninjas (PC/PS3/WII) */ + meta_MSS, /* Guerilla: ShellShock Nam '67 (PS2/Xbox), Killzone (PS2) */ + meta_PS2_HSF, /* Lowrider (PS2) */ + meta_IVAG, + meta_PS2_2PFS, /* Konami: Mahoromatic: Moetto - KiraKira Maid-San, GANTZ (PS2) */ + meta_PS2_VBK, /* Disney's Stitch - Experiment 626 */ + meta_OTM, /* Otomedius (Arcade) */ + meta_CSTM, /* Nintendo/HAL Labs NW4C Soundmaker CSTM (Century Stream) */ + meta_FSTM, /* Nintendo/HAL Labs NW4F Soundmaker FSTM (caFe? Stream) */ + meta_IDSP_NAMCO, + meta_KT_WIIBGM, /* Koei Tecmo WiiBGM */ + meta_KTSS, /* Koei Tecmo Nintendo Stream (KNS) */ + meta_MCA, /* Capcom MCA "MADP" */ + meta_XB3D_ADX, /* Xenoblade Chronicles 3D ADX */ + meta_HCA, /* CRI HCA */ + meta_SVAG_SNK, + meta_PS2_VDS_VDM, /* Graffiti Kingdom */ + meta_FFMPEG, + meta_FFMPEG_faulty, + meta_CXS, + meta_AKB, + meta_PASX, + meta_XMA_RIFF, + meta_ASTB, + meta_WWISE_RIFF, /* Audiokinetic Wwise RIFF/RIFX */ + meta_UBI_RAKI, /* Ubisoft RAKI header (Rayman Legends, Just Dance 2017) */ + meta_SXD, /* Sony SXD (Gravity Rush, Freedom Wars PSV) */ + meta_OGL, /* Shin'en Wii/WiiU (Jett Rocket (Wii), FAST Racing NEO (WiiU)) */ + meta_MC3, /* Paradigm games (T3 PS2, MX Rider PS2, MI: Operation Surma PS2) */ + meta_GHS, + meta_AAC_TRIACE, + meta_MTA2, + meta_NGC_ULW, /* Burnout 1 (GC only) */ + meta_XA_XA30, + meta_XA_04SW, + meta_TXTH, /* generic text header */ + meta_SK_AUD, /* Silicon Knights .AUD (Eternal Darkness GC) */ + meta_AHX, + meta_STM, /* Angel Studios/Rockstar San Diego Games */ + meta_BINK, /* RAD Game Tools BINK audio/video */ + meta_EA_SNU, /* Electronic Arts SNU (Dead Space) */ + meta_AWC, /* Rockstar AWC (GTA5, RDR) */ + meta_OPUS, /* Nintendo Opus [Lego City Undercover (Switch)] */ + meta_RAW_AL, + meta_PC_AST, /* Dead Rising (PC) */ + meta_NAAC, /* Namco AAC (3DS) */ + meta_UBI_SB, /* Ubisoft banks */ + meta_EZW, /* EZ2DJ (Arcade) EZWAV */ + meta_VXN, /* Gameloft mobile games */ + meta_EA_SNR_SNS, /* Electronic Arts SNR+SNS (Burnout Paradise) */ + meta_EA_SPS, /* Electronic Arts SPS (Burnout Crash) */ + meta_VID1, + meta_PC_FLX, /* Ultima IX PC */ + meta_MOGG, /* Harmonix Music Systems MOGG Vorbis */ + meta_OGG_VORBIS, /* Ogg Vorbis */ + meta_OGG_SLI, /* Ogg Vorbis file w/ companion .sli for looping */ + meta_OPUS_SLI, /* Ogg Opus file w/ companion .sli for looping */ + meta_OGG_SFL, /* Ogg Vorbis file w/ .sfl (RIFF SFPL) for looping */ + meta_OGG_KOVS, /* Ogg Vorbis with header and encryption (Koei Tecmo Games) */ + meta_OGG_encrypted, /* Ogg Vorbis with encryption */ + meta_KMA9, /* Koei Tecmo [Nobunaga no Yabou - Souzou (Vita)] */ + meta_XWC, /* Starbreeze games */ + meta_SQEX_SAB, /* Square-Enix newest middleware (sound) */ + meta_SQEX_MAB, /* Square-Enix newest middleware (music) */ + meta_WAF, /* KID WAF [Ever 17 (PC)] */ + meta_WAVE, /* EngineBlack games [Mighty Switch Force! (3DS)] */ + meta_WAVE_segmented, /* EngineBlack games, segmented [Shantae and the Pirate's Curse (PC)] */ + meta_SMV, /* Cho Aniki Zero (PSP) */ + meta_NXAP, /* Nex Entertainment games [Time Crisis 4 (PS3), Time Crisis Razing Storm (PS3)] */ + meta_EA_WVE_AU00, /* Electronic Arts PS movies [Future Cop - L.A.P.D. (PS), Supercross 2000 (PS)] */ + meta_EA_WVE_AD10, /* Electronic Arts PS movies [Wing Commander 3/4 (PS)] */ + meta_STHD, /* STHD .stx [Kakuto Chojin (Xbox)] */ + meta_MP4, /* MP4/AAC */ + meta_PCM_SRE, /* .PCM+SRE [Viewtiful Joe (PS2)] */ + meta_DSP_MCADPCM, /* Skyrim (Switch) */ + meta_UBI_LYN, /* Ubisoft LyN engine [The Adventures of Tintin (multi)] */ + meta_MSB_MSH, /* sfx companion of MIH+MIB */ + meta_TXTP, /* generic text playlist */ + meta_SMC_SMH, /* Wangan Midnight (System 246) */ + meta_PPST, /* PPST [Parappa the Rapper (PSP)] */ + meta_SPS_N1, + meta_UBI_BAO, /* Ubisoft BAO */ + meta_DSP_SWITCH_AUDIO, /* Gal Gun 2 (Switch) */ + meta_H4M, /* Hudson HVQM4 video [Resident Evil 0 (GC), Tales of Symphonia (GC)] */ + meta_ASF, /* Argonaut ASF [Croc 2 (PC)] */ + meta_XMD, /* Konami XMD [Silent Hill 4 (Xbox), Castlevania: Curse of Darkness (Xbox)] */ + meta_CKS, /* Cricket Audio stream [Part Time UFO (Android), Mega Man 1-6 (Android)] */ + meta_CKB, /* Cricket Audio bank [Fire Emblem Heroes (Android), Mega Man 1-6 (Android)] */ + meta_WV6, /* Gorilla Systems PC games */ + meta_WAVEBATCH, /* Firebrand Games */ + meta_HD3_BD3, /* Sony PS3 bank */ + meta_BNK_SONY, /* Sony Scream Tool bank */ + meta_SSCF, + meta_DSP_VAG, /* Penny-Punching Princess (Switch) sfx */ + meta_DSP_ITL, /* Charinko Hero (GC) */ + meta_A2M, /* Scooby-Doo! Unmasked (PS2) */ + meta_AHV, /* Headhunter (PS2) */ + meta_MSV, + meta_SDF, + meta_SVG, /* Hunter - The Reckoning - Wayward (PS2) */ + meta_VIS, /* AirForce Delta Strike (PS2) */ + meta_VAI, /* Ratatouille (GC) */ + meta_AIF_ASOBO, /* Ratatouille (PC) */ + meta_AO, /* Cloudphobia (PC) */ + meta_APC, /* MegaRace 3 (PC) */ + meta_WV2, /* Slave Zero (PC) */ + meta_XAU_KONAMI, /* Yu-Gi-Oh - The Dawn of Destiny (Xbox) */ + meta_DERF, /* Stupid Invaders (PC) */ + meta_SADF, + meta_UTK, + meta_NXA, + meta_ADPCM_CAPCOM, + meta_UE4OPUS, + meta_XWMA, + meta_VA3, /* DDR Supernova 2 AC */ + meta_XOPUS, + meta_VS_SQUARE, + meta_NWAV, + meta_XPCM, + meta_MSF_TAMASOFT, + meta_XPS_DAT, + meta_ZSND, + meta_DSP_ADPY, + meta_DSP_ADPX, + meta_OGG_OPUS, + meta_IMC, + meta_GIN, + meta_DSF, + meta_208, + meta_DSP_DS2, + meta_MUS_VC, + meta_STRM_ABYLIGHT, + meta_MSF_KONAMI, + meta_XWMA_KONAMI, + meta_9TAV, + meta_BWAV, + meta_RAD, + meta_SMACKER, + meta_MZRT, + meta_XAVS, + meta_PSF, + meta_DSP_ITL_i, + meta_IMA, + meta_XWV_VALVE, + meta_UBI_HX, + meta_BMP_KONAMI, + meta_ISB, + meta_XSSB, + meta_XMA_UE3, + meta_FWSE, + meta_FDA, + meta_TGC, + meta_KWB, + meta_LRMD, + meta_WWISE_FX, + meta_DIVA, + meta_IMUSE, + meta_KTSR, + meta_KAT, + meta_PCM_SUCCESS, + meta_ADP_KONAMI, + meta_SDRH, + meta_WADY, + meta_DSP_SQEX, + meta_DSP_WIIVOICE, + meta_SBK, + meta_DSP_WIIADPCM, + meta_DSP_CWAC, + meta_COMPRESSWAVE, + meta_KTAC, + meta_MJB_MJH, + meta_BSNF, + meta_TAC, + meta_IDSP_TOSE, + meta_DSP_KWA, + meta_OGV_3RDEYE, + meta_PIFF_TPCM, + meta_WXD_WXH, + meta_BNK_RELIC, + meta_XSH_XSD_XSS, + meta_PSB, + meta_LOPU_FB, + meta_LPCM_FB, + meta_WBK, + meta_WBK_NSLB, + meta_DSP_APEX, + meta_MPEG, + meta_SSPF, + meta_S3V, + meta_ESF, + meta_ADM3, + meta_TT_AD, + meta_SNDZ, + meta_VAB, + meta_BIGRP, +} \ No newline at end of file diff --git a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs index 11670ce..d5483d2 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs @@ -1,10 +1,15 @@ -namespace Kermalis.VGMusicStudio.Core.NDS.DSE; +using Kermalis.VGMusicStudio.Core.Codec; +using Kermalis.VGMusicStudio.Core.Wii; +using System; + +namespace Kermalis.VGMusicStudio.Core.NDS.DSE; internal sealed class DSEChannel { public readonly byte Index; public DSETrack? Owner; + public string? SWDType; public EnvelopeState State; public byte RootKey; public byte Key; @@ -14,6 +19,7 @@ internal sealed class DSEChannel public ushort Timer; public uint NoteLength; public byte Volume; + public static readonly float Root12Of2 = MathF.Pow(2, 1f / 12); private int _pos; private short _prevLeft; @@ -32,14 +38,19 @@ internal sealed class DSEChannel private byte _decay2; private byte _release; - // PCM8, PCM16, ADPCM - private SWD.SampleBlock _sample; + // PCM8, PCM16, IMA-ADPCM, DSP-ADPCM + private SWD.SampleBlock? _sample; // PCM8, PCM16 private int _dataOffset; - // ADPCM - private ADPCMDecoder _adpcmDecoder; + // IMA-ADPCM + private IMAADPCM _adpcmDecoder; private short _adpcmLoopLastSample; private short _adpcmLoopStepIndex; + // DSP-ADPCM + private DSPADPCM _dspADPCM; + // PSG + private byte _psgDuty; + private int _psgCounter; public DSEChannel(byte i) { @@ -49,7 +60,23 @@ public DSEChannel(byte i) public bool StartPCM(SWD localswd, SWD masterswd, byte voice, int key, uint noteLength) { - SWD.IProgramInfo? programInfo = localswd.Programs?.ProgramInfos[voice]; + if (localswd == null) { SWDType = masterswd.Type; } + else { SWDType = localswd.Type; } + + SWD.IProgramInfo? programInfo = null; // Declaring Program Info Interface here, to ensure VGMS compiles + if (localswd == null) + { + // Failsafe to check if SWD.ProgramBank contains an instance, if it doesn't, it will be skipped + // This is especially important for initializing a main SWD before the local SWDs + // accompaning the SMDs with the same names are loaded in. + if (masterswd.Programs != null) { programInfo = masterswd.Programs!.ProgramInfos![voice]; } + } + else if (voice > localswd.Programs!.ProgramInfos!.Length) + { + programInfo = masterswd.Programs!.ProgramInfos![voice]; + } + else { programInfo = localswd.Programs!.ProgramInfos![voice]; } + if (programInfo is null) { return false; @@ -63,47 +90,82 @@ public bool StartPCM(SWD localswd, SWD masterswd, byte voice, int key, uint note continue; } + //if (_sample == null) { throw new NullReferenceException("Null Reference Exception:\n\nThere's no data associated with this Sample Block in this SWD. Please check to make sure the samples are being read correctly.\n\nCall Stack:"); } _sample = masterswd.Samples![split.SampleId]; Key = (byte)key; RootKey = split.SampleRootKey; - BaseTimer = (ushort)(NDSUtils.ARM7_CLOCK / _sample.WavInfo.SampleRate); - if (_sample.WavInfo.SampleFormat == SampleFormat.ADPCM) + if (_sample != null) { - _adpcmDecoder.Init(_sample.Data); + switch (SWDType) // Configures the base timer based on the specific console's sound framework and sample rate + { + case "wds ": throw new NotImplementedException("The base timer for the WDS type is not yet implemented."); // PlayStation + case "swdm": throw new NotImplementedException("The base timer for the SWDM type is not yet implemented."); // PlayStation 2 + case "swdl": BaseTimer = (ushort)(NDSUtils.ARM7_CLOCK / _sample.WavInfo!.SampleRate); break; // Nintendo DS // Time Base algorithm is the ARM7 CPU clock rate divided by SampleRate + case "swdb": BaseTimer = (ushort)(256 * 65536 / _sample.WavInfo!.SampleRate); break; // Wii // The AX Time Base algorithm is 256 multiplied by 65536, divided by SampleRate + } + if (_sample.WavInfo!.SampleFormat == SampleFormat.ADPCM) + { + _adpcmDecoder.Init(_sample.Data!); + } + if (masterswd.Type == "swdb") + { + _dspADPCM.Init(_sample.DSPADPCM.Data, _sample.DSPADPCM.Info); + } + //attackVolume = sample.WavInfo.AttackVolume == 0 ? split.AttackVolume : sample.WavInfo.AttackVolume; + //attack = sample.WavInfo.Attack == 0 ? split.Attack : sample.WavInfo.Attack; + //decay = sample.WavInfo.Decay == 0 ? split.Decay : sample.WavInfo.Decay; + //sustain = sample.WavInfo.Sustain == 0 ? split.Sustain : sample.WavInfo.Sustain; + //hold = sample.WavInfo.Hold == 0 ? split.Hold : sample.WavInfo.Hold; + //decay2 = sample.WavInfo.Decay2 == 0 ? split.Decay2 : sample.WavInfo.Decay2; + //release = sample.WavInfo.Release == 0 ? split.Release : sample.WavInfo.Release; + //attackVolume = split.AttackVolume == 0 ? sample.WavInfo.AttackVolume : split.AttackVolume; + //attack = split.Attack == 0 ? sample.WavInfo.Attack : split.Attack; + //decay = split.Decay == 0 ? sample.WavInfo.Decay : split.Decay; + //sustain = split.Sustain == 0 ? sample.WavInfo.Sustain : split.Sustain; + //hold = split.Hold == 0 ? sample.WavInfo.Hold : split.Hold; + //decay2 = split.Decay2 == 0 ? sample.WavInfo.Decay2 : split.Decay2; + //release = split.Release == 0 ? sample.WavInfo.Release : split.Release; + _attackVolume = split.AttackVolume == 0 ? _sample.WavInfo.AttackVolume == 0 ? (byte)0x7F : _sample.WavInfo.AttackVolume : split.AttackVolume; + _attack = split.Attack == 0 ? _sample.WavInfo.Attack == 0 ? (byte)0x7F : _sample.WavInfo.Attack : split.Attack; + _decay = split.Decay1 == 0 ? _sample.WavInfo.Decay1 == 0 ? (byte)0x7F : _sample.WavInfo.Decay1 : split.Decay1; + _sustain = split.Sustain == 0 ? _sample.WavInfo.Sustain == 0 ? (byte)0x7F : _sample.WavInfo.Sustain : split.Sustain; + _hold = split.Hold == 0 ? _sample.WavInfo.Hold == 0 ? (byte)0x7F : _sample.WavInfo.Hold : split.Hold; + _decay2 = split.Decay2 == 0 ? _sample.WavInfo.Decay2 == 0 ? (byte)0x7F : _sample.WavInfo.Decay2 : split.Decay2; + _release = split.Release == 0 ? _sample.WavInfo.Release == 0 ? (byte)0x7F : _sample.WavInfo.Release : split.Release; + DetermineEnvelopeStartingPoint(); + _pos = 0; + _prevLeft = _prevRight = 0; + NoteLength = noteLength; + return true; } - //attackVolume = sample.WavInfo.AttackVolume == 0 ? split.AttackVolume : sample.WavInfo.AttackVolume; - //attack = sample.WavInfo.Attack == 0 ? split.Attack : sample.WavInfo.Attack; - //decay = sample.WavInfo.Decay == 0 ? split.Decay : sample.WavInfo.Decay; - //sustain = sample.WavInfo.Sustain == 0 ? split.Sustain : sample.WavInfo.Sustain; - //hold = sample.WavInfo.Hold == 0 ? split.Hold : sample.WavInfo.Hold; - //decay2 = sample.WavInfo.Decay2 == 0 ? split.Decay2 : sample.WavInfo.Decay2; - //release = sample.WavInfo.Release == 0 ? split.Release : sample.WavInfo.Release; - //attackVolume = split.AttackVolume == 0 ? sample.WavInfo.AttackVolume : split.AttackVolume; - //attack = split.Attack == 0 ? sample.WavInfo.Attack : split.Attack; - //decay = split.Decay == 0 ? sample.WavInfo.Decay : split.Decay; - //sustain = split.Sustain == 0 ? sample.WavInfo.Sustain : split.Sustain; - //hold = split.Hold == 0 ? sample.WavInfo.Hold : split.Hold; - //decay2 = split.Decay2 == 0 ? sample.WavInfo.Decay2 : split.Decay2; - //release = split.Release == 0 ? sample.WavInfo.Release : split.Release; - _attackVolume = split.AttackVolume == 0 ? _sample.WavInfo.AttackVolume == 0 ? (byte)0x7F : _sample.WavInfo.AttackVolume : split.AttackVolume; - _attack = split.Attack == 0 ? _sample.WavInfo.Attack == 0 ? (byte)0x7F : _sample.WavInfo.Attack : split.Attack; - _decay = split.Decay == 0 ? _sample.WavInfo.Decay == 0 ? (byte)0x7F : _sample.WavInfo.Decay : split.Decay; - _sustain = split.Sustain == 0 ? _sample.WavInfo.Sustain == 0 ? (byte)0x7F : _sample.WavInfo.Sustain : split.Sustain; - _hold = split.Hold == 0 ? _sample.WavInfo.Hold == 0 ? (byte)0x7F : _sample.WavInfo.Hold : split.Hold; - _decay2 = split.Decay2 == 0 ? _sample.WavInfo.Decay2 == 0 ? (byte)0x7F : _sample.WavInfo.Decay2 : split.Decay2; - _release = split.Release == 0 ? _sample.WavInfo.Release == 0 ? (byte)0x7F : _sample.WavInfo.Release : split.Release; - DetermineEnvelopeStartingPoint(); - _pos = 0; - _prevLeft = _prevRight = 0; - NoteLength = noteLength; - return true; } return false; } + public void StartPSG(byte duty, uint noteDuration) + { + _sample!.WavInfo!.SampleFormat = SampleFormat.PSG; + _psgCounter = 0; + _psgDuty = duty; + BaseTimer = 8006; // NDSUtils.ARM7_CLOCK / 2093 + Start(noteDuration); + } + + private void Start(uint noteDuration) + { + State = EnvelopeState.One; + _velocity = -92544; + _pos = 0; + _prevLeft = _prevRight = 0; + NoteLength = noteDuration; + } + public void Stop() { - Owner?.Channels.Remove(this); + if (Owner is not null) + { + Owner.Channels.Remove(this); + } Owner = null; Volume = 0; } @@ -111,9 +173,9 @@ public void Stop() private bool CMDB1___sub_2074CA0() { bool b = true; - bool ge = _sample.WavInfo.EnvMult >= 0x7F; - bool ee = _sample.WavInfo.EnvMult == 0x7F; - if (_sample.WavInfo.EnvMult > 0x7F) + bool ge = _sample!.WavInfo!.EnvMulti >= 0x7F; + bool ee = _sample.WavInfo.EnvMulti == 0x7F; + if (_sample.WavInfo.EnvMulti > 0x7F) { ge = _attackVolume >= 0x7F; ee = _attackVolume == 0x7F; @@ -271,9 +333,9 @@ private void UpdateEnvelopePlan(byte targetVolume, int envelopeParam) else { _targetVolume = targetVolume; - _envelopeTimeLeft = _sample.WavInfo.EnvMult == 0 + _envelopeTimeLeft = _sample!.WavInfo!.EnvMulti == 0 ? DSEUtils.Duration32[envelopeParam] * 1_000 / 10_000 - : DSEUtils.Duration16[envelopeParam] * _sample.WavInfo.EnvMult * 1_000 / 10_000; + : DSEUtils.Duration16[envelopeParam] * _sample.WavInfo.EnvMulti * 1_000 / 10_000; _volumeIncrement = _envelopeTimeLeft == 0 ? 0 : ((targetVolume << 23) - _velocity) / _envelopeTimeLeft; } } @@ -292,80 +354,121 @@ public void Process(out short left, out short right) // prevLeft and prevRight are stored because numSamples can be 0. for (int i = 0; i < numSamples; i++) { - short samp; - switch (_sample.WavInfo.SampleFormat) + switch (SWDType) { - case SampleFormat.PCM8: - { - // If hit end - if (_dataOffset >= _sample.Data.Length) - { - if (_sample.WavInfo.Loop) - { - _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); - } - else - { - left = right = _prevLeft = _prevRight = 0; - Stop(); - return; - } - } - samp = (short)((sbyte)_sample.Data[_dataOffset++] << 8); - break; - } - case SampleFormat.PCM16: - { - // If hit end - if (_dataOffset >= _sample.Data.Length) + case "wds ": + case "swdm": + case "swdl": { - if (_sample.WavInfo.Loop) + short samp; + switch (_sample!.WavInfo!.SampleFormat) { - _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); + case SampleFormat.PCM8: + { + // If hit end + if (_dataOffset >= _sample.Data!.Length) + { + if (_sample.WavInfo.Loop) + { + _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); // DS counts LoopStart 32-bits (4 bytes) at a time, so LoopStart needs to be bigger + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } + } + samp = (short)((sbyte)_sample.Data[_dataOffset++] << 8); + break; + } + case SampleFormat.PCM16: + { + // If hit end + if (_dataOffset >= _sample.Data!.Length) + { + if (_sample.WavInfo.Loop) + { + _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } + } + samp = (short)(_sample.Data[_dataOffset++] | (_sample.Data[_dataOffset++] << 8)); + break; + } + case SampleFormat.ADPCM: + { + // If just looped + if (_adpcmDecoder!.DataOffset == _sample.WavInfo.LoopStart * 4 && !_adpcmDecoder.OnSecondNibble) + { + _adpcmLoopLastSample = _adpcmDecoder.LastSample; + _adpcmLoopStepIndex = _adpcmDecoder.StepIndex; + } + // If hit end + if (_adpcmDecoder.DataOffset >= _sample.Data!.Length && !_adpcmDecoder.OnSecondNibble) + { + if (_sample.WavInfo.Loop) + { + _adpcmDecoder.DataOffset = (int)(_sample.WavInfo.LoopStart * 4); + _adpcmDecoder.StepIndex = _adpcmLoopStepIndex; + _adpcmDecoder.LastSample = _adpcmLoopLastSample; + _adpcmDecoder.OnSecondNibble = false; + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } + } + samp = _adpcmDecoder.GetSample(); + break; + } + case SampleFormat.PSG: + { + samp = _psgCounter <= _psgDuty ? short.MinValue : short.MaxValue; + _psgCounter++; + if (_psgCounter >= 8) + { + _psgCounter = 0; + } + break; + } + default: samp = 0; break; } - else - { - left = right = _prevLeft = _prevRight = 0; - Stop(); - return; - } - } - samp = (short)(_sample.Data[_dataOffset++] | (_sample.Data[_dataOffset++] << 8)); - break; - } - case SampleFormat.ADPCM: - { - // If just looped - if (_adpcmDecoder.DataOffset == _sample.WavInfo.LoopStart * 4 && !_adpcmDecoder.OnSecondNibble) - { - _adpcmLoopLastSample = _adpcmDecoder.LastSample; - _adpcmLoopStepIndex = _adpcmDecoder.StepIndex; + samp = (short)(samp * Volume / 0x7F); + _prevLeft = (short)(samp * (-Panpot + 0x40) / 0x80); + _prevRight = (short)(samp * (Panpot + 0x40) / 0x80); + break; } - // If hit end - if (_adpcmDecoder.DataOffset >= _sample.Data.Length && !_adpcmDecoder.OnSecondNibble) + case "swdb": { - if (_sample.WavInfo.Loop) - { - _adpcmDecoder.DataOffset = (int)(_sample.WavInfo.LoopStart * 4); - _adpcmDecoder.StepIndex = _adpcmLoopStepIndex; - _adpcmDecoder.LastSample = _adpcmLoopLastSample; - _adpcmDecoder.OnSecondNibble = false; - } - else + // If hit end + if (_dataOffset >= DSPADPCM.NibblesToSamples((int)_sample!.WavInfo!.LoopEnd)) // Wii DSE always reads the LoopEnd address (in nibbles) when looping is enabled for a SWD entry, instead of reading until the end of the sample data { - left = right = _prevLeft = _prevRight = 0; - Stop(); - return; + if (_sample.WavInfo!.Loop) + { + _dataOffset = DSPADPCM.NibblesToSamples((int)_sample.WavInfo.LoopStart); // Wii values for LoopStart offset are counted in nibbles (4-bits or half a byte) at a time, but because DataOutput is using a 16-bit array, LoopStart value needs to be converted to a 16-bit PCM sample offset + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } } + short samp = _sample.DSPADPCM!.DataOutput![_dataOffset++]; // Since DataOutput is already a 16-bit array, only one array entry is needed per loop, no bitshifting needed either + samp = (short)(samp * Volume / 0x7F); + _prevLeft = (short)(samp * (-Panpot + 0x40) / 0x80); + _prevRight = (short)(samp * (Panpot + 0x40) / 0x80); + break; } - samp = _adpcmDecoder.GetSample(); - break; - } - default: samp = 0; break; } - samp = (short)(samp * Volume / 0x7F); - _prevLeft = (short)(samp * (-Panpot + 0x40) / 0x80); - _prevRight = (short)(samp * (Panpot + 0x40) / 0x80); } left = _prevLeft; right = _prevRight; diff --git a/VG Music Studio - Core/NDS/DSE/DSECommands.cs b/VG Music Studio - Core/NDS/DSE/DSECommands.cs index 76e8e5b..f173227 100644 --- a/VG Music Studio - Core/NDS/DSE/DSECommands.cs +++ b/VG Music Studio - Core/NDS/DSE/DSECommands.cs @@ -85,6 +85,14 @@ internal sealed class RestCommand : ICommand public uint Rest { get; set; } } +internal sealed class CheckIntervalCommand : ICommand +{ + public Color Color => Color.DarkViolet; + public string Label => "Check Interval"; + public string Arguments => Interval.ToString(); + + public uint Interval { get; set; } +} internal sealed class SkipBytesCommand : ICommand { public Color Color => Color.MediumVioletRed; diff --git a/VG Music Studio - Core/NDS/DSE/DSEConfig.cs b/VG Music Studio - Core/NDS/DSE/DSEConfig.cs index 6a68eed..eb36d83 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEConfig.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEConfig.cs @@ -7,29 +7,39 @@ namespace Kermalis.VGMusicStudio.Core.NDS.DSE; public sealed class DSEConfig : Config { - public readonly string BGMPath; - public readonly string[] BGMFiles; + public readonly string SMDPath; + public readonly string[] SMDFiles; + internal SMD.Header? Header; - internal DSEConfig(string bgmPath) + internal DSEConfig(string smdPath) { - BGMPath = bgmPath; - BGMFiles = Directory.GetFiles(bgmPath, "bgm*.smd", SearchOption.TopDirectoryOnly); - if (BGMFiles.Length == 0) + SMDPath = smdPath; + SMDFiles = Directory.GetFiles(smdPath, "*.smd", SearchOption.TopDirectoryOnly); + if (SMDFiles.Length == 0) { - throw new DSENoSequencesException(bgmPath); + throw new DSENoSequencesException(smdPath); } - // TODO: Big endian files - var songs = new List(BGMFiles.Length); - for (int i = 0; i < BGMFiles.Length; i++) + // TODO: Big endian for SMDS (PlayStation 1), and mixed endian for SMDM (PlayStation 2) + var songs = new List(SMDFiles.Length); + for (int i = 0; i < SMDFiles.Length; i++) { - using (FileStream stream = File.OpenRead(BGMFiles[i])) + using FileStream stream = File.OpenRead(SMDFiles[i]); + // This will read the SMD header to determine if the majority of the file is little endian or big endian + var r = new EndianBinaryReader(stream, ascii: true); + Header = new SMD.Header(r); + if (Header.Type == "smdl") { - var r = new EndianBinaryReader(stream, ascii: true); - SMD.Header header = r.ReadObject(); - char[] chars = header.Label.ToCharArray(); + char[] chars = Header.Label.ToCharArray(); EndianBinaryPrimitives.TrimNullTerminators(ref chars); - songs.Add(new Song(i, $"{Path.GetFileNameWithoutExtension(BGMFiles[i])} - {new string(chars)}")); + songs.Add(new Song(i, $"{Path.GetFileNameWithoutExtension(SMDFiles[i])} - {new string(chars)}")); + } + else if (Header.Type == "smdb") + { + r.Endianness = Endianness.BigEndian; + char[] chars = Header.Label.ToCharArray(); + EndianBinaryPrimitives.TrimNullTerminators(ref chars); + songs.Add(new Song(i, $"{Path.GetFileNameWithoutExtension(SMDFiles[i])} - {new string(chars)}")); } } Playlists.Add(new Playlist(Strings.PlaylistMusic, songs)); @@ -41,8 +51,8 @@ public override string GetGameName() } public override string GetSongName(int index) { - return index < 0 || index >= BGMFiles.Length + return index < 0 || index >= SMDFiles.Length ? index.ToString() - : '\"' + BGMFiles[index] + '\"'; + : '\"' + SMDFiles[index] + '\"'; } } diff --git a/VG Music Studio - Core/NDS/DSE/DSEEngine.cs b/VG Music Studio - Core/NDS/DSE/DSEEngine.cs index a7a933e..956be54 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEEngine.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEEngine.cs @@ -1,4 +1,6 @@ -namespace Kermalis.VGMusicStudio.Core.NDS.DSE; +using System; + +namespace Kermalis.VGMusicStudio.Core.NDS.DSE; public sealed class DSEEngine : Engine { @@ -8,11 +10,11 @@ public sealed class DSEEngine : Engine public override DSEMixer Mixer { get; } public override DSEPlayer Player { get; } - public DSEEngine(string bgmPath) + public DSEEngine(string[] SWDFiles, string bgmPath) { Config = new DSEConfig(bgmPath); Mixer = new DSEMixer(); - Player = new DSEPlayer(Config, Mixer); + Player = new DSEPlayer(SWDFiles, Config, Mixer); DSEInstance = this; Instance = this; diff --git a/VG Music Studio - Core/NDS/DSE/DSEEnums.cs b/VG Music Studio - Core/NDS/DSE/DSEEnums.cs index 911c5f0..e10ca02 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEEnums.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEEnums.cs @@ -15,7 +15,8 @@ internal enum EnvelopeState : byte internal enum SampleFormat : ushort { - PCM8 = 0x000, - PCM16 = 0x100, - ADPCM = 0x200, + PCM8 = 0, + PCM16 = 1, + ADPCM = 2, + PSG = 3 } diff --git a/VG Music Studio - Core/NDS/DSE/DSEExceptions.cs b/VG Music Studio - Core/NDS/DSE/DSEExceptions.cs index 82c22e9..149ee6d 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEExceptions.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEExceptions.cs @@ -4,11 +4,11 @@ namespace Kermalis.VGMusicStudio.Core.NDS.DSE; public sealed class DSENoSequencesException : Exception { - public string BGMPath { get; } + public string SMDPath { get; } internal DSENoSequencesException(string bgmPath) { - BGMPath = bgmPath; + SMDPath = bgmPath; } } diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs index 2cee106..3ef7158 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs @@ -1,5 +1,6 @@ using Kermalis.EndianBinaryIO; using Kermalis.VGMusicStudio.Core.Util; +using System; using System.Collections.Generic; using System.IO; @@ -12,51 +13,51 @@ internal sealed partial class DSELoadedSong : ILoadedSong public int LongestTrack; private readonly DSEPlayer _player; - private readonly SWD LocalSWD; + private readonly SWD? LocalSWD; private readonly byte[] SMDFile; + public SMD.SongChunk SongChunk; + public SMD.Header Header; public readonly DSETrack[] Tracks; public DSELoadedSong(DSEPlayer player, string bgm) { _player = player; + //StringComparison comparison = StringComparison.CurrentCultureIgnoreCase; - LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); - SMDFile = File.ReadAllBytes(bgm); - using (var stream = new MemoryStream(SMDFile)) + if (_player.LocalSWD != null) + { + LocalSWD = _player.LocalSWD; + } + else { - var r = new EndianBinaryReader(stream, ascii: true); - SMD.Header header = r.ReadObject(); - SMD.ISongChunk songChunk; - switch (header.Version) + // Check if a local SWD is accompaning a SMD + if (new FileInfo(Path.ChangeExtension(bgm, "swd")).Exists) { - case 0x402: - { - songChunk = r.ReadObject(); - break; - } - case 0x415: - { - songChunk = r.ReadObject(); - break; - } - default: throw new DSEInvalidHeaderVersionException(header.Version); + LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); // If it exists, this will be loaded as the local SWD } + } - Tracks = new DSETrack[songChunk.NumTracks]; - Events = new List[songChunk.NumTracks]; - for (byte trackIndex = 0; trackIndex < songChunk.NumTracks; trackIndex++) - { - long chunkStart = r.Stream.Position; - r.Stream.Position += 0x14; // Skip header - Tracks[trackIndex] = new DSETrack(trackIndex, (int)r.Stream.Position); + SMDFile = File.ReadAllBytes(bgm); + using var stream = new MemoryStream(SMDFile); + var r = new EndianBinaryReader(stream, ascii: true); + Header = new SMD.Header(r); + if (Header.Version != 0x415) { throw new DSEInvalidHeaderVersionException(Header.Version); } + SongChunk = new SMD.SongChunk(r); + + Tracks = new DSETrack[SongChunk.NumTracks]; + Events = new List[SongChunk.NumTracks]; + for (byte trackIndex = 0; trackIndex < SongChunk.NumTracks; trackIndex++) + { + long chunkStart = r.Stream.Position; + r.Stream.Position += 0x14; // Skip header + Tracks[trackIndex] = new DSETrack(trackIndex, (int)r.Stream.Position); - AddTrackEvents(trackIndex, r); + AddTrackEvents(trackIndex, r); - r.Stream.Position = chunkStart + 0xC; - uint chunkLength = r.ReadUInt32(); - r.Stream.Position += chunkLength; - r.Stream.Align(16); - } + r.Stream.Position = chunkStart + 0xC; + uint chunkLength = r.ReadUInt32(); + r.Stream.Position += chunkLength; + r.Stream.Align(4); } } } diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs index b37dde9..1e61984 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs @@ -1,4 +1,6 @@ using Kermalis.EndianBinaryIO; +using Kermalis.VGMusicStudio.Core.Util; +using Kermalis.VGMusicStudio.Core.Util.EndianBinaryExtras; using System; using System.Collections.Generic; using System.Linq; @@ -108,274 +110,973 @@ private void AddTrackEvents(byte trackIndex, EndianBinaryReader r) } case 0x94: { - lastRest = (uint)(r.ReadByte() | (r.ReadByte() << 8) | (r.ReadByte() << 16)); + lastRest = new EndianBinaryReaderExtras(r).ReadUInt24(); if (!EventExists(trackIndex, cmdOffset)) { AddEvent(trackIndex, cmdOffset, new RestCommand { Rest = lastRest }); } break; } - case 0x96: - case 0x97: - case 0x9A: - case 0x9B: - case 0x9F: - case 0xA2: - case 0xA3: - case 0xA6: - case 0xA7: - case 0xAD: - case 0xAE: - case 0xB7: - case 0xB8: - case 0xB9: - case 0xBA: - case 0xBB: - case 0xBD: - case 0xC1: - case 0xC2: - case 0xC4: - case 0xC5: - case 0xC6: - case 0xC7: - case 0xC8: - case 0xC9: - case 0xCA: - case 0xCC: - case 0xCD: - case 0xCE: - case 0xCF: - case 0xD9: - case 0xDA: - case 0xDE: - case 0xE6: - case 0xEB: - case 0xEE: - case 0xF4: - case 0xF5: - case 0xF7: - case 0xF9: - case 0xFA: - case 0xFB: - case 0xFC: - case 0xFD: - case 0xFE: - case 0xFF: + case 0x95: { + uint intervals = r.ReadByte(); if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + AddEvent(trackIndex, cmdOffset, new CheckIntervalCommand { Interval = intervals }); } break; } - case 0x98: + case 0x96: { if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new FinishCommand()); + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); } - cont = false; break; } - case 0x99: + case 0x97: { if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new LoopStartCommand { Offset = r.Stream.Position }); + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); } break; } - case 0xA0: + case 0x98: { - byte octave = r.ReadByte(); if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new OctaveSetCommand { Octave = octave }); + AddEvent(trackIndex, cmdOffset, new FinishCommand()); + r.Stream.Align(4); } + cont = false; break; } + case 0x99: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new LoopStartCommand { Offset = r.Stream.Position }); + } + break; + } + case 0x9A: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0x9B: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0x9C: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0x9D: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd }); + } + break; + } + case 0x9E: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd }); + } + break; + } + case 0x9F: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xA0: + { + byte octave = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new OctaveSetCommand { Octave = octave }); + } + break; + } case 0xA1: - { - sbyte change = r.ReadSByte(); - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new OctaveAddCommand { OctaveChange = change }); + sbyte change = r.ReadSByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new OctaveAddCommand { OctaveChange = change }); + } + break; + } + case 0xA2: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xA3: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; } - break; - } case 0xA4: + { + byte tempoArg = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new TempoCommand { Command = cmd, Tempo = tempoArg }); + } + break; + } case 0xA5: // The code for these two is identical - { - byte tempoArg = r.ReadByte(); - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new TempoCommand { Command = cmd, Tempo = tempoArg }); + byte tempoArg = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new TempoCommand { Command = cmd, Tempo = tempoArg }); + } + break; } - break; - } - case 0xAB: - { - byte[] bytes = new byte[1]; - r.ReadBytes(bytes); - if (!EventExists(trackIndex, cmdOffset)) + case 0xA6: { - AddEvent(trackIndex, cmdOffset, new SkipBytesCommand { Command = cmd, SkippedBytes = bytes }); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; } - break; - } - case 0xAC: - { - byte voice = r.ReadByte(); - if (!EventExists(trackIndex, cmdOffset)) + case 0xA7: { - AddEvent(trackIndex, cmdOffset, new VoiceCommand { Voice = voice }); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; } - break; - } - case 0xCB: - case 0xF8: - { - byte[] bytes = new byte[2]; - r.ReadBytes(bytes); - if (!EventExists(trackIndex, cmdOffset)) + case 0xA8: { - AddEvent(trackIndex, cmdOffset, new SkipBytesCommand { Command = cmd, SkippedBytes = bytes }); + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0xD7: - { - ushort bend = r.ReadUInt16(); - if (!EventExists(trackIndex, cmdOffset)) + case 0xA9: { - AddEvent(trackIndex, cmdOffset, new PitchBendCommand { Bend = bend }); + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0xE0: - { - byte volume = r.ReadByte(); - if (!EventExists(trackIndex, cmdOffset)) + case 0xAA: { - AddEvent(trackIndex, cmdOffset, new VolumeCommand { Volume = volume }); + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0xE3: - { - byte expression = r.ReadByte(); - if (!EventExists(trackIndex, cmdOffset)) + case 0xAB: { - AddEvent(trackIndex, cmdOffset, new ExpressionCommand { Expression = expression }); + byte[] bytes = new byte[1]; + r.ReadBytes(bytes); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new SkipBytesCommand { Command = cmd, SkippedBytes = bytes }); + } + break; } - break; - } - case 0xE8: - { - byte panArg = r.ReadByte(); - if (!EventExists(trackIndex, cmdOffset)) + case 0xAC: { - AddEvent(trackIndex, cmdOffset, new PanpotCommand { Panpot = (sbyte)(panArg - 0x40) }); + byte voice = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new VoiceCommand { Voice = voice }); + } + break; + } + case 0xAD: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xAE: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xAF: + { + byte[] args = new byte[3]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0x9D: case 0xB0: - case 0xC0: - { - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = Array.Empty() }); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd }); + } + break; } - break; - } - case 0x9C: - case 0xA9: - case 0xAA: case 0xB1: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xB2: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xB3: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xB4: + { + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xB5: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xB6: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xB7: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xB8: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xB9: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xBA: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xBB: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } case 0xBC: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xBD: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } case 0xBE: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xBF: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xC0: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xC1: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC2: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } case 0xC3: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xC4: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC5: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC6: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC7: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC8: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC9: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xCA: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xCB: + { + byte[] bytes = new byte[2]; + r.ReadBytes(bytes); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new SkipBytesCommand { Command = cmd, SkippedBytes = bytes }); + } + break; + } + case 0xCC: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xCD: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xCE: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xCF: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } case 0xD0: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xD1: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xD2: - case 0xDB: - case 0xDF: - case 0xE1: - case 0xE7: - case 0xE9: - case 0xEF: - case 0xF6: - { - byte[] args = new byte[1]; - r.ReadBytes(args); - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0xA8: - case 0xB4: case 0xD3: + { + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xD4: + { + byte[] args = new byte[3]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xD5: + { + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xD6: + { + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xD7: + { + ushort bend = r.ReadUInt16(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new PitchBendCommand { Bend = bend }); + } + break; + } case 0xD8: - case 0xF2: - { - byte[] args = new byte[2]; - r.ReadBytes(args); - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0xAF: - case 0xD4: - case 0xE2: - case 0xEA: - case 0xF3: - { - byte[] args = new byte[3]; - r.ReadBytes(args); - if (!EventExists(trackIndex, cmdOffset)) + case 0xD9: { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; } - break; - } - case 0xDD: - case 0xE5: - case 0xED: - case 0xF1: - { - byte[] args = new byte[4]; - r.ReadBytes(args); - if (!EventExists(trackIndex, cmdOffset)) + case 0xDA: { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xDB: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } case 0xDC: + { + byte[] args = new byte[5]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xDD: + { + byte[] args = new byte[4]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xDE: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xDF: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE0: + { + byte volume = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new VolumeCommand { Volume = volume }); + } + break; + } + case 0xE1: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE2: + { + byte[] args = new byte[3]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE3: + { + byte expression = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new ExpressionCommand { Expression = expression }); + } + break; + } case 0xE4: + { + byte[] args = new byte[5]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE5: + { + byte[] args = new byte[4]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE6: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xE7: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE8: + { + byte panArg = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new PanpotCommand { Panpot = (sbyte)(panArg - 0x40) }); + } + break; + } + case 0xE9: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xEA: + { + byte[] args = new byte[3]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xEB: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } case 0xEC: + { + byte[] args = new byte[5]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xED: + { + byte[] args = new byte[4]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xEE: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xEF: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xF0: - { - byte[] args = new byte[5]; - r.ReadBytes(args); - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + byte[] args = new byte[5]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xF1: + { + byte[] args = new byte[4]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xF2: + { + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xF3: + { + byte[] args = new byte[3]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xF4: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xF5: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xF6: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xF7: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xF8: + { + byte[] bytes = new byte[2]; + r.ReadBytes(bytes); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new SkipBytesCommand { Command = cmd, SkippedBytes = bytes }); + } + break; + } + case 0xF9: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFA: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFB: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFC: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFD: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFE: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFF: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; } - break; - } default: throw new DSEInvalidCMDException(trackIndex, (int)cmdOffset, cmd); } } @@ -429,26 +1130,58 @@ internal void SetCurTick(long ticks) goto finish; } - while (_player.TempoStack >= 240) + switch (Header.Type) { - _player.TempoStack -= 240; - for (int trackIndex = 0; trackIndex < Tracks.Length; trackIndex++) - { - DSETrack track = Tracks[trackIndex]; - if (!track.Stopped) + case "smdl": { - track.Tick(); - while (track.Rest == 0 && !track.Stopped) + while (_player.TempoStack >= 240) { - ExecuteNext(track); + _player.TempoStack -= 240; + for (int trackIndex = 0; trackIndex < Tracks.Length; trackIndex++) + { + DSETrack track = Tracks[trackIndex]; + if (!track.Stopped) + { + track.Tick(); + while (track.Rest == 0 && !track.Stopped) + { + ExecuteNext(track); + } + } + } + _player.ElapsedTicks++; + if (_player.ElapsedTicks == ticks) + { + goto finish; + } } + break; + } + case "smdb": + { + while (_player.TempoStack >= 120) + { + _player.TempoStack -= 120; + for (int trackIndex = 0; trackIndex < Tracks.Length; trackIndex++) + { + DSETrack track = Tracks[trackIndex]; + if (!track.Stopped) + { + track.Tick(); + while (track.Rest == 0 && !track.Stopped) + { + ExecuteNext(track); + } + } + } + _player.ElapsedTicks++; + if (_player.ElapsedTicks == ticks) + { + goto finish; + } + } + break; } - } - _player.ElapsedTicks++; - if (_player.ElapsedTicks == ticks) - { - goto finish; - } } _player.TempoStack += _player.Tempo; } diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs index 90e30e4..7a9b3bc 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs @@ -45,7 +45,7 @@ public void ExecuteNext(DSETrack track) channel.Stop(); track.Octave = (byte)(track.Octave + oct); - if (channel.StartPCM(LocalSWD, _player.MasterSWD, track.Voice, n + (12 * track.Octave), duration)) + if (channel.StartPCM(LocalSWD!, _player.MainSWD, track.Voice, n + (12 * track.Octave), duration)) { channel.NoteVelocity = cmd; channel.Owner = track; diff --git a/VG Music Studio - Core/NDS/DSE/DSEMixer.cs b/VG Music Studio - Core/NDS/DSE/DSEMixer.cs index 89f6c52..caec7c2 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEMixer.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEMixer.cs @@ -98,7 +98,7 @@ internal void ChannelTick() { chan.Volume = SDATUtils.GetChannelVolume(vol); chan.Panpot = chan.Owner.Panpot; - chan.Timer = SDATUtils.GetChannelTimer(chan.BaseTimer, pitch); + chan.Timer = DSEUtils.GetChannelTimer(chan.BaseTimer, pitch); } } } diff --git a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs index bfdcda2..242b723 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; namespace Kermalis.VGMusicStudio.Core.NDS.DSE; @@ -8,7 +9,8 @@ public sealed class DSEPlayer : Player private readonly DSEConfig _config; internal readonly DSEMixer DMixer; - internal readonly SWD MasterSWD; + internal readonly SWD MainSWD; + internal readonly SWD? LocalSWD; private DSELoadedSong? _loadedSong; internal byte Tempo; @@ -18,13 +20,17 @@ public sealed class DSEPlayer : Player public override ILoadedSong? LoadedSong => _loadedSong; protected override Mixer Mixer => DMixer; - public DSEPlayer(DSEConfig config, DSEMixer mixer) + public DSEPlayer(string[] SWDFiles, DSEConfig config, DSEMixer mixer) : base(192) { DMixer = mixer; _config = config; - MasterSWD = new SWD(Path.Combine(config.BGMPath, "bgm.swd")); + MainSWD = new SWD(SWDFiles[0]); + if (SWDFiles.Length > 1 ) + { + LocalSWD = new SWD(SWDFiles[1]); + } } public override void LoadSong(int index) @@ -35,7 +41,7 @@ public override void LoadSong(int index) } // If there's an exception, this will remain null - _loadedSong = new DSELoadedSong(this, _config.BGMFiles[index]); + _loadedSong = new DSELoadedSong(this, _config.SMDFiles[index]); _loadedSong.SetTicks(); } public override void UpdateSongState(SongState info) @@ -74,18 +80,42 @@ protected override bool Tick(bool playing, bool recording) DSELoadedSong s = _loadedSong!; bool allDone = false; - while (!allDone && TempoStack >= 240) + switch (_config.Header!.Type) { - TempoStack -= 240; - allDone = true; - for (int i = 0; i < s.Tracks.Length; i++) - { - TickTrack(s, s.Tracks[i], ref allDone); - } - if (DMixer.IsFadeDone()) - { - allDone = true; - } + case "smdl": + { + while (!allDone && TempoStack >= 240) + { + TempoStack -= 240; + allDone = true; + for (int i = 0; i < s.Tracks.Length; i++) + { + TickTrack(s, s.Tracks[i], ref allDone); + } + if (DMixer.IsFadeDone()) + { + allDone = true; + } + } + break; + } + case "smdb": + { + while (!allDone && TempoStack >= 120) // Wii tempo is 120 by default + { + TempoStack -= 120; + allDone = true; + for (int i = 0; i < s.Tracks.Length; i++) + { + TickTrack(s, s.Tracks[i], ref allDone); + } + if (DMixer.IsFadeDone()) + { + allDone = true; + } + } + break; + } } if (!allDone) { diff --git a/VG Music Studio - Core/NDS/DSE/DSETrack.cs b/VG Music Studio - Core/NDS/DSE/DSETrack.cs index a15e380..5bf092d 100644 --- a/VG Music Studio - Core/NDS/DSE/DSETrack.cs +++ b/VG Music Studio - Core/NDS/DSE/DSETrack.cs @@ -96,6 +96,7 @@ public void UpdateSongState(SongState.Track tin) for (int j = 0; j < channels.Length; j++) { DSEChannel c = channels[j]; + c ??= new DSEChannel((byte)j); // Failsafe in the rare event that the c variable becomes null if (!DSEUtils.IsStateRemovable(c.State)) { tin.Keys[numKeys++] = c.Key; diff --git a/VG Music Studio - Core/NDS/DSE/DSEUtils.cs b/VG Music Studio - Core/NDS/DSE/DSEUtils.cs index 8264b31..ceb5287 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEUtils.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEUtils.cs @@ -1,4 +1,7 @@ -using System; +using Kermalis.EndianBinaryIO; +using Kermalis.VGMusicStudio.Core.Util; +using System; +using System.Diagnostics; namespace Kermalis.VGMusicStudio.Core.NDS.DSE; @@ -42,6 +45,199 @@ internal static class DSEUtils 0x000341B0, 0x000355F8, 0x00036A90, 0x00037F79, 0x000394B4, 0x0003AA41, 0x0003C021, 0x0003D654, 0x0003ECDA, 0x000403B5, 0x00041AE5, 0x0004326A, 0x00044A45, 0x00046277, 0x00047B00, 0x7FFFFFFF, }; + private static ReadOnlySpan PitchTable => new ushort[768] + { + 0, 59, 118, 178, 237, 296, 356, 415, + 475, 535, 594, 654, 714, 773, 833, 893, + 953, 1013, 1073, 1134, 1194, 1254, 1314, 1375, + 1435, 1496, 1556, 1617, 1677, 1738, 1799, 1859, + 1920, 1981, 2042, 2103, 2164, 2225, 2287, 2348, + 2409, 2471, 2532, 2593, 2655, 2716, 2778, 2840, + 2902, 2963, 3025, 3087, 3149, 3211, 3273, 3335, + 3397, 3460, 3522, 3584, 3647, 3709, 3772, 3834, + 3897, 3960, 4022, 4085, 4148, 4211, 4274, 4337, + 4400, 4463, 4526, 4590, 4653, 4716, 4780, 4843, + 4907, 4971, 5034, 5098, 5162, 5226, 5289, 5353, + 5417, 5481, 5546, 5610, 5674, 5738, 5803, 5867, + 5932, 5996, 6061, 6125, 6190, 6255, 6320, 6384, + 6449, 6514, 6579, 6645, 6710, 6775, 6840, 6906, + 6971, 7037, 7102, 7168, 7233, 7299, 7365, 7431, + 7496, 7562, 7628, 7694, 7761, 7827, 7893, 7959, + 8026, 8092, 8159, 8225, 8292, 8358, 8425, 8492, + 8559, 8626, 8693, 8760, 8827, 8894, 8961, 9028, + 9096, 9163, 9230, 9298, 9366, 9433, 9501, 9569, + 9636, 9704, 9772, 9840, 9908, 9976, 10045, 10113, + 10181, 10250, 10318, 10386, 10455, 10524, 10592, 10661, + 10730, 10799, 10868, 10937, 11006, 11075, 11144, 11213, + 11283, 11352, 11421, 11491, 11560, 11630, 11700, 11769, + 11839, 11909, 11979, 12049, 12119, 12189, 12259, 12330, + 12400, 12470, 12541, 12611, 12682, 12752, 12823, 12894, + 12965, 13036, 13106, 13177, 13249, 13320, 13391, 13462, + 13533, 13605, 13676, 13748, 13819, 13891, 13963, 14035, + 14106, 14178, 14250, 14322, 14394, 14467, 14539, 14611, + 14684, 14756, 14829, 14901, 14974, 15046, 15119, 15192, + 15265, 15338, 15411, 15484, 15557, 15630, 15704, 15777, + 15850, 15924, 15997, 16071, 16145, 16218, 16292, 16366, + 16440, 16514, 16588, 16662, 16737, 16811, 16885, 16960, + 17034, 17109, 17183, 17258, 17333, 17408, 17483, 17557, + 17633, 17708, 17783, 17858, 17933, 18009, 18084, 18160, + 18235, 18311, 18387, 18462, 18538, 18614, 18690, 18766, + 18842, 18918, 18995, 19071, 19147, 19224, 19300, 19377, + 19454, 19530, 19607, 19684, 19761, 19838, 19915, 19992, + 20070, 20147, 20224, 20302, 20379, 20457, 20534, 20612, + 20690, 20768, 20846, 20924, 21002, 21080, 21158, 21236, + 21315, 21393, 21472, 21550, 21629, 21708, 21786, 21865, + 21944, 22023, 22102, 22181, 22260, 22340, 22419, 22498, + 22578, 22658, 22737, 22817, 22897, 22977, 23056, 23136, + 23216, 23297, 23377, 23457, 23537, 23618, 23698, 23779, + 23860, 23940, 24021, 24102, 24183, 24264, 24345, 24426, + 24507, 24589, 24670, 24752, 24833, 24915, 24996, 25078, + 25160, 25242, 25324, 25406, 25488, 25570, 25652, 25735, + 25817, 25900, 25982, 26065, 26148, 26230, 26313, 26396, + 26479, 26562, 26645, 26729, 26812, 26895, 26979, 27062, + 27146, 27230, 27313, 27397, 27481, 27565, 27649, 27733, + 27818, 27902, 27986, 28071, 28155, 28240, 28324, 28409, + 28494, 28579, 28664, 28749, 28834, 28919, 29005, 29090, + 29175, 29261, 29346, 29432, 29518, 29604, 29690, 29776, + 29862, 29948, 30034, 30120, 30207, 30293, 30380, 30466, + 30553, 30640, 30727, 30814, 30900, 30988, 31075, 31162, + 31249, 31337, 31424, 31512, 31599, 31687, 31775, 31863, + 31951, 32039, 32127, 32215, 32303, 32392, 32480, 32568, + 32657, 32746, 32834, 32923, 33012, 33101, 33190, 33279, + 33369, 33458, 33547, 33637, 33726, 33816, 33906, 33995, + 34085, 34175, 34265, 34355, 34446, 34536, 34626, 34717, + 34807, 34898, 34988, 35079, 35170, 35261, 35352, 35443, + 35534, 35626, 35717, 35808, 35900, 35991, 36083, 36175, + 36267, 36359, 36451, 36543, 36635, 36727, 36820, 36912, + 37004, 37097, 37190, 37282, 37375, 37468, 37561, 37654, + 37747, 37841, 37934, 38028, 38121, 38215, 38308, 38402, + 38496, 38590, 38684, 38778, 38872, 38966, 39061, 39155, + 39250, 39344, 39439, 39534, 39629, 39724, 39819, 39914, + 40009, 40104, 40200, 40295, 40391, 40486, 40582, 40678, + 40774, 40870, 40966, 41062, 41158, 41255, 41351, 41448, + 41544, 41641, 41738, 41835, 41932, 42029, 42126, 42223, + 42320, 42418, 42515, 42613, 42710, 42808, 42906, 43004, + 43102, 43200, 43298, 43396, 43495, 43593, 43692, 43790, + 43889, 43988, 44087, 44186, 44285, 44384, 44483, 44583, + 44682, 44781, 44881, 44981, 45081, 45180, 45280, 45381, + 45481, 45581, 45681, 45782, 45882, 45983, 46083, 46184, + 46285, 46386, 46487, 46588, 46690, 46791, 46892, 46994, + 47095, 47197, 47299, 47401, 47503, 47605, 47707, 47809, + 47912, 48014, 48117, 48219, 48322, 48425, 48528, 48631, + 48734, 48837, 48940, 49044, 49147, 49251, 49354, 49458, + 49562, 49666, 49770, 49874, 49978, 50082, 50187, 50291, + 50396, 50500, 50605, 50710, 50815, 50920, 51025, 51131, + 51236, 51341, 51447, 51552, 51658, 51764, 51870, 51976, + 52082, 52188, 52295, 52401, 52507, 52614, 52721, 52827, + 52934, 53041, 53148, 53256, 53363, 53470, 53578, 53685, + 53793, 53901, 54008, 54116, 54224, 54333, 54441, 54549, + 54658, 54766, 54875, 54983, 55092, 55201, 55310, 55419, + 55529, 55638, 55747, 55857, 55966, 56076, 56186, 56296, + 56406, 56516, 56626, 56736, 56847, 56957, 57068, 57179, + 57289, 57400, 57511, 57622, 57734, 57845, 57956, 58068, + 58179, 58291, 58403, 58515, 58627, 58739, 58851, 58964, + 59076, 59189, 59301, 59414, 59527, 59640, 59753, 59866, + 59979, 60092, 60206, 60319, 60433, 60547, 60661, 60774, + 60889, 61003, 61117, 61231, 61346, 61460, 61575, 61690, + 61805, 61920, 62035, 62150, 62265, 62381, 62496, 62612, + 62727, 62843, 62959, 63075, 63191, 63308, 63424, 63540, + 63657, 63774, 63890, 64007, 64124, 64241, 64358, 64476, + 64593, 64711, 64828, 64946, 65064, 65182, 65300, 65418, + }; + private static ReadOnlySpan VolumeTable => new byte[724] + { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 7, + 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 10, 10, 10, + 10, 10, 10, 10, 10, 11, 11, 11, + 11, 11, 11, 11, 11, 12, 12, 12, + 12, 12, 12, 12, 13, 13, 13, 13, + 13, 13, 13, 14, 14, 14, 14, 14, + 14, 15, 15, 15, 15, 15, 16, 16, + 16, 16, 16, 16, 17, 17, 17, 17, + 17, 18, 18, 18, 18, 19, 19, 19, + 19, 19, 20, 20, 20, 20, 21, 21, + 21, 21, 22, 22, 22, 22, 23, 23, + 23, 23, 24, 24, 24, 25, 25, 25, + 25, 26, 26, 26, 27, 27, 27, 28, + 28, 28, 29, 29, 29, 30, 30, 30, + 31, 31, 31, 32, 32, 33, 33, 33, + 34, 34, 35, 35, 35, 36, 36, 37, + 37, 38, 38, 38, 39, 39, 40, 40, + 41, 41, 42, 42, 43, 43, 44, 44, + 45, 45, 46, 46, 47, 47, 48, 48, + 49, 50, 50, 51, 51, 52, 52, 53, + 54, 54, 55, 56, 56, 57, 58, 58, + 59, 60, 60, 61, 62, 62, 63, 64, + 65, 66, 66, 67, 68, 69, 70, 70, + 71, 72, 73, 74, 75, 75, 76, 77, + 78, 79, 80, 81, 82, 83, 84, 85, + 86, 87, 88, 89, 90, 91, 92, 93, + 94, 95, 96, 97, 98, 99, 101, 102, + 103, 104, 105, 106, 108, 109, 110, 111, + 113, 114, 115, 117, 118, 119, 121, 122, + 124, 125, 126, 127, + }; public static ReadOnlySpan FixedRests => new byte[0x10] { 96, 72, 64, 48, 36, 32, 24, 18, 16, 12, 9, 8, 6, 4, 3, 2, @@ -51,4 +247,108 @@ public static bool IsStateRemovable(EnvelopeState state) { return state is EnvelopeState.Two or >= EnvelopeState.Seven; } + + + #region FindChunk + internal static long FindChunk(EndianBinaryReader r, string chunk) + { + long pos = -1; + long oldPosition = r.Stream.Position; + r.Stream.Position = 0; + while (r.Stream.Position < r.Stream.Length) + { + string str = r.ReadString_Count(4); + if (str == chunk) + { + pos = r.Stream.Position - 4; + break; + } + switch (str) + { + case "swdb" or "swdl": + { + r.Stream.Position += 0x4C; + break; + } + case "smdb" or "smdl": + { + r.Stream.Position += 0x3C; + break; + } + default: + { + Debug.WriteLine($"Ignoring {str} chunk"); + r.Stream.Position += 0x8; + uint length = r.ReadUInt32(); + r.Stream.Position += length; + r.Stream.Align(16); + break; + } + } + } + r.Stream.Position = oldPosition; + return pos; + } + #endregion + + public static ushort GetChannelTimer(ushort baseTimer, int pitch) + { + int shift = 0; + pitch = -pitch; + + while (pitch < 0) + { + shift--; + pitch += 0x300; + } + + while (pitch >= 0x300) + { + shift++; + pitch -= 0x300; + } + + ulong timer = (PitchTable[pitch] + 0x10000uL) * baseTimer; + shift -= 16; + if (shift <= 0) + { + timer >>= -shift; + } + else if (shift < 32) + { + if ((timer & (ulong.MaxValue << (32 - shift))) != 0) + { + return ushort.MaxValue; + } + timer <<= shift; + } + else + { + return ushort.MaxValue; + } + + if (timer < 0x10) + { + return 0x10; + } + if (timer > ushort.MaxValue) + { + timer = ushort.MaxValue; + } + return (ushort)timer; + } + public static byte GetChannelVolume(int vol) + { + int a = vol / 0x80; + if (a < -723) + { + a = -723; + } + else if (a > 0) + { + a = 0; + } + return VolumeTable[a + 723]; + } + } diff --git a/VG Music Studio - Core/NDS/DSE/SMD.cs b/VG Music Studio - Core/NDS/DSE/SMD.cs index e9a9083..f3cc670 100644 --- a/VG Music Studio - Core/NDS/DSE/SMD.cs +++ b/VG Music Studio - Core/NDS/DSE/SMD.cs @@ -1,19 +1,21 @@ using Kermalis.EndianBinaryIO; +using Kermalis.VGMusicStudio.Core.Util; +using System.Collections.Generic; +using System.IO; namespace Kermalis.VGMusicStudio.Core.NDS.DSE; internal sealed class SMD { + + #region Header public sealed class Header // Size 0x40 { - [BinaryStringFixedLength(4)] - public string Type { get; set; } = null!; // "smdb" or "smdl" - [BinaryArrayFixedLength(4)] - public byte[] Unknown1 { get; set; } = null!; + public string Type { get; set; } // "smdb" or "smdl" + public byte[] Unknown1 { get; set; } public uint Length { get; set; } public ushort Version { get; set; } - [BinaryArrayFixedLength(10)] - public byte[] Unknown2 { get; set; } = null!; + public byte[] Unknown2 { get; set; } public ushort Year { get; set; } public byte Month { get; set; } public byte Day { get; set; } @@ -21,40 +23,86 @@ public sealed class Header // Size 0x40 public byte Minute { get; set; } public byte Second { get; set; } public byte Centisecond { get; set; } - [BinaryStringFixedLength(16)] - public string Label { get; set; } = null!; - [BinaryArrayFixedLength(16)] - public byte[] Unknown3 { get; set; } = null!; + public string Label { get; set; } + public byte[] Unknown3 { get; set; } + + public Header(EndianBinaryReader r) + { + Type = r.ReadString_Count(4); + + if (Type == "smdb") { r.Endianness = Endianness.BigEndian; } + + Unknown1 = new byte[4]; + r.ReadBytes(Unknown1); + + Length = r.ReadUInt32(); + + Version = r.ReadUInt16(); + + Unknown2 = new byte[10]; + r.ReadBytes(Unknown2); + + r.Endianness = Endianness.LittleEndian; + + Year = r.ReadUInt16(); + + Month = r.ReadByte(); + + Day = r.ReadByte(); + + Hour = r.ReadByte(); + + Minute = r.ReadByte(); + + Second = r.ReadByte(); + + Centisecond = r.ReadByte(); + + Label = r.ReadString_Count(16); + + Unknown3 = new byte[16]; + r.ReadBytes(Unknown3); + + if (Type == "smdb") { r.Endianness = Endianness.BigEndian; } + } } + #endregion + #region SongChunk public interface ISongChunk { byte NumTracks { get; } } - public sealed class SongChunk_V402 : ISongChunk // Size 0x20 + public sealed class SongChunk : ISongChunk // Size 0x40 { - [BinaryStringFixedLength(4)] - public string Type { get; set; } = null!; - [BinaryArrayFixedLength(16)] - public byte[] Unknown1 { get; set; } = null!; + public string Type { get; set; } + public byte[] Unknown1 { get; set; } + public ushort TicksPerQuarter { get; set; } + public byte[] Unknown2 { get; set; } public byte NumTracks { get; set; } - public byte NumChannels { get; set; } - [BinaryArrayFixedLength(4)] - public byte[] Unknown2 { get; set; } = null!; - public sbyte MasterVolume { get; set; } - public sbyte MasterPanpot { get; set; } - [BinaryArrayFixedLength(4)] - public byte[] Unknown3 { get; set; } = null!; - } - public sealed class SongChunk_V415 : ISongChunk // Size 0x40 - { - [BinaryStringFixedLength(4)] - public string Type { get; set; } = null!; - [BinaryArrayFixedLength(18)] - public byte[] Unknown1 { get; set; } = null!; - public byte NumTracks { get; set; } - public byte NumChannels { get; set; } - [BinaryArrayFixedLength(40)] - public byte[] Unknown2 { get; set; } = null!; + public byte Channel { get; set; } + public byte[] Unknown3 { get; set; } + + public SongChunk(EndianBinaryReader r) + { + Type = r.ReadString_Count(4); + + Unknown1 = new byte[14]; + r.ReadBytes(Unknown1); + + TicksPerQuarter = r.ReadUInt16(); + + Unknown2 = new byte[2]; + r.ReadBytes(Unknown2); + + NumTracks = r.ReadByte(); + + Channel = r.ReadByte(); + + Unknown3 = new byte[40]; + r.ReadBytes(Unknown3); + } } + #endregion + } diff --git a/VG Music Studio - Core/NDS/DSE/SWD.cs b/VG Music Studio - Core/NDS/DSE/SWD.cs index 90c28ad..c428e58 100644 --- a/VG Music Studio - Core/NDS/DSE/SWD.cs +++ b/VG Music Studio - Core/NDS/DSE/SWD.cs @@ -1,42 +1,29 @@ using Kermalis.EndianBinaryIO; +using Kermalis.VGMusicStudio.Core.Codec; using Kermalis.VGMusicStudio.Core.Util; using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; namespace Kermalis.VGMusicStudio.Core.NDS.DSE; internal sealed class SWD { + #region Header public interface IHeader { // } - private sealed class Header_V402 : IHeader // Size 0x40 + public class Header : IHeader // Size 0x40 { - [BinaryArrayFixedLength(8)] - public byte[] Unknown1 { get; set; } = null!; - public ushort Year { get; set; } - public byte Month { get; set; } - public byte Day { get; set; } - public byte Hour { get; set; } - public byte Minute { get; set; } - public byte Second { get; set; } - public byte Centisecond { get; set; } - [BinaryStringFixedLength(16)] - public string Label { get; set; } = null!; - [BinaryArrayFixedLength(22)] - public byte[] Unknown2 { get; set; } = null!; - public byte NumWAVISlots { get; set; } - public byte NumPRGISlots { get; set; } - public byte NumKeyGroups { get; set; } - [BinaryArrayFixedLength(7)] - public byte[] Padding { get; set; } = null!; - } - private sealed class Header_V415 : IHeader // Size 0x40 - { - [BinaryArrayFixedLength(8)] - public byte[] Unknown1 { get; set; } = null!; + public string Type { get; set; } + public byte[]? Unknown1 { get; set; } + public uint Length { get; set; } + public ushort Version { get; set; } + public byte[]? Unknown2 { get; set; } + public byte[]? Padding1 { get; set; } public ushort Year { get; set; } public byte Month { get; set; } public byte Day { get; set; } @@ -44,77 +31,154 @@ private sealed class Header_V415 : IHeader // Size 0x40 public byte Minute { get; set; } public byte Second { get; set; } public byte Centisecond { get; set; } - [BinaryStringFixedLength(16)] - public string Label { get; set; } = null!; - [BinaryArrayFixedLength(16)] - public byte[] Unknown2 { get; set; } = null!; + public string Label { get; set; } + public byte[]? Unknown3 { get; set; } public uint PCMDLength { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown3 { get; set; } = null!; + public byte[]? Unknown4 { get; set; } public ushort NumWAVISlots { get; set; } public ushort NumPRGISlots { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown4 { get; set; } = null!; + public byte NumKeyGroups { get; set; } + public byte[]? Unknown5 { get; set; } public uint WAVILength { get; set; } + public byte[]? Padding2 { get; set; } + + public Header(EndianBinaryReader r) + { + // File type metadata - The file type, version, and size of the file + Type = r.ReadString_Count(4); + if (Type.StartsWith("swd") == false) // Failsafe, to check if the file is a valid SWD file + { + throw new InvalidDataException("Invalid Data Exception:\nThis file is not a Wave Data (.SWD) file, please make sure the file extension is correct before opening.\nCall Stack:"); + } + if (Type == "swdb") + { + r.Endianness = Endianness.BigEndian; + } + Unknown1 = new byte[4]; + r.ReadBytes(Unknown1); + Length = r.ReadUInt32(); + Version = r.ReadUInt16(); + Unknown2 = new byte[2]; + r.ReadBytes(Unknown2); + + // Timestamp metadata - The time the SWD was published + r.Endianness = Endianness.LittleEndian; // Timestamp is always Little Endian, regardless of version or type, so it must be set to Little Endian to be read + + Padding1 = new byte[8]; // Padding + r.ReadBytes(Padding1); + Year = r.ReadUInt16(); // Year + Month = r.ReadByte(); // Month + Day = r.ReadByte(); // Day + Hour = r.ReadByte(); // Hour + Minute = r.ReadByte(); // Minute + Second = r.ReadByte(); // Second + Centisecond = r.ReadByte(); // Centisecond + if (Type == "swdb") { r.Endianness = Endianness.BigEndian; } // If type is swdb, restore back to Big Endian + + + // Info table + Label = r.ReadString_Count(16); + + switch (Version) // To ensure the version differences apply beyond this point + { + case 1026: + { + Unknown3 = new byte[22]; + r.ReadBytes(Unknown3); + + NumWAVISlots = r.ReadByte(); + + NumPRGISlots = r.ReadByte(); + + NumKeyGroups = r.ReadByte(); + + Padding2 = new byte[7]; + r.ReadBytes(Padding2); + + break; + } + case 1045: + { + Unknown3 = new byte[16]; + r.ReadBytes(Unknown3); + + PCMDLength = r.ReadUInt32(); + + Unknown4 = new byte[2]; + r.ReadBytes(Unknown4); + + NumWAVISlots = r.ReadUInt16(); + + NumPRGISlots = r.ReadUInt16(); + + Unknown5 = new byte[2]; + r.ReadBytes(Unknown5); + + WAVILength = r.ReadUInt32(); + + break; + } + } + } + } + + public class ChunkHeader : IHeader // Size 0x10 + { + public string Name { get; set; } + public byte[] Padding { get; set; } + public ushort Version { get; set; } + public uint ChunkBegin { get; set; } + public uint ChunkEnd { get; set; } + + public ChunkHeader(EndianBinaryReader r, long chunkOffset, SWD swd) + { + long oldOffset = r.Stream.Position; + r.Stream.Position = chunkOffset; + + // Chunk Name + Name = r.ReadString_Count(4); + + // Padding + Padding = new byte[2]; + r.ReadBytes(Padding); + + // Version + Version = r.ReadUInt16(); + + // Chunk Begin + r.Endianness = Endianness.LittleEndian; // To ensure this is read in Little Endian in all versions and types + ChunkBegin = r.ReadUInt32(); + if (swd.Type == "swdb") { r.Endianness = Endianness.BigEndian; } // To revert back to Big Endian when the type is "swdb" + + // Chunk End + ChunkEnd = r.ReadUInt32(); + + r.Stream.Position = oldOffset; + } } + #endregion + #region SplitEntry public interface ISplitEntry { byte LowKey { get; } byte HighKey { get; } - int SampleId { get; } + ushort SampleId { get; } byte SampleRootKey { get; } sbyte SampleTranspose { get; } byte AttackVolume { get; set; } byte Attack { get; set; } - byte Decay { get; set; } + byte Decay1 { get; set; } byte Sustain { get; set; } byte Hold { get; set; } byte Decay2 { get; set; } byte Release { get; set; } } - public sealed class SplitEntry_V402 : ISplitEntry // Size 0x30 - { - public ushort Id { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown1 { get; set; } = null!; - public byte LowKey { get; set; } - public byte HighKey { get; set; } - public byte LowKey2 { get; set; } - public byte HighKey2 { get; set; } - public byte LowVelocity { get; set; } - public byte HighVelocity { get; set; } - public byte LowVelocity2 { get; set; } - public byte HighVelocity2 { get; set; } - [BinaryArrayFixedLength(5)] - public byte[] Unknown2 { get; set; } = null!; - public byte SampleId { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown3 { get; set; } = null!; - public byte SampleRootKey { get; set; } - public sbyte SampleTranspose { get; set; } - public byte SampleVolume { get; set; } - public sbyte SamplePanpot { get; set; } - public byte KeyGroupId { get; set; } - [BinaryArrayFixedLength(15)] - public byte[] Unknown4 { get; set; } = null!; - public byte AttackVolume { get; set; } - public byte Attack { get; set; } - public byte Decay { get; set; } - public byte Sustain { get; set; } - public byte Hold { get; set; } - public byte Decay2 { get; set; } - public byte Release { get; set; } - public byte Unknown5 { get; set; } - - [BinaryIgnore] - int ISplitEntry.SampleId => SampleId; - } - public sealed class SplitEntry_V415 : ISplitEntry // 0x30 + public class SplitEntry : ISplitEntry // 0x30 { - public ushort Id { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown1 { get; set; } = null!; + public byte Unknown1 { get; set; } + public byte Id { get; set; } + public byte[] Unknown2 { get; set; } public byte LowKey { get; set; } public byte HighKey { get; set; } public byte LowKey2 { get; set; } @@ -123,80 +187,252 @@ public sealed class SplitEntry_V415 : ISplitEntry // 0x30 public byte HighVelocity { get; set; } public byte LowVelocity2 { get; set; } public byte HighVelocity2 { get; set; } - [BinaryArrayFixedLength(6)] - public byte[] Unknown2 { get; set; } = null!; + public byte[] Unknown3 { get; set; } public ushort SampleId { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown3 { get; set; } = null!; + public byte[] Unknown4 { get; set; } public byte SampleRootKey { get; set; } public sbyte SampleTranspose { get; set; } public byte SampleVolume { get; set; } public sbyte SamplePanpot { get; set; } public byte KeyGroupId { get; set; } - [BinaryArrayFixedLength(13)] - public byte[] Unknown4 { get; set; } = null!; + public byte[]? Unknown5 { get; set; } public byte AttackVolume { get; set; } public byte Attack { get; set; } - public byte Decay { get; set; } + public byte Decay1 { get; set; } public byte Sustain { get; set; } public byte Hold { get; set; } public byte Decay2 { get; set; } public byte Release { get; set; } - public byte Unknown5 { get; set; } + public byte Break { get; set; } + + ushort ISplitEntry.SampleId => SampleId; + + public SplitEntry(EndianBinaryReader r, SWD swd) + { + Unknown1 = r.ReadByte(); + + Id = r.ReadByte(); + + Unknown2 = new byte[2]; + r.ReadBytes(Unknown2); + + LowKey = r.ReadByte(); + + HighKey = r.ReadByte(); + + LowKey2 = r.ReadByte(); + + HighKey2 = r.ReadByte(); + + LowVelocity = r.ReadByte(); + + HighVelocity = r.ReadByte(); + + LowVelocity2 = r.ReadByte(); + + HighVelocity2 = r.ReadByte(); + + switch (swd.Version) + { + case 1026: + { + Unknown3 = new byte[5]; + r.ReadBytes(Unknown3); + + SampleId = r.ReadByte(); + + Unknown4 = new byte[2]; + r.ReadBytes(Unknown4); + + SampleRootKey = r.ReadByte(); + + SampleTranspose = r.ReadSByte(); + + SampleVolume = r.ReadByte(); + + SamplePanpot = r.ReadSByte(); + + KeyGroupId = r.ReadByte(); + + Unknown5 = new byte[15]; + r.ReadBytes(Unknown5); + + AttackVolume = r.ReadByte(); + + Attack = r.ReadByte(); + + Decay1 = r.ReadByte(); + + Sustain = r.ReadByte(); + + Hold = r.ReadByte(); + + Decay2 = r.ReadByte(); + + Release = r.ReadByte(); + + Break = r.ReadByte(); + + break; + } + case 1045: + { + Unknown2 = new byte[6]; + r.ReadBytes(Unknown2); + + SampleId = r.ReadUInt16(); - [BinaryIgnore] - int ISplitEntry.SampleId => SampleId; + Unknown3 = new byte[2]; + r.ReadBytes(Unknown3); + + SampleRootKey = r.ReadByte(); + + SampleTranspose = r.ReadSByte(); + + SampleVolume = r.ReadByte(); + + SamplePanpot = r.ReadSByte(); + + KeyGroupId = r.ReadByte(); + + Unknown4 = new byte[13]; + r.ReadBytes(Unknown4); + + AttackVolume = r.ReadByte(); + + Attack = r.ReadByte(); + + Decay1 = r.ReadByte(); + + Sustain = r.ReadByte(); + + Hold = r.ReadByte(); + + Decay2 = r.ReadByte(); + + Release = r.ReadByte(); + + Break = r.ReadByte(); + + break; + } + + // In the event that there's a SWD version that hasn't been discovered yet + default: throw new NotImplementedException("This version of the SWD specification has not been implemented into VG Music Studio."); + } + } } +#endregion + #region ProgramInfo public interface IProgramInfo { ISplitEntry[] SplitEntries { get; } } - public sealed class ProgramInfo_V402 : IProgramInfo - { - public byte Id { get; set; } - public byte NumSplits { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown1 { get; set; } = null!; - public byte Volume { get; set; } - public byte Panpot { get; set; } - [BinaryArrayFixedLength(5)] - public byte[] Unknown2 { get; set; } = null!; - public byte NumLFOs { get; set; } - [BinaryArrayFixedLength(4)] - public byte[] Unknown3 { get; set; } = null!; - [BinaryArrayFixedLength(16)] - public KeyGroup[] KeyGroups { get; set; } = null!; - [BinaryArrayVariableLength(nameof(NumLFOs))] - public LFOInfo LFOInfos { get; set; } = null!; - [BinaryArrayVariableLength(nameof(NumSplits))] - public SplitEntry_V402[] SplitEntries { get; set; } = null!; - - [BinaryIgnore] - ISplitEntry[] IProgramInfo.SplitEntries => SplitEntries; - } - public sealed class ProgramInfo_V415 : IProgramInfo + public class ProgramInfo : IProgramInfo { public ushort Id { get; set; } - public ushort NumSplits { get; set; } + public byte NumSplits { get; set; } + public byte[] Unknown1 { get; set; } public byte Volume { get; set; } public byte Panpot { get; set; } - [BinaryArrayFixedLength(5)] - public byte[] Unknown1 { get; set; } = null!; + public byte[] Unknown2 { get; set; } public byte NumLFOs { get; set; } - [BinaryArrayFixedLength(4)] - public byte[] Unknown2 { get; set; } = null!; - [BinaryArrayVariableLength(nameof(NumLFOs))] - public LFOInfo[] LFOInfos { get; set; } = null!; - [BinaryArrayFixedLength(16)] - public byte[] Unknown3 { get; set; } = null!; - [BinaryArrayVariableLength(nameof(NumSplits))] - public SplitEntry_V415[] SplitEntries { get; set; } = null!; - - [BinaryIgnore] + public byte[] Unknown3 { get; set; } + public LFOInfo[] LFOInfos { get; set; } + public byte[]? Unknown4 { get; set; } + public KeyGroup[]? KeyGroups { get; set; } + public SplitEntry[] SplitEntries { get; set; } + ISplitEntry[] IProgramInfo.SplitEntries => SplitEntries; + + public ProgramInfo(EndianBinaryReader r, SWD swd) + { + switch(swd.Version) + { + case 1026: + { + Id = r.ReadByte(); + + NumSplits = r.ReadByte(); + + Unknown1 = new byte[2]; + r.ReadBytes(Unknown1); + + Volume = r.ReadByte(); + + Panpot = r.ReadByte(); + + Unknown2 = new byte[5]; + r.ReadBytes(Unknown2); + + NumLFOs = r.ReadByte(); + + Unknown3 = new byte[4]; + r.ReadBytes(Unknown3); + + KeyGroups = new KeyGroup[16]; + + LFOInfos = new LFOInfo[NumLFOs]; + for (int i = 0; i < NumLFOs; i++) + { + LFOInfos[i] = new LFOInfo(r); + }; + + SplitEntries = new SplitEntry[NumSplits]; + + break; + } + + case 1045: + { + Id = r.ReadUInt16(); + + NumSplits = r.ReadByte(); + + Unknown1 = new byte[1]; + r.ReadBytes(Unknown1); + + Volume = r.ReadByte(); + + Panpot = r.ReadByte(); + + Unknown2 = new byte[5]; + r.ReadBytes(Unknown2); + + NumLFOs = r.ReadByte(); + + Unknown3 = new byte[4]; + r.ReadBytes(Unknown3); + + LFOInfos = new LFOInfo[NumLFOs]; + for (int i = 0; i < NumLFOs; i++) + { + LFOInfos[i] = new LFOInfo(r); + }; + + Unknown4 = new byte[16]; + r.ReadBytes(Unknown4); + + SplitEntries = new SplitEntry[NumSplits]; + for (int i = 0; i < NumSplits; i++) + { + SplitEntries[i] = new SplitEntry(r, swd); + } + + break; + } + + // In the event that there's a version that hasn't been discovered yet + default: throw new NotImplementedException("This Digital Sound Elements version has not been implemented into VG Music Studio."); + } + + } + } + #endregion + #region WavInfo public interface IWavInfo { byte RootNote { get; } @@ -207,61 +443,26 @@ public interface IWavInfo uint SampleOffset { get; } uint LoopStart { get; } uint LoopEnd { get; } - byte EnvMult { get; } + byte EnvMulti { get; } byte AttackVolume { get; } byte Attack { get; } - byte Decay { get; } + byte Decay1 { get; } byte Sustain { get; } byte Hold { get; } byte Decay2 { get; } byte Release { get; } } - public sealed class WavInfo_V402 : IWavInfo // Size 0x40 - { - public byte Unknown1 { get; set; } - public byte Id { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown2 { get; set; } = null!; - public byte RootNote { get; set; } - public sbyte Transpose { get; set; } - public byte Volume { get; set; } - public sbyte Panpot { get; set; } - public SampleFormat SampleFormat { get; set; } - [BinaryArrayFixedLength(7)] - public byte[] Unknown3 { get; set; } = null!; - public bool Loop { get; set; } - public uint SampleRate { get; set; } - public uint SampleOffset { get; set; } - public uint LoopStart { get; set; } - public uint LoopEnd { get; set; } - [BinaryArrayFixedLength(16)] - public byte[] Unknown4 { get; set; } = null!; - public byte EnvOn { get; set; } - public byte EnvMult { get; set; } - [BinaryArrayFixedLength(6)] - public byte[] Unknown5 { get; set; } = null!; - public byte AttackVolume { get; set; } - public byte Attack { get; set; } - public byte Decay { get; set; } - public byte Sustain { get; set; } - public byte Hold { get; set; } - public byte Decay2 { get; set; } - public byte Release { get; set; } - public byte Unknown6 { get; set; } - } - public sealed class WavInfo_V415 : IWavInfo // 0x40 + + public class WavInfo : IWavInfo // Size 0x40 { - [BinaryArrayFixedLength(2)] - public byte[] Unknown1 { get; set; } = null!; + public byte[] Entry { get; set; } public ushort Id { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown2 { get; set; } = null!; + public byte[] Unknown2 { get; set; } public byte RootNote { get; set; } public sbyte Transpose { get; set; } public byte Volume { get; set; } public sbyte Panpot { get; set; } - [BinaryArrayFixedLength(6)] - public byte[] Unknown3 { get; set; } = null!; + public byte[] Unknown3 { get; set; } public ushort Version { get; set; } public SampleFormat SampleFormat { get; set; } public byte Unknown4 { get; set; } @@ -270,35 +471,270 @@ public sealed class WavInfo_V415 : IWavInfo // 0x40 public byte SamplesPer32Bits { get; set; } public byte Unknown6 { get; set; } public byte BitDepth { get; set; } - [BinaryArrayFixedLength(6)] - public byte[] Unknown7 { get; set; } = null!; + public byte[] Unknown7 { get; set; } public uint SampleRate { get; set; } public uint SampleOffset { get; set; } public uint LoopStart { get; set; } public uint LoopEnd { get; set; } public byte EnvOn { get; set; } - public byte EnvMult { get; set; } - [BinaryArrayFixedLength(6)] - public byte[] Unknown8 { get; set; } = null!; + public byte EnvMulti { get; set; } + public byte[] Unknown8 { get; set; } public byte AttackVolume { get; set; } public byte Attack { get; set; } - public byte Decay { get; set; } + public byte Decay1 { get; set; } public byte Sustain { get; set; } public byte Hold { get; set; } public byte Decay2 { get; set; } public byte Release { get; set; } - public byte Unknown9 { get; set; } + public byte Break { get; set; } + + public WavInfo(EndianBinaryReader r, SWD swd) + { + // SWD version format check + switch(swd.Version) + { + + case 1026: + { + // The wave table Entry Variable + Entry = new byte[1]; // Specify a variable with a byte array before doing EndianBinaryReader.ReadBytes() + r.ReadBytes(Entry); // Reads the byte + + // Wave ID + Id = r.ReadByte(); // Reads the ID of the wave sample + + // Currently undocumented variable(s) + Unknown2 = new byte[2]; // Specify a variable with a byte array before doing EndianBinaryReader.ReadBytes() + r.ReadBytes(Unknown2); // Reads the bytes + + // Root Note + RootNote = r.ReadByte(); + + // Transpose + Transpose = r.ReadSByte(); + + // Volume + Volume = r.ReadByte(); + + // Panpot + Panpot = r.ReadSByte(); + + // Sample Format + if (swd.Type == "swdb") + { + r.Endianness = Endianness.LittleEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.BigEndian; + } + else + { + r.Endianness = Endianness.BigEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.LittleEndian; + } + + // Undocumented variable(s) + Unknown3 = new byte[7]; + r.ReadBytes(Unknown3); + + // Version + Version = r.ReadUInt16(); + + // Loop enable and disable + Loop = r.ReadBoolean(); + + // Sample Rate + SampleRate = r.ReadUInt32(); + + // Sample Offset + SampleOffset = r.ReadUInt32(); + + // Loop Start + LoopStart = r.ReadUInt32(); + + // Loop End + LoopEnd = r.ReadUInt32(); + + // Undocumented variable(s) + Unknown7 = new byte[16]; + r.ReadBytes(Unknown7); + + // Volume Envelop On + EnvOn = r.ReadByte(); + + // Volume Envelop Multiple + EnvMulti = r.ReadByte(); + + // Undocumented variable(s) + Unknown8 = new byte[6]; + r.ReadBytes(Unknown8); + + // Attack Volume + AttackVolume = r.ReadByte(); + + // Attack + Attack = r.ReadByte(); + + // Decay 1 + Decay1 = r.ReadByte(); + + // Sustain + Sustain = r.ReadByte(); + + // Hold + Hold = r.ReadByte(); + + // Decay 2 + Decay2 = r.ReadByte(); + + // Release + Release = r.ReadByte(); + + // The wave table Break Variable + Break = r.ReadByte(); + + break; + } + + case 1045: // Digital Sound Elements - SWD Specification 4.21 + { + // The wave table Entry Variable + Entry = new byte[2]; // Specify a variable with a byte array before doing EndianBinaryReader.ReadBytes() + r.ReadBytes(Entry); // Reads the bytes + + // Wave ID + r.Endianness = Endianness.LittleEndian; // Changes the reader to Little Endian + Id = r.ReadUInt16(); // Reads the ID of the wave sample as Little Endian + if (swd.Type == "swdb") // Checks if the str string value matches "swdb" + { + r.Endianness = Endianness.BigEndian; // Restores the reader back to Big Endian + } + + // Currently undocumented variable + Unknown2 = new byte[2]; // Same as the one before + r.ReadBytes(Unknown2); + + // Root Note + RootNote = r.ReadByte(); + + // Transpose + Transpose = r.ReadSByte(); + + // Volume + Volume = r.ReadByte(); + + // Panpot + Panpot = r.ReadSByte(); + + // Undocumented variable + Unknown3 = new byte[6]; // Same as before, except we need to read 6 bytes instead of 2 + r.ReadBytes(Unknown3); + + // Version + Version = r.ReadUInt16(); + + // Sample Format + if (swd.Type == "swdb") + { + r.Endianness = Endianness.LittleEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.BigEndian; + } + else + { + r.Endianness = Endianness.BigEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.LittleEndian; + } + + // Undocumented variable(s) + Unknown4 = r.ReadByte(); + + // Loop enable or disable + Loop = r.ReadBoolean(); + + // Undocumented variable(s) + Unknown5 = r.ReadByte(); + + // Samples per 32 bits + SamplesPer32Bits = r.ReadByte(); + + // Undocumented variable(s) + Unknown6 = r.ReadByte(); + + // Bit Depth + BitDepth = r.ReadByte(); + + // Undocumented variable(s) + Unknown7 = new byte[6]; // Once again, create a variable to specify 6 bytes and to read using it + r.ReadBytes(Unknown7); + + // Sample Rate + SampleRate = r.ReadUInt32(); + + // Sample Offset + SampleOffset = r.ReadUInt32(); + + // Loop Start + LoopStart = r.ReadUInt32(); + + // Loop End + LoopEnd = r.ReadUInt32(); + + // Volume Envelop On + EnvOn = r.ReadByte(); + + // Volume Envelop Multiple + EnvMulti = r.ReadByte(); + + // Undocumented variable(s) + Unknown8 = new byte[6]; // Same as before + r.ReadBytes(Unknown8); + + // Attack Volume + AttackVolume = r.ReadByte(); + + // Attack + Attack = r.ReadByte(); + + // Decay 1 + Decay1 = r.ReadByte(); + + // Sustain + Sustain = r.ReadByte(); + + // Hold + Hold = r.ReadByte(); + + // Decay 2 + Decay2 = r.ReadByte(); + + // Release + Release = r.ReadByte(); + + // The wave table Break Variable + Break = r.ReadByte(); + + break; + } + + // In the event that there's a version that hasn't been discovered yet + default: throw new NotImplementedException("This version of the SWD specification has not yet been implemented into VG Music Studio."); + } + } } + #endregion public class SampleBlock { - public IWavInfo WavInfo = null!; - public byte[] Data = null!; + public WavInfo? WavInfo; + public DSPADPCM DSPADPCM; + public byte[]? Data; } public class ProgramBank { - public IProgramInfo?[] ProgramInfos = null!; - public KeyGroup[] KeyGroups = null!; + public ProgramInfo[]? ProgramInfos; + public KeyGroup[]? KeyGroups; } public class KeyGroup // Size 0x8 { @@ -308,8 +744,25 @@ public class KeyGroup // Size 0x8 public byte LowNote { get; set; } public byte HighNote { get; set; } public ushort Unknown { get; set; } + + public KeyGroup(EndianBinaryReader r, SWD swd) + { + r.Endianness = Endianness.LittleEndian; + Id = r.ReadUInt16(); + if (swd.Type == "swdb") { r.Endianness = Endianness.BigEndian; } + + Poly = r.ReadByte(); + + Priority = r.ReadByte(); + + LowNote = r.ReadByte(); + + HighNote = r.ReadByte(); + + Unknown = r.ReadUInt16(); + } } - public sealed class LFOInfo + public class LFOInfo { public byte Unknown1 { get; set; } public byte HasData { get; set; } @@ -321,173 +774,201 @@ public sealed class LFOInfo public ushort UnknownC { get; set; } public byte UnknownE { get; set; } public byte UnknownF { get; set; } + + public LFOInfo(EndianBinaryReader r) + { + Unknown1 = r.ReadByte(); + + HasData = r.ReadByte(); + + Type = r.ReadByte(); + + CallbackType = r.ReadByte(); + + Unknown4 = r.ReadUInt32(); + + Unknown8 = r.ReadUInt16(); + + UnknownA = r.ReadUInt16(); + + UnknownC = r.ReadUInt16(); + + UnknownE = r.ReadByte(); + + UnknownF = r.ReadByte(); + } } + public string FileName; + public Header? Info; public string Type; // "swdb" or "swdl" - public byte[] Unknown1; public uint Length; public ushort Version; - public IHeader Header; - public byte[] Unknown2; + + public long WaviChunkOffset, WaviDataOffset, + PrgiChunkOffset, PrgiDataOffset, + KgrpChunkOffset, KgrpDataOffset, + PcmdChunkOffset, PcmdDataOffset, + EodChunkOffset; + public ChunkHeader? WaviInfo, PrgiInfo, KgrpInfo, PcmdInfo, EodInfo; public ProgramBank? Programs; public SampleBlock[]? Samples; public SWD(string path) { - using (FileStream stream = File.OpenRead(path)) - { - var r = new EndianBinaryReader(stream, ascii: true); - - Type = r.ReadString_Count(4); - Unknown1 = new byte[4]; - r.ReadBytes(Unknown1); - Length = r.ReadUInt32(); - Version = r.ReadUInt16(); - Unknown2 = new byte[2]; - r.ReadBytes(Unknown2); + FileName = new FileInfo(path).Name; + var stream = File.OpenRead(path); + var r = new EndianBinaryReader(stream, ascii: true); + Info = new Header(r); + Type = Info.Type; + Length = Info.Length; + Version = Info.Version; + Programs = ReadPrograms(r, Info.NumPRGISlots, this); - switch (Version) - { - case 0x402: + switch (Version) + { + case 0x402: { - Header_V402 header = r.ReadObject(); - Header = header; - Programs = ReadPrograms(r, header.NumPRGISlots); - Samples = ReadSamples(r, header.NumWAVISlots); + Samples = ReadSamples(r, Info.NumWAVISlots, this); break; } - case 0x415: + case 0x415: { - Header_V415 header = r.ReadObject(); - Header = header; - Programs = ReadPrograms(r, header.NumPRGISlots); - if (header.PCMDLength != 0 && (header.PCMDLength & 0xFFFF0000) != 0xAAAA0000) + if (Info.PCMDLength != 0 && (Info.PCMDLength & 0xFFFF0000) != 0xAAAA0000) { - Samples = ReadSamples(r, header.NumWAVISlots); + Samples = ReadSamples(r, Info.NumWAVISlots, this); } break; } - default: throw new InvalidDataException(); - } + default: throw new InvalidDataException(); } + return; } - private static long FindChunk(EndianBinaryReader r, string chunk) + #region SampleBlock + private SampleBlock[] ReadSamples(EndianBinaryReader r, int numWAVISlots, SWD swd) { - long pos = -1; - long oldPosition = r.Stream.Position; - r.Stream.Position = 0; - - while (r.Stream.Position < r.Stream.Length) - { - string str = r.ReadString_Count(4); - if (str == chunk) - { - pos = r.Stream.Position - 4; - break; - } - - switch (str) - { - case "swdb": - case "swdl": - { - r.Stream.Position += 0x4C; - break; - } - default: - { - Debug.WriteLine($"Ignoring {str} chunk"); - r.Stream.Position += 0x8; - uint length = r.ReadUInt32(); - r.Stream.Position += length; - r.Stream.Align(16); - break; - } - } - } - - r.Stream.Position = oldPosition; - return pos; - } - - private static SampleBlock[] ReadSamples(EndianBinaryReader r, int numWAVISlots) - where T : IWavInfo, new() - { - long waviChunkOffset = FindChunk(r, "wavi"); - long pcmdChunkOffset = FindChunk(r, "pcmd"); + // These apply the chunk offsets that are found to both local and the field functions, chunk header constructors are available here incase they're needed + long waviChunkOffset = swd.WaviChunkOffset = DSEUtils.FindChunk(r, "wavi"); + long pcmdChunkOffset = swd.PcmdChunkOffset = DSEUtils.FindChunk(r, "pcmd"); + long eodChunkOffset = swd.EodChunkOffset = DSEUtils.FindChunk(r, "eod "); if (waviChunkOffset == -1 || pcmdChunkOffset == -1) { throw new InvalidDataException(); } else { - waviChunkOffset += 0x10; - pcmdChunkOffset += 0x10; + WaviInfo = new ChunkHeader(r, waviChunkOffset, swd); + long waviDataOffset = WaviDataOffset = waviChunkOffset + 0x10; + PcmdInfo = new ChunkHeader(r, pcmdChunkOffset, swd); + long pcmdDataOffset = PcmdDataOffset = pcmdChunkOffset + 0x10; + EodInfo = new ChunkHeader(r, eodChunkOffset, swd); var samples = new SampleBlock[numWAVISlots]; for (int i = 0; i < numWAVISlots; i++) { - r.Stream.Position = waviChunkOffset + (2 * i); + r.Stream.Position = waviDataOffset + (2 * i); ushort offset = r.ReadUInt16(); if (offset != 0) { - r.Stream.Position = offset + waviChunkOffset; - T wavInfo = r.ReadObject(); - samples[i] = new SampleBlock + r.Stream.Position = offset + waviDataOffset; + var wavInfo = new WavInfo(r, swd); + switch (Type) { - WavInfo = wavInfo, - Data = new byte[(int)((wavInfo.LoopStart + wavInfo.LoopEnd) * 4)], - }; - r.Stream.Position = pcmdChunkOffset + wavInfo.SampleOffset; - r.ReadBytes(samples[i].Data); + case "swdm": + { + throw new NotImplementedException("This Digital Sound Elements type has not yet been implemented."); + } + + case "swdl": + { + samples[i] = new SampleBlock + { + WavInfo = wavInfo, + Data = new byte[(int)((wavInfo.LoopStart + wavInfo.LoopEnd) * 4)], + }; + r.Stream.Position = pcmdDataOffset + wavInfo.SampleOffset; + r.ReadBytes(samples[i].Data); + + break; + } + + case "swdb": + { + samples[i] = new SampleBlock + { + WavInfo = wavInfo, // This is the only variable we can use for this initializer declarator, since the samples are DSP-ADPCM compressed + }; + r.Stream.Position = pcmdDataOffset + wavInfo.SampleOffset; // This sets the EndianBinaryReader stream position offset to the DSP-ADPCM header + + samples[i].DSPADPCM = new DSPADPCM(r, null); // Reads the entire DSP-ADPCM header and encoded data, also the SWD spec doesn't define number of channels + samples[i].DSPADPCM.Decode(); // Decodes all bytes into PCM16 data +#if DEBUG + // This is for dumping both the encoded and decoded samples, for ensuring that the decoder works correctly + new FileInfo("./ExtractedSamples/" + FileName + "/dsp/").Directory!.Create(); + File.WriteAllBytes("./ExtractedSamples/" + FileName + "/dsp/" + "sample" + i.ToString() + ".dsp", [.. samples[i].DSPADPCM.Info[0].ToBytes(), .. samples[i].DSPADPCM.Data]); + new FileInfo("./ExtractedSamples/" + FileName + "/wav/").Directory!.Create(); + File.WriteAllBytes("./ExtractedSamples/" + FileName + "/wav/" + "sample" + i.ToString() + ".wav", samples[i].DSPADPCM.ConvertToWav()); +#endif + break; + } + default: + { + throw new NotImplementedException("This Digital Sound Elements type has not yet been implemented."); + } + } } } return samples; } } - private static ProgramBank? ReadPrograms(EndianBinaryReader r, int numPRGISlots) - where T : IProgramInfo, new() + #endregion + + #region ProgramBank and KeyGroup + private static ProgramBank? ReadPrograms(EndianBinaryReader r, int numPRGISlots, SWD swd) { - long chunkOffset = FindChunk(r, "prgi"); + long chunkOffset = swd.PrgiChunkOffset = DSEUtils.FindChunk(r, "prgi"); if (chunkOffset == -1) { return null; } - chunkOffset += 0x10; - var programInfos = new IProgramInfo?[numPRGISlots]; + swd.PrgiInfo = new ChunkHeader(r, chunkOffset, swd); + long dataOffset = swd.PrgiDataOffset = chunkOffset + 0x10; + var programInfos = new ProgramInfo[numPRGISlots]; for (int i = 0; i < programInfos.Length; i++) { - r.Stream.Position = chunkOffset + (2 * i); + r.Stream.Position = dataOffset + (2 * i); ushort offset = r.ReadUInt16(); if (offset != 0) { - r.Stream.Position = offset + chunkOffset; - programInfos[i] = r.ReadObject(); + r.Stream.Position = offset + dataOffset; + programInfos[i] = new ProgramInfo(r, swd); } } return new ProgramBank { ProgramInfos = programInfos, - KeyGroups = ReadKeyGroups(r), + KeyGroups = ReadKeyGroups(r, swd), }; } - private static KeyGroup[] ReadKeyGroups(EndianBinaryReader r) + private static KeyGroup[] ReadKeyGroups(EndianBinaryReader r, SWD swd) { - long chunkOffset = FindChunk(r, "kgrp"); + long chunkOffset = swd.KgrpChunkOffset = DSEUtils.FindChunk(r, "kgrp"); if (chunkOffset == -1) { - return Array.Empty(); + return []; } - r.Stream.Position = chunkOffset + 0xC; - uint chunkLength = r.ReadUInt32(); - var keyGroups = new KeyGroup[chunkLength / 8]; // 8 is the size of a KeyGroup + ChunkHeader info = swd.KgrpInfo = new ChunkHeader(r, chunkOffset, swd); + swd.KgrpDataOffset = chunkOffset + 0x10; + r.Stream.Position = swd.KgrpDataOffset; + var keyGroups = new KeyGroup[info.ChunkEnd / 8]; // 8 is the size of a KeyGroup for (int i = 0; i < keyGroups.Length; i++) { - keyGroups[i] = r.ReadObject(); + keyGroups[i] = new KeyGroup(r, swd); } return keyGroups; } + #endregion } diff --git a/VG Music Studio - Core/NDS/SDAT/SDATChannel.cs b/VG Music Studio - Core/NDS/SDAT/SDATChannel.cs index a925176..b422874 100644 --- a/VG Music Studio - Core/NDS/SDAT/SDATChannel.cs +++ b/VG Music Studio - Core/NDS/SDAT/SDATChannel.cs @@ -1,4 +1,5 @@ using System; +using Kermalis.VGMusicStudio.Core.Codec; namespace Kermalis.VGMusicStudio.Core.NDS.SDAT; @@ -49,7 +50,7 @@ internal sealed class SDATChannel // PCM8, PCM16 private int _dataOffset; // ADPCM - private ADPCMDecoder _adpcmDecoder; + private IMAADPCM _adpcmDecoder; private short _adpcmLoopLastSample; private short _adpcmLoopStepIndex; // PSG diff --git a/VG Music Studio - Core/Properties/Strings.Designer.cs b/VG Music Studio - Core/Properties/Strings.Designer.cs index eea96fb..b3841cc 100644 --- a/VG Music Studio - Core/Properties/Strings.Designer.cs +++ b/VG Music Studio - Core/Properties/Strings.Designer.cs @@ -375,6 +375,15 @@ public static string FilterOpenSDAT { } } + /// + /// Looks up a localized string similar to Wave Data. + /// + public static string FilterOpenSWD { + get { + return ResourceManager.GetString("FilterOpenSWD", resourceCulture); + } + } + /// /// Looks up a localized string similar to DLS Files. /// @@ -448,7 +457,7 @@ public static string MenuOpenAlphaDream { } /// - /// Looks up a localized string similar to Open DSE Folder. + /// Looks up a localized string similar to Open DSE Files. /// public static string MenuOpenDSE { get { @@ -474,6 +483,24 @@ public static string MenuOpenSDAT { } } + /// + /// Looks up a localized string similar to Open Folder Containing SMD Files. + /// + public static string MenuOpenSMD { + get { + return ResourceManager.GetString("MenuOpenSMD", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Open Main SWD File. + /// + public static string MenuOpenSWD { + get { + return ResourceManager.GetString("MenuOpenSWD", resourceCulture); + } + } + /// /// Looks up a localized string similar to Playlist. /// diff --git a/VG Music Studio - Core/Properties/Strings.resx b/VG Music Studio - Core/Properties/Strings.resx index 916279d..ab98afd 100644 --- a/VG Music Studio - Core/Properties/Strings.resx +++ b/VG Music Studio - Core/Properties/Strings.resx @@ -204,8 +204,8 @@ End Current Playlist - - Open DSE Folder + + Open Folder Containing SMD Files Open GBA ROM (AlphaDream) @@ -369,4 +369,13 @@ songs|0_0|song|1_1|songs|2_*| + + Wave Data + + + Open DSE Files + + + Open Main SWD File + \ No newline at end of file diff --git a/VG Music Studio - Core/SongState.cs b/VG Music Studio - Core/SongState.cs index 02d5e92..5b172f3 100644 --- a/VG Music Studio - Core/SongState.cs +++ b/VG Music Studio - Core/SongState.cs @@ -48,7 +48,7 @@ public void Reset() } public const int MAX_KEYS = 32 + 1; // DSE is currently set to use 32 channels - public const int MAX_TRACKS = 18; // PMD2 has a few songs with 18 tracks + public const int MAX_TRACKS = 255 + 1; // PMD2 has a few songs with 18 tracks, where as PMD WiiWare has some that are up to 27 tracks, but made it 255 to future proof it public ushort Tempo; public readonly Track[] Tracks; diff --git a/VG Music Studio - Core/Util/DataUtils.cs b/VG Music Studio - Core/Util/DataUtils.cs index fe16180..14b0a69 100644 --- a/VG Music Studio - Core/Util/DataUtils.cs +++ b/VG Music Studio - Core/Util/DataUtils.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; namespace Kermalis.VGMusicStudio.Core.Util; @@ -11,4 +12,20 @@ public static void Align(this Stream s, int num) s.Position++; } } + + public static int RoundUp(int numToRound, int multiple) + { + if (multiple == 0) + { + return numToRound; + } + + int remainder = Math.Abs(numToRound) % multiple; + if (remainder == 0) + { + return numToRound; + } + + return (numToRound < 0) ? -(Math.Abs(numToRound) - remainder) : (numToRound + multiple - remainder); + } } diff --git a/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryPrimitivesExtras.cs b/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryPrimitivesExtras.cs new file mode 100644 index 0000000..238528d --- /dev/null +++ b/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryPrimitivesExtras.cs @@ -0,0 +1,333 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Kermalis.EndianBinaryIO; + +namespace Kermalis.VGMusicStudio.Core.Util.EndianBinaryExtras; + +public static class EndianBinaryPrimitivesExtras +{ + public static readonly Endianness SystemEndianness = BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian; + + #region Read + + // 24-bits (3 bytes) + public static int ReadInt24(ReadOnlySpan src, Endianness endianness) + { + uint val; // Do a "sign extend" to maintain the sign from 24->32 bits + if (endianness == Endianness.LittleEndian) + { + val = ((uint)src[0] << 8) | ((uint)src[1] << 16) | ((uint)src[2] << 24); + } + else + { + val = ((uint)src[2] << 8) | ((uint)src[1] << 16) | ((uint)src[0] << 24); + } + return (int)val >> 8; + } + public static void ReadInt24s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadInt24(src.Slice(i * 3, 3), endianness); + } + } + public static uint ReadUInt24(ReadOnlySpan src, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + return (uint)((src[0]) | (src[1] << 8) | (src[2] << 16)); + } + else + { + return (uint)((src[2]) | (src[1] << 8) | (src[0] << 16)); + } + } + public static void ReadUInt24s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadUInt24(src.Slice(i * 3, 3), endianness); + } + } + + // 40-bits (5 bytes) + public static long ReadInt40(ReadOnlySpan src, Endianness endianness) + { + ulong val; // Do a "sign extend" to maintain the sign from 40->64 bits + if (endianness == Endianness.LittleEndian) + { + val = ((uint)src[0] << 8) | ((uint)src[1] << 16) | ((uint)src[2] << 24) | ((uint)src[3] << 32) | ((uint)src[4] << 40); + } + else + { + val = ((uint)src[4] << 8) | ((uint)src[3] << 16) | ((uint)src[2] << 24) | ((uint)src[1] << 32) | ((uint)src[0] << 40); + } + return (long)val >> 24; + } + public static void ReadInt40s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadInt40(src.Slice(i * 5, 5), endianness); + } + } + public static ulong ReadUInt40(ReadOnlySpan src, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + return (uint)((src[0]) | (src[1] << 8) | (src[2] << 16) | (src[3] << 24) | (src[4] << 32)); + } + else + { + return (uint)((src[4]) | (src[3] << 8) | (src[2] << 16) | (src[1] << 24) | (src[0] << 32)); + } + } + public static void ReadUInt40s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadUInt40(src.Slice(i * 5, 5), endianness); + } + } + + // 48-bits (6 bytes) + public static long ReadInt48(ReadOnlySpan src, Endianness endianness) + { + ulong val; // Do a "sign extend" to maintain the sign from 48->64 bits + if (endianness == Endianness.LittleEndian) + { + val = ((uint)src[0] << 8) | ((uint)src[1] << 16) | ((uint)src[2] << 24) | ((uint)src[3] << 32) | ((uint)src[4] << 40) | ((uint)src[5] << 48); + } + else + { + val = ((uint)src[5] << 8) | ((uint)src[4] << 16) | ((uint)src[3] << 24) | ((uint)src[2] << 32) | ((uint)src[1] << 40) | ((uint)src[0] << 48); + } + return (long)val >> 16; + } + public static void ReadInt48s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadInt48(src.Slice(i * 6, 6), endianness); + } + } + public static ulong ReadUInt48(ReadOnlySpan src, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + return (uint)((src[0]) | (src[1] << 8) | (src[2] << 16) | (src[3] << 24) | (src[4] << 32) | (src[5] << 40)); + } + else + { + return (uint)((src[5]) | (src[4] << 8) | (src[3] << 16) | (src[2] << 24) | (src[1] << 32) | (src[0] << 40)); + } + } + public static void ReadUInt48s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadUInt48(src.Slice(i * 6, 6), endianness); + } + } + + // 56-bits (7 bytes) + public static long ReadInt56(ReadOnlySpan src, Endianness endianness) + { + ulong val; // Do a "sign extend" to maintain the sign from 56->64 bits + if (endianness == Endianness.LittleEndian) + { + val = ((uint)src[0] << 8) | ((uint)src[1] << 16) | ((uint)src[2] << 24) | ((uint)src[3] << 32) | ((uint)src[4] << 40) | ((uint)src[5] << 48) | ((uint)src[6] << 56); + } + else + { + val = ((uint)src[6] << 8) | ((uint)src[5] << 16) | ((uint)src[4] << 24) | ((uint)src[3] << 32) | ((uint)src[2] << 40) | ((uint)src[1] << 48) | ((uint)src[0] << 56); + } + return (long)val >> 8; + } + public static void ReadInt56s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadInt56(src.Slice(i * 7, 7), endianness); + } + } + public static ulong ReadUInt56(ReadOnlySpan src, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + return (uint)((src[0]) | (src[1] << 8) | (src[2] << 16) | (src[3] << 24) | (src[4] << 32) | (src[5] << 40) | (src[6] << 48)); + } + else + { + return (uint)((src[6]) | (src[5] << 8) | (src[4] << 16) | (src[3] << 24) | (src[2] << 32) | (src[1] << 40) | (src[0] << 48)); + } + } + public static void ReadUInt56s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadUInt56(src.Slice(i * 7, 7), endianness); + } + } + + #endregion + + #region Write + + public static void WriteInt24(Span dest, int value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); + } + else + { + dest[2] = (byte)value; dest[1] = (byte)(value >> 8); dest[0] = (byte)(value >> 16); + } + } + public static void WriteInt24s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteInt24(dest.Slice(i * 3, 3), src[i], endianness); + } + } + public static void WriteUInt24(Span dest, uint value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); + } + else + { + dest[2] = (byte)value; dest[1] = (byte)(value >> 8); dest[0] = (byte)(value >> 16); + } + } + public static void WriteUInt24s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteUInt24(dest.Slice(i * 3, 3), src[i], endianness); + } + } + + public static void WriteInt40(Span dest, long value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[4] = (byte)(value >> 32); + } + else + { + dest[4] = (byte)value; dest[3] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[1] = (byte)(value >> 24); dest[0] = (byte)(value >> 32); + } + } + public static void WriteInt40s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteInt40(dest.Slice(i * 5, 5), src[i], endianness); + } + } + + public static void WriteUInt40(Span dest, ulong value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[4] = (byte)(value >> 32); + } + else + { + dest[4] = (byte)value; dest[3] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[1] = (byte)(value >> 24); dest[0] = (byte)(value >> 32); + } + } + public static void WriteUInt40s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteUInt40(dest.Slice(i * 5, 5), src[i], endianness); + } + } + + public static void WriteInt48(Span dest, long value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[4] = (byte)(value >> 32); dest[5] = (byte)(value >> 40); + } + else + { + dest[5] = (byte)value; dest[4] = (byte)(value >> 8); dest[3] = (byte)(value >> 16); dest[2] = (byte)(value >> 24); dest[1] = (byte)(value >> 32); dest[0] = (byte)(value >> 40); + } + } + public static void WriteInt48s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteInt48(dest.Slice(i * 6, 6), src[i], endianness); + } + } + + public static void WriteUInt48(Span dest, ulong value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[4] = (byte)(value >> 32); dest[5] = (byte)(value >> 40); + } + else + { + dest[5] = (byte)value; dest[4] = (byte)(value >> 8); dest[3] = (byte)(value >> 16); dest[2] = (byte)(value >> 24); dest[1] = (byte)(value >> 32); dest[0] = (byte)(value >> 40); + } + } + public static void WriteUInt48s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteUInt48(dest.Slice(i * 6, 6), src[i], endianness); + } + } + + public static void WriteInt56(Span dest, long value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[4] = (byte)(value >> 32); dest[5] = (byte)(value >> 40); dest[6] = (byte)(value >> 48); + } + else + { + dest[6] = (byte)value; dest[5] = (byte)(value >> 8); dest[4] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[2] = (byte)(value >> 32); dest[1] = (byte)(value >> 40); dest[0] = (byte)(value >> 48); + } + } + public static void WriteInt56s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteInt56(dest.Slice(i * 7, 7), src[i], endianness); + } + } + + public static void WriteUInt56(Span dest, ulong value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[4] = (byte)(value >> 32); dest[5] = (byte)(value >> 40); dest[6] = (byte)(value >> 48); + } + else + { + dest[6] = (byte)value; dest[5] = (byte)(value >> 8); dest[4] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[2] = (byte)(value >> 32); dest[1] = (byte)(value >> 40); dest[0] = (byte)(value >> 48); + } + } + public static void WriteUInt56s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteUInt56(dest.Slice(i * 7, 7), src[i], endianness); + } + } + + #endregion +} + diff --git a/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryReaderExtras.cs b/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryReaderExtras.cs new file mode 100644 index 0000000..824d7cf --- /dev/null +++ b/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryReaderExtras.cs @@ -0,0 +1,120 @@ +using System; +using System.IO; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Kermalis.EndianBinaryIO; + +namespace Kermalis.VGMusicStudio.Core.Util.EndianBinaryExtras; + +public partial class EndianBinaryReaderExtras +{ + protected delegate void ReadArrayMethod(ReadOnlySpan src, Span dest, Endianness endianness); + EndianBinaryReader Reader { get; set; } + + protected readonly byte[] _buffer; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EndianBinaryReaderExtras(EndianBinaryReader reader) + { + Reader = reader; + _buffer = new byte[64]; + } + + protected void ReadArray(Span dest, int elementSize, ReadArrayMethod readArray) + { + int num = dest.Length * elementSize; + int num2 = 0; + while (num != 0) + { + int num3 = Math.Min(num, 64); + Span span = _buffer.AsSpan(0, num3); + Reader.ReadBytes(span); + readArray(span, dest.Slice(num2, num3 / elementSize), Reader.Endianness); + num -= num3; + num2 += num3 / elementSize; + } + } + + public int ReadInt24() + { + Span buffer = _buffer.AsSpan(0, 3); + Reader.ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadInt24(buffer, Reader.Endianness); + } + public void ReadInt24s(Span dest) + { + ReadArray(dest, 3, EndianBinaryPrimitivesExtras.ReadInt24s); + } + public uint ReadUInt24() + { + Span buffer = _buffer.AsSpan(0, 3); + Reader.ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadUInt24(buffer, Reader.Endianness); + } + public void ReadUInt24s(Span dest) + { + ReadArray(dest, 3, EndianBinaryPrimitivesExtras.ReadUInt24s); + } + public long ReadInt40() + { + Span buffer = _buffer.AsSpan(0, 5); + Reader.ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadInt40(buffer, Reader.Endianness); + } + public void ReadInt40s(Span dest) + { + ReadArray(dest, 5, EndianBinaryPrimitivesExtras.ReadInt40s); + } + public ulong ReadUInt40() + { + Span buffer = _buffer.AsSpan(0, 5); + Reader.ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadUInt40(buffer, Reader.Endianness); + } + public void ReadUInt40s(Span dest) + { + ReadArray(dest, 5, EndianBinaryPrimitivesExtras.ReadUInt40s); + } + public long ReadInt48() + { + Span buffer = _buffer.AsSpan(0, 6); + Reader.ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadInt48(buffer, Reader.Endianness); + } + public void ReadInt48s(Span dest) + { + ReadArray(dest, 6, EndianBinaryPrimitivesExtras.ReadInt48s); + } + public ulong ReadUInt48() + { + Span buffer = _buffer.AsSpan(0, 6); + Reader.ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadUInt48(buffer, Reader.Endianness); + } + public void ReadUInt48s(Span dest) + { + ReadArray(dest, 6, EndianBinaryPrimitivesExtras.ReadUInt48s); + } + public long ReadInt56() + { + Span buffer = _buffer.AsSpan(0, 7); + Reader.ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadInt56(buffer, Reader.Endianness); + } + public void ReadInt56s(Span dest) + { + ReadArray(dest, 7, EndianBinaryPrimitivesExtras.ReadInt56s); + } + public ulong ReadUInt56() + { + Span buffer = _buffer.AsSpan(0, 7); + Reader.ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadUInt56(buffer, Reader.Endianness); + } + public void ReadUInt56s(Span dest) + { + ReadArray(dest, 7, EndianBinaryPrimitivesExtras.ReadUInt56s); + } +} + diff --git a/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryWriterExtras.cs b/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryWriterExtras.cs new file mode 100644 index 0000000..2b83d05 --- /dev/null +++ b/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryWriterExtras.cs @@ -0,0 +1,123 @@ +using System; +using System.IO; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Kermalis.EndianBinaryIO; + +namespace Kermalis.VGMusicStudio.Core.Util.EndianBinaryExtras; + +public partial class EndianBinaryWriterExtras +{ + protected delegate void WriteArrayMethod(Span dest, ReadOnlySpan src, Endianness endianness); + EndianBinaryWriter Writer { get; set; } + + protected readonly byte[] _buffer; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EndianBinaryWriterExtras(EndianBinaryWriter writer) + { + Writer = writer; + + _buffer = new byte[64]; + } + + protected void WriteArray(ReadOnlySpan src, int elementSize, WriteArrayMethod writeArray) + { + int num = src.Length * elementSize; + int num2 = 0; + while (num != 0) + { + int num3 = Math.Min(num, 64); + Span span = _buffer.AsSpan(0, num3); + writeArray(span, src.Slice(num2, num3 / elementSize), Writer.Endianness); + Writer.Stream.Write(span); + num -= num3; + num2 += num3 / elementSize; + } + } + + public void WriteInt24(int value) + { + Span buffer = _buffer.AsSpan(0, 3); + EndianBinaryPrimitivesExtras.WriteInt24(buffer, value, Writer.Endianness); + Writer.Stream.Write(buffer); + } + public void WriteInt24s(ReadOnlySpan values) + { + WriteArray(values, 3, EndianBinaryPrimitivesExtras.WriteInt24s); + } + public void WriteUInt24(uint value) + { + Span buffer = _buffer.AsSpan(0, 3); + EndianBinaryPrimitivesExtras.WriteUInt24(buffer, value, Writer.Endianness); + Writer.Stream.Write(buffer); + } + public void WriteUInt24s(ReadOnlySpan values) + { + WriteArray(values, 3, EndianBinaryPrimitivesExtras.WriteUInt24s); + } + + public void WriteInt40(long value) + { + Span buffer = _buffer.AsSpan(0, 5); + EndianBinaryPrimitivesExtras.WriteInt40(buffer, value, Writer.Endianness); + Writer.Stream.Write(buffer); + } + public void WriteInt40s(ReadOnlySpan values) + { + WriteArray(values, 5, EndianBinaryPrimitivesExtras.WriteInt40s); + } + public void WriteUInt40(ulong value) + { + Span buffer = _buffer.AsSpan(0, 5); + EndianBinaryPrimitivesExtras.WriteUInt40(buffer, value, Writer.Endianness); + Writer.Stream.Write(buffer); + } + public void WriteUInt40s(ReadOnlySpan values) + { + WriteArray(values, 5, EndianBinaryPrimitivesExtras.WriteUInt40s); + } + + public void WriteInt48(long value) + { + Span buffer = _buffer.AsSpan(0, 6); + EndianBinaryPrimitivesExtras.WriteInt48(buffer, value, Writer.Endianness); + Writer.Stream.Write(buffer); + } + public void WriteInt48s(ReadOnlySpan values) + { + WriteArray(values, 6, EndianBinaryPrimitivesExtras.WriteInt48s); + } + public void WriteUInt48(ulong value) + { + Span buffer = _buffer.AsSpan(0, 6); + EndianBinaryPrimitivesExtras.WriteUInt48(buffer, value, Writer.Endianness); + Writer.Stream.Write(buffer); + } + public void WriteUInt48s(ReadOnlySpan values) + { + WriteArray(values, 6, EndianBinaryPrimitivesExtras.WriteUInt48s); + } + public void WriteInt56(long value) + { + Span buffer = _buffer.AsSpan(0, 7); + EndianBinaryPrimitivesExtras.WriteInt56(buffer, value, Writer.Endianness); + Writer.Stream.Write(buffer); + } + public void WriteInt56s(ReadOnlySpan values) + { + WriteArray(values, 7, EndianBinaryPrimitivesExtras.WriteInt56s); + } + public void WriteUInt56(ulong value) + { + Span buffer = _buffer.AsSpan(0, 7); + EndianBinaryPrimitivesExtras.WriteUInt56(buffer, value, Writer.Endianness); + Writer.Stream.Write(buffer); + } + public void WriteUInt56s(ReadOnlySpan values) + { + WriteArray(values, 7, EndianBinaryPrimitivesExtras.WriteUInt56s); + } +} + diff --git a/VG Music Studio - Core/VG Music Studio - Core.csproj b/VG Music Studio - Core/VG Music Studio - Core.csproj index 1d8bb4e..b83b088 100644 --- a/VG Music Studio - Core/VG Music Studio - Core.csproj +++ b/VG Music Studio - Core/VG Music Studio - Core.csproj @@ -12,9 +12,9 @@ - - - + + + Dependencies\DLS2.dll diff --git a/VG Music Studio - Core/Wii/WiiUtils.cs b/VG Music Studio - Core/Wii/WiiUtils.cs new file mode 100644 index 0000000..797775f --- /dev/null +++ b/VG Music Studio - Core/Wii/WiiUtils.cs @@ -0,0 +1,7 @@ +namespace Kermalis.VGMusicStudio.Core.Wii +{ + internal static class WiiUtils + { + public const int PPC_Broadway_Clock = 382_205_952; // (764.411904 / 2) = 382.205952 = 382,205,952 + } +} diff --git a/VG Music Studio - WinForms/MainForm.cs b/VG Music Studio - WinForms/MainForm.cs index 8820c08..efc1319 100644 --- a/VG Music Studio - WinForms/MainForm.cs +++ b/VG Music Studio - WinForms/MainForm.cs @@ -264,9 +264,14 @@ private void EndCurrentPlaylist(object? sender, EventArgs e) private void OpenDSE(object? sender, EventArgs e) { + var f = WinFormsUtils.CreateLoadMultipleDialog(".swd", Strings.MenuOpenSWD, Strings.FilterOpenSWD + " (*.swd)|*.swd"); + if (f is null) + { + return; + } var d = new FolderBrowserDialog { - Description = Strings.MenuOpenDSE, + Description = Strings.MenuOpenSMD, UseDescriptionForTitle = true, }; if (d.ShowDialog() != DialogResult.OK) @@ -277,7 +282,7 @@ private void OpenDSE(object? sender, EventArgs e) DisposeEngine(); try { - _ = new DSEEngine(d.SelectedPath); + _ = new DSEEngine([.. f], d.SelectedPath); } catch (Exception ex) { @@ -286,7 +291,7 @@ private void OpenDSE(object? sender, EventArgs e) } DSEConfig config = DSEEngine.DSEInstance!.Config; - FinishLoading(config.BGMFiles.Length); + FinishLoading(config.SMDFiles.Length); _songNumerical.Visible = false; _exportDLSItem.Visible = false; _exportMIDIItem.Visible = false; diff --git a/VG Music Studio - WinForms/TrackViewer.cs b/VG Music Studio - WinForms/TrackViewer.cs index 81520cc..8b7cafa 100644 --- a/VG Music Studio - WinForms/TrackViewer.cs +++ b/VG Music Studio - WinForms/TrackViewer.cs @@ -23,7 +23,7 @@ public TrackViewer() const int H = 400 - 12 - 11; _listView = new ObjectListView - { + { FullRowSelect = true, HeaderStyle = ColumnHeaderStyle.Nonclickable, HideSelection = false, diff --git a/VG Music Studio - WinForms/Util/WinFormsUtils.cs b/VG Music Studio - WinForms/Util/WinFormsUtils.cs index 33a0766..895accd 100644 --- a/VG Music Studio - WinForms/Util/WinFormsUtils.cs +++ b/VG Music Studio - WinForms/Util/WinFormsUtils.cs @@ -55,7 +55,25 @@ public static float Lerp(float value, float a1, float a2, float b1, float b2) } return null; } - public static string? CreateSaveDialog(string fileName, string extension, string title, string filter) + public static string[]? CreateLoadMultipleDialog(string extension, string title, string filter) + { + var d = new OpenFileDialog + { + DefaultExt = extension, + ValidateNames = true, + Multiselect = true, + CheckFileExists = true, + CheckPathExists = true, + Title = title, + Filter = $"{filter}|All files (*.*)|*.*", + }; + if (d.ShowDialog() == DialogResult.OK) + { + return d.FileNames; + } + return null; + } + public static string? CreateSaveDialog(string fileName, string extension, string title, string filter) { var d = new SaveFileDialog {