From 0aae6b20f21d71572cf484846480b83e07e5116e Mon Sep 17 00:00:00 2001 From: Kermalis <29823718+Kermalis@users.noreply.github.com> Date: Wed, 29 Aug 2018 21:40:11 -0400 Subject: [PATCH] InfoChunk editing --- README.md | 4 +- SoundFont2/SF2.cs | 81 +++++++------- SoundFont2/SF2Chunks.cs | 234 +++++++++++++++++++++++++++++++++------- SoundFont2/SF2Types.cs | 9 +- 4 files changed, 240 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index d6cbd37..73c5404 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ A C# library that can read and write SF2 sound bank files. ---- # To Do: -* Adding info to InfoListChunk * Prevent loading the terminal data * Find a way to "public seal" SF2Chunk and SF2ListChunk * Getting info such as samples, modulators and generators -* Separate chunks into their own files \ No newline at end of file +* Separate chunks into their own files +* Certain generators must be in a specified index (KeyRange, VelRange, SampleID and Instrument) according to spec \ No newline at end of file diff --git a/SoundFont2/SF2.cs b/SoundFont2/SF2.cs index 1ec824a..9db36da 100644 --- a/SoundFont2/SF2.cs +++ b/SoundFont2/SF2.cs @@ -6,23 +6,16 @@ namespace Kermalis.SoundFont2 public sealed class SF2 { uint size; - InfoListChunk infoChunk; - SdtaListChunk soundChunk; - PdtaListChunk hydraChunk; - - internal uint IBAGCount => hydraChunk.IBAGSubChunk.Count; - internal uint IGENCount => hydraChunk.IGENSubChunk.Count; - internal uint IMODCount => hydraChunk.IMODSubChunk.Count; - internal uint PBAGCount => hydraChunk.PBAGSubChunk.Count; - internal uint PGENCount => hydraChunk.PGENSubChunk.Count; - internal uint PMODCount => hydraChunk.PMODSubChunk.Count; + public readonly InfoListChunk InfoChunk; + public readonly SdtaListChunk SoundChunk; + public readonly PdtaListChunk HydraChunk; // For creating - public SF2(string engine = "", string bank = "") + public SF2() { - infoChunk = new InfoListChunk(this, engine, bank); - soundChunk = new SdtaListChunk(this); - hydraChunk = new PdtaListChunk(this); + InfoChunk = new InfoListChunk(this); + SoundChunk = new SdtaListChunk(this); + HydraChunk = new PdtaListChunk(this); } // For reading @@ -39,9 +32,9 @@ public SF2(string path) if (new string(chars) != "sfbk") throw new InvalidDataException("sfbk header was not found at the expected offset."); - infoChunk = new InfoListChunk(this, reader); - soundChunk = new SdtaListChunk(this, reader); - hydraChunk = new PdtaListChunk(this, reader); + InfoChunk = new InfoListChunk(this, reader); + SoundChunk = new SdtaListChunk(this, reader); + HydraChunk = new PdtaListChunk(this, reader); } } @@ -55,9 +48,9 @@ public void Save(string path) writer.Write(size); writer.Write("sfbk".ToCharArray()); - infoChunk.Write(writer); - soundChunk.Write(writer); - hydraChunk.Write(writer); + InfoChunk.Write(writer); + SoundChunk.Write(writer); + HydraChunk.Write(writer); } } @@ -65,7 +58,7 @@ public void Save(string path) // Returns sample index public uint AddSample(short[] pcm16, string name, bool bLoop, uint loopPos, uint sampleRate, byte originalKey, sbyte pitchCorrection) { - uint start = soundChunk.SMPLSubChunk.AddSample(pcm16, bLoop, loopPos); + uint start = SoundChunk.SMPLSubChunk.AddSample(pcm16, bLoop, loopPos); // If the sample is looped the standard requires us to add the 8 bytes from the start of the loop to the end uint end, loopEnd, loopStart; @@ -86,66 +79,66 @@ public uint AddSample(short[] pcm16, string name, bool bLoop, uint loopPos, uint // Returns instrument index public uint AddInstrument(string name) { - return hydraChunk.INSTSubChunk.AddInstrument(new SF2Instrument(this) + return HydraChunk.INSTSubChunk.AddInstrument(new SF2Instrument(this) { InstrumentName = name, - InstrumentBagIndex = (ushort)IBAGCount + InstrumentBagIndex = (ushort)HydraChunk.IBAGSubChunk.Count }); } public void AddInstrumentBag() { - hydraChunk.IBAGSubChunk.AddBag(new SF2Bag(this, false)); + HydraChunk.IBAGSubChunk.AddBag(new SF2Bag(this, false)); } public void AddInstrumentModulator() { - hydraChunk.IMODSubChunk.AddModulator(new SF2ModulatorList(this)); + HydraChunk.IMODSubChunk.AddModulator(new SF2ModulatorList(this)); } public void AddInstrumentGenerator() { - hydraChunk.IGENSubChunk.AddGenerator(new SF2GeneratorList(this)); + HydraChunk.IGENSubChunk.AddGenerator(new SF2GeneratorList(this)); } - public void AddInstrumentGenerator(SF2Generator operation, SF2GeneratorAmount genAmountType) + public void AddInstrumentGenerator(SF2Generator generator, SF2GeneratorAmount amount) { - hydraChunk.IGENSubChunk.AddGenerator(new SF2GeneratorList(this) + HydraChunk.IGENSubChunk.AddGenerator(new SF2GeneratorList(this) { - Generator = operation, - GeneratorAmount = genAmountType + Generator = generator, + GeneratorAmount = amount }); } public void AddPreset(string name, ushort preset, ushort bank) { - hydraChunk.PHDRSubChunk.AddPreset(new SF2PresetHeader(this) + HydraChunk.PHDRSubChunk.AddPreset(new SF2PresetHeader(this) { PresetName = name, Preset = preset, Bank = bank, - PresetBagIndex = (ushort)PBAGCount + PresetBagIndex = (ushort)HydraChunk.PBAGSubChunk.Count }); } public void AddPresetBag() { - hydraChunk.PBAGSubChunk.AddBag(new SF2Bag(this, true)); + HydraChunk.PBAGSubChunk.AddBag(new SF2Bag(this, true)); } public void AddPresetModulator() { - hydraChunk.PMODSubChunk.AddModulator(new SF2ModulatorList(this)); + HydraChunk.PMODSubChunk.AddModulator(new SF2ModulatorList(this)); } public void AddPresetGenerator() { - hydraChunk.PGENSubChunk.AddGenerator(new SF2GeneratorList(this)); + HydraChunk.PGENSubChunk.AddGenerator(new SF2GeneratorList(this)); } - public void AddPresetGenerator(SF2Generator operation, SF2GeneratorAmount genAmountType) + public void AddPresetGenerator(SF2Generator generator, SF2GeneratorAmount amount) { - hydraChunk.PGENSubChunk.AddGenerator(new SF2GeneratorList(this) + HydraChunk.PGENSubChunk.AddGenerator(new SF2GeneratorList(this) { - Generator = operation, - GeneratorAmount = genAmountType + Generator = generator, + GeneratorAmount = amount }); } uint AddSampleHeader(string name, uint start, uint end, uint loopStart, uint loopEnd, uint sampleRate, byte originalKey, sbyte pitchCorrection) { - return hydraChunk.SHDRSubChunk.AddSample(new SF2SampleHeader(this) + return HydraChunk.SHDRSubChunk.AddSample(new SF2SampleHeader(this) { SampleName = name, Start = start, @@ -172,12 +165,12 @@ void AddTerminals() internal void UpdateSize() { - if (infoChunk == null || soundChunk == null || hydraChunk == null) + if (InfoChunk == null || SoundChunk == null || HydraChunk == null) return; size = 4 - + infoChunk.UpdateSize() + 8 - + soundChunk.UpdateSize() + 8 - + hydraChunk.UpdateSize() + 8; + + InfoChunk.UpdateSize() + 8 + + SoundChunk.UpdateSize() + 8 + + HydraChunk.UpdateSize() + 8; } } } diff --git a/SoundFont2/SF2Chunks.cs b/SoundFont2/SF2Chunks.cs index a882162..e1e6f22 100644 --- a/SoundFont2/SF2Chunks.cs +++ b/SoundFont2/SF2Chunks.cs @@ -62,7 +62,7 @@ public sealed class SF2PresetHeader char[] presetName; // Length 20 public string PresetName { - get => new string(presetName); + get => new string(presetName).TrimEnd('\0'); set => SF2Utils.TruncateOrNot(value, 20, ref presetName); } public ushort Preset, Bank, PresetBagIndex; @@ -115,13 +115,13 @@ internal SF2Bag(SF2 inSf2, bool preset) sf2 = inSf2; if (preset) { - GeneratorIndex = (ushort)sf2.PGENCount; - ModulatorIndex = (ushort)sf2.PMODCount; + GeneratorIndex = (ushort)sf2.HydraChunk.PGENSubChunk.Count; + ModulatorIndex = (ushort)sf2.HydraChunk.PMODSubChunk.Count; } else { - GeneratorIndex = (ushort)sf2.IGENCount; - ModulatorIndex = (ushort)sf2.IMODCount; + GeneratorIndex = (ushort)sf2.HydraChunk.IGENSubChunk.Count; + ModulatorIndex = (ushort)sf2.HydraChunk.IMODSubChunk.Count; } } internal SF2Bag(SF2 inSf2, BinaryReader reader) @@ -217,7 +217,7 @@ public sealed class SF2Instrument char[] instrumentName; // Length 20 public string InstrumentName { - get => new string(instrumentName); + get => new string(instrumentName).TrimEnd('\0'); set => SF2Utils.TruncateOrNot(value, 20, ref instrumentName); } public ushort InstrumentBagIndex; @@ -250,7 +250,7 @@ public sealed class SF2SampleHeader char[] sampleName; // Length 20 public string SampleName { - get => new string(sampleName); + get => new string(sampleName).TrimEnd('\0'); set => SF2Utils.TruncateOrNot(value, 20, ref sampleName); } public uint Start; @@ -313,11 +313,7 @@ internal VersionSubChunk(SF2 inSf2, string subChunkName) : base(inSf2, subChunkN } internal VersionSubChunk(SF2 inSf2, BinaryReader reader) : base(inSf2, reader) { - Version = new SF2VersionTag - { - Major = reader.ReadUInt16(), - Minor = reader.ReadUInt16() - }; + Version = new SF2VersionTag(reader.ReadUInt16(), reader.ReadUInt16()); } internal override void Write(BinaryWriter writer) { @@ -336,7 +332,7 @@ public sealed class HeaderSubChunk : SF2Chunk char[] field; public string Field { - get => new string(field); + get => new string(field).TrimEnd('\0'); set { var strAsList = value.ToCharArray().ToList(); @@ -606,34 +602,192 @@ public sealed class InfoListChunk : SF2ListChunk { readonly List subChunks = new List(); - /*public InfoListChunk(SF2 inSf2, string engine, string bank, string rom, SF2VersionTag rom_revision, string date, string designer, string products, string copyright, string comment, string tools) - : base(inSf2, "INFO") - { - - // Optional sub-chunks - if (!string.IsNullOrEmpty(rom)) - subChunks.Add(new HeaderSubChunk(inSf2, "irom", rom)); - if (rom_revision != null) - subChunks.Add(new VersionSubChunk(inSf2, "iver", rom_revision)); - if (!string.IsNullOrEmpty(date)) - subChunks.Add(new HeaderSubChunk(inSf2, "ICRD", date)); - if (!string.IsNullOrEmpty(designer)) - subChunks.Add(new HeaderSubChunk(inSf2, "IENG", designer)); - if (!string.IsNullOrEmpty(products)) - subChunks.Add(new HeaderSubChunk(inSf2, "IPRD", products)); - if (!string.IsNullOrEmpty(copyright)) - subChunks.Add(new HeaderSubChunk(inSf2, "ICOP", copyright)); - if (!string.IsNullOrEmpty(comment)) - subChunks.Add(new HeaderSubChunk(inSf2, "ICMT", comment, 0x10000)); - if (!string.IsNullOrEmpty(tools)) - subChunks.Add(new HeaderSubChunk(inSf2, "ISFT", tools)); - }*/ - internal InfoListChunk(SF2 inSf2, string engine = "", string bank = "") : base(inSf2, "INFO") + const string defaultEngine = "EMU8000"; + public string Engine + { + get + { + if (subChunks.Find(s => s.ChunkName == "isng") is HeaderSubChunk chunk) + return chunk.Field; + else + { + subChunks.Add(new HeaderSubChunk(sf2, "isng") { Field = defaultEngine }); + return defaultEngine; + } + } + set + { + if (subChunks.Find(s => s.ChunkName == "isng") is HeaderSubChunk chunk) + chunk.Field = value; + else + subChunks.Add(new HeaderSubChunk(sf2, "isng") { Field = value }); + } + } + const string defaultBank = "General MIDI"; + public string Bank + { + get + { + if (subChunks.Find(s => s.ChunkName == "INAM") is HeaderSubChunk chunk) + return chunk.Field; + else + { + subChunks.Add(new HeaderSubChunk(sf2, "INAM") { Field = defaultBank }); + return defaultBank; + } + } + set + { + if (subChunks.Find(s => s.ChunkName == "INAM") is HeaderSubChunk chunk) + chunk.Field = value; + else + subChunks.Add(new HeaderSubChunk(sf2, "INAM") { Field = value }); + } + } + public string ROM + { + get + { + if (subChunks.Find(s => s.ChunkName == "irom") is HeaderSubChunk chunk) + return chunk.Field; + else + return string.Empty; + } + set + { + if (subChunks.Find(s => s.ChunkName == "irom") is HeaderSubChunk chunk) + chunk.Field = value; + else + subChunks.Add(new HeaderSubChunk(sf2, "irom") { Field = value }); + } + } + public SF2VersionTag ROMVersion + { + get + { + if (subChunks.Find(s => s.ChunkName == "iver") is VersionSubChunk chunk) + return chunk.Version; + else + return null; + } + set + { + if (subChunks.Find(s => s.ChunkName == "iver") is VersionSubChunk chunk) + chunk.Version = value; + else + subChunks.Add(new VersionSubChunk(sf2, "iver") { Version = value }); + } + } + public string Date + { + get + { + if (subChunks.Find(s => s.ChunkName == "ICRD") is HeaderSubChunk chunk) + return chunk.Field; + else + return string.Empty; + } + set + { + if (subChunks.Find(s => s.ChunkName == "ICRD") is HeaderSubChunk chunk) + chunk.Field = value; + else + subChunks.Add(new HeaderSubChunk(sf2, "ICRD") { Field = value }); + } + } + public string Designer + { + get + { + if (subChunks.Find(s => s.ChunkName == "IENG") is HeaderSubChunk chunk) + return chunk.Field; + else + return string.Empty; + } + set + { + if (subChunks.Find(s => s.ChunkName == "IENG") is HeaderSubChunk chunk) + chunk.Field = value; + else + subChunks.Add(new HeaderSubChunk(sf2, "IENG") { Field = value }); + } + } + public string Products + { + get + { + if (subChunks.Find(s => s.ChunkName == "IPRD") is HeaderSubChunk chunk) + return chunk.Field; + else + return string.Empty; + } + set + { + if (subChunks.Find(s => s.ChunkName == "IPRD") is HeaderSubChunk chunk) + chunk.Field = value; + else + subChunks.Add(new HeaderSubChunk(sf2, "IPRD") { Field = value }); + } + } + public string Copyright + { + get + { + if (subChunks.Find(s => s.ChunkName == "ICOP") is HeaderSubChunk icop) + return icop.Field; + else + return string.Empty; + } + set + { + if (subChunks.Find(s => s.ChunkName == "ICOP") is HeaderSubChunk chunk) + chunk.Field = value; + else + subChunks.Add(new HeaderSubChunk(sf2, "ICOP") { Field = value }); + } + } + const int commentMaxSize = 0x10000; + public string Comment + { + get + { + if (subChunks.Find(s => s.ChunkName == "ICMT") is HeaderSubChunk chunk) + return chunk.Field; + else + return string.Empty; + } + set + { + if (subChunks.Find(s => s.ChunkName == "ICMT") is HeaderSubChunk chunk) + chunk.Field = value; + else + subChunks.Add(new HeaderSubChunk(sf2, "ICMT", commentMaxSize) { Field = value }); + } + } + public string Tools + { + get + { + if (subChunks.Find(s => s.ChunkName == "ISFT") is HeaderSubChunk chunk) + return chunk.Field; + else + return string.Empty; + } + set + { + if (subChunks.Find(s => s.ChunkName == "ISFT") is HeaderSubChunk chunk) + chunk.Field = value; + else + subChunks.Add(new HeaderSubChunk(sf2, "ISFT") { Field = value }); + } + } + + internal InfoListChunk(SF2 inSf2) : base(inSf2, "INFO") { // Mandatory sub-chunks - subChunks.Add(new VersionSubChunk(inSf2, "ifil") { Version = new SF2VersionTag { Major = 2, Minor = 1 } }); - subChunks.Add(new HeaderSubChunk(inSf2, "isng") { Field = string.IsNullOrEmpty(engine) ? "EMU8000" : engine }); - subChunks.Add(new HeaderSubChunk(inSf2, "INAM") { Field = string.IsNullOrEmpty(bank) ? "General MIDI" : bank }); + subChunks.Add(new VersionSubChunk(inSf2, "ifil") { Version = new SF2VersionTag(2, 1) }); + subChunks.Add(new HeaderSubChunk(inSf2, "isng") { Field = defaultEngine }); + subChunks.Add(new HeaderSubChunk(inSf2, "INAM") { Field = defaultBank }); sf2.UpdateSize(); } internal InfoListChunk(SF2 inSf2, BinaryReader reader) : base(inSf2, reader) @@ -647,7 +801,7 @@ internal InfoListChunk(SF2 inSf2, BinaryReader reader) : base(inSf2, reader) string strName = new string(name); switch (strName) { - case "ICMT": subChunks.Add(new HeaderSubChunk(inSf2, reader, 0x10000)); break; + case "ICMT": subChunks.Add(new HeaderSubChunk(inSf2, reader, commentMaxSize)); break; case "ifil": case "iver": subChunks.Add(new VersionSubChunk(inSf2, reader)); break; case "isng": diff --git a/SoundFont2/SF2Types.cs b/SoundFont2/SF2Types.cs index a34adb7..9a5b153 100644 --- a/SoundFont2/SF2Types.cs +++ b/SoundFont2/SF2Types.cs @@ -7,8 +7,13 @@ public sealed class SF2VersionTag { public const uint Size = 4; - public ushort Major; - public ushort Minor; + public readonly ushort Major; + public readonly ushort Minor; + + public SF2VersionTag(ushort major, ushort minor) + { + Major = major; Minor = minor; + } public override string ToString() => $"v{Major}.{Minor}"; }