diff --git a/AutomationKeyframe.cs b/AutomationKeyframe.cs index 28e4b5c..9ff9c2a 100644 --- a/AutomationKeyframe.cs +++ b/AutomationKeyframe.cs @@ -5,5 +5,6 @@ public class AutomationKeyframe public int Position { get; set; } = 0; public double Value { get; set; } = 0; public float Tension { get; set; } = 0; + public byte Mode { get; set; } = 0; } } diff --git a/Enums.cs b/Enums.cs index 45dd681..7a81eb8 100644 --- a/Enums.cs +++ b/Enums.cs @@ -129,6 +129,40 @@ public enum PluginType Vst = 8 } + /* + * Modes: + * 0 - Single Curve + * 1 - Double Curve + * 2 - Hold + * 3 - Stairs + * 4 - Smooth Stairs + * 5 - Pulse + * 6 - Wave + * 7 - Single Curve 2 + * 8 - Double Curve 2 + * 9 - Half Sine + * 10 - Smooth + * 11 - Single Curve 3 + * 12 - Double Curve 3 + */ + + public enum AutomationMode + { + SingleCurve = 0, + DoubleCurve = 1, + Hold = 2, + Stairs = 3, + SmoothStairs = 4, + Pulse = 5, + Wave = 6, + SingleCurve2 = 7, + DoubleCurve2 = 8, + HalfSine = 9, + Smooth = 10, + SingleCurve3 = 11, + DoubleCurve3 = 12 + } + /*public enum FilterType { LowPass = 0, diff --git a/GeneratorData.cs b/GeneratorData.cs index cfb533d..927c7bb 100644 --- a/GeneratorData.cs +++ b/GeneratorData.cs @@ -8,6 +8,9 @@ public class GeneratorData : IChannelData public double Volume { get; set; } = 100; public double Panning { get; set; } = 0; public uint BaseNote { get; set; } = 57; + public byte Echo { get; set; } = 0; + public uint EchoFeed { get; set; } = 0; + public uint EchoTime { get; set; } = 0; public int Insert { get; set; } = -1; public int LayerParent { get; set; } = -1; diff --git a/IPlaylistItem.cs b/IPlaylistItem.cs index e46b531..fbb5b0c 100644 --- a/IPlaylistItem.cs +++ b/IPlaylistItem.cs @@ -5,6 +5,7 @@ public interface IPlaylistItem int Position { get; set; } int Length { get; set; } int StartOffset { get; set; } + bool Muted { get; set; } int EndOffset { get; set; } } } diff --git a/Monad.FLParser.csproj b/Monad.FLParser.csproj index 6909094..83a13ae 100644 --- a/Monad.FLParser.csproj +++ b/Monad.FLParser.csproj @@ -1,59 +1,30 @@ - - - + + - Debug - AnyCPU - {CE83037E-41AA-4C72-BE2E-1DF90583AD17} Library - Properties - Monad.FLParser - Monad.FLParser - v4.5.2 - 512 + netstandard2.0 + AnyCPU;x64 - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 + + + true - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 + + + true + + + true + + + + true + + - - - - - - - - - - - - - - - - - - - - - - - - + + + - \ No newline at end of file diff --git a/Note.cs b/Note.cs index 9dfbe08..318c29d 100644 --- a/Note.cs +++ b/Note.cs @@ -3,6 +3,7 @@ public class Note { public int Position { get; set; } + public byte Color { get; set; } public int Length { get; set; } public byte Key { get; set; } public ushort FinePitch { get; set; } diff --git a/Project.cs b/Project.cs index 67ac34f..e13017f 100644 --- a/Project.cs +++ b/Project.cs @@ -6,7 +6,7 @@ namespace Monad.FLParser public class Project { public const int MaxInsertCount = 127; - public const int MaxTrackCount = 199; + //public const int MaxTrackCount = 499; //No longer used public int MainVolume { get; set; } = 300; public int MainPitch { get; set; } = 0; @@ -19,24 +19,20 @@ public class Project public string VersionString { get; set; } = string.Empty; public int Version { get; set; } = 0x100; public List Channels { get; set; } = new List(); - public Track[] Tracks { get; set; } = new Track[MaxTrackCount]; + public Track[] Tracks { get; set; } public List Patterns = new List(); public Insert[] Inserts { get; set; } = new Insert[MaxInsertCount]; public bool PlayTruncatedNotes { get; set; } = false; public Project() { - for (var i = 0; i < MaxTrackCount; i++) - { - Tracks[i] = new Track(); - } - for (var i = 0; i < MaxInsertCount; i++) { Inserts[i] = new Insert { Id = i, Name = $"Insert {i}" }; } Inserts[0].Name = "Master"; + InitTracks(199); } public static Project Load(string path, bool verbose) @@ -60,5 +56,15 @@ public static Project Load(BinaryReader reader, bool verbose) var parser = new ProjectParser(verbose); return parser.Parse(reader); } + + internal void InitTracks(int count) + { + Tracks = new Track[count]; + + for(var i = 0; i < Tracks.Length; i++) + { + Tracks[i] = new Track(); + } + } } } diff --git a/ProjectParser.cs b/ProjectParser.cs index ef574d6..667131a 100644 --- a/ProjectParser.cs +++ b/ProjectParser.cs @@ -76,8 +76,8 @@ private void ParseFldt(BinaryReader reader) len = reader.ReadInt32(); // sanity check - if (len < 0 || len > 0x10000000) - throw new FlParseException($"Invalid chunk length: {len}", reader.BaseStream.Position); + //if (len < 0 || len > 0x10000000) + // throw new FlParseException($"Invalid chunk length: {len}", reader.BaseStream.Position); } while (id != "FLdt"); } @@ -243,6 +243,10 @@ private void ParseTextEvent(Enums.Event eventId, BinaryReader reader) _project.Version = (int.Parse(numbers[0]) << 8) + (int.Parse(numbers[1]) << 4) + (int.Parse(numbers[2]) << 0); + + var trackCount = _versionMajor <= 12 ? 199 : 500; + if(_project.Tracks.Length != trackCount) + _project.InitTracks(trackCount); break; case Enums.Event.GeneratorName: if (genData != null) genData.GeneratorName = unicodeString; @@ -250,6 +254,14 @@ private void ParseTextEvent(Enums.Event eventId, BinaryReader reader) case Enums.Event.TextInsertName: _curInsert.Name = unicodeString; break; + case Enums.Event.TextDelay: + if(genData != null) + { + genData.Echo = dataBytes[12]; + genData.EchoFeed = (uint)((dataBytes[1] << 8) | dataBytes[0]); + genData.EchoTime = (uint)((dataBytes[17] << 8) | dataBytes[16]); + } + break; } } @@ -286,6 +298,7 @@ private void ParseDataEvent(Enums.Event eventId, BinaryReader reader) case Enums.Event.DataChanParams: { if (genData == null) break; + //Console.WriteLine(reader.BaseStream.Position); var unknown1 = reader.ReadBytes(40); genData.ArpDir = (Enums.ArpDirection)reader.ReadInt32(); genData.ArpRange = reader.ReadInt32(); @@ -315,7 +328,8 @@ private void ParseDataEvent(Enums.Event eventId, BinaryReader reader) var unknown3 = reader.ReadInt16(); var unknown4 = reader.ReadByte(); var finePitch = reader.ReadUInt16(); - var release = reader.ReadUInt16(); + var release = reader.ReadByte(); //Its actually a byte, next byte after is note channel + var color = reader.ReadByte(); //Used for map note color to midi channel var pan = reader.ReadByte(); var velocity = reader.ReadByte(); var x1 = reader.ReadByte(); @@ -326,6 +340,7 @@ private void ParseDataEvent(Enums.Event eventId, BinaryReader reader) _curPattern.Notes[channel].Add(new Note { Position = pos, + Color = color, Length = length, Key = key, FinePitch = finePitch, @@ -418,12 +433,24 @@ private void ParseDataEvent(Enums.Event eventId, BinaryReader reader) var unknown2 = reader.ReadUInt32(); var unknown3 = reader.ReadByte(); var param = reader.ReadUInt16(); - var paramDestination = reader.ReadInt16(); + var paramDestination = reader.ReadUInt16(); var unknown4 = reader.ReadUInt64(); var channel = _project.Channels[automationChannel]; - if ((paramDestination & 0x2000) == 0) // Automation on channel + if(paramDestination > _project.Channels.Count) + { + //Console.WriteLine("Tempo Automation Track found, but is currently not supported. Please use SAFC to merge the tempo back"); + //break; + channel.Data = new AutomationData // automation on insert slot + { + Parameter = param & 0x7fff + //InsertId = (paramDestination & 0x0FF0) >> 6, // seems to be out by one + //SlotId = paramDestination & 0x003F + }; + } + + if ((paramDestination & 0x2000) == 0 && paramDestination < _project.Channels.Count) // Automation on channel { channel.Data = new AutomationData { @@ -451,10 +478,10 @@ private void ParseDataEvent(Enums.Event eventId, BinaryReader reader) var patternId = reader.ReadUInt16(); var length = reader.ReadInt32(); var track = reader.ReadInt32(); - if (_versionMajor == 20) - track = 501 - track; + if(_versionMajor == 20) + track = 499 - track; else - track = 198 - track; + track = 198 - track; var unknown1 = reader.ReadUInt16(); var itemFlags = reader.ReadUInt16(); var unknown3 = reader.ReadUInt32(); @@ -481,15 +508,28 @@ private void ParseDataEvent(Enums.Event eventId, BinaryReader reader) var startOffset = reader.ReadInt32(); var endOffset = reader.ReadInt32(); - _project.Tracks[track].Items.Add(new PatternPlaylistItem + if((patternId - patternBase - 1) > _project.Patterns.Count) { + Console.WriteLine("Pattern found on track " + (track) + ", but it seems to be empty. Skipping..."); + break; + } + + try + { + _project.Tracks[track].Items.Add(new PatternPlaylistItem + { Position = startTime, Length = length, StartOffset = startOffset, EndOffset = endOffset, Pattern = _project.Patterns[patternId - patternBase - 1], Muted = muted - }); + }); + } catch(Exception e) + { + Console.WriteLine("Pattern found on track " + (track) + ", but it seems to be empty. Skipping..."); + break; + } } } break; @@ -508,12 +548,15 @@ private void ParseDataEvent(Enums.Event eventId, BinaryReader reader) for (var i = 0; i < keyCount; i++) { + reader.ReadBytes(3); var startPos = reader.BaseStream.Position; - var keyPos = reader.ReadDouble(); - var keyVal = reader.ReadDouble(); - var keyTension = reader.ReadSingle(); - var unknown7 = reader.ReadUInt32(); // seems linked to tension? + var keyPos = reader.ReadUInt32(); //This is a UInt32 + reader.ReadUInt32(); //Skip 4 bytes + var keyVal = reader.ReadUInt32(); //This is a UInt32 + reader.ReadByte(); //Skip a byte, since it's useless + var keyTension = reader.ReadSingle(); // Tension is a single + var mode = reader.ReadByte(); // Seems to be the mode of the keyframe var endPos = reader.BaseStream.Position; reader.BaseStream.Position = startPos; @@ -524,8 +567,10 @@ private void ParseDataEvent(Enums.Event eventId, BinaryReader reader) { Position = (int)(keyPos * _project.Ppq), Tension = keyTension, - Value = keyVal + Value = keyVal, + Mode = mode }; + reader.ReadBytes(3); } // remaining data is unknown @@ -563,7 +608,7 @@ private Plugin ParsePluginChunk(byte[] chunk) if (pluginType != Enums.PluginType.Vst) { - return null; + return null; } while (reader.BaseStream.Position < reader.BaseStream.Length) @@ -591,6 +636,7 @@ private Plugin ParsePluginChunk(byte[] chunk) } } + //Console.WriteLine(reader.BaseStream.Position); return plugin; } } diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs deleted file mode 100644 index b52e19c..0000000 --- a/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Monad.FLParser")] -[assembly: AssemblyDescription("An FL Studio project file parser for .NET")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("MONAD Demogroup")] -[assembly: AssemblyProduct("FLParser")] -[assembly: AssemblyCopyright("Copyright © MONAD 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("ce83037e-41aa-4c72-be2e-1df90583ad17")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.1.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/README.md b/README.md index d0d2e85..0b06a34 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ Represents an individual keyframe in an automation track. Has the following prop int Position; // keyframe position in pulses double Value; // keyframe value float Tension; // keyframe tension +byte Mode; // keyframe mode ``` #### `class Track` @@ -264,6 +265,24 @@ Flags for inserts to specify their states. Values are: - `Lock = 1 << 11` - insert is locked - `Solo = 1 << 12` - insert is solo'd (all other inserts will be muted) +### `enum AutomationMode` + +Automation ketframe modes. Values are: + + - `SingleCurve = 1` + - `SingleCurve = 2` + - `Hold = 2` + - `Stairs = 3` + - `SmoothStairs = 4` + - `Pulse = 5` + - `Wave = 6` + - `SingleCurve2 = 7` + - `DoubleCurve2 = 8` + - `HalfSine = 9` + - `Smooth = 10` + - `SingleCurve3 = 11` + - `DoubleCurve3 = 12` + ## license -Licensed under the GPL3 license. See the LICENSE file for more information. \ No newline at end of file +Licensed under the GPL3 license. See the LICENSE file for more information.