diff --git a/Libraries/at3plusdecoder.dll b/Libraries/at3plusdecoder.dll new file mode 100644 index 0000000..2251113 Binary files /dev/null and b/Libraries/at3plusdecoder.dll differ diff --git a/Libraries/vgmstream.LICENSE.txt b/Libraries/vgmstream.LICENSE.txt new file mode 100644 index 0000000..3603925 --- /dev/null +++ b/Libraries/vgmstream.LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2010 Adam Gashlin, Fastelbja, Ronny Elfert + +Portions Copyright (c) 2004-2008, Marko Kreen +Portions Copyright 2001-2007 jagarl / Kazunori Ueno +Portions Copyright (c) 1998, Justin Frankel/Nullsoft Inc. +Portions Copyright (C) 2006 Nullsoft, Inc. +Portions Copyright (c) 2005-2007 Paul Hsieh +Portions Public Domain originating with Sun Microsystems + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/Libraries/vgmstream.dll b/Libraries/vgmstream.dll new file mode 100644 index 0000000..8602e90 Binary files /dev/null and b/Libraries/vgmstream.dll differ diff --git a/Source/AcbEditor/AcbEditor.csproj b/Source/AcbEditor/AcbEditor.csproj index 36e8983..b152070 100644 --- a/Source/AcbEditor/AcbEditor.csproj +++ b/Source/AcbEditor/AcbEditor.csproj @@ -9,9 +9,10 @@ Properties AcbEditor AcbEditor - v4.5.2 + v4.6.1 512 true + AnyCPU @@ -22,6 +23,7 @@ DEBUG;TRACE prompt 4 + false AnyCPU diff --git a/Source/AcbEditor/App.config b/Source/AcbEditor/App.config index 5a9f962..ed71b0c 100644 --- a/Source/AcbEditor/App.config +++ b/Source/AcbEditor/App.config @@ -1,12 +1,12 @@ - + - -
+ +
- + @@ -21,4 +21,4 @@ - \ No newline at end of file + diff --git a/Source/AcbEditor/Program.cs b/Source/AcbEditor/Program.cs index 45c9388..42d8c2a 100644 --- a/Source/AcbEditor/Program.cs +++ b/Source/AcbEditor/Program.cs @@ -9,7 +9,7 @@ using AcbEditor.Properties; using SonicAudioLib; using SonicAudioLib.CriMw; using SonicAudioLib.IO; -using SonicAudioLib.Archive; +using SonicAudioLib.Archives; namespace AcbEditor { @@ -17,9 +17,15 @@ namespace AcbEditor { static void Main(string[] args) { + if (!File.Exists(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile)) + { + Settings.Default.Reset(); + Settings.Default.Save(); + } + if (args.Length < 1) { - Console.WriteLine(Properties.Resources.Description); + Console.WriteLine(Resources.Description); Console.ReadLine(); return; } @@ -72,7 +78,7 @@ namespace AcbEditor long awbPosition = acbReader.GetPosition("AwbFile"); if (acbReader.GetLength("AwbFile") > 0) { - using (Substream afs2Stream = acbReader.GetSubstream("AwbFile")) + using (SubStream afs2Stream = acbReader.GetSubStream("AwbFile")) { cpkMode = !CheckIfAfs2(afs2Stream); @@ -92,7 +98,7 @@ namespace AcbEditor { cpkMode = false; - using (Substream extAfs2Stream = acbReader.GetSubstream("StreamAwbAfs2Header")) + using (SubStream extAfs2Stream = acbReader.GetSubStream("StreamAwbAfs2Header")) { extAfs2Archive.Read(extAfs2Stream); } @@ -103,7 +109,7 @@ namespace AcbEditor } } - using (Substream waveformTableStream = acbReader.GetSubstream("WaveformTable")) + using (SubStream waveformTableStream = acbReader.GetSubStream("WaveformTable")) using (CriTableReader waveformReader = CriTableReader.Create(waveformTableStream)) { while (waveformReader.Read()) @@ -353,7 +359,7 @@ namespace AcbEditor static bool CheckIfAfs2(Stream source) { long oldPosition = source.Position; - bool result = EndianStream.ReadCString(source, 4) == "AFS2"; + bool result = DataStream.ReadCString(source, 4) == "AFS2"; source.Seek(oldPosition, SeekOrigin.Begin); return result; diff --git a/Source/CsbBuilder/App.config b/Source/CsbBuilder/App.config index 88fa402..bae5d6d 100644 --- a/Source/CsbBuilder/App.config +++ b/Source/CsbBuilder/App.config @@ -1,6 +1,6 @@ - + - + - \ No newline at end of file + diff --git a/Source/CsbBuilder/Audio/AdxFileReader.cs b/Source/CsbBuilder/Audio/AdxFileReader.cs deleted file mode 100644 index a383bf4..0000000 --- a/Source/CsbBuilder/Audio/AdxFileReader.cs +++ /dev/null @@ -1,478 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; - -using SonicAudioLib.IO; - -using NAudio.Wave; -using NAudio.Wave.SampleProviders; - -namespace CsbBuilder.Audio -{ - public struct AdxHeader - { - public ushort Identifier; - public ushort DataPosition; - public byte EncodeType; - public byte BlockLength; - public byte SampleBitdepth; - public byte ChannelCount; - public uint SampleRate; - public uint SampleCount; - public ushort CutoffFrequency; - public ushort Version; - public short[][] SampleHistories; - } - - public class AdxFileReader : WaveStream - { - private class SampleHistory - { - public short Sample1 = 0; - public short Sample2 = 0; - } - - private Stream source; - - private AdxHeader header; - private WaveFormat waveFormat; - - private short coef1; - private short coef2; - - private SampleHistory[] histories; - - private int sampleCount; - private int readSamples; - - private byte[] previousSamples; - - private double volume = 1; - private double pitch = 0; - private long delayTime = 0; - private DateTime startTime; - - public override WaveFormat WaveFormat - { - get - { - return waveFormat; - } - } - - public override long Length - { - get - { - return sampleCount * 2; - } - } - - public override long Position - { - get - { - return readSamples * 2; - } - - set - { - throw new NotImplementedException(); - } - } - - public bool IsFinished - { - get - { - return readSamples >= sampleCount; - } - } - - public bool IsLoopEnabled { get; set; } - - public double Volume - { - set - { - volume = value; - } - } - - public double Pitch - { - set - { - pitch = value; - } - } - - public int DelayTime - { - set - { - startTime = DateTime.Now; - delayTime = value * 10000; - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - if (IsFinished && !IsLoopEnabled) - { - return 0; - } - - if (delayTime > 0) - { - if ((DateTime.Now - startTime).Ticks < delayTime) - { - return count; - } - - delayTime = 0; - } - - int length = count; - - while ((length % (header.ChannelCount * 64)) != 0) - { - length++; - } - - byte[] samples = new byte[length]; - - int currentLength = 0; - - while (currentLength < length) - { - int sampleLength = GetNextSamples(samples, currentLength); - - if (sampleLength < 0) - { - count = count > currentLength ? currentLength : count; - break; - } - - currentLength = sampleLength; - } - - if (previousSamples != null) - { - samples = previousSamples.Concat(samples).ToArray(); - length = samples.Length; - } - - if (length > count) - { - previousSamples = samples.Skip(count).ToArray(); - } - - else if (length < count) - { - previousSamples = null; - count = length; - } - - else - { - previousSamples = null; - } - - Array.Copy(samples, 0, buffer, offset, count); - - return count; - } - - private int GetNextSamples(byte[] destination, int startIndex) - { - short[][] channelSamples = new short[header.ChannelCount][]; - - for (int i = 0; i < header.ChannelCount; i++) - { - if (!DecodeBlock(i, out short[] samples)) - { - if (IsLoopEnabled) - { - Reset(); - DecodeBlock(i, out samples); - } - - else - { - readSamples = sampleCount; - return -1; - } - } - - channelSamples[i] = samples; - } - - int position = startIndex; - - for (int i = 0; i < 32; i++) - { - for (int j = 0; j < header.ChannelCount; j++) - { - short sample = (short)(channelSamples[j][i] * volume); - - destination[position++] = (byte)sample; - destination[position++] = (byte)(sample >> 8); - } - } - - return position; - } - - private bool DecodeBlock(int c, out short[] samples) - { - int scale = EndianStream.ReadUInt16BE(source) + 1; - - // There seems to be a null sample block at the end of every adx file. - // It always added a half second delay between intro and loop, so - // I wanted to get rid of it. - if (scale > short.MaxValue + 1) - { - samples = null; - return false; - } - - samples = new short[32]; - - int sampleByte = 0; - - SampleHistory history = histories[c]; - for (int i = 0; i < 32; i++) - { - if ((i % 2) == 0) - { - sampleByte = source.ReadByte(); - } - - int sample = ((i & 1) != 0 ? - (sampleByte & 7) - (sampleByte & 8) : - ((sampleByte & 0x70) - (sampleByte & 0x80)) >> 4) * scale + - ((coef1 * history.Sample1 + coef2 * history.Sample2) >> 12); - - sample = sample > short.MaxValue ? short.MaxValue : sample < short.MinValue ? short.MinValue : sample; - - samples[i] = (short)sample; - - history.Sample2 = history.Sample1; - history.Sample1 = (short)sample; - - readSamples++; - } - - return true; - } - - public void Reset() - { - source.Seek(header.DataPosition + 4, SeekOrigin.Begin); - readSamples = 0; - } - - public void ReplaceHistories(AdxFileReader reader) - { - histories = reader.histories; - } - - public static AdxHeader LoadHeader(string sourceFileName) - { - using (Stream source = File.OpenRead(sourceFileName)) - { - return ReadHeader(source); - } - } - - public static AdxHeader ReadHeader(Stream source) - { - AdxHeader header = new AdxHeader(); - header.Identifier = EndianStream.ReadUInt16BE(source); - header.DataPosition = EndianStream.ReadUInt16BE(source); - header.EncodeType = EndianStream.ReadByte(source); - header.BlockLength = EndianStream.ReadByte(source); - header.SampleBitdepth = EndianStream.ReadByte(source); - header.ChannelCount = EndianStream.ReadByte(source); - header.SampleRate = EndianStream.ReadUInt32BE(source); - header.SampleCount = EndianStream.ReadUInt32BE(source); - header.CutoffFrequency = EndianStream.ReadUInt16BE(source); - header.Version = EndianStream.ReadUInt16BE(source); - return header; - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - source.Close(); - } - - base.Dispose(disposing); - } - - public AdxFileReader(string fileName) : this(File.OpenRead(fileName)) - { - } - - public AdxFileReader(Stream source) - { - this.source = source; - - header = AdxFileReader.ReadHeader(this.source); - source.Seek(header.DataPosition + 4, SeekOrigin.Begin); - - // Calculate coefficients - double a = Math.Sqrt(2.0); - double b = a - Math.Cos(header.CutoffFrequency * Math.PI * 2.0 / header.SampleRate); - double c = (b - Math.Sqrt((b - (a - 1.0)) * (a - 1.0 + b))) / (a - 1.0); - - coef1 = (short)(8192.0 * c); - coef2 = (short)(c * c * -4096.0); - - histories = new SampleHistory[header.ChannelCount]; - for (int i = 0; i < histories.Length; i++) - { - histories[i] = new SampleHistory(); - } - - sampleCount = (int)(header.SampleCount * header.ChannelCount); - waveFormat = new WaveFormat((int)header.SampleRate, 16, header.ChannelCount); - } - } - - public class ExtendedAdxFileReader : WaveStream - { - private List readers = new List(); - private int currentIndex = 0; - - public override long Length - { - get - { - long totalLength = 0; - - foreach (AdxFileReader reader in readers) - { - totalLength += reader.Length; - } - - return totalLength; - } - } - - public override long Position - { - get - { - long position = 0; - - foreach (AdxFileReader reader in readers) - { - if (reader == readers[currentIndex]) - { - position += reader.Position; - break; - } - - position += reader.Length; - } - - return position; - } - - set - { - throw new NotImplementedException(); - } - } - - public override WaveFormat WaveFormat - { - get - { - return readers[currentIndex].WaveFormat; - } - } - - public double Volume - { - set - { - readers.ForEach(reader => reader.Volume = value); - } - } - - public double Pitch - { - set - { - readers.ForEach(reader => reader.Pitch = value); - } - } - - public int DelayTime - { - set - { - readers.First().DelayTime = value; - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - int num = readers[currentIndex].Read(buffer, 0, count); - - if ((num < count) && !readers[currentIndex].IsLoopEnabled) - { - currentIndex++; - - readers[currentIndex].ReplaceHistories(readers[currentIndex - 1]); - - int num2 = readers[currentIndex].Read(buffer, num, count - num); - return num + num2; - } - - else if (readers[currentIndex].IsFinished && !readers[currentIndex].IsLoopEnabled) - { - currentIndex++; - - readers[currentIndex].ReplaceHistories(readers[currentIndex - 1]); - - num = readers[currentIndex].Read(buffer, 0, count); - } - - return num; - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - readers.ForEach(reader => reader.Dispose()); - } - - base.Dispose(disposing); - } - - public ExtendedAdxFileReader(params string[] fileNames) : this(fileNames.Select(fileName => File.OpenRead(fileName)).ToArray()) - { - } - - public ExtendedAdxFileReader(params Stream[] sources) - { - foreach (Stream source in sources) - { - readers.Add(new AdxFileReader(source)); - } - - // The last one is the one to loop - readers.Last().IsLoopEnabled = true; - } - } -} diff --git a/Source/CsbBuilder/Audio/ExtendedWaveStream.cs b/Source/CsbBuilder/Audio/ExtendedWaveStream.cs new file mode 100644 index 0000000..bcf9f88 --- /dev/null +++ b/Source/CsbBuilder/Audio/ExtendedWaveStream.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using NAudio.Wave; + +namespace CsbBuilder.Audio +{ + public class ExtendedWaveStream : WaveStream + { + private int currentStreamIndex = 0; + private readonly List streams = new List(); + + private double volume = 1; + private double pitch = 0; + private long delayTime = 0; + private DateTime startTime; + + public override WaveFormat WaveFormat + { + get + { + WaveFormat waveFormat = streams[currentStreamIndex].WaveFormat; + + return new WaveFormat(waveFormat.SampleRate + (int)(waveFormat.SampleRate * pitch), waveFormat.Channels); + } + } + + public override long Length + { + get + { + return streams.Sum(reader => reader.Length); + } + } + + public override long Position + { + get + { + return streams.Take(currentStreamIndex).Sum(reader => reader.Length) + streams[currentStreamIndex].Position; + } + + set + { + long position = 0; + + for (int i = 0; i < streams.Count; i++) + { + if (position + streams[i].Length > value) + { + currentStreamIndex = i; + streams[i].Position = value - position; + break; + } + + position += streams[i].Length; + } + } + } + + public double Volume + { + set + { + volume = value; + } + } + + public double Pitch + { + set + { + pitch = value; + } + } + + public int DelayTime + { + set + { + startTime = DateTime.Now; + delayTime = value * 10000; + } + } + + public bool ForceLoop { get; set; } + + public override int Read(byte[] buffer, int offset, int count) + { + if (delayTime > 0) + { + if ((DateTime.Now - startTime).Ticks < delayTime) + { + return count; + } + + delayTime = 0; + } + + int num = streams[currentStreamIndex].Read(buffer, 0, count); + + if (num < count && currentStreamIndex != streams.Count - 1) + { + currentStreamIndex++; + num += streams[currentStreamIndex].Read(buffer, num, count - num); + } + + else if (ForceLoop && num < count && currentStreamIndex == streams.Count - 1) + { + streams[currentStreamIndex].Position = 0; + num += streams[currentStreamIndex].Read(buffer, num, count - num); + } + + if (volume != 1) + { + PostSampleEditor.ApplyVolume(buffer, offset, num, volume); + } + + return num; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + foreach (var reader in streams) + { + reader.Dispose(); + } + } + + base.Dispose(disposing); + } + + public ExtendedWaveStream(params WaveStream[] waveStreams) + { + if (waveStreams.Length == 0) + { + throw new ArgumentException("You must at least specify one source!", nameof(waveStreams)); + } + + streams.AddRange(waveStreams); + } + } +} diff --git a/Source/CsbBuilder/Audio/PostSampleEditor.cs b/Source/CsbBuilder/Audio/PostSampleEditor.cs new file mode 100644 index 0000000..f7f1f18 --- /dev/null +++ b/Source/CsbBuilder/Audio/PostSampleEditor.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CsbBuilder.Audio +{ + public static class PostSampleEditor + { + public static void ApplyVolume(byte[] buffer, int offset, int count, double volume) + { + for (int i = offset; i < count; i += 2) + { + ApplyVolume(buffer, i, volume); + } + } + + public static void ApplyVolume(byte[] buffer, int offset, double volume) + { + int sample = (int)((short)(buffer[offset] | buffer[offset + 1] << 8) * volume); + + short sample16 = + sample > short.MaxValue ? short.MaxValue : + sample < short.MinValue ? short.MinValue : + (short)sample; + + buffer[offset] = (byte)sample16; + buffer[offset + 1] = (byte)(sample16 >> 8); + } + } +} diff --git a/Source/CsbBuilder/Audio/VGMStreamNative.cs b/Source/CsbBuilder/Audio/VGMStreamNative.cs new file mode 100644 index 0000000..44ebd48 --- /dev/null +++ b/Source/CsbBuilder/Audio/VGMStreamNative.cs @@ -0,0 +1,313 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +namespace CsbBuilder.Audio +{ + /// + /// Represents a static class for calling native VGMSTREAM methods. + /// + public static class VGMStreamNative + { + /// + /// Path of the VGMSTREAM DLL file. + /// + public const string DllName = "vgmstream.dll"; + + /// + /// Size of VGMSTREAM structure. + /// + public const int SizeOfVgmStream = 152; + + /// + /// Size of VGMSTREAMCHANNEL structure. + /// + public const int SizeOfVgmStreamChannel = 552; + + #region VGMStream Exports + /// + /// Initializes a VGMSTREAM from source file name by doing format detection and returns a usable pointer + /// to it, or NULL on failure. + /// + /// Path to source file name. + /// Pointer to VGMSTREAM or NULL on failure. + [DllImport(DllName, EntryPoint = "init_vgmstream", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr Initialize(string sourceFileName); + + /// + /// Initializes a VGMSTREAM from stream file by doing format detection and returns a usable pointer + /// to it, or NULL on failure. + /// + /// Pointer to stream file. + /// Pointer to VGMSTREAM or NULL on failure. + [DllImport(DllName, EntryPoint = "init_from_STREAMFILE", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr InitializeFromStreamFile(IntPtr streamFile); + + /// + /// Resets a VGMSTREAM to start of stream. + /// + /// Pointer to VGMSTREAM. + [DllImport(DllName, EntryPoint = "reset_vgmstream", CallingConvention = CallingConvention.Cdecl)] + public static extern void Reset(IntPtr vgmstream); + + /// + /// Closes an open VGMSTREAM. + /// + /// Pointer to VGMSTREAM. + [DllImport(DllName, EntryPoint = "close_vgmstream", CallingConvention = CallingConvention.Cdecl)] + public static extern void Close(IntPtr vgmstream); + + /// + /// Calculates the number of samples to be played based on looping parameters. + /// + [DllImport(DllName, EntryPoint = "get_vgmstream_play_samples", CallingConvention = CallingConvention.Cdecl)] + public static extern int GetPlaySamples(double loopTimes, double fadeSeconds, double fadeDelaySeconds, IntPtr vgmstream); + + /// + /// Renders VGMSTREAM to sample buffer. + /// + /// Destination sample buffer. + /// Amount of samples to render. + /// Pointer to VGMSTREAM. + [DllImport(DllName, EntryPoint = "render_vgmstream", CallingConvention = CallingConvention.Cdecl)] + public static extern void Render(short[] buffer, int sampleCount, IntPtr vgmstream); + + /// + /// Renders VGMSTREAM to byte buffer. + /// + /// Destination byte buffer. + /// Amount of samples to render. + /// Pointer to VGMSTREAM. + [DllImport(DllName, EntryPoint = "render_vgmstream", CallingConvention = CallingConvention.Cdecl)] + public static extern void Render8(byte[] buffer, int sampleCount, IntPtr vgmstream); + + /// + /// Writes a description of the stream into array pointed by description, + /// which must be length bytes long. Will always be null-terminated if length > 0 + /// + [DllImport(DllName, EntryPoint = "describe_vgmstream", CallingConvention = CallingConvention.Cdecl)] + public static extern void Describe(IntPtr vgmstream, IntPtr description, int length); + + /// + /// Returns the average bitrate in bps of all unique files contained within + /// this stream. Compares files by absolute paths. + /// + /// Pointer to VGMSTREAM. + [DllImport(DllName, EntryPoint = "get_vgmstream_average_bitrate", CallingConvention = CallingConvention.Cdecl)] + public static extern int GetAverageBitrate(IntPtr vgmstream); + + /// + /// Allocates a VGMSTREAM. + /// + [DllImport(DllName, EntryPoint = "allocate_vgmstream", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr Allocate(int channelCount, [MarshalAs(UnmanagedType.I4)]bool looped); + + /// + /// smallest self-contained group of samples is a frame + /// + [DllImport(DllName, EntryPoint = "get_vgmstream_samples_per_frame", CallingConvention = CallingConvention.Cdecl)] + public static extern int GetSamplesPerFrame(IntPtr vgmstream); + + /// + /// Gets the number of bytes per frame. + /// + [DllImport(DllName, EntryPoint = "get_vgmstream_frame_size", CallingConvention = CallingConvention.Cdecl)] + public static extern int GetFrameSize(IntPtr vgmstream); + + /// + /// in NDS IMA the frame size is the block size, so the last one is short + /// + [DllImport(DllName, EntryPoint = "get_vgmstream_samples_per_shortframe", CallingConvention = CallingConvention.Cdecl)] + public static extern int GetSamplesPerShortframe(IntPtr vgmstream); + + [DllImport(DllName, EntryPoint = "get_vgmstream_shortframe_size", CallingConvention = CallingConvention.Cdecl)] + public static extern int GetShortframeSize(IntPtr vgmstream); + + /// + /// Assumes that we have written samplesWrittem into the buffer already, and we have samplesToDo consecutive + /// samples ahead of us. Decode those samples into the buffer. + /// + [DllImport(DllName, EntryPoint = "decode_vgmstream", CallingConvention = CallingConvention.Cdecl)] + public static extern void Decode(IntPtr vgmstream, int samplesWritten, int samplesToDo, short[] buffer); + + /// + /// Assumes additionally that we have samplesToDo consecutive samples in "data", + /// and this this is for channel number "channel". + /// + [DllImport(DllName, EntryPoint = "decode_vgmstream_mem", CallingConvention = CallingConvention.Cdecl)] + public static extern void DecodeMem(IntPtr vgmstream, int samplesWritten, int samplesToDo, short[] buffer, byte[] data, int channel); + + /// + /// Calculates number of consecutive samples to do (taking into account stopping for loop start and end.) + /// + [DllImport(DllName, EntryPoint = "vgmstream_samples_to_do", CallingConvention = CallingConvention.Cdecl)] + public static extern int SamplesToDo(int samplesThisBlock, int samplesPerFrame, IntPtr vgmstream); + + /// + /// Detects start and save values, also detects end and restore values. Only works on exact sample values. + /// + [DllImport(DllName, EntryPoint = "vgmstream_do_loop", CallingConvention = CallingConvention.Cdecl)] + public static extern int DoLoop(IntPtr vgmstream); + + /// + /// Opens a stream for reading at offset (standarized taking into account layouts, channels and so on.) + /// returns 0 on failure + /// + [DllImport(DllName, EntryPoint = "vgmstream_open_stream", CallingConvention = CallingConvention.Cdecl)] + public static extern int OpenStream(IntPtr vgmstream, IntPtr streamFile, long position); + #endregion + + #region Format Exports + /// + /// Gets a pointer to the array of supported formats. + /// + [DllImport(DllName, EntryPoint = "vgmstream_get_formats", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr GetFormatsPtr(); + + /// + /// Gets the length of the format array. + /// + /// + [DllImport(DllName, EntryPoint = "vgmstream_get_formats_length", CallingConvention = CallingConvention.Cdecl)] + public static extern int GetFormatsLength(); + + [DllImport(DllName, EntryPoint = "get_vgmstream_coding_description", CallingConvention = CallingConvention.Cdecl)] + public static extern string GetCodingDescription(int codingEnumCode); + + [DllImport(DllName, EntryPoint = "get_vgmstream_layout_description", CallingConvention = CallingConvention.Cdecl)] + public static extern string GetLayoutDescription(int layoutEnumCode); + + [DllImport(DllName, EntryPoint = "get_vgmstream_meta_description", CallingConvention = CallingConvention.Cdecl)] + public static extern string GetMetaDescription(int metaEnumCode); + #endregion + + #region Helper Methods + /// + /// Gets the sample count of a VGMSTREAM. + /// + /// Pointer to VGMSTREAM. + public static int GetSampleCount(IntPtr vgmstream) + { + return Marshal.ReadInt32(vgmstream); + } + + /// + /// Gets the sample rate of a VGMSTREAM. + /// + /// Pointer to VGMSTREAM. + public static int GetSampleRate(IntPtr vgmstream) + { + return Marshal.ReadInt32(vgmstream, 4); + } + + /// + /// Gets the channel count of a VGMSTREAM. + /// + /// Pointer to VGMSTREAM. + public static int GetChannelCount(IntPtr vgmstream) + { + return Marshal.ReadInt32(vgmstream, 8); + } + + /// + /// Gets the absolute sample count of a VGMSTREAM. + /// (sample count * channel count) + /// + /// Pointer to VGMSTREAM. + public static int GetAbsoluteSampleCount(IntPtr vgmstream) + { + return GetSampleCount(vgmstream) * GetChannelCount(vgmstream); + } + + /// + /// Gets the loop flag of a VGMSTREAM. + /// + /// Pointer to VGMSTREAM. + public static bool GetLoopFlag(IntPtr vgmstream) + { + return Marshal.ReadInt32(vgmstream, 28) != 0; + } + + /// + /// Sets the loop flag of a VGMSTREAM. + /// + /// Pointer to VGMSTREAM. + public static void SetLoopFlag(IntPtr vgmstream, bool value) + { + if (value && !GetLoopFlag(vgmstream)) + { + Marshal.WriteIntPtr(vgmstream, 48, Marshal.AllocHGlobal(GetChannelCount(vgmstream) * SizeOfVgmStreamChannel)); + } + + Marshal.WriteInt32(vgmstream, 28, value ? 1 : 0); + } + + /// + /// Gets the loop start sample of a VGMSTREAM. + /// + /// Pointer to VGMSTREAM. + public static int GetLoopStartSample(IntPtr vgmstream) + { + return Marshal.ReadInt32(vgmstream, 32); + } + + /// + /// Sets the loop start sample of a VGMSTREAM. + /// + /// Pointer to VGMSTREAM. + public static void SetLoopStartSample(IntPtr vgmstream, int value) + { + Marshal.WriteInt32(vgmstream, 32, value); + } + + /// + /// Gets the loop end sample of a VGMSTREAM. + /// + /// Pointer to VGMSTREAM. + public static int GetLoopEndSample(IntPtr vgmstream) + { + return Marshal.ReadInt32(vgmstream, 36); + } + + /// + /// Sets the loop end sample of a VGMSTREAM. + /// + /// Pointer to VGMSTREAM. + public static void SetLoopEndSample(IntPtr vgmstream, int value) + { + Marshal.WriteInt32(vgmstream, 36, value); + } + + /// + /// Gets the current sample of a VGMSTREAM. + /// + /// Pointer to VGMSTREAM. + public static int GetCurrentSample(IntPtr vgmstream) + { + return Marshal.ReadInt32(vgmstream, 52); + } + + /// + /// Gets an array of supported formats. + /// + /// Pointer to VGMSTREAM. + public static string[] GetFormats() + { + string[] formats = new string[GetFormatsLength()]; + + IntPtr ptr = GetFormatsPtr(); + for (int i = 0; i < formats.Length; i++) + { + IntPtr stringPtr = Marshal.ReadIntPtr(ptr, i * IntPtr.Size); + formats[i] = Marshal.PtrToStringAnsi(stringPtr); + } + + return formats; + } + + #endregion + } +} diff --git a/Source/CsbBuilder/Audio/VGMStreamReader.cs b/Source/CsbBuilder/Audio/VGMStreamReader.cs new file mode 100644 index 0000000..dc13499 --- /dev/null +++ b/Source/CsbBuilder/Audio/VGMStreamReader.cs @@ -0,0 +1,282 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; +using System.Runtime.InteropServices; + +using NAudio.Wave; +using NAudio.Wave.SampleProviders; + +namespace CsbBuilder.Audio +{ + /// + /// Represents a NAudio WaveStream for VGMSTREAM playback. + /// + public class VGMStreamReader : WaveStream + { + private IntPtr vgmstream; + private WaveFormat waveFormat; + private int sampleCount; + private bool loopFlag; + + private byte[] cache; + + /// + /// Gets the wave format of the VGMSTREAM. + /// + public override WaveFormat WaveFormat + { + get + { + return waveFormat; + } + } + + /// + /// Gets the length of this VGMSTREAM in bytes. + /// + public override long Length + { + get + { + return sampleCount * waveFormat.Channels * 2; + } + } + + /// + /// Gets or sets the position of this VGMSTREAM in bytes. + /// + public override long Position + { + get + { + return VGMStreamNative.GetCurrentSample(vgmstream) * waveFormat.Channels * 2; + } + + set + { + CurrentSample = (int)(value / waveFormat.Channels / 2); + } + } + + /// + /// Gets or sets the current sample of this VGMSTREAM. + /// + public int CurrentSample + { + get + { + return VGMStreamNative.GetCurrentSample(vgmstream); + } + + set + { + int currentSample = VGMStreamNative.GetCurrentSample(vgmstream); + + if (value == currentSample || value > sampleCount) + { + return; + } + + if (value > currentSample) + { + value = value - currentSample; + } + + else if (value < currentSample) + { + VGMStreamNative.Reset(vgmstream); + } + + int cacheSampleLength = cache.Length / waveFormat.Channels / 2; + int length = 0; + + while (length < value) + { + if (length + cacheSampleLength >= value) + { + cacheSampleLength = value - length; + } + + VGMStreamNative.Render8(cache, cacheSampleLength, vgmstream); + length += cacheSampleLength; + } + } + } + + /// + /// Determines whether the loop is enabled for the VGMSTREAM. + /// + public bool LoopFlag + { + get + { + return loopFlag; + } + } + + /// + /// Gets the loop start sample of the VGMSTREAM. + /// + public long LoopStartSample + { + get + { + return VGMStreamNative.GetLoopStartSample(vgmstream); + } + } + + /// + /// Gets the loop start position of the VGMSTREAM in bytes. + /// + public long LoopStartPosition + { + get + { + return LoopStartSample * waveFormat.Channels * 2; + } + } + + /// + /// Gets the loop end sample of the VGMSTREAM. + /// + public long LoopEndSample + { + get + { + return VGMStreamNative.GetLoopEndSample(vgmstream); + } + } + + /// + /// Gets the loop end position of the VGMSTREAM in bytes. + /// + public long LoopEndPosition + { + get + { + return LoopEndSample * waveFormat.Channels * 2; + } + } + + /// + /// Forces the VGMSTREAM to loop from start to end. + /// This method destroys the previous loop points. + /// + public void ForceLoop() + { + VGMStreamNative.SetLoopFlag(vgmstream, true); + VGMStreamNative.SetLoopStartSample(vgmstream, 0); + VGMStreamNative.SetLoopEndSample(vgmstream, sampleCount); + + loopFlag = true; + } + + /// + /// Disables the loop for this VGMSTREAM. + /// + public void DisableLoop() + { + loopFlag = false; + VGMStreamNative.SetLoopFlag(vgmstream, false); + } + + /// + /// Resets the VGMSTREAM to beginning. + /// + public void Reset() + { + VGMStreamNative.Reset(vgmstream); + } + + /// + /// Renders the VGMSTREAM to byte buffer. + /// + /// Destination byte buffer. + /// Offset within buffer to write to. + /// Count of bytes to render. + /// Number of bytes read. + public override int Read(byte[] buffer, int offset, int count) + { + if (cache == null || cache.Length < buffer.Length) + { + cache = new byte[buffer.Length]; + } + + int currentSample = VGMStreamNative.GetCurrentSample(vgmstream); + int sampleCount_vgmstream = count / waveFormat.Channels / 2; + + if (currentSample >= sampleCount && !loopFlag) + { + return 0; + } + + if (!loopFlag && currentSample + sampleCount_vgmstream > sampleCount) + { + sampleCount_vgmstream = sampleCount - currentSample; + } + + count = sampleCount_vgmstream * waveFormat.Channels * 2; + + VGMStreamNative.Render8(cache, sampleCount_vgmstream, vgmstream); + Array.Copy(cache, 0, buffer, offset, count); + + return count; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + VGMStreamNative.Close(vgmstream); + } + + base.Dispose(disposing); + } + + private void FromPtr(IntPtr vgmstream) + { + if (vgmstream == IntPtr.Zero) + { + throw new NullReferenceException("VGMSTREAM pointer is set to null!"); + } + + waveFormat = new WaveFormat(VGMStreamNative.GetSampleRate(vgmstream), 16, VGMStreamNative.GetChannelCount(vgmstream)); + sampleCount = VGMStreamNative.GetSampleCount(vgmstream); + loopFlag = VGMStreamNative.GetLoopFlag(vgmstream); + + cache = new byte[4096]; + } + + /// + /// Constructs from source file name. + /// + /// Source file name to load. + public VGMStreamReader(string sourceFileName) + { + if (!File.Exists(sourceFileName)) + { + throw new FileNotFoundException($"VGMStream could not find file: {sourceFileName}"); + } + + vgmstream = VGMStreamNative.Initialize(sourceFileName); + if (vgmstream == IntPtr.Zero) + { + throw new NullReferenceException($"VGMStream could not initialize file: {sourceFileName}"); + } + + FromPtr(vgmstream); + } + + /// + /// Constructs from pointer. + /// + /// Pointer to VGMSTREAM. + public VGMStreamReader(IntPtr vgmstream) + { + FromPtr(vgmstream); + } + } +} diff --git a/Source/CsbBuilder/Builder/CsbBuilder.cs b/Source/CsbBuilder/Builder/CsbBuilder.cs index 87b9046..9863ac0 100644 --- a/Source/CsbBuilder/Builder/CsbBuilder.cs +++ b/Source/CsbBuilder/Builder/CsbBuilder.cs @@ -5,12 +5,12 @@ using System.Text; using System.IO; using CsbBuilder.Project; -using CsbBuilder.BuilderNode; +using CsbBuilder.BuilderNodes; using CsbBuilder.Serialization; using SonicAudioLib.CriMw; using SonicAudioLib.CriMw.Serialization; -using SonicAudioLib.Archive; +using SonicAudioLib.Archives; namespace CsbBuilder.Builder { @@ -192,6 +192,8 @@ namespace CsbBuilder.Builder Flag = CriAaxEntryFlag.Intro, FilePath = new FileInfo(Path.Combine(project.AudioDirectory.FullName, soundElementNode.Intro)), }); + + aaxArchive.SetModeFromExtension(soundElementNode.Intro); } if (!string.IsNullOrEmpty(soundElementNode.Loop)) @@ -201,6 +203,8 @@ namespace CsbBuilder.Builder Flag = CriAaxEntryFlag.Loop, FilePath = new FileInfo(Path.Combine(project.AudioDirectory.FullName, soundElementNode.Loop)), }); + + aaxArchive.SetModeFromExtension(soundElementNode.Loop); } byte[] data = new byte[0]; @@ -228,7 +232,7 @@ namespace CsbBuilder.Builder { Name = soundElementNode.Name, Data = data, - FormatType = 0, + FormatType = (byte)aaxArchive.Mode, SoundFrequency = soundElementNode.SampleRate, NumberChannels = soundElementNode.ChannelCount, Streaming = soundElementNode.Streaming, diff --git a/Source/CsbBuilder/BuilderNode/BuilderAisacGraphNode.cs b/Source/CsbBuilder/BuilderNodes/BuilderAisacGraphNode.cs similarity index 97% rename from Source/CsbBuilder/BuilderNode/BuilderAisacGraphNode.cs rename to Source/CsbBuilder/BuilderNodes/BuilderAisacGraphNode.cs index f39a9cd..4536a48 100644 --- a/Source/CsbBuilder/BuilderNode/BuilderAisacGraphNode.cs +++ b/Source/CsbBuilder/BuilderNodes/BuilderAisacGraphNode.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; using System.ComponentModel; -namespace CsbBuilder.BuilderNode +namespace CsbBuilder.BuilderNodes { public class BuilderAisacGraphNode : BuilderBaseNode { diff --git a/Source/CsbBuilder/BuilderNode/BuilderAisacNode.cs b/Source/CsbBuilder/BuilderNodes/BuilderAisacNode.cs similarity index 96% rename from Source/CsbBuilder/BuilderNode/BuilderAisacNode.cs rename to Source/CsbBuilder/BuilderNodes/BuilderAisacNode.cs index 86cf513..753dfb6 100644 --- a/Source/CsbBuilder/BuilderNode/BuilderAisacNode.cs +++ b/Source/CsbBuilder/BuilderNodes/BuilderAisacNode.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; using System.ComponentModel; -namespace CsbBuilder.BuilderNode +namespace CsbBuilder.BuilderNodes { public class BuilderAisacNode : BuilderBaseNode { diff --git a/Source/CsbBuilder/BuilderNode/BuilderAisacPointNode.cs b/Source/CsbBuilder/BuilderNodes/BuilderAisacPointNode.cs similarity index 94% rename from Source/CsbBuilder/BuilderNode/BuilderAisacPointNode.cs rename to Source/CsbBuilder/BuilderNodes/BuilderAisacPointNode.cs index e7c6a78..26d7144 100644 --- a/Source/CsbBuilder/BuilderNode/BuilderAisacPointNode.cs +++ b/Source/CsbBuilder/BuilderNodes/BuilderAisacPointNode.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; using System.ComponentModel; -namespace CsbBuilder.BuilderNode +namespace CsbBuilder.BuilderNodes { public class BuilderAisacPointNode : BuilderBaseNode { diff --git a/Source/CsbBuilder/BuilderNode/BuilderBaseNode.cs b/Source/CsbBuilder/BuilderNodes/BuilderBaseNode.cs similarity index 66% rename from Source/CsbBuilder/BuilderNode/BuilderBaseNode.cs rename to Source/CsbBuilder/BuilderNodes/BuilderBaseNode.cs index 10ec999..35be81e 100644 --- a/Source/CsbBuilder/BuilderNode/BuilderBaseNode.cs +++ b/Source/CsbBuilder/BuilderNodes/BuilderBaseNode.cs @@ -6,12 +6,12 @@ using System.Threading.Tasks; using System.ComponentModel; using System.Globalization; -namespace CsbBuilder.BuilderNode +namespace CsbBuilder.BuilderNodes { public abstract class BuilderBaseNode : ICloneable { [Category("General"), ReadOnly(true)] - [Description("The name of this node. Shift JIS encoding is used for this in the Cue Sheet Binary, so try to avoid using special characters that this codec does not support.")] + [Description("The name of this node.")] public string Name { get; set; } public object Clone() diff --git a/Source/CsbBuilder/BuilderNode/BuilderCueNode.cs b/Source/CsbBuilder/BuilderNodes/BuilderCueNode.cs similarity index 82% rename from Source/CsbBuilder/BuilderNode/BuilderCueNode.cs rename to Source/CsbBuilder/BuilderNodes/BuilderCueNode.cs index 1ca49f6..ad446bb 100644 --- a/Source/CsbBuilder/BuilderNode/BuilderCueNode.cs +++ b/Source/CsbBuilder/BuilderNodes/BuilderCueNode.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; using System.ComponentModel; -namespace CsbBuilder.BuilderNode +namespace CsbBuilder.BuilderNodes { public class BuilderCueNode : BuilderBaseNode { @@ -19,7 +19,7 @@ namespace CsbBuilder.BuilderNode public string SynthReference { get; set; } [Category("General"), DisplayName("User Comment")] - [Description("User comment of this Cue. Shift JIS encoding is used for this in the Cue Sheet Binary, so try to avoid using special characters that this codec does not support.")] + [Description("User comment of this Cue.")] public string UserComment { get; set; } [Category("General")] diff --git a/Source/CsbBuilder/BuilderNode/BuilderSoundElementNode.cs b/Source/CsbBuilder/BuilderNodes/BuilderSoundElementNode.cs similarity index 98% rename from Source/CsbBuilder/BuilderNode/BuilderSoundElementNode.cs rename to Source/CsbBuilder/BuilderNodes/BuilderSoundElementNode.cs index 418f8f5..d803fae 100644 --- a/Source/CsbBuilder/BuilderNode/BuilderSoundElementNode.cs +++ b/Source/CsbBuilder/BuilderNodes/BuilderSoundElementNode.cs @@ -5,7 +5,7 @@ using System.Text; using System.IO; using System.ComponentModel; -namespace CsbBuilder.BuilderNode +namespace CsbBuilder.BuilderNodes { public class BuilderSoundElementNode : BuilderBaseNode { diff --git a/Source/CsbBuilder/BuilderNode/BuilderSynthNode.cs b/Source/CsbBuilder/BuilderNodes/BuilderSynthNode.cs similarity index 96% rename from Source/CsbBuilder/BuilderNode/BuilderSynthNode.cs rename to Source/CsbBuilder/BuilderNodes/BuilderSynthNode.cs index c2f9035..f2b7b77 100644 --- a/Source/CsbBuilder/BuilderNode/BuilderSynthNode.cs +++ b/Source/CsbBuilder/BuilderNodes/BuilderSynthNode.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using System.ComponentModel; using System.Reflection; -namespace CsbBuilder.BuilderNode +namespace CsbBuilder.BuilderNodes { public enum BuilderSynthType { @@ -28,7 +28,7 @@ namespace CsbBuilder.BuilderNode { private BuilderSynthPlaybackType playbackType = BuilderSynthPlaybackType.Polyphonic; private Random random = new Random(); - private List indices = new List(); + private int previousChild = -1; private int nextChild = -1; private byte playbackProbability = 100; @@ -362,20 +362,15 @@ namespace CsbBuilder.BuilderNode { if (playbackType == BuilderSynthPlaybackType.RandomNoRepeat) { - if (indices.Count == Children.Count) + int randomChild = random.Next(Children.Count); + + while (randomChild == previousChild) { - indices.Clear(); + randomChild = random.Next(Children.Count); } - int nextChild = random.Next(Children.Count); - - while (indices.Contains(nextChild)) - { - nextChild = random.Next(Children.Count); - } - - indices.Add(nextChild); - return nextChild; + previousChild = randomChild; + return randomChild; } return random.Next(Children.Count); diff --git a/Source/CsbBuilder/BuilderNode/BuilderVoiceLimitGroupNode.cs b/Source/CsbBuilder/BuilderNodes/BuilderVoiceLimitGroupNode.cs similarity index 91% rename from Source/CsbBuilder/BuilderNode/BuilderVoiceLimitGroupNode.cs rename to Source/CsbBuilder/BuilderNodes/BuilderVoiceLimitGroupNode.cs index ba32ff9..54b9255 100644 --- a/Source/CsbBuilder/BuilderNode/BuilderVoiceLimitGroupNode.cs +++ b/Source/CsbBuilder/BuilderNodes/BuilderVoiceLimitGroupNode.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; using System.ComponentModel; -namespace CsbBuilder.BuilderNode +namespace CsbBuilder.BuilderNodes { public class BuilderVoiceLimitGroupNode : BuilderBaseNode { diff --git a/Source/CsbBuilder/CsbBuilder.csproj b/Source/CsbBuilder/CsbBuilder.csproj index 7945c71..724701b 100644 --- a/Source/CsbBuilder/CsbBuilder.csproj +++ b/Source/CsbBuilder/CsbBuilder.csproj @@ -9,9 +9,10 @@ Properties CsbBuilder CsbBuilder - v4.5.2 + v4.6.1 512 true + AnyCPU @@ -22,6 +23,7 @@ DEBUG;TRACE prompt 4 + false AnyCPU @@ -53,15 +55,18 @@ - - - - - - - - - + + + + + + + + + + + + Form @@ -228,7 +233,11 @@ + + + copy "$(SolutionDir)Libraries\*.dll" "$(TargetDir)" +