diff --git a/Phase/Mscorlib/system/CsMath.hx b/Phase/Mscorlib/system/CsMath.hx index 9b9983a90..7adc8b880 100644 --- a/Phase/Mscorlib/system/CsMath.hx +++ b/Phase/Mscorlib/system/CsMath.hx @@ -17,6 +17,7 @@ class CsMath public static inline function Round_Single(a:Single) : Single return Math.round(a.ToHaxeFloat()); public static inline function Sin(a:Double) : Double return Math.sin(a.ToHaxeFloat()); public static inline function Cos(a:Double) : Double return Math.cos(a.ToHaxeFloat()); + public static inline function Tan(a:Double) : Double return Math.tan(a.ToHaxeFloat()); public static inline function Pow(v:Double, exp:Double) : Double return Math.pow(v.ToHaxeFloat(), exp.ToHaxeFloat()); public static inline function Ceiling_Single(v:Single) : Single return Math.ceil(v.ToHaxeFloat()); public static inline function Ceiling_Double(v:Double) : Double return Math.ceil(v.ToHaxeFloat()); diff --git a/README.md b/README.md index b380c2bb1..9a495f076 100644 --- a/README.md +++ b/README.md @@ -57,4 +57,6 @@ alphaTab can load music notation from various sources like Guitar Pro 3-5, Guita

+... to [Bernhard Schelling](https://github.com/schellingb/TinySoundFont) the author of TinySoundFont and [Steve Folta](https://github.com/stevefolta/SFZero) the author of SFZero for providing the core of the synthesis engine. + ... to all you people using alphaTab providing new feature ideas and and bug reports. diff --git a/Source/AlphaTab.CSharp/Collections/SampleArray.cs b/Source/AlphaTab.CSharp/Collections/SampleArray.cs deleted file mode 100644 index d8322520f..000000000 --- a/Source/AlphaTab.CSharp/Collections/SampleArray.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; - -namespace AlphaTab.Audio.Synth.Ds -{ - /// - /// Represents an array of audiosamples. - /// - public class SampleArray - { - /// - /// Gets the audio samples as floats. - /// - public float[] Samples - { - get; - } - - /// - /// Initializes a new instance of the class. - /// - /// The length of the array. - public SampleArray(int length) - { - Samples = new float[length]; - } - - /// - /// Gets or sets the sample at the specified index. - /// - /// The index of the sample to get or set. - /// The sample at the specified index. - public float this[int index] - { - get => Samples[index]; - set => Samples[index] = value; - } - - /// - /// Gets the total number of samples contained in this array - /// - public int Length => Samples.Length; - - /// - /// Resets all samples in the array to 0. - /// - public void Clear() - { - Array.Clear(Samples, 0, Samples.Length); - } - - /// - /// Copies a range of samples from the given source array into the specified destination. - /// - /// The array where to copy the samples from. - /// The start index from which to start copying. - /// The array where to copy the samples to. - /// The start index at which the samples should be written in the destination array. - /// The number of samples to copy. - public static void Blit(SampleArray src, int srcPos, SampleArray dest, int destPos, int len) - { - Array.Copy(src.Samples, srcPos, dest.Samples, destPos, len); - } - } -} diff --git a/Source/AlphaTab.CSharp/Platform/CSharp/AlphaSynthWorkerApiBase.cs b/Source/AlphaTab.CSharp/Platform/CSharp/AlphaSynthWorkerApiBase.cs index 5180599dd..617f4f8f5 100644 --- a/Source/AlphaTab.CSharp/Platform/CSharp/AlphaSynthWorkerApiBase.cs +++ b/Source/AlphaTab.CSharp/Platform/CSharp/AlphaSynthWorkerApiBase.cs @@ -145,7 +145,7 @@ public void SetChannelSolo(int channel, bool solo) DispatchOnWorkerThread(() => { Player.SetChannelSolo(channel, solo); }); } - public void SetChannelVolume(int channel, double volume) + public void SetChannelVolume(int channel, float volume) { DispatchOnWorkerThread(() => { Player.SetChannelVolume(channel, volume); }); } diff --git a/Source/AlphaTab.CSharp/Platform/CSharp/Wpf/NAudioSynthOutput.cs b/Source/AlphaTab.CSharp/Platform/CSharp/Wpf/NAudioSynthOutput.cs index 37a481741..fa075eec5 100644 --- a/Source/AlphaTab.CSharp/Platform/CSharp/Wpf/NAudioSynthOutput.cs +++ b/Source/AlphaTab.CSharp/Platform/CSharp/Wpf/NAudioSynthOutput.cs @@ -48,6 +48,7 @@ public void Open() Ready(); } + /// public void Dispose() { Close(); @@ -85,7 +86,7 @@ public void SequencerFinished() } /// - public void AddSamples(SampleArray f) + public void AddSamples(float[] f) { _circularBuffer.Write(f, 0, f.Length); } @@ -122,7 +123,7 @@ public override int Read(float[] buffer, int offset, int count) } else { - var read = new SampleArray(count); + var read = new float[count]; _circularBuffer.Read(read, 0, read.Length); for (var i = 0; i < count; i++) diff --git a/Source/AlphaTab.JavaScript/Collections/SampleArray.cs b/Source/AlphaTab.JavaScript/Collections/SampleArray.cs deleted file mode 100644 index d3869727b..000000000 --- a/Source/AlphaTab.JavaScript/Collections/SampleArray.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Haxe.Js.Html; -using Phase; -using Phase.Attributes; - -namespace AlphaTab.Audio.Synth.Ds -{ - [Abstract("js.html.Float32Array")] - [NativeConstructors] - public class SampleArray - { - [Inline] - public SampleArray(int length) - { - Script.AbstractThis = new Float32Array(length); - } - - public Float32Array ToFloat32Array() - { - return Script.AbstractThis.As(); - } - - public float this[int index] - { - [Inline] get => Script.This()[index]; - [Inline] set => Script.This()[index] = value; - } - - public int Length - { - [Inline] get => Script.This().Length; - } - - [Inline] - public void Clear() - { - Script.AbstractThis = new Float32Array(Length); - } - - [Inline] - public static void Blit(SampleArray src, int srcPos, SampleArray dest, int destPos, int len) - { - dest.ToFloat32Array().Set(src.ToFloat32Array().SubArray(srcPos, srcPos + len), destPos); - } - } -} diff --git a/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthFlashOutput.cs b/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthFlashOutput.cs index 4a0c4755f..68dd07a8f 100644 --- a/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthFlashOutput.cs +++ b/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthFlashOutput.cs @@ -124,9 +124,10 @@ public void SequencerFinished() FlashOutput.AlphaSynthSequencerFinished(); } - public void AddSamples(SampleArray samples) + public void AddSamples(float[] samples) { - var uint8 = new Uint8Array(samples.ToFloat32Array().Buffer); + Float32Array f32 = samples.As(); + var uint8 = new Uint8Array(f32.Buffer); var b64 = Script.Write( "untyped __js__(\"window.btoa(String.fromCharCode.apply(null, {0}))\", uint8)"); FlashOutput.AlphaSynthAddSamples(b64); diff --git a/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWebAudioOutput.cs b/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWebAudioOutput.cs index c58b7591f..89700e896 100644 --- a/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWebAudioOutput.cs +++ b/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWebAudioOutput.cs @@ -27,9 +27,6 @@ internal class AlphaSynthWebAudioOutput : ISynthOutput private CircularSampleBuffer _circularBuffer; private bool _finished; - private double _contextTimeOnGenerate; - private int _samplesGenerated; - public int SampleRate => (int)_context.SampleRate; public void Open() @@ -142,7 +139,7 @@ public void SequencerFinished() _finished = true; } - public void AddSamples(SampleArray f) + public void AddSamples(float[] f) { _circularBuffer.Write(f, 0, f.Length); } @@ -180,10 +177,7 @@ private void GenerateSound(AudioProcessingEvent e) } else { - _contextTimeOnGenerate = _context.CurrentTime; - _samplesGenerated = left.Length; - - var buffer = new SampleArray(samples); + var buffer = new float[samples]; _circularBuffer.Read(buffer, 0, buffer.Length); var s = 0; diff --git a/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWebWorkerApi.cs b/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWebWorkerApi.cs index a42c28255..a65d16251 100644 --- a/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWebWorkerApi.cs +++ b/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWebWorkerApi.cs @@ -1,7 +1,6 @@ using System; using AlphaTab.Audio.Synth; using AlphaTab.Audio.Synth.Midi; -using AlphaTab.Audio.Synth.Synthesis; using AlphaTab.Audio.Synth.Util; using AlphaTab.Collections; using AlphaTab.Haxe.Js.Html; @@ -368,9 +367,9 @@ public void SetChannelSolo(int channel, bool solo) } /// - public void SetChannelVolume(int channel, double volume) + public void SetChannelVolume(int channel, float volume) { - volume = SynthHelper.ClampD(volume, SynthConstants.MinVolume, SynthConstants.MaxVolume); + volume = SynthHelper.ClampF(volume, SynthConstants.MinVolume, SynthConstants.MaxVolume); _synth.PostMessage(new { cmd = AlphaSynthWebWorker.CmdSetChannelVolume, diff --git a/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWorkerSynthOutput.cs b/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWorkerSynthOutput.cs index 17303a939..9221fbb4a 100644 --- a/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWorkerSynthOutput.cs +++ b/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWorkerSynthOutput.cs @@ -24,8 +24,8 @@ internal class AlphaSynthWorkerSynthOutput : ISynthOutput public const string CmdOutputSamplesPlayed = CmdOutputPrefix + "samplesPlayed"; - // this value is initialized by the alphaSynth WebWorker wrapper - // that also includes the alphaSynth library into the worker. + // this value is initialized by the alphaSynth WebWorker wrapper + // that also includes the alphaSynth library into the worker. public static int PreferredSampleRate { get; set; } private DedicatedWorkerGlobalScope _worker; @@ -72,7 +72,7 @@ public void SequencerFinished() }); } - public void AddSamples(SampleArray samples) + public void AddSamples(float[] samples) { _worker.PostMessage(new { diff --git a/Source/AlphaTab.JavaScript/Platform/Platform.cs b/Source/AlphaTab.JavaScript/Platform/Platform.cs index 9643d0ec0..274099cca 100644 --- a/Source/AlphaTab.JavaScript/Platform/Platform.cs +++ b/Source/AlphaTab.JavaScript/Platform/Platform.cs @@ -326,6 +326,13 @@ public static bool SupportsTextDecoder [Inline] get => !!Lib.Global.TextDecoder; } + [Inline] + public static void ArrayCopy(float[] src, int srcOffset, float[] dst, int dstOffset, int count) + { + Script.Write( + "untyped __js__(\"{2}.set({0}.subarray({1},{1}+{4}), {3})\", src, srcOffset, dst, dstOffset, count);"); + } + [Inline] public static void ArrayCopy(int[] src, int srcOffset, int[] dst, int dstOffset, int count) { diff --git a/Source/AlphaTab.Test.Js/test/alphaTab.tests.specs.js b/Source/AlphaTab.Test.Js/test/alphaTab.tests.specs.js index 401a517d7..1ee41aa25 100644 --- a/Source/AlphaTab.Test.Js/test/alphaTab.tests.specs.js +++ b/Source/AlphaTab.Test.Js/test/alphaTab.tests.specs.js @@ -1,10 +1,6 @@ /// describe("alphaTab.test.audio.AlphaSynthTests", function() { var __instance = new alphaTab.test.audio.AlphaSynthTests(); - it("TestLoadSf2PatchBank", function(done) { - alphaTab.test.TestPlatform.Done = done; - __instance.TestLoadSf2PatchBank(); - }); it("TestPcmGeneration", function(done) { alphaTab.test.TestPlatform.Done = done; __instance.TestPcmGeneration(); @@ -12,6 +8,10 @@ describe("alphaTab.test.audio.AlphaSynthTests", function() { }); describe("alphaTab.test.audio.MidiFileGeneratorTest", function() { var __instance = new alphaTab.test.audio.MidiFileGeneratorTest(); + it("TestFullSong", function(done) { + alphaTab.test.TestPlatform.Done = done; + __instance.TestFullSong(); + }); it("TestCorrectMidiOrder", function(done) { alphaTab.test.TestPlatform.Done = done; __instance.TestCorrectMidiOrder(); diff --git a/Source/AlphaTab.Test/Audio/AlphaSynthTests.cs b/Source/AlphaTab.Test/Audio/AlphaSynthTests.cs index bb01bd676..00e3d630c 100644 --- a/Source/AlphaTab.Test/Audio/AlphaSynthTests.cs +++ b/Source/AlphaTab.Test/Audio/AlphaSynthTests.cs @@ -1,11 +1,8 @@ using System; -using System.IO; using AlphaTab.Audio.Generator; using AlphaTab.Audio.Synth; -using AlphaTab.Audio.Synth.Bank; -using AlphaTab.Audio.Synth.Ds; using AlphaTab.Audio.Synth.Midi; -using AlphaTab.Audio.Synth.Util; +using AlphaTab.Audio.Synth.SoundFont; using AlphaTab.Collections; using AlphaTab.Importer; using AlphaTab.IO; @@ -16,50 +13,6 @@ namespace AlphaTab.Test.Audio [TestClass] public class AlphaSynthTests { - [TestMethod, AsyncTestMethod] - public void TestLoadSf2PatchBank() - { - TestPlatform.LoadFile("TestFiles/Audio/default.sf2", data => - { - var patchBank = new PatchBank(); - var input = ByteBuffer.FromBuffer(data); - patchBank.LoadSf2(input); - - Assert.AreEqual("GS sound set (16 bit)", patchBank.Name); - Assert.AreEqual("960920 ver. 1.00.16", patchBank.Comments); - Assert.AreEqual("0,1,2,3,4,5,6,7,8,9,16,24,32,128", string.Join(",", patchBank.LoadedBanks)); - - var gmBank = patchBank.GetBank(0); - var expectedPatches = new string[] - { - "Piano 1", "Piano 2", "Piano 3", "Honky-tonk", "E.Piano 1", "E.Piano 2", "Harpsichord", "Clav.", - "Celesta", "Glockenspiel", "Music Box", "Vibraphone", "Marimba", "Xylophone", "Tubular-bell", "Santur", "Organ 1", - "Organ 2", "Organ 3", "Church Org.1", "Reed Organ", "Accordion Fr", "Harmonica", "Bandoneon", - "Nylon-str.Gt", "Steel-str.Gt", "Jazz Gt.", "Clean Gt.", "Muted Gt.", "Overdrive Gt", "DistortionGt", "Gt.Harmonics", - "Acoustic Bs.", "Fingered Bs.", "Picked Bs.", "Fretless Bs.", "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", - "Synth Bass 2", "Violin", "Viola", "Cello", "Contrabass", "Tremolo Str", "PizzicatoStr", "Harp", "Timpani", "Strings", - "Slow Strings", "Syn.Strings1", "Syn.Strings2", "Choir Aahs", "Voice Oohs", "SynVox", "OrchestraHit", "Trumpet", "Trombone", "Tuba", - "MutedTrumpet", "French Horns", "Brass 1", "Synth Brass1", "Synth Brass2", "Soprano Sax", "Alto Sax", "Tenor Sax", "Baritone Sax", - "Oboe", "English Horn", "Bassoon", "Clarinet", "Piccolo", "Flute", "Recorder", "Pan Flute", "Bottle Blow", "Shakuhachi", "Whistle", - "Ocarina", "Square Wave", "Saw Wave", "Syn.Calliope", "Chiffer Lead", "Charang", "Solo Vox", "5th Saw Wave", - "Bass & Lead", "Fantasia", "Warm Pad", "Polysynth", "Space Voice", "Bowed Glass", "Metal Pad", "Halo Pad", "Sweep Pad", - "Ice Rain", "Soundtrack", "Crystal", "Atmosphere", "Brightness", "Goblin", "Echo Drops", "Star Theme", "Sitar", - "Banjo", "Shamisen", "Koto", "Kalimba", "Bagpipe", "Fiddle", "Shanai", "Tinkle Bell", "Agogo", "Steel Drums", "Woodblock", - "Taiko", "Melo. Tom 1", "Synth Drum", "Reverse Cym.", "Gt.FretNoise", "Breath Noise", "Seashore", "Bird", "Telephone 1", - "Helicopter", "Applause", "Gun Shot" - }; - var actualPatches = new FastList(); - foreach (var patch in gmBank) - { - if (patch != null) - { - actualPatches.Add(patch.Name); - } - } - Assert.AreEqual(string.Join(",", expectedPatches), string.Join(",", actualPatches)); - }); - } - [TestMethod, AsyncTestMethod] public void TestPcmGeneration() { @@ -144,7 +97,7 @@ public void Pause() { } - public void AddSamples(SampleArray f) + public void AddSamples(float[] f) { for (var i = 0; i < f.Length; i++) { diff --git a/Source/AlphaTab.Test/Audio/MidiFileGeneratorTest.cs b/Source/AlphaTab.Test/Audio/MidiFileGeneratorTest.cs index d6da1b86c..7bdef7e96 100644 --- a/Source/AlphaTab.Test/Audio/MidiFileGeneratorTest.cs +++ b/Source/AlphaTab.Test/Audio/MidiFileGeneratorTest.cs @@ -100,25 +100,25 @@ public void TestBend() new FlatMidiEventGenerator.TempoEvent { Tick = 0, Tempo = 120 }, // bend effect - new FlatMidiEventGenerator.BendEvent { Tick = 0, Track = 0, Channel = info.SecondaryChannel, Value = 64 }, // no bend - new FlatMidiEventGenerator.BendEvent { Tick = 0, Track = 0, Channel = info.SecondaryChannel, Value = 64 }, - new FlatMidiEventGenerator.BendEvent { Tick = 87, Track = 0, Channel = info.SecondaryChannel, Value = 65 }, - new FlatMidiEventGenerator.BendEvent { Tick = 174, Track = 0, Channel = info.SecondaryChannel, Value = 66 }, - new FlatMidiEventGenerator.BendEvent { Tick = 261, Track = 0, Channel = info.SecondaryChannel, Value = 67 }, - new FlatMidiEventGenerator.BendEvent { Tick = 349, Track = 0, Channel = info.SecondaryChannel, Value = 68 }, - new FlatMidiEventGenerator.BendEvent { Tick = 436, Track = 0, Channel = info.SecondaryChannel, Value = 69 }, - new FlatMidiEventGenerator.BendEvent { Tick = 523, Track = 0, Channel = info.SecondaryChannel, Value = 70 }, - new FlatMidiEventGenerator.BendEvent { Tick = 610, Track = 0, Channel = info.SecondaryChannel, Value = 71 }, - new FlatMidiEventGenerator.BendEvent { Tick = 698, Track = 0, Channel = info.SecondaryChannel, Value = 72 }, - new FlatMidiEventGenerator.BendEvent { Tick = 785, Track = 0, Channel = info.SecondaryChannel, Value = 73 }, - new FlatMidiEventGenerator.BendEvent { Tick = 872, Track = 0, Channel = info.SecondaryChannel, Value = 74 }, - new FlatMidiEventGenerator.BendEvent { Tick = 959, Track = 0, Channel = info.SecondaryChannel, Value = 75 }, + new FlatMidiEventGenerator.BendEvent { Tick = 0, Track = 0, Channel = info.SecondaryChannel, Value = 32 }, // no bend + new FlatMidiEventGenerator.BendEvent { Tick = 0, Track = 0, Channel = info.SecondaryChannel, Value = 32 }, + new FlatMidiEventGenerator.BendEvent { Tick = 87, Track = 0, Channel = info.SecondaryChannel, Value = 33 }, + new FlatMidiEventGenerator.BendEvent { Tick = 174, Track = 0, Channel = info.SecondaryChannel, Value = 34 }, + new FlatMidiEventGenerator.BendEvent { Tick = 261, Track = 0, Channel = info.SecondaryChannel, Value = 35 }, + new FlatMidiEventGenerator.BendEvent { Tick = 349, Track = 0, Channel = info.SecondaryChannel, Value = 36 }, + new FlatMidiEventGenerator.BendEvent { Tick = 436, Track = 0, Channel = info.SecondaryChannel, Value = 37 }, + new FlatMidiEventGenerator.BendEvent { Tick = 523, Track = 0, Channel = info.SecondaryChannel, Value = 38 }, + new FlatMidiEventGenerator.BendEvent { Tick = 610, Track = 0, Channel = info.SecondaryChannel, Value = 39 }, + new FlatMidiEventGenerator.BendEvent { Tick = 698, Track = 0, Channel = info.SecondaryChannel, Value = 40 }, + new FlatMidiEventGenerator.BendEvent { Tick = 785, Track = 0, Channel = info.SecondaryChannel, Value = 41 }, + new FlatMidiEventGenerator.BendEvent { Tick = 872, Track = 0, Channel = info.SecondaryChannel, Value = 42 }, + new FlatMidiEventGenerator.BendEvent { Tick = 959, Track = 0, Channel = info.SecondaryChannel, Value = 43 }, // note itself new FlatMidiEventGenerator.NoteEvent { Tick = 0, Track = 0, Channel = info.SecondaryChannel, DynamicValue = note.Dynamic, Key = (byte) note.RealValue, Length = note.Beat.Duration.ToTicks() }, // reset bend - new FlatMidiEventGenerator.BendEvent { Tick = 960, Track = 0, Channel = info.PrimaryChannel, Value = 64 }, + new FlatMidiEventGenerator.BendEvent { Tick = 960, Track = 0, Channel = info.PrimaryChannel, Value = 32 }, new FlatMidiEventGenerator.NoteEvent { Tick = 960, Track = 0, Channel = info.PrimaryChannel, DynamicValue = note.Dynamic, Key = (byte) note.RealValue, Length = note.Beat.Duration.ToTicks() }, // end of track @@ -223,40 +223,40 @@ public void TestGraceBeatGeneration() new FlatMidiEventGenerator.TempoEvent { Tick = 0, Tempo = 120 }, // on beat - new FlatMidiEventGenerator.BendEvent { Tick = ticks[0], Track = 0, Channel = info.PrimaryChannel, Value = 64 }, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[0], Track = 0, Channel = info.PrimaryChannel, Value = 32 }, new FlatMidiEventGenerator.NoteEvent { Tick = ticks[0], Track = 0, Channel = info.PrimaryChannel, DynamicValue = DynamicValue.F, Key = (byte) 67, Length = 3840 }, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[1], Track = 0, Channel = info.PrimaryChannel, Value = 64 }, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[1], Track = 0, Channel = info.PrimaryChannel, Value = 32 }, new FlatMidiEventGenerator.NoteEvent { Tick = ticks[1], Track = 0, Channel = info.PrimaryChannel, DynamicValue = DynamicValue.F, Key = (byte) 67, Length = 120 }, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[2], Track = 0, Channel = info.PrimaryChannel, Value = 64 }, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[2], Track = 0, Channel = info.PrimaryChannel, Value = 32 }, new FlatMidiEventGenerator.NoteEvent { Tick = ticks[2], Track = 0, Channel = info.PrimaryChannel, DynamicValue = DynamicValue.F, Key = (byte) 67, Length = 3720}, // before beat - new FlatMidiEventGenerator.BendEvent { Tick = ticks[3], Track = 0, Channel = info.PrimaryChannel, Value = 64 }, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[3], Track = 0, Channel = info.PrimaryChannel, Value = 32 }, new FlatMidiEventGenerator.NoteEvent { Tick = ticks[3], Track = 0, Channel = info.PrimaryChannel, DynamicValue = DynamicValue.F, Key = (byte) 67, Length = 3720 }, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[4], Track = 0, Channel = info.PrimaryChannel, Value = 64 }, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[4], Track = 0, Channel = info.PrimaryChannel, Value = 32 }, new FlatMidiEventGenerator.NoteEvent { Tick = ticks[4], Track = 0, Channel = info.PrimaryChannel, DynamicValue = DynamicValue.F, Key = (byte) 67, Length = 120 }, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[5], Track = 0, Channel = info.PrimaryChannel, Value = 64 }, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[5], Track = 0, Channel = info.PrimaryChannel, Value = 32 }, new FlatMidiEventGenerator.NoteEvent { Tick = ticks[5], Track = 0, Channel = info.PrimaryChannel, DynamicValue = DynamicValue.F, Key = (byte) 67, Length = 3840}, // bend beat - new FlatMidiEventGenerator.BendEvent { Tick = ticks[6], Track = 0, Channel = info.SecondaryChannel, Value = 64}, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 0, Track = 0, Channel = info.SecondaryChannel, Value = 64}, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 1, Track = 0, Channel = info.SecondaryChannel, Value = 65}, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 2, Track = 0, Channel = info.SecondaryChannel, Value = 66}, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 3, Track = 0, Channel = info.SecondaryChannel, Value = 67}, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 4, Track = 0, Channel = info.SecondaryChannel, Value = 68}, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 5, Track = 0, Channel = info.SecondaryChannel, Value = 69}, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 6, Track = 0, Channel = info.SecondaryChannel, Value = 70}, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 7, Track = 0, Channel = info.SecondaryChannel, Value = 71}, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 8, Track = 0, Channel = info.SecondaryChannel, Value = 72}, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 9, Track = 0, Channel = info.SecondaryChannel, Value = 73}, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 10, Track = 0, Channel = info.SecondaryChannel, Value = 74}, - new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 11 + 1, Track = 0, Channel = info.SecondaryChannel, Value = 75}, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[6], Track = 0, Channel = info.SecondaryChannel, Value = 32}, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 0, Track = 0, Channel = info.SecondaryChannel, Value = 32}, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 1, Track = 0, Channel = info.SecondaryChannel, Value = 33}, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 2, Track = 0, Channel = info.SecondaryChannel, Value = 34}, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 3, Track = 0, Channel = info.SecondaryChannel, Value = 35}, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 4, Track = 0, Channel = info.SecondaryChannel, Value = 36}, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 5, Track = 0, Channel = info.SecondaryChannel, Value = 37}, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 6, Track = 0, Channel = info.SecondaryChannel, Value = 38}, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 7, Track = 0, Channel = info.SecondaryChannel, Value = 39}, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 8, Track = 0, Channel = info.SecondaryChannel, Value = 40}, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 9, Track = 0, Channel = info.SecondaryChannel, Value = 41}, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 10, Track = 0, Channel = info.SecondaryChannel, Value = 42}, + new FlatMidiEventGenerator.BendEvent { Tick = ticks[6] + 13 * 11 + 1, Track = 0, Channel = info.SecondaryChannel, Value = 43}, new FlatMidiEventGenerator.NoteEvent { Tick = ticks[6], Track = 0, Channel = info.SecondaryChannel, Length = 3840, Key = 67, DynamicValue = DynamicValue.F}, // end of track @@ -321,38 +321,38 @@ public void TestBendMultiPoint() new FlatMidiEventGenerator.TempoEvent { Tick = 0, Tempo = 120 }, // bend effect - new FlatMidiEventGenerator.BendEvent { Tick = 0, Track = 0, Channel = info.SecondaryChannel, Value = 64 }, // no bend - new FlatMidiEventGenerator.BendEvent { Tick = 0, Track = 0, Channel = info.SecondaryChannel, Value = 64 }, - new FlatMidiEventGenerator.BendEvent { Tick = 43, Track = 0, Channel = info.SecondaryChannel, Value = 65 }, - new FlatMidiEventGenerator.BendEvent { Tick = 87, Track = 0, Channel = info.SecondaryChannel, Value = 66 }, - new FlatMidiEventGenerator.BendEvent { Tick = 130, Track = 0, Channel = info.SecondaryChannel, Value = 67 }, - new FlatMidiEventGenerator.BendEvent { Tick = 174, Track = 0, Channel = info.SecondaryChannel, Value = 68 }, - new FlatMidiEventGenerator.BendEvent { Tick = 218, Track = 0, Channel = info.SecondaryChannel, Value = 69 }, - new FlatMidiEventGenerator.BendEvent { Tick = 261, Track = 0, Channel = info.SecondaryChannel, Value = 70 }, - new FlatMidiEventGenerator.BendEvent { Tick = 305, Track = 0, Channel = info.SecondaryChannel, Value = 71 }, - new FlatMidiEventGenerator.BendEvent { Tick = 349, Track = 0, Channel = info.SecondaryChannel, Value = 72 }, - new FlatMidiEventGenerator.BendEvent { Tick = 392, Track = 0, Channel = info.SecondaryChannel, Value = 73 }, - new FlatMidiEventGenerator.BendEvent { Tick = 436, Track = 0, Channel = info.SecondaryChannel, Value = 74 }, - new FlatMidiEventGenerator.BendEvent { Tick = 479, Track = 0, Channel = info.SecondaryChannel, Value = 75 }, // full bend - - new FlatMidiEventGenerator.BendEvent { Tick = 480, Track = 0, Channel = info.SecondaryChannel, Value = 75 }, // full bend - new FlatMidiEventGenerator.BendEvent { Tick = 523, Track = 0, Channel = info.SecondaryChannel, Value = 74 }, - new FlatMidiEventGenerator.BendEvent { Tick = 567, Track = 0, Channel = info.SecondaryChannel, Value = 73 }, - new FlatMidiEventGenerator.BendEvent { Tick = 610, Track = 0, Channel = info.SecondaryChannel, Value = 72 }, - new FlatMidiEventGenerator.BendEvent { Tick = 654, Track = 0, Channel = info.SecondaryChannel, Value = 71 }, - new FlatMidiEventGenerator.BendEvent { Tick = 698, Track = 0, Channel = info.SecondaryChannel, Value = 70 }, - new FlatMidiEventGenerator.BendEvent { Tick = 741, Track = 0, Channel = info.SecondaryChannel, Value = 69 }, - new FlatMidiEventGenerator.BendEvent { Tick = 785, Track = 0, Channel = info.SecondaryChannel, Value = 68 }, - new FlatMidiEventGenerator.BendEvent { Tick = 829, Track = 0, Channel = info.SecondaryChannel, Value = 67 }, - new FlatMidiEventGenerator.BendEvent { Tick = 872, Track = 0, Channel = info.SecondaryChannel, Value = 66 }, - new FlatMidiEventGenerator.BendEvent { Tick = 916, Track = 0, Channel = info.SecondaryChannel, Value = 65 }, - new FlatMidiEventGenerator.BendEvent { Tick = 959, Track = 0, Channel = info.SecondaryChannel, Value = 64 }, // no bend + new FlatMidiEventGenerator.BendEvent { Tick = 0, Track = 0, Channel = info.SecondaryChannel, Value = 32 }, // no bend + new FlatMidiEventGenerator.BendEvent { Tick = 0, Track = 0, Channel = info.SecondaryChannel, Value = 32 }, + new FlatMidiEventGenerator.BendEvent { Tick = 43, Track = 0, Channel = info.SecondaryChannel, Value = 33 }, + new FlatMidiEventGenerator.BendEvent { Tick = 87, Track = 0, Channel = info.SecondaryChannel, Value = 34 }, + new FlatMidiEventGenerator.BendEvent { Tick = 130, Track = 0, Channel = info.SecondaryChannel, Value = 35 }, + new FlatMidiEventGenerator.BendEvent { Tick = 174, Track = 0, Channel = info.SecondaryChannel, Value = 36 }, + new FlatMidiEventGenerator.BendEvent { Tick = 218, Track = 0, Channel = info.SecondaryChannel, Value = 37 }, + new FlatMidiEventGenerator.BendEvent { Tick = 261, Track = 0, Channel = info.SecondaryChannel, Value = 38 }, + new FlatMidiEventGenerator.BendEvent { Tick = 305, Track = 0, Channel = info.SecondaryChannel, Value = 39 }, + new FlatMidiEventGenerator.BendEvent { Tick = 349, Track = 0, Channel = info.SecondaryChannel, Value = 40 }, + new FlatMidiEventGenerator.BendEvent { Tick = 392, Track = 0, Channel = info.SecondaryChannel, Value = 41 }, + new FlatMidiEventGenerator.BendEvent { Tick = 436, Track = 0, Channel = info.SecondaryChannel, Value = 42 }, + new FlatMidiEventGenerator.BendEvent { Tick = 479, Track = 0, Channel = info.SecondaryChannel, Value = 43 }, // full bend + + new FlatMidiEventGenerator.BendEvent { Tick = 480, Track = 0, Channel = info.SecondaryChannel, Value = 43 }, // full bend + new FlatMidiEventGenerator.BendEvent { Tick = 523, Track = 0, Channel = info.SecondaryChannel, Value = 42 }, + new FlatMidiEventGenerator.BendEvent { Tick = 567, Track = 0, Channel = info.SecondaryChannel, Value = 41 }, + new FlatMidiEventGenerator.BendEvent { Tick = 610, Track = 0, Channel = info.SecondaryChannel, Value = 40 }, + new FlatMidiEventGenerator.BendEvent { Tick = 654, Track = 0, Channel = info.SecondaryChannel, Value = 39 }, + new FlatMidiEventGenerator.BendEvent { Tick = 698, Track = 0, Channel = info.SecondaryChannel, Value = 38 }, + new FlatMidiEventGenerator.BendEvent { Tick = 741, Track = 0, Channel = info.SecondaryChannel, Value = 37 }, + new FlatMidiEventGenerator.BendEvent { Tick = 785, Track = 0, Channel = info.SecondaryChannel, Value = 36 }, + new FlatMidiEventGenerator.BendEvent { Tick = 829, Track = 0, Channel = info.SecondaryChannel, Value = 35 }, + new FlatMidiEventGenerator.BendEvent { Tick = 872, Track = 0, Channel = info.SecondaryChannel, Value = 34 }, + new FlatMidiEventGenerator.BendEvent { Tick = 916, Track = 0, Channel = info.SecondaryChannel, Value = 33 }, + new FlatMidiEventGenerator.BendEvent { Tick = 959, Track = 0, Channel = info.SecondaryChannel, Value = 32 }, // no bend // note itself new FlatMidiEventGenerator.NoteEvent { Tick = 0, Track = 0, Channel = info.SecondaryChannel, DynamicValue = note.Dynamic, Key = (byte) note.RealValue, Length = note.Beat.Duration.ToTicks() }, // reset bend - new FlatMidiEventGenerator.BendEvent { Tick = 960, Track = 0, Channel = info.PrimaryChannel, Value = 64 }, // finish + new FlatMidiEventGenerator.BendEvent { Tick = 960, Track = 0, Channel = info.PrimaryChannel, Value = 32 }, // finish new FlatMidiEventGenerator.NoteEvent { Tick = 960, Track = 0, Channel = info.PrimaryChannel, DynamicValue = note.Dynamic, Key = (byte) note.RealValue, Length = note.Beat.Duration.ToTicks() }, // end of track new FlatMidiEventGenerator.TrackEndEvent { Tick = 3840, Track = 0 } // 3840 = end of bar diff --git a/Source/AlphaTab/AlphaTab.Shared.projitems b/Source/AlphaTab/AlphaTab.Shared.projitems index 7f4d95fe0..c8109414c 100644 --- a/Source/AlphaTab/AlphaTab.Shared.projitems +++ b/Source/AlphaTab/AlphaTab.Shared.projitems @@ -19,30 +19,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - @@ -56,44 +32,37 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - + + + + + - diff --git a/Source/AlphaTab/AlphaTabApi.cs b/Source/AlphaTab/AlphaTabApi.cs index f423c2d5e..aee3785cb 100644 --- a/Source/AlphaTab/AlphaTabApi.cs +++ b/Source/AlphaTab/AlphaTabApi.cs @@ -3,7 +3,6 @@ using AlphaTab.Audio.Generator; using AlphaTab.Audio.Synth; using AlphaTab.Audio.Synth.Midi; -using AlphaTab.Audio.Synth.Synthesis; using AlphaTab.Collections; using AlphaTab.Importer; using AlphaTab.IO; @@ -592,6 +591,15 @@ private void SetupPlayer() Player.ReadyForPlayback += () => { UiFacade.TriggerEvent(Container, "playerReady"); + if(Tracks != null) + { + foreach (var track in Tracks) + { + var volume = track.PlaybackInfo.Volume / 16f; + Player.SetChannelVolume(track.PlaybackInfo.PrimaryChannel, volume); + Player.SetChannelVolume(track.PlaybackInfo.SecondaryChannel, volume); + } + } }; Player.SoundFontLoaded += () => diff --git a/Source/AlphaTab/Audio/Generator/MidiFileGenerator.cs b/Source/AlphaTab/Audio/Generator/MidiFileGenerator.cs index f97ab6961..0455b3f2d 100644 --- a/Source/AlphaTab/Audio/Generator/MidiFileGenerator.cs +++ b/Source/AlphaTab/Audio/Generator/MidiFileGenerator.cs @@ -648,7 +648,7 @@ private void GenerateVibratorWithParams( } } - private const int DefaultBend = 0x40; + private const int DefaultBend = 0x20; private const float DefaultBendSemitone = 2.75f; private void GenerateBend(Note note, int noteStart, MidiNoteDuration noteDuration, int channel) diff --git a/Source/AlphaTab/Audio/Synth/AlphaSynth.cs b/Source/AlphaTab/Audio/Synth/AlphaSynth.cs index 219354521..a5e3c4b85 100644 --- a/Source/AlphaTab/Audio/Synth/AlphaSynth.cs +++ b/Source/AlphaTab/Audio/Synth/AlphaSynth.cs @@ -1,10 +1,10 @@ using System; -using AlphaTab.Audio.Synth.Bank; using AlphaTab.Audio.Synth.Midi; -using AlphaTab.Audio.Synth.Synthesis; +using AlphaTab.Audio.Synth.SoundFont; using AlphaTab.Audio.Synth.Util; using AlphaTab.IO; using AlphaTab.Util; +using AlphaTab.Audio.Synth.Synthesis; namespace AlphaTab.Audio.Synth { @@ -15,7 +15,7 @@ namespace AlphaTab.Audio.Synth public class AlphaSynth : IAlphaSynth { private readonly MidiFileSequencer _sequencer; - private readonly Synthesizer _synthesizer; + private readonly TinySoundFont _synthesizer; private bool _isSoundFontLoaded; private bool _isMidiLoaded; @@ -46,22 +46,22 @@ public LogLevel LogLevel /// public float MasterVolume { - get => _synthesizer.MasterVolume; + get => _synthesizer.GlobalGainDb; set { value = SynthHelper.ClampF(value, SynthConstants.MinVolume, SynthConstants.MaxVolume); - _synthesizer.MasterVolume = value; + _synthesizer.GlobalGainDb = value; } } /// public float MetronomeVolume { - get => _synthesizer.MetronomeVolume; + get => _synthesizer.ChannelGetMixVolume(SynthConstants.MetronomeChannel); set { value = SynthHelper.ClampF(value, SynthConstants.MinVolume, SynthConstants.MaxVolume); - _synthesizer.MetronomeVolume = value; + _synthesizer.ChannelSetMixVolume(SynthConstants.MetronomeChannel, value); } } @@ -164,17 +164,16 @@ public AlphaSynth(ISynthOutput output) { // synthesize buffer _sequencer.FillMidiEventQueue(); - _synthesizer.Synthesize(); + var samples = _synthesizer.Synthesize(); // send it to output - Output.AddSamples(_synthesizer.SampleBuffer); + Output.AddSamples(samples); // tell sequencer to check whether its work is done _sequencer.CheckForStop(); }; Output.SamplesPlayed += OnSamplesPlayed; Logger.Debug("AlphaSynth", "Creating synthesizer"); - _synthesizer = new Synthesizer(Output.SampleRate, SynthConstants.AudioChannels, 441, 3, 100); - + _synthesizer = new TinySoundFont(Output.SampleRate); _sequencer = new MidiFileSequencer(_synthesizer); _sequencer.Finished += Output.SequencerFinished; @@ -191,6 +190,7 @@ public bool Play() } Output.Activate(); + _synthesizer.SetupMetronomeChannel(); Logger.Debug("AlphaSynth", "Starting playback"); State = PlayerState.Playing; @@ -230,7 +230,7 @@ public void PlayPause() /// public void Stop() { - if (State == PlayerState.Paused || !IsReadyForPlayback) + if (!IsReadyForPlayback) { return; } @@ -238,6 +238,7 @@ public void Stop() Logger.Debug("AlphaSynth", "Stopping playback"); State = PlayerState.Paused; Output.Pause(); + _sequencer.Stop(); _synthesizer.NoteOffAll(true); TickPosition = _sequencer.PlaybackRange != null ? _sequencer.PlaybackRange.StartTick : 0; OnStateChanged(new PlayerStateChangedEventArgs(State, true)); @@ -252,10 +253,9 @@ public void LoadSoundFont(byte[] data) try { Logger.Info("AlphaSynth", "Loading soundfont from bytes"); - var bank = new PatchBank(); - bank.LoadSf2(input); - _synthesizer.LoadBank(bank); - + var soundFont = new Hydra(); + soundFont.Load(input); + _synthesizer.LoadPresets(soundFont); _isSoundFontLoaded = true; OnSoundFontLoaded(); @@ -273,6 +273,7 @@ private void CheckReadyForPlayback() { if (IsReadyForPlayback) { + _synthesizer.SetupMetronomeChannel(); OnReadyForPlayback(); } } @@ -308,7 +309,7 @@ public void LoadMidiFile(MidiFile midiFile) /// public void SetChannelMute(int channel, bool mute) { - _synthesizer.SetChannelMute(channel, mute); + _synthesizer.ChannelSetMute(channel, mute); } /// @@ -320,14 +321,14 @@ public void ResetChannelStates() /// public void SetChannelSolo(int channel, bool solo) { - _synthesizer.SetChannelSolo(channel, solo); + _synthesizer.ChannelSetSolo(channel, solo); } /// - public void SetChannelVolume(int channel, double volume) + public void SetChannelVolume(int channel, float volume) { - volume = SynthHelper.ClampD(volume, SynthConstants.MinVolume, SynthConstants.MaxVolume); - _synthesizer.SetChannelVolume(channel, volume); + volume = SynthHelper.ClampF(volume, SynthConstants.MinVolume, SynthConstants.MaxVolume); + _synthesizer.ChannelSetMixVolume(channel, volume); } /// @@ -335,12 +336,12 @@ public void SetChannelProgram(int channel, byte program) { program = SynthHelper.ClampB(program, SynthConstants.MinProgram, SynthConstants.MaxProgram); _sequencer.SetChannelProgram(channel, program); - _synthesizer.SetChannelProgram(channel, program); + _synthesizer.ChannelSetPresetNumber(channel, program); } private void OnSamplesPlayed(int sampleCount) { - var playedMillis = sampleCount / (double)_synthesizer.SampleRate * 1000; + var playedMillis = sampleCount / (double)_synthesizer.OutSampleRate * 1000; UpdateTimePosition(_timePosition + playedMillis); } @@ -355,7 +356,7 @@ private void UpdateTimePosition(double timePosition) Logger.Debug("AlphaSynth", "Position changed: (time: " + currentTime + "/" + endTime + ", tick: " + currentTick + "/" + endTime + - ", Active Voices: " + _synthesizer.ActiveVoices + ", Free Voices: " + _synthesizer.FreeVoices + ")"); + ", Active Voices: " + _synthesizer.ActiveVoiceCount); OnPositionChanged(new PositionChangedEventArgs(currentTime, endTime, currentTick, endTick)); } diff --git a/Source/AlphaTab/Audio/Synth/Bank/AssetManager.cs b/Source/AlphaTab/Audio/Synth/Bank/AssetManager.cs deleted file mode 100644 index 1ecaa840c..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/AssetManager.cs +++ /dev/null @@ -1,42 +0,0 @@ -using AlphaTab.Collections; - -namespace AlphaTab.Audio.Synth.Bank -{ - internal class AssetManager - { - public FastList PatchAssets { get; } - public FastList SampleAssets { get; } - - public AssetManager() - { - PatchAssets = new FastList(); - SampleAssets = new FastList(); - } - - public PatchAsset FindPatch(string name) - { - foreach (var patchAsset in PatchAssets) - { - if (patchAsset.Name == name) - { - return patchAsset; - } - } - - return null; - } - - public SampleDataAsset FindSample(string name) - { - foreach (var sampleDataAsset in SampleAssets) - { - if (sampleDataAsset.Name == name) - { - return sampleDataAsset; - } - } - - return null; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Components/Envelope.cs b/Source/AlphaTab/Audio/Synth/Bank/Components/Envelope.cs deleted file mode 100644 index 278eaa367..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Components/Envelope.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Bank.Descriptors; -using AlphaTab.Audio.Synth.Ds; -using AlphaTab.Audio.Synth.Util; - -namespace AlphaTab.Audio.Synth.Bank.Components -{ - internal enum EnvelopeState - { - Delay = 0, - Attack = 1, - Hold = 2, - Decay = 3, - Sustain = 4, - Release = 5, - None = 6 - } - - internal class Envelope - { - private readonly EnvelopeStage[] _stages; - private int _index; - private EnvelopeStage _stage; - - public float Value { get; set; } - public EnvelopeState CurrentStage { get; private set; } - public float Depth { get; set; } - - public Envelope() - { - Value = 0; - Depth = 0; - _stages = new EnvelopeStage[7]; - for (var x = 0; x < _stages.Length; x++) - { - _stages[x] = new EnvelopeStage(); - _stages[x].Graph = Tables.EnvelopeTables(0); - } - - _stages[3].Reverse = true; - _stages[5].Reverse = true; - _stages[6].Time = 100000000; - - CurrentStage = EnvelopeState.Delay; - _stage = _stages[(int)CurrentStage]; - } - - public void QuickSetupSf2( - int sampleRate, - int note, - short keyNumToHold, - short keyNumToDecay, - bool isVolumeEnvelope, - EnvelopeDescriptor envelopeInfo) - { - Depth = envelopeInfo.Depth; - // Delay - _stages[0].Offset = 0; - _stages[0].Scale = 0; - _stages[0].Time = Math.Max(0, sampleRate * envelopeInfo.DelayTime); - // Attack - _stages[1].Offset = envelopeInfo.StartLevel; - _stages[1].Scale = envelopeInfo.PeakLevel - envelopeInfo.StartLevel; - _stages[1].Time = Math.Max(0, sampleRate * envelopeInfo.AttackTime); - _stages[1].Graph = Tables.EnvelopeTables(envelopeInfo.AttackGraph); - // Hold - _stages[2].Offset = 0; - _stages[2].Scale = envelopeInfo.PeakLevel; - _stages[2].Time = Math.Max(0, - sampleRate * envelopeInfo.HoldTime * Math.Pow(2, (60 - note) * keyNumToHold / 1200.0)); - // Decay - _stages[3].Offset = envelopeInfo.SustainLevel; - _stages[3].Scale = envelopeInfo.PeakLevel - envelopeInfo.SustainLevel; - if (envelopeInfo.SustainLevel == envelopeInfo.PeakLevel) - { - _stages[3].Time = 0; - } - else - { - _stages[3].Time = Math.Max(0, - sampleRate * envelopeInfo.DecayTime * Math.Pow(2, (60 - note) * keyNumToDecay / 1200.0)); - } - - _stages[3].Graph = Tables.EnvelopeTables(envelopeInfo.DecayGraph); - // Sustain - _stages[4].Offset = 0; - _stages[4].Scale = envelopeInfo.SustainLevel; - _stages[4].Time = sampleRate * envelopeInfo.SustainTime; - // Release - _stages[5].Scale = _stages[3].Time == 0 && _stages[4].Time == 0 ? envelopeInfo.PeakLevel : _stages[4].Scale; - if (isVolumeEnvelope) - { - _stages[5].Offset = -100; - _stages[5].Scale += 100; - _stages[6].Scale = -100; - } - else - { - _stages[5].Offset = 0; - _stages[6].Scale = 0; - } - - _stages[5].Time = Math.Max(0, (int)(sampleRate * envelopeInfo.ReleaseTime)); - _stages[5].Graph = Tables.EnvelopeTables(envelopeInfo.ReleaseGraph); - - _index = 0; - Value = 0; - CurrentStage = EnvelopeState.Delay; - while (Math.Abs(_stages[(int)CurrentStage].Time) < 0.01) - { - CurrentStage++; - } - - _stage = _stages[(int)CurrentStage]; - } - - public void Increment(int samples) - { - do - { - var neededSamples = (int)_stage.Time - _index; - if (neededSamples > samples) - { - _index += samples; - samples = 0; - } - else - { - _index = 0; - if (CurrentStage != EnvelopeState.None) - { - do - { - _stage = _stages[(int)++CurrentStage]; - } while (_stage.Time == 0); - } - - samples -= neededSamples; - } - } while (samples > 0); - - var i = (int)(_stage.Graph.Length * (_index / (double)_stage.Time)); - if (_stage.Reverse) - { - Value = (1f - _stage.Graph[i]) * _stage.Scale + _stage.Offset; - } - else - { - Value = _stage.Graph[i] * _stage.Scale + _stage.Offset; - } - } - - public void Release(double lowerLimit) - { - if (Value <= lowerLimit) - { - _index = 0; - CurrentStage = EnvelopeState.None; - _stage = _stages[(int)CurrentStage]; - } - else if (CurrentStage < EnvelopeState.Release) - { - _index = 0; - CurrentStage = EnvelopeState.Release; - _stage = _stages[(int)CurrentStage]; - _stage.Scale = Value; - } - } - - public void ReleaseSf2VolumeEnvelope() - { - if (Value <= -100) - { - _index = 0; - CurrentStage = EnvelopeState.None; - _stage = _stages[(int)CurrentStage]; - } - else if (CurrentStage < EnvelopeState.Release) - { - _index = 0; - CurrentStage = EnvelopeState.Release; - _stage = _stages[(int)CurrentStage]; - _stage.Offset = -100; - _stage.Scale = 100 + Value; - } - } - } - - internal class EnvelopeStage - { - public double Time { get; set; } - public SampleArray Graph { get; set; } - public float Scale { get; set; } - public float Offset { get; set; } - public bool Reverse { get; set; } - - public EnvelopeStage() - { - Time = 0; - Graph = null; - Scale = 0; - Offset = 0; - Reverse = false; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Components/Filter.cs b/Source/AlphaTab/Audio/Synth/Bank/Components/Filter.cs deleted file mode 100644 index 57d639907..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Components/Filter.cs +++ /dev/null @@ -1,238 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Bank.Descriptors; -using AlphaTab.Audio.Synth.Ds; -using AlphaTab.Audio.Synth.Util; - -namespace AlphaTab.Audio.Synth.Bank.Components -{ - internal enum FilterType - { - None = 0, - BiquadLowpass = 1, - BiquadHighpass = 2, - OnePoleLowpass = 3 - } - - internal class Filter - { - private float _a1; - private float _a2; - - private float _b1; - private float _b2; - - private float _m1; - private float _m2; - private float _m3; - private double _cutOff; - private double _resonance; - - public FilterType FilterMethod { get; private set; } - - public double CutOff - { - get => _cutOff; - set - { - _cutOff = value; - CoeffNeedsUpdating = true; - } - } - - public double Resonance - { - get => _resonance; - set - { - _resonance = value; - CoeffNeedsUpdating = true; - } - } - - public bool Enabled => FilterMethod != FilterType.None; - - public bool CoeffNeedsUpdating { get; private set; } - - public Filter() - { - _a1 = 0; - _a2 = 0; - _b1 = 0; - _b2 = 0; - _m1 = 0; - _m2 = 0; - _m3 = 0; - FilterMethod = FilterType.None; - CutOff = 0; - Resonance = 0; - } - - public void Disable() - { - FilterMethod = FilterType.None; - } - - public void QuickSetup(int sampleRate, int note, float velocity, FilterDescriptor filterInfo) - { - CoeffNeedsUpdating = false; - CutOff = filterInfo.CutOff; - Resonance = filterInfo.Resonance; - FilterMethod = filterInfo.FilterMethod; - _a1 = 0; - _a2 = 0; - _b1 = 0; - _b2 = 0; - _m1 = 0; - _m2 = 0; - _m3 = 0; - if (CutOff <= 0 || Resonance <= 0) - { - FilterMethod = FilterType.None; - } - - if (FilterMethod != FilterType.None) - { - CutOff *= SynthHelper.CentsToPitch((note - filterInfo.RootKey) * filterInfo.KeyTrack + - (int)(velocity * filterInfo.VelTrack)); - UpdateCoefficients(sampleRate); - } - } - - public float ApplyFilter(float sample) - { - switch (FilterMethod) - { - case FilterType.BiquadHighpass: - case FilterType.BiquadLowpass: - _m3 = sample - _a1 * _m1 - _a2 * _m2; - sample = _b2 * (_m3 + _m2) + _b1 * _m1; - _m2 = _m1; - _m1 = _m3; - return sample; - case FilterType.OnePoleLowpass: - _m1 += _a1 * (sample - _m1); - return _m1; - default: - return 0f; - } - } - - public void ApplyFilter(SampleArray data) - { - switch (FilterMethod) - { - case FilterType.BiquadHighpass: - case FilterType.BiquadLowpass: - for (var x = 0; x < data.Length; x++) - { - _m3 = data[x] - _a1 * _m1 - _a2 * _m2; - data[x] = _b2 * (_m3 + _m2) + _b1 * _m1; - _m2 = _m1; - _m1 = _m3; - } - - break; - case FilterType.OnePoleLowpass: - for (var x = 0; x < data.Length; x++) - { - _m1 += _a1 * (data[x] - _m1); - data[x] = _m1; - } - - break; - } - } - - public void ApplyFilterInterp(SampleArray data, int sampleRate) - { - var ic = GenerateFilterCoeff(CutOff / sampleRate, Resonance); - var a1Inc = (ic[0] - _a1) / data.Length; - var a2Inc = (ic[1] - _a2) / data.Length; - var b1Inc = (ic[2] - _b1) / data.Length; - var b2Inc = (ic[3] - _b2) / data.Length; - switch (FilterMethod) - { - case FilterType.BiquadHighpass: - case FilterType.BiquadLowpass: - for (var x = 0; x < data.Length; x++) - { - _a1 += a1Inc; - _a2 += a2Inc; - _b1 += b1Inc; - _b2 += b2Inc; - _m3 = data[x] - _a1 * _m1 - _a2 * _m2; - data[x] = _b2 * (_m3 + _m2) + _b1 * _m1; - _m2 = _m1; - _m1 = _m3; - } - - _a1 = ic[0]; - _a2 = ic[1]; - _b1 = ic[2]; - _b2 = ic[3]; - break; - case FilterType.OnePoleLowpass: - for (var x = 0; x < data.Length; x++) - { - _a1 += a1Inc; - _m1 += _a1 * (data[x] - _m1); - data[x] = _m1; - } - - _a1 = ic[0]; - break; - } - - CoeffNeedsUpdating = false; - } - - public void UpdateCoefficients(int sampleRate) - { - var coeff = GenerateFilterCoeff(CutOff / sampleRate, Resonance); - _a1 = coeff[0]; - _a2 = coeff[1]; - _b1 = coeff[2]; - _b2 = coeff[3]; - CoeffNeedsUpdating = false; - } - - private float[] GenerateFilterCoeff(double fc, double q) - { - fc = SynthHelper.ClampD(fc, SynthConstants.DenormLimit, .49); - var coeff = new float[4]; - switch (FilterMethod) - { - case FilterType.BiquadLowpass: - { - var w0 = SynthConstants.TwoPi * fc; - var cosw0 = Math.Cos(w0); - var alpha = Math.Sin(w0) / (2.0 * q); - var a0Inv = 1.0 / (1.0 + alpha); - coeff[0] = (float)(-2.0 * cosw0 * a0Inv); - coeff[1] = (float)((1.0 - alpha) * a0Inv); - coeff[2] = (float)((1.0 - cosw0) * a0Inv * (1.0 / Math.Sqrt(q))); - coeff[3] = _b1 * 0.5f; - } - break; - case FilterType.BiquadHighpass: - { - var w0 = SynthConstants.TwoPi * fc; - var cosw0 = Math.Cos(w0); - var alpha = Math.Sin(w0) / (2.0 * q); - var a0Inv = 1.0 / (1.0 + alpha); - var qinv = 1.0 / Math.Sqrt(q); - coeff[0] = (float)(-2.0 * cosw0 * a0Inv); - coeff[1] = (float)((1.0 - alpha) * a0Inv); - coeff[2] = (float)((-1.0 - cosw0) * a0Inv * qinv); - coeff[3] = (float)((1.0 + cosw0) * a0Inv * qinv * 0.5); - } - break; - case FilterType.OnePoleLowpass: - coeff[0] = 1.0f - (float)Math.Exp(-2.0 * Math.PI * fc); - break; - } - - return coeff; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/DefaultGenerators.cs b/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/DefaultGenerators.cs deleted file mode 100644 index 37e2401a5..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/DefaultGenerators.cs +++ /dev/null @@ -1,12 +0,0 @@ -using AlphaTab.Audio.Synth.Bank.Descriptors; - -namespace AlphaTab.Audio.Synth.Bank.Components.Generators -{ - internal class DefaultGenerators - { - public static readonly Generator DefaultSine = new SineGenerator(new GeneratorDescriptor()); - public static readonly Generator DefaultSaw = new SawGenerator(new GeneratorDescriptor()); - public static readonly Generator DefaultSquare = new SquareGenerator(new GeneratorDescriptor()); - public static readonly Generator DefaultTriangle = new TriangleGenerator(new GeneratorDescriptor()); - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/Generator.cs b/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/Generator.cs deleted file mode 100644 index d4202cbee..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/Generator.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Bank.Descriptors; -using AlphaTab.Audio.Synth.Ds; - -namespace AlphaTab.Audio.Synth.Bank.Components.Generators -{ - internal enum LoopMode - { - NoLoop = 0, - OneShot = 1, - Continuous = 2, - LoopUntilNoteOff = 3 - } - - internal enum GeneratorState - { - PreLoop = 0, - Loop = 1, - PostLoop = 2, - Finished = 3 - } - - internal abstract class Generator - { - public LoopMode LoopMode { get; set; } - public double LoopStartPhase { get; set; } - public double LoopEndPhase { get; set; } - public double StartPhase { get; set; } - public double EndPhase { get; set; } - public double Offset { get; set; } - public double Period { get; set; } - public double Frequency { get; set; } - public short RootKey { get; set; } - public short KeyTrack { get; set; } - public short VelocityTrack { get; set; } - public short Tune { get; set; } - - - protected Generator(GeneratorDescriptor description) - { - LoopMode = description.LoopMethod; - LoopStartPhase = description.LoopStartPhase; - LoopEndPhase = description.LoopEndPhase; - StartPhase = description.StartPhase; - EndPhase = description.EndPhase; - Offset = description.Offset; - Period = description.Period; - Frequency = 0; - RootKey = description.RootKey; - KeyTrack = description.KeyTrack; - VelocityTrack = description.VelTrack; - Tune = description.Tune; - } - - public void Release(GeneratorParameters generatorParams) - { - if (LoopMode == LoopMode.LoopUntilNoteOff) - { - generatorParams.CurrentState = GeneratorState.PostLoop; - generatorParams.CurrentStart = StartPhase; - generatorParams.CurrentEnd = EndPhase; - } - } - - public abstract float GetValue(double phase); - - public virtual void GetValues(GeneratorParameters generatorParams, SampleArray blockBuffer, double increment) - { - var proccessed = 0; - do - { - var samplesAvailable = - (int)Math.Ceiling((generatorParams.CurrentEnd - generatorParams.Phase) / increment); - if (samplesAvailable > blockBuffer.Length - proccessed) - { - while (proccessed < blockBuffer.Length) - { - blockBuffer[proccessed++] = GetValue(generatorParams.Phase); - generatorParams.Phase += increment; - } - } - else - { - var endProccessed = proccessed + samplesAvailable; - while (proccessed < endProccessed) - { - blockBuffer[proccessed++] = GetValue(generatorParams.Phase); - generatorParams.Phase += increment; - } - - switch (generatorParams.CurrentState) - { - case GeneratorState.PreLoop: - generatorParams.CurrentStart = LoopStartPhase; - generatorParams.CurrentEnd = LoopEndPhase; - generatorParams.CurrentState = GeneratorState.Loop; - break; - case GeneratorState.Loop: - generatorParams.Phase += generatorParams.CurrentStart - generatorParams.CurrentEnd; - break; - case GeneratorState.PostLoop: - generatorParams.CurrentState = GeneratorState.Finished; - while (proccessed < blockBuffer.Length) - { - blockBuffer[proccessed++] = 0; - } - - break; - } - } - } while (proccessed < blockBuffer.Length); - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/GeneratorParameters.cs b/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/GeneratorParameters.cs deleted file mode 100644 index 849102a8c..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/GeneratorParameters.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace AlphaTab.Audio.Synth.Bank.Components.Generators -{ - internal class GeneratorParameters - { - public double Phase { get; set; } - public double CurrentStart { get; set; } - public double CurrentEnd { get; set; } - public GeneratorState CurrentState { get; set; } - - public GeneratorParameters() - { - Phase = 0; - CurrentStart = 0; - CurrentEnd = 0; - CurrentState = 0; - } - - public void QuickSetup(Generator generator) - { - CurrentStart = generator.StartPhase; - Phase = CurrentStart + generator.Offset; - switch (generator.LoopMode) - { - case LoopMode.Continuous: - case LoopMode.LoopUntilNoteOff: - if (Phase >= generator.EndPhase) - { - //phase is greater than the end index so generator is finished - CurrentState = GeneratorState.Finished; - } - else if (Phase >= generator.LoopEndPhase) - { - //phase is greater than the loop end point so generator is in post loop - CurrentState = GeneratorState.PostLoop; - CurrentEnd = generator.EndPhase; - } - else if (Phase >= generator.LoopStartPhase) - { - //phase is greater than loop start so we are inside the loop - CurrentState = GeneratorState.Loop; - CurrentEnd = generator.LoopEndPhase; - CurrentStart = generator.LoopStartPhase; - } - else - { - //phase is less than the loop so generator is in pre loop - CurrentState = GeneratorState.PreLoop; - CurrentEnd = generator.LoopStartPhase; - } - - break; - default: - CurrentEnd = generator.EndPhase; - if (Phase >= CurrentEnd) - { - CurrentState = GeneratorState.Finished; - } - else - { - CurrentState = GeneratorState.PostLoop; - } - - break; - } - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SampleGenerator.cs b/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SampleGenerator.cs deleted file mode 100644 index ebec55457..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SampleGenerator.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Bank.Descriptors; -using AlphaTab.Audio.Synth.Ds; - -namespace AlphaTab.Audio.Synth.Bank.Components.Generators -{ - internal class SampleGenerator : Generator - { - public PcmData Samples { get; set; } - - public SampleGenerator() - : base(new GeneratorDescriptor()) - { - } - - public override float GetValue(double phase) - { - return Samples[(int)phase]; - } - - public override void GetValues(GeneratorParameters generatorParams, SampleArray blockBuffer, double increment) - { - var proccessed = 0; - do - { - var samplesAvailable = - (int)Math.Ceiling((generatorParams.CurrentEnd - generatorParams.Phase) / increment); - if (samplesAvailable > blockBuffer.Length - proccessed) - { - Interpolate(generatorParams, blockBuffer, increment, proccessed, blockBuffer.Length); - return; //proccessed = blockBuffer.Length; - } - - var endProccessed = proccessed + samplesAvailable; - Interpolate(generatorParams, blockBuffer, increment, proccessed, endProccessed); - proccessed = endProccessed; - switch (generatorParams.CurrentState) - { - case GeneratorState.PreLoop: - generatorParams.CurrentStart = LoopStartPhase; - generatorParams.CurrentEnd = LoopEndPhase; - generatorParams.CurrentState = GeneratorState.Loop; - break; - case GeneratorState.Loop: - generatorParams.Phase += generatorParams.CurrentStart - generatorParams.CurrentEnd; - break; - case GeneratorState.PostLoop: - generatorParams.CurrentState = GeneratorState.Finished; - while (proccessed < blockBuffer.Length) - { - blockBuffer[proccessed++] = 0f; - } - - break; - } - } while (proccessed < blockBuffer.Length); - } - - private void Interpolate( - GeneratorParameters generatorParams, - SampleArray blockBuffer, - double increment, - int bufferStart, - int bufferEnd) - { - var phaseEnd = generatorParams.CurrentState == GeneratorState.Loop ? LoopEndPhase - 1 : EndPhase - 1; - int index; - float s1, s2, mu; - while (bufferStart < bufferEnd && generatorParams.Phase < phaseEnd) //do this until we reach an edge case or fill the buffer - { - index = (int)generatorParams.Phase; - s1 = Samples[index]; - s2 = Samples[index + 1]; - mu = (float)(generatorParams.Phase - index); - blockBuffer[bufferStart++] = s1 + mu * (s2 - s1); - generatorParams.Phase += increment; - } - - while (bufferStart < bufferEnd) //edge case, if in loop wrap to loop start else use duplicate sample - { - index = (int)generatorParams.Phase; - s1 = Samples[index]; - if (generatorParams.CurrentState == GeneratorState.Loop) - { - s2 = Samples[(int)generatorParams.CurrentStart]; - } - else - { - s2 = s1; - } - - mu = (float)(generatorParams.Phase - index); - blockBuffer[bufferStart++] = s1 + mu * (s2 - s1); - generatorParams.Phase += increment; - } - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SawGenerator.cs b/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SawGenerator.cs deleted file mode 100644 index 6ce2bf88e..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SawGenerator.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Bank.Descriptors; - -namespace AlphaTab.Audio.Synth.Bank.Components.Generators -{ - internal class SawGenerator : Generator - { - public SawGenerator(GeneratorDescriptor description) - : base(description) - { - if (EndPhase < 0) - { - EndPhase = 1; - } - - if (StartPhase < 0) - { - StartPhase = 0; - } - - if (LoopEndPhase < 0) - { - LoopEndPhase = EndPhase; - } - - if (LoopStartPhase < 0) - { - LoopStartPhase = StartPhase; - } - - if (Period < 0) - { - Period = 1; - } - - if (RootKey < 0) - { - RootKey = 69; - } - - Frequency = 440; - } - - public override float GetValue(double phase) - { - return (float)(2.0 * (phase - Math.Floor(phase + 0.5))); - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SineGenerator.cs b/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SineGenerator.cs deleted file mode 100644 index 80c233cc9..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SineGenerator.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Bank.Descriptors; -using AlphaTab.Audio.Synth.Util; - -namespace AlphaTab.Audio.Synth.Bank.Components.Generators -{ - internal class SineGenerator : Generator - { - public SineGenerator(GeneratorDescriptor description) - : base(description) - { - if (EndPhase < 0) - { - EndPhase = SynthConstants.TwoPi; - } - - if (StartPhase < 0) - { - StartPhase = 0; - } - - if (LoopEndPhase < 0) - { - LoopEndPhase = EndPhase; - } - - if (LoopStartPhase < 0) - { - LoopStartPhase = StartPhase; - } - - if (Period < 0) - { - Period = SynthConstants.TwoPi; - } - - if (RootKey < 0) - { - RootKey = 69; - } - - Frequency = 440; - } - - public override float GetValue(double phase) - { - return (float)Math.Sin(phase); - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SquareGenerator.cs b/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SquareGenerator.cs deleted file mode 100644 index 64a95b8c3..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SquareGenerator.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Bank.Descriptors; -using AlphaTab.Audio.Synth.Util; - -namespace AlphaTab.Audio.Synth.Bank.Components.Generators -{ - internal class SquareGenerator : Generator - { - public SquareGenerator(GeneratorDescriptor description) - : base(description) - { - if (EndPhase < 0) - { - EndPhase = SynthConstants.TwoPi; - } - - if (StartPhase < 0) - { - StartPhase = 0; - } - - if (LoopEndPhase < 0) - { - LoopEndPhase = EndPhase; - } - - if (LoopStartPhase < 0) - { - LoopStartPhase = StartPhase; - } - - if (Period < 0) - { - Period = SynthConstants.TwoPi; - } - - if (RootKey < 0) - { - RootKey = 69; - } - - Frequency = 440; - } - - public override float GetValue(double phase) - { - return Math.Sign(Math.Sin(phase)); - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/TriangleGenerator.cs b/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/TriangleGenerator.cs deleted file mode 100644 index ec8706267..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/TriangleGenerator.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Bank.Descriptors; - -namespace AlphaTab.Audio.Synth.Bank.Components.Generators -{ - internal class TriangleGenerator : Generator - { - public TriangleGenerator(GeneratorDescriptor description) - : base(description) - { - if (EndPhase < 0) - { - EndPhase = 1.25; - } - - if (StartPhase < 0) - { - StartPhase = 0.25; - } - - if (LoopEndPhase < 0) - { - LoopEndPhase = EndPhase; - } - - if (LoopStartPhase < 0) - { - LoopStartPhase = StartPhase; - } - - if (Period < 0) - { - Period = 1; - } - - if (RootKey < 0) - { - RootKey = 69; - } - - Frequency = 440; - } - - public override float GetValue(double phase) - { - return (float)(Math.Abs(phase - Math.Floor(phase + 0.5)) * 4.0 - 1.0); - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/WhiteNoiseGenerator.cs b/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/WhiteNoiseGenerator.cs deleted file mode 100644 index bda9955ec..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Components/Generators/WhiteNoiseGenerator.cs +++ /dev/null @@ -1,48 +0,0 @@ -using AlphaTab.Audio.Synth.Bank.Descriptors; - -namespace AlphaTab.Audio.Synth.Bank.Components.Generators -{ - internal class WhiteNoiseGenerator : Generator - { - public WhiteNoiseGenerator(GeneratorDescriptor description) - : base(description) - { - if (EndPhase < 0) - { - EndPhase = 1; - } - - if (StartPhase < 0) - { - StartPhase = 0; - } - - if (LoopEndPhase < 0) - { - LoopEndPhase = EndPhase; - } - - if (LoopStartPhase < 0) - { - LoopStartPhase = StartPhase; - } - - if (Period < 0) - { - Period = 1; - } - - if (RootKey < 0) - { - RootKey = 69; - } - - Frequency = 440; - } - - public override float GetValue(double phase) - { - return (float)(Platform.Platform.RandomDouble() * 2.0 - 1.0); - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Components/Lfo.cs b/Source/AlphaTab/Audio/Synth/Bank/Components/Lfo.cs deleted file mode 100644 index 5de0d0909..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Components/Lfo.cs +++ /dev/null @@ -1,87 +0,0 @@ -using AlphaTab.Audio.Synth.Bank.Components.Generators; -using AlphaTab.Audio.Synth.Bank.Descriptors; - -namespace AlphaTab.Audio.Synth.Bank.Components -{ - internal enum LfoState - { - Delay = 0, - Sustain = 1 - } - - internal class Lfo - { - private double _phase; - private double _increment; - private int _delayTime; - private Generators.Generator _generator; - - public float Frequency { get; private set; } - public LfoState CurrentState { get; private set; } - public double Value { get; set; } - public double Depth { get; set; } - - public Lfo() - { - CurrentState = LfoState.Delay; - _generator = DefaultGenerators.DefaultSine; - _delayTime = 0; - _increment = 0; - _phase = 0; - Frequency = 0; - CurrentState = 0; - Value = 0; - Depth = 0; - } - - public void QuickSetup(int sampleRate, LfoDescriptor lfoInfo) - { - _generator = lfoInfo.Generator; - _delayTime = (int)(sampleRate * lfoInfo.DelayTime); - Frequency = lfoInfo.Frequency; - _increment = _generator.Period * Frequency / sampleRate; - Depth = lfoInfo.Depth; - Reset(); - } - - public void Increment(int amount) - { - if (CurrentState == LfoState.Delay) - { - _phase -= amount; - while (_phase <= 0.0) - { - _phase = _generator.LoopStartPhase + _increment * -_phase; - Value = _generator.GetValue(_phase); - CurrentState = LfoState.Sustain; - } - } - else - { - _phase += _increment * amount; - while (_phase >= _generator.LoopEndPhase) - { - _phase = _generator.LoopStartPhase + (_phase - _generator.LoopEndPhase) % - (_generator.LoopEndPhase - _generator.LoopStartPhase); - } - - Value = _generator.GetValue(_phase); - } - } - - public void Reset() - { - Value = 0; - if (_delayTime > 0) - { - _phase = _delayTime; - CurrentState = LfoState.Delay; - } - else - { - _phase = 0.0f; - CurrentState = LfoState.Sustain; - } - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Components/PanComponent.cs b/Source/AlphaTab/Audio/Synth/Bank/Components/PanComponent.cs deleted file mode 100644 index d9e20dac8..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Components/PanComponent.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Util; - -namespace AlphaTab.Audio.Synth.Bank.Components -{ - internal enum PanFormulaEnum - { - Neg3dBCenter = 0, - Neg6dBCenter = 1, - ZeroCenter = 2 - } - - internal class PanComponent - { - public float Left { get; set; } - public float Right { get; set; } - - public void SetValue(float value, PanFormulaEnum formula) - { - value = SynthHelper.ClampF(value, -1, 1); - double dvalue; - switch (formula) - { - case PanFormulaEnum.Neg3dBCenter: - dvalue = SynthConstants.HalfPi * (value + 1) / 2.0; - Left = (float)Math.Cos(dvalue); - Right = (float)Math.Sin(dvalue); - break; - case PanFormulaEnum.Neg6dBCenter: - Left = (float)(.5 + value * -.5); - Right = (float)(.5 + value * .5); - break; - case PanFormulaEnum.ZeroCenter: - dvalue = SynthConstants.HalfPi * (value + 1.0) / 2.0; - Left = (float)(Math.Cos(dvalue) / SynthConstants.InverseSqrtOfTwo); - Right = (float)(Math.Sin(dvalue) / SynthConstants.InverseSqrtOfTwo); - break; - default: - throw new Exception("Invalid pan law selected."); - } - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Descriptors/EnvelopeDescriptor.cs b/Source/AlphaTab/Audio/Synth/Bank/Descriptors/EnvelopeDescriptor.cs deleted file mode 100644 index d9acb114a..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Descriptors/EnvelopeDescriptor.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace AlphaTab.Audio.Synth.Bank.Descriptors -{ - internal class EnvelopeDescriptor - { - public float DelayTime { get; set; } - public float AttackTime { get; set; } - public short AttackGraph { get; set; } - public float HoldTime { get; set; } - public float DecayTime { get; set; } - public short DecayGraph { get; set; } - public float SustainTime { get; set; } - public float ReleaseTime { get; set; } - public short ReleaseGraph { get; set; } - public float SustainLevel { get; set; } - public float PeakLevel { get; set; } - public float StartLevel { get; set; } - public float Depth { get; set; } - public float Vel2Delay { get; set; } - public float Vel2Attack { get; set; } - public float Vel2Hold { get; set; } - public float Vel2Decay { get; set; } - public float Vel2Sustain { get; set; } - public float Vel2Release { get; set; } - public float Vel2Depth { get; set; } - - public EnvelopeDescriptor() - { - DelayTime = 0; - AttackTime = 0; - AttackGraph = 1; - HoldTime = 0; - DecayTime = 0; - DecayGraph = 1; - SustainTime = 3600; - ReleaseTime = 0; - ReleaseGraph = 1; - SustainLevel = 0; - PeakLevel = 1; - StartLevel = 0; - Depth = 1; - Vel2Delay = 0; - Vel2Attack = 0; - Vel2Hold = 0; - Vel2Decay = 0; - Vel2Sustain = 0; - Vel2Release = 0; - Vel2Depth = 0; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Descriptors/FilterDescriptor.cs b/Source/AlphaTab/Audio/Synth/Bank/Descriptors/FilterDescriptor.cs deleted file mode 100644 index 50291ee89..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Descriptors/FilterDescriptor.cs +++ /dev/null @@ -1,24 +0,0 @@ -using AlphaTab.Audio.Synth.Bank.Components; - -namespace AlphaTab.Audio.Synth.Bank.Descriptors -{ - internal class FilterDescriptor - { - public FilterType FilterMethod { get; set; } - public float CutOff { get; set; } - public float Resonance { get; set; } - public short RootKey { get; set; } - public short KeyTrack { get; set; } - public short VelTrack { get; set; } - - public FilterDescriptor() - { - FilterMethod = FilterType.None; - CutOff = -1; - Resonance = 1; - RootKey = 60; - KeyTrack = 0; - VelTrack = 0; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Descriptors/GeneratorDescriptor.cs b/Source/AlphaTab/Audio/Synth/Bank/Descriptors/GeneratorDescriptor.cs deleted file mode 100644 index a41032699..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Descriptors/GeneratorDescriptor.cs +++ /dev/null @@ -1,48 +0,0 @@ -using AlphaTab.Audio.Synth.Bank.Components.Generators; - -namespace AlphaTab.Audio.Synth.Bank.Descriptors -{ - internal enum Waveform - { - Sine = 0, - Square = 1, - Saw = 2, - Triangle = 3, - SampleData = 4, - WhiteNoise = 5 - } - - internal class GeneratorDescriptor - { - public LoopMode LoopMethod { get; set; } - public Waveform SamplerType { get; set; } - public string AssetName { get; set; } - public double EndPhase { get; set; } - public double StartPhase { get; set; } - public double LoopEndPhase { get; set; } - public double LoopStartPhase { get; set; } - public double Offset { get; set; } - public double Period { get; set; } - public short RootKey { get; set; } - public short KeyTrack { get; set; } - public short VelTrack { get; set; } - public short Tune { get; set; } - - public GeneratorDescriptor() - { - LoopMethod = LoopMode.NoLoop; - SamplerType = Waveform.Sine; - AssetName = "null"; - EndPhase = -1; - StartPhase = -1; - LoopEndPhase = -1; - LoopStartPhase = -1; - Offset = 0; - Period = -1; - RootKey = -1; - KeyTrack = 100; - VelTrack = 0; - Tune = 0; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Descriptors/LfoDescriptor.cs b/Source/AlphaTab/Audio/Synth/Bank/Descriptors/LfoDescriptor.cs deleted file mode 100644 index fd1768454..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Descriptors/LfoDescriptor.cs +++ /dev/null @@ -1,21 +0,0 @@ -using AlphaTab.Audio.Synth.Bank.Components.Generators; -using AlphaTab.Audio.Synth.Util; - -namespace AlphaTab.Audio.Synth.Bank.Descriptors -{ - internal class LfoDescriptor - { - public float DelayTime { get; set; } - public float Frequency { get; set; } - public float Depth { get; set; } - public Components.Generators.Generator Generator { get; set; } - - public LfoDescriptor() - { - DelayTime = 0; - Frequency = SynthConstants.DefaultLfoFrequency; - Depth = 1; - Generator = DefaultGenerators.DefaultSine; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Patch/MultiPatch.cs b/Source/AlphaTab/Audio/Synth/Bank/Patch/MultiPatch.cs deleted file mode 100644 index db8765461..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Patch/MultiPatch.cs +++ /dev/null @@ -1,225 +0,0 @@ -using AlphaTab.Audio.Synth.Sf2; -using AlphaTab.Audio.Synth.Synthesis; - -namespace AlphaTab.Audio.Synth.Bank.Patch -{ - internal class MultiPatch : Patch - { - private IntervalType _intervalType; - private PatchInterval[] _intervalList; - - public MultiPatch(string name) - : base(name) - { - _intervalType = IntervalType.ChannelKeyVelocity; - } - - public int FindPatches(int channel, int key, int velocity, Patch[] layers) - { - var count = 0; - switch (_intervalType) - { - case IntervalType.ChannelKeyVelocity: - for (var x = 0; x < _intervalList.Length; x++) - { - if (_intervalList[x].CheckAllIntervals(channel, key, velocity)) - { - layers[count++] = _intervalList[x].Patch; - if (count == layers.Length) - { - break; - } - } - } - - break; - case IntervalType.ChannelKey: - for (var x = 0; x < _intervalList.Length; x++) - { - if (_intervalList[x].CheckChannelAndKey(channel, key)) - { - layers[count++] = _intervalList[x].Patch; - if (count == layers.Length) - { - break; - } - } - } - - break; - case IntervalType.KeyVelocity: - for (var x = 0; x < _intervalList.Length; x++) - { - if (_intervalList[x].CheckKeyAndVelocity(key, velocity)) - { - layers[count++] = _intervalList[x].Patch; - if (count == layers.Length) - { - break; - } - } - } - - break; - case IntervalType.Key: - for (var x = 0; x < _intervalList.Length; x++) - { - if (_intervalList[x].CheckKey(key)) - { - layers[count++] = _intervalList[x].Patch; - if (count == layers.Length) - { - break; - } - } - } - - break; - } - - return count; - } - - public override bool Start(VoiceParameters voiceparams) - { - return false; - } - - public override void Process( - VoiceParameters voiceparams, - int startIndex, - int endIndex, - bool isMuted, - bool isSilentProcess) - { - } - - public override void Stop(VoiceParameters voiceparams) - { - } - - public void LoadSf2(Sf2Region[] regions, AssetManager assets) - { - _intervalList = new PatchInterval[regions.Length]; - for (var x = 0; x < regions.Length; x++) - { - byte loKey; - byte hiKey; - byte loVel; - byte hiVel; - loKey = Platform.Platform.ToUInt8(regions[x].Generators[(int)GeneratorEnum.KeyRange] & 0xFF); - hiKey = Platform.Platform.ToUInt8((regions[x].Generators[(int)GeneratorEnum.KeyRange] >> 8) & 0xFF); - loVel = Platform.Platform.ToUInt8(regions[x].Generators[(int)GeneratorEnum.VelocityRange] & 0xFF); - hiVel = Platform.Platform.ToUInt8((regions[x].Generators[(int)GeneratorEnum.VelocityRange] >> 8) & - 0xFF); - - var sf2 = new Sf2Patch(Name + "_" + x); - sf2.Load(regions[x], assets); - _intervalList[x] = new PatchInterval(sf2, 0, 15, loKey, hiKey, loVel, hiVel); - } - - DetermineIntervalType(); - } - - private void DetermineIntervalType() - { - var checkChannel = false; - var checkVelocity = false; - for (var x = 0; x < _intervalList.Length; x++) - { - if (_intervalList[x].StartChannel != 0 || _intervalList[x].EndChannel != 15) - { - checkChannel = true; - if (checkChannel && checkVelocity) - { - break; - } - } - - if (_intervalList[x].StartVelocity != 0 || _intervalList[x].EndVelocity != 127) - { - checkVelocity = true; - if (checkChannel && checkVelocity) - { - break; - } - } - } - - if (checkChannel & checkVelocity) - { - _intervalType = IntervalType.ChannelKeyVelocity; - } - else if (checkChannel) - { - _intervalType = IntervalType.ChannelKey; - } - else if (checkVelocity) - { - _intervalType = IntervalType.KeyVelocity; - } - else - { - _intervalType = IntervalType.Key; - } - } - } - - internal enum IntervalType - { - ChannelKeyVelocity = 0, - ChannelKey = 1, - KeyVelocity = 2, - Key = 3 - } - - internal class PatchInterval - { - public Patch Patch { get; set; } - public byte StartChannel { get; set; } - public byte StartKey { get; set; } - public byte StartVelocity { get; set; } - public byte EndChannel { get; set; } - public byte EndKey { get; set; } - public byte EndVelocity { get; set; } - - public PatchInterval( - Patch patch, - byte startChannel, - byte endChannel, - byte startKey, - byte endKey, - byte startVelocity, - byte endVelocity) - { - Patch = patch; - StartChannel = startChannel; - EndChannel = endChannel; - StartKey = startKey; - EndKey = endKey; - StartVelocity = startVelocity; - EndVelocity = endVelocity; - } - - public bool CheckAllIntervals(int channel, int key, int velocity) - { - return channel >= StartChannel && channel <= EndChannel && key >= StartKey && key <= EndKey && - velocity >= StartVelocity && velocity <= EndVelocity; - } - - public bool CheckChannelAndKey(int channel, int key) - { - return channel >= StartChannel && channel <= EndChannel && key >= StartKey && key <= EndKey; - } - - public bool CheckKeyAndVelocity(int key, int velocity) - { - return key >= StartKey && key <= EndKey && velocity >= StartVelocity && velocity <= EndVelocity; - } - - public bool CheckKey(int key) - { - return key >= StartKey && key <= EndKey; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Patch/Patch.cs b/Source/AlphaTab/Audio/Synth/Bank/Patch/Patch.cs deleted file mode 100644 index f052f988b..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Patch/Patch.cs +++ /dev/null @@ -1,29 +0,0 @@ -using AlphaTab.Audio.Synth.Synthesis; - -namespace AlphaTab.Audio.Synth.Bank.Patch -{ - internal abstract class Patch - { - public int ExclusiveGroupTarget { get; set; } - public int ExclusiveGroup { get; set; } - public string Name { get; private set; } - - protected Patch(string name) - { - Name = name; - ExclusiveGroup = 0; - ExclusiveGroupTarget = 0; - } - - public abstract bool Start(VoiceParameters voiceparams); - - public abstract void Process( - VoiceParameters voiceparams, - int startIndex, - int endIndex, - bool isMuted, - bool isSilentProcess); - - public abstract void Stop(VoiceParameters voiceparams); - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/Patch/Sf2Patch.cs b/Source/AlphaTab/Audio/Synth/Bank/Patch/Sf2Patch.cs deleted file mode 100644 index 99c8752f9..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/Patch/Sf2Patch.cs +++ /dev/null @@ -1,482 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Bank.Components; -using AlphaTab.Audio.Synth.Bank.Components.Generators; -using AlphaTab.Audio.Synth.Bank.Descriptors; -using AlphaTab.Audio.Synth.Sf2; -using AlphaTab.Audio.Synth.Synthesis; -using AlphaTab.Audio.Synth.Util; - -namespace AlphaTab.Audio.Synth.Bank.Patch -{ - internal class Sf2Patch : Patch - { - // private int iniFilterFc; - // private double filterQ; - private float _initialAttn; - - private short _keyOverride; - - // private short velOverride; - private short _keynumToModEnvHold; - private short _keynumToModEnvDecay; - private short _keynumToVolEnvHold; - private short _keynumToVolEnvDecay; - private PanComponent _pan; - private short _modLfoToPitch; - private short _vibLfoToPitch; - private short _modEnvToPitch; - private short _modLfoToFilterFc; - private short _modEnvToFilterFc; - private float _modLfoToVolume; - private SampleGenerator _gen; - private EnvelopeDescriptor _modEnv; - private EnvelopeDescriptor _velEnv; - private LfoDescriptor _modLFO; - private LfoDescriptor _vibLFO; - private FilterDescriptor _fltr; - - public Sf2Patch(string name) - : base(name) - { - } - - public override bool Start(VoiceParameters voiceparams) - { - var note = _keyOverride > -1 ? _keyOverride : voiceparams.Note; - // int vel = velOverride > -1 ? velOverride : voiceparams.Velocity; - //setup generator - voiceparams.GeneratorParams[0].QuickSetup(_gen); - //setup envelopes - voiceparams.Envelopes[0].QuickSetupSf2(voiceparams.SynthParams.Synth.SampleRate, - note, - _keynumToModEnvHold, - _keynumToModEnvDecay, - false, - _modEnv); - voiceparams.Envelopes[1].QuickSetupSf2(voiceparams.SynthParams.Synth.SampleRate, - note, - _keynumToVolEnvHold, - _keynumToVolEnvDecay, - true, - _velEnv); - //setup filter - //voiceparams.pData[0].int1 = iniFilterFc - (int)(2400 * CalculateModulator(SourceTypeEnum.Linear, TransformEnum.Linear, DirectionEnum.MaxToMin, PolarityEnum.Unipolar, voiceparams.velocity, 0, 127)); - //if (iniFilterFc >= 13500 && fltr.Resonance <= 1) - voiceparams.Filters[0].Disable(); - //else - // voiceparams.filters[0].QuickSetup(voiceparams.synthParams.synth.SampleRate, note, 1f, fltr); - //setup lfos - voiceparams.Lfos[0].QuickSetup(voiceparams.SynthParams.Synth.SampleRate, _modLFO); - voiceparams.Lfos[1].QuickSetup(voiceparams.SynthParams.Synth.SampleRate, _vibLFO); - //calculate initial pitch - voiceparams.PitchOffset = (note - _gen.RootKey) * _gen.KeyTrack + _gen.Tune; - voiceparams.PitchOffset += (int)(100.0 * (voiceparams.SynthParams.MasterCoarseTune + - (voiceparams.SynthParams.MasterFineTune.Combined - 8192.0) / - 8192.0)); - //calculate initial volume - voiceparams.VolOffset = _initialAttn; - voiceparams.VolOffset -= 96.0f * (float)CalculateModulator(SourceTypeEnum.Concave, - TransformEnum.Linear, - DirectionEnum.MaxToMin, - PolarityEnum.Unipolar, - voiceparams.Velocity, - 0, - 127); - voiceparams.VolOffset -= 96.0f * (float)CalculateModulator(SourceTypeEnum.Concave, - TransformEnum.Linear, - DirectionEnum.MaxToMin, - PolarityEnum.Unipolar, - voiceparams.SynthParams.Volume.Coarse, - 0, - 127); - //check if we have finished before we have begun - return voiceparams.GeneratorParams[0].CurrentState != GeneratorState.Finished && - voiceparams.Envelopes[1].CurrentStage != EnvelopeState.None; - } - - public override void Stop(VoiceParameters voiceparams) - { - _gen.Release(voiceparams.GeneratorParams[0]); - if (_gen.LoopMode != LoopMode.OneShot) - { - voiceparams.Envelopes[0].Release(SynthConstants.DenormLimit); - voiceparams.Envelopes[1].ReleaseSf2VolumeEnvelope(); - } - } - - public override void Process( - VoiceParameters voiceparams, - int startIndex, - int endIndex, - bool isMuted, - bool isSilentProcess) - { - //--Base pitch calculation - var basePitchFrequency = SynthHelper.CentsToPitch(voiceparams.SynthParams.CurrentPitch) * _gen.Frequency; - var pitchWithBend = basePitchFrequency * SynthHelper.CentsToPitch(voiceparams.PitchOffset); - var basePitch = pitchWithBend / voiceparams.SynthParams.Synth.SampleRate; - - var baseVolume = voiceparams.SynthParams.Synth.MasterVolume * voiceparams.SynthParams.CurrentVolume * - SynthConstants.DefaultMixGain * voiceparams.SynthParams.MixVolume; - - if (isSilentProcess) - { - voiceparams.State = VoiceStateEnum.Stopped; - } - else - { - //--Main Loop - for (var x = startIndex; - x < endIndex; - x += SynthConstants.DefaultBlockSize * SynthConstants.AudioChannels) - { - voiceparams.Envelopes[0].Increment(SynthConstants.DefaultBlockSize); - voiceparams.Envelopes[1].Increment(SynthConstants.DefaultBlockSize); - voiceparams.Lfos[0].Increment(SynthConstants.DefaultBlockSize); - voiceparams.Lfos[1].Increment(SynthConstants.DefaultBlockSize); - - //--Calculate pitch and get next block of samples - _gen.GetValues(voiceparams.GeneratorParams[0], - voiceparams.BlockBuffer, - basePitch * - SynthHelper.CentsToPitch((int)(voiceparams.Envelopes[0].Value * _modEnvToPitch + - voiceparams.Lfos[0].Value * _modLfoToPitch + - voiceparams.Lfos[1].Value * _vibLfoToPitch))); - //--Filter - if (voiceparams.Filters[0].Enabled) - { - var centsFc = voiceparams.PData[0].Int1 + voiceparams.Lfos[0].Value * _modLfoToFilterFc + - voiceparams.Envelopes[0].Value * _modEnvToFilterFc; - if (centsFc > 13500) - { - centsFc = 13500; - } - - voiceparams.Filters[0].CutOff = SynthHelper.KeyToFrequency(centsFc / 100.0, 69); - if (voiceparams.Filters[0].CoeffNeedsUpdating) - { - voiceparams.Filters[0].ApplyFilterInterp(voiceparams.BlockBuffer, - voiceparams.SynthParams.Synth.SampleRate); - } - else - { - voiceparams.Filters[0].ApplyFilter(voiceparams.BlockBuffer); - } - } - - //--Volume calculation - var volume = - (float)SynthHelper.DBtoLinear(voiceparams.VolOffset + voiceparams.Envelopes[1].Value + - voiceparams.Lfos[0].Value * _modLfoToVolume) * baseVolume; - - // only mix if needed - if (!isMuted) - { - //--Mix block based on number of channels - voiceparams.MixMonoToStereoInterp(x, - volume * _pan.Left * voiceparams.SynthParams.CurrentPan.Left, - volume * _pan.Right * voiceparams.SynthParams.CurrentPan.Right); - } - - //--Check and end early if necessary - if ((voiceparams.Envelopes[1].CurrentStage > EnvelopeState.Hold && - volume <= SynthConstants.NonAudible) || - voiceparams.GeneratorParams[0].CurrentState == GeneratorState.Finished) - { - voiceparams.State = VoiceStateEnum.Stopped; - return; - } - } - } - } - - - public void Load(Sf2Region region, AssetManager assets) - { - ExclusiveGroup = region.Generators[(int)GeneratorEnum.ExclusiveClass]; - ExclusiveGroupTarget = ExclusiveGroup; - - // iniFilterFc = region.Generators[(int)GeneratorEnum.InitialFilterCutoffFrequency]; - // filterQ = SynthHelper.DBtoLinear(region.Generators[(int)GeneratorEnum.InitialFilterQ] / 10.0); - _initialAttn = -region.Generators[(int)GeneratorEnum.InitialAttenuation] / 10f; - _keyOverride = region.Generators[(int)GeneratorEnum.KeyNumber]; - // velOverride = region.Generators[(int)GeneratorEnum.Velocity]; - _keynumToModEnvHold = region.Generators[(int)GeneratorEnum.KeyNumberToModulationEnvelopeHold]; - _keynumToModEnvDecay = region.Generators[(int)GeneratorEnum.KeyNumberToModulationEnvelopeDecay]; - _keynumToVolEnvHold = region.Generators[(int)GeneratorEnum.KeyNumberToVolumeEnvelopeHold]; - _keynumToVolEnvDecay = region.Generators[(int)GeneratorEnum.KeyNumberToVolumeEnvelopeDecay]; - _pan = new PanComponent(); - _pan.SetValue(region.Generators[(int)GeneratorEnum.Pan] / 500f, PanFormulaEnum.Neg3dBCenter); - _modLfoToPitch = region.Generators[(int)GeneratorEnum.ModulationLFOToPitch]; - _vibLfoToPitch = region.Generators[(int)GeneratorEnum.VibratoLFOToPitch]; - _modEnvToPitch = region.Generators[(int)GeneratorEnum.ModulationEnvelopeToPitch]; - _modLfoToFilterFc = region.Generators[(int)GeneratorEnum.ModulationLFOToFilterCutoffFrequency]; - _modEnvToFilterFc = region.Generators[(int)GeneratorEnum.ModulationEnvelopeToFilterCutoffFrequency]; - _modLfoToVolume = region.Generators[(int)GeneratorEnum.ModulationLFOToVolume] / 10f; - - LoadGen(region, assets); - LoadEnvelopes(region); - LoadLfos(region); - LoadFilter(region); - } - - private void LoadGen(Sf2Region region, AssetManager assets) - { - var sda = assets.SampleAssets[region.Generators[(int)GeneratorEnum.SampleId]]; - _gen = new SampleGenerator(); - _gen.EndPhase = sda.End + region.Generators[(int)GeneratorEnum.EndAddressOffset] + - 32768 * region.Generators[(int)GeneratorEnum.EndAddressCoarseOffset]; - _gen.Frequency = sda.SampleRate; - _gen.KeyTrack = region.Generators[(int)GeneratorEnum.ScaleTuning]; - _gen.LoopEndPhase = sda.LoopEnd + region.Generators[(int)GeneratorEnum.EndLoopAddressOffset] + - 32768 * region.Generators[(int)GeneratorEnum.EndLoopAddressCoarseOffset]; - switch (region.Generators[(int)GeneratorEnum.SampleModes] & 0x3) - { - case 0x0: - case 0x2: - _gen.LoopMode = LoopMode.NoLoop; - break; - case 0x1: - _gen.LoopMode = LoopMode.Continuous; - break; - case 0x3: - _gen.LoopMode = LoopMode.LoopUntilNoteOff; - break; - } - - _gen.LoopStartPhase = sda.LoopStart + region.Generators[(int)GeneratorEnum.StartLoopAddressOffset] + - 32768 * region.Generators[(int)GeneratorEnum.StartLoopAddressCoarseOffset]; - _gen.Offset = 0; - _gen.Period = 1.0; - if (region.Generators[(int)GeneratorEnum.OverridingRootKey] > -1) - { - _gen.RootKey = region.Generators[(int)GeneratorEnum.OverridingRootKey]; - } - else - { - _gen.RootKey = sda.RootKey; - } - - _gen.StartPhase = sda.Start + region.Generators[(int)GeneratorEnum.StartAddressOffset] + - 32768 * region.Generators[(int)GeneratorEnum.StartAddressCoarseOffset]; - _gen.Tune = (short)(sda.Tune + region.Generators[(int)GeneratorEnum.FineTune] + - 100 * region.Generators[(int)GeneratorEnum.CoarseTune]); - _gen.VelocityTrack = 0; - ((SampleGenerator)_gen).Samples = sda.SampleData; - } - - private void LoadEnvelopes(Sf2Region region) - { - // - //mod env - _modEnv = new EnvelopeDescriptor(); - _modEnv.AttackTime = - (float)Math.Pow(2, region.Generators[(int)GeneratorEnum.AttackModulationEnvelope] / 1200.0); - _modEnv.AttackGraph = 3; - _modEnv.DecayTime = - (float)Math.Pow(2, region.Generators[(int)GeneratorEnum.DecayModulationEnvelope] / 1200.0); - _modEnv.DelayTime = - (float)Math.Pow(2, region.Generators[(int)GeneratorEnum.DelayModulationEnvelope] / 1200.0); - _modEnv.HoldTime = - (float)Math.Pow(2, region.Generators[(int)GeneratorEnum.HoldModulationEnvelope] / 1200.0); - _modEnv.PeakLevel = 1; - _modEnv.ReleaseTime = - (float)Math.Pow(2, region.Generators[(int)GeneratorEnum.ReleaseModulationEnvelope] / 1200.0); - _modEnv.StartLevel = 0; - _modEnv.SustainLevel = - 1f - SynthHelper.ClampS(region.Generators[(int)GeneratorEnum.SustainModulationEnvelope], - (short)0, - (short)1000) / 1000f; - //checks - if (_modEnv.AttackTime < 0.001f) - { - _modEnv.AttackTime = 0.001f; - } - else if (_modEnv.AttackTime > 100f) - { - _modEnv.AttackTime = 100f; - } - - if (_modEnv.DecayTime < 0.001f) - { - _modEnv.DecayTime = 0; - } - else if (_modEnv.DecayTime > 100f) - { - _modEnv.DecayTime = 100f; - } - - if (_modEnv.DelayTime < 0.001f) - { - _modEnv.DelayTime = 0; - } - else if (_modEnv.DelayTime > 20f) - { - _modEnv.DelayTime = 20f; - } - - if (_modEnv.HoldTime < 0.001f) - { - _modEnv.HoldTime = 0; - } - else if (_modEnv.HoldTime > 20f) - { - _modEnv.HoldTime = 20f; - } - - if (_modEnv.ReleaseTime < 0.001f) - { - _modEnv.ReleaseTime = 0.001f; - } - else if (_modEnv.ReleaseTime > 100f) - { - _modEnv.ReleaseTime = 100f; - } - - // - // volume env - _velEnv = new EnvelopeDescriptor(); - _velEnv.AttackTime = - (float)Math.Pow(2, region.Generators[(int)GeneratorEnum.AttackVolumeEnvelope] / 1200.0); - _velEnv.AttackGraph = 3; - _velEnv.DecayTime = (float)Math.Pow(2, region.Generators[(int)GeneratorEnum.DecayVolumeEnvelope] / 1200.0); - _velEnv.DelayTime = (float)Math.Pow(2, region.Generators[(int)GeneratorEnum.DelayVolumeEnvelope] / 1200.0); - _velEnv.HoldTime = (float)Math.Pow(2, region.Generators[(int)GeneratorEnum.HoldVolumeEnvelope] / 1200.0); - _velEnv.PeakLevel = 0; - _velEnv.ReleaseTime = - (float)Math.Pow(2, region.Generators[(int)GeneratorEnum.ReleaseVolumeEnvelope] / 1200.0); - _velEnv.StartLevel = -100; - _velEnv.SustainLevel = SynthHelper.ClampS(region.Generators[(int)GeneratorEnum.SustainVolumeEnvelope], - (short)0, - (short)1000) / -10f; - // checks - if (_velEnv.AttackTime < 0.001f) - { - _velEnv.AttackTime = 0.001f; - } - else if (_velEnv.AttackTime > 100f) - { - _velEnv.AttackTime = 100f; - } - - if (_velEnv.DecayTime < 0.001f) - { - _velEnv.DecayTime = 0; - } - else if (_velEnv.DecayTime > 100f) - { - _velEnv.DecayTime = 100f; - } - - if (_velEnv.DelayTime < 0.001f) - { - _velEnv.DelayTime = 0; - } - else if (_velEnv.DelayTime > 20f) - { - _velEnv.DelayTime = 20f; - } - - if (_velEnv.HoldTime < 0.001f) - { - _velEnv.HoldTime = 0; - } - else if (_velEnv.HoldTime > 20f) - { - _velEnv.HoldTime = 20f; - } - - if (_velEnv.ReleaseTime < 0.001f) - { - _velEnv.ReleaseTime = 0.001f; - } - else if (_velEnv.ReleaseTime > 100f) - { - _velEnv.ReleaseTime = 100f; - } - } - - private void LoadLfos(Sf2Region region) - { - _modLFO = new LfoDescriptor(); - _modLFO.DelayTime = (float)Math.Pow(2, region.Generators[(int)GeneratorEnum.DelayModulationLFO] / 1200.0); - _modLFO.Frequency = - (float)(Math.Pow(2, region.Generators[(int)GeneratorEnum.FrequencyModulationLFO] / 1200.0) * 8.176); - _modLFO.Generator = DefaultGenerators.DefaultSine; - _vibLFO = new LfoDescriptor(); - _vibLFO.DelayTime = (float)Math.Pow(2, region.Generators[(int)GeneratorEnum.DelayVibratoLFO] / 1200.0); - _vibLFO.Frequency = - (float)(Math.Pow(2, region.Generators[(int)GeneratorEnum.FrequencyVibratoLFO] / 1200.0) * 8.176); - _vibLFO.Generator = DefaultGenerators.DefaultSine; - } - - private void LoadFilter(Sf2Region region) - { - _fltr = new FilterDescriptor(); - _fltr.FilterMethod = FilterType.BiquadLowpass; - _fltr.CutOff = - (float)SynthHelper.KeyToFrequency(region.Generators[(int)GeneratorEnum.InitialFilterCutoffFrequency] / - 100.0, - 69); - _fltr.Resonance = - (float)SynthHelper.DBtoLinear(region.Generators[(int)GeneratorEnum.InitialFilterQ] / 10.0); - } - - private static double CalculateModulator( - SourceTypeEnum s, - TransformEnum t, - DirectionEnum d, - PolarityEnum p, - int value, - int min, - int max) - { - double output = 0; - int i; - value = value - min; - max = max - min; - if (d == DirectionEnum.MaxToMin) - { - value = max - value; - } - - switch (s) - { - case SourceTypeEnum.Linear: - output = value / max; - break; - case SourceTypeEnum.Concave: - i = 127 - value; - output = -(20.0 / 96.0) * Math.Log10((i * i) / (double)(max * max)); - break; - case SourceTypeEnum.Convex: - i = value; - output = 1 + (20.0 / 96.0) * Math.Log10((i * i) / (double)(max * max)); - break; - case SourceTypeEnum.Switch: - if (value <= (max / 2)) - { - output = 0; - } - else - { - output = 1; - } - - break; - } - - if (p == PolarityEnum.Bipolar) - { - output = (output * 2) - 1; - } - - if (t == TransformEnum.AbsoluteValue) - { - output = Math.Abs(output); - } - - return output; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/PatchAsset.cs b/Source/AlphaTab/Audio/Synth/Bank/PatchAsset.cs deleted file mode 100644 index ba489ae8b..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/PatchAsset.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace AlphaTab.Audio.Synth.Bank -{ - internal class PatchAsset - { - public string Name { get; } - public Patch.Patch Patch { get; } - - public PatchAsset(string name, Patch.Patch patch) - { - Name = name; - Patch = patch; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/PatchBank.cs b/Source/AlphaTab/Audio/Synth/Bank/PatchBank.cs deleted file mode 100644 index 4422457cc..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/PatchBank.cs +++ /dev/null @@ -1,341 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Bank.Patch; -using AlphaTab.Audio.Synth.Sf2; -using AlphaTab.Collections; -using AlphaTab.IO; -using AlphaTab.Util; - -namespace AlphaTab.Audio.Synth.Bank -{ - internal class PatchBank - { - public const int DrumBank = 128; - public const int BankSize = 128; - - private FastDictionary _bank; - private AssetManager _assets; - - public string Name { get; set; } - public string Comments { get; set; } - - public PatchBank() - { - Reset(); - } - - public void Reset() - { - _bank = new FastDictionary(); - _assets = new AssetManager(); - Name = ""; - Comments = ""; - } - - public int[] LoadedBanks - { - get - { - var banks = new FastList(); - foreach (var bank in _bank) - { - banks.Add(bank); - } - - banks.Sort((a, b) => a - b); - return banks.ToArray(); - } - } - - public Patch.Patch[] GetBank(int bankNumber) - { - return _bank.ContainsKey(bankNumber) ? _bank[bankNumber] : null; - } - - public Patch.Patch GetPatchByNumber(int bankNumber, int patchNumber) - { - return _bank.ContainsKey(bankNumber) ? _bank[bankNumber][patchNumber] : null; - } - - public Patch.Patch GetPatchByName(int bankNumber, string name) - { - if (_bank.ContainsKey(bankNumber)) - { - var patches = _bank[bankNumber]; - foreach (var patch in patches) - { - if (patch != null && patch.Name == name) - { - return patch; - } - } - } - - return null; - } - - public bool IsBankLoaded(int bankNumber) - { - return _bank.ContainsKey(bankNumber); - } - - public void LoadSf2(IReadable input) - { - Reset(); - - Logger.Debug("PatchBank", "Reading SF2"); - var sf = new SoundFont(); - sf.Load(input); - - Logger.Debug("PatchBank", "Building patchbank"); - Name = sf.Info.BankName; - Comments = sf.Info.Comments; - - //load samples - foreach (var sampleHeader in sf.Presets.SampleHeaders) - { - _assets.SampleAssets.Add(new SampleDataAsset(sampleHeader, sf.SampleData)); - } - - //create instrument regions first - var sfinsts = ReadSf2Instruments(sf.Presets.Instruments); - //load each patch - foreach (var p in sf.Presets.PresetHeaders) - { - Sf2.Generator[] globalGens = null; - int i; - if (p.Zones[0].Generators.Length == 0 || - p.Zones[0].Generators[p.Zones[0].Generators.Length - 1].GeneratorType != GeneratorEnum.Instrument) - { - globalGens = p.Zones[0].Generators; - i = 1; - } - else - { - i = 0; - } - - var regionList = new FastList(); - while (i < p.Zones.Length) - { - byte presetLoKey = 0; - byte presetHiKey = 127; - byte presetLoVel = 0; - byte presetHiVel = 127; - - if (p.Zones[i].Generators[0].GeneratorType == GeneratorEnum.KeyRange) - { - presetLoKey = Platform.Platform.ToUInt8(p.Zones[i].Generators[0].AmountInt16 & 0xFF); - presetHiKey = Platform.Platform.ToUInt8((p.Zones[i].Generators[0].AmountInt16 >> 8) & 0xFF); - - if (p.Zones[i].Generators.Length > 1 && - p.Zones[i].Generators[1].GeneratorType == GeneratorEnum.VelocityRange) - { - presetLoVel = Platform.Platform.ToUInt8(p.Zones[i].Generators[1].AmountInt16 & 0xFF); - presetHiVel = Platform.Platform.ToUInt8((p.Zones[i].Generators[1].AmountInt16 >> 8) & 0xFF); - } - } - else if (p.Zones[i].Generators[0].GeneratorType == GeneratorEnum.VelocityRange) - { - presetLoVel = Platform.Platform.ToUInt8(p.Zones[i].Generators[0].AmountInt16 & 0xFF); - presetHiVel = Platform.Platform.ToUInt8((p.Zones[i].Generators[0].AmountInt16 >> 8) & 0xFF); - } - - if (p.Zones[i].Generators[p.Zones[i].Generators.Length - 1].GeneratorType == - GeneratorEnum.Instrument) - { - var insts = sfinsts[p.Zones[i].Generators[p.Zones[i].Generators.Length - 1].AmountInt16]; - foreach (var inst in insts) - { - byte instLoKey; - byte instHiKey; - byte instLoVel; - byte instHiVel; - - instLoKey = Platform.Platform.ToUInt8(inst.Generators[(int)GeneratorEnum.KeyRange] & 0xFF); - instHiKey = Platform.Platform.ToUInt8( - (inst.Generators[(int)GeneratorEnum.KeyRange] >> 8) & 0xFF); - instLoVel = Platform.Platform.ToUInt8( - inst.Generators[(int)GeneratorEnum.VelocityRange] & 0xFF); - instHiVel = Platform.Platform.ToUInt8( - (inst.Generators[(int)GeneratorEnum.VelocityRange] >> 8) & 0xFF); - - if (instLoKey <= presetHiKey && presetLoKey <= instHiKey && instLoVel <= presetHiVel && - presetLoVel <= instHiVel) - { - var r = new Sf2Region(); - Platform.Platform.ArrayCopy(inst.Generators, 0, r.Generators, 0, r.Generators.Length); - ReadSf2Region(r, globalGens, p.Zones[i].Generators, true); - regionList.Add(r); - } - } - } - - i++; - } - - var mp = new MultiPatch(p.Name); - mp.LoadSf2(regionList.ToArray(), _assets); - _assets.PatchAssets.Add(new PatchAsset(mp.Name, mp)); - AssignPatchToBank(mp, p.BankNumber, p.PatchNumber, p.PatchNumber); - } - } - - private Sf2Region[][] ReadSf2Instruments(Instrument[] instruments) - { - var regions = new Sf2Region[instruments.Length][]; - for (var x = 0; x < instruments.Length; x++) - { - Sf2.Generator[] globalGens = null; - int i; - if (instruments[x].Zones[0].Generators.Length == 0 || - instruments[x].Zones[0].Generators[instruments[x].Zones[0].Generators.Length - 1].GeneratorType != - GeneratorEnum.SampleId) - { - globalGens = instruments[x].Zones[0].Generators; - i = 1; - } - else - { - i = 0; - } - - regions[x] = new Sf2Region[instruments[x].Zones.Length - i]; - for (var j = 0; j < regions[x].Length; j++) - { - var r = new Sf2Region(); - r.ApplyDefaultValues(); - ReadSf2Region(r, globalGens, instruments[x].Zones[j + i].Generators, false); - regions[x][j] = r; - } - } - - return regions; - } - - private void ReadSf2Region(Sf2Region region, Sf2.Generator[] globals, Sf2.Generator[] gens, bool isRelative) - { - if (!isRelative) - { - if (globals != null) - { - for (var x = 0; x < globals.Length; x++) - { - region.Generators[(int)globals[x].GeneratorType] = globals[x].AmountInt16; - } - } - - for (var x = 0; x < gens.Length; x++) - { - region.Generators[(int)gens[x].GeneratorType] = gens[x].AmountInt16; - } - } - else - { - var genList = new FastList(); - foreach (var generator in gens) - { - genList.Add(generator); - } - - if (globals != null) - { - for (var x = 0; x < globals.Length; x++) - { - var found = false; - for (var i = 0; i < genList.Count; i++) - { - if (genList[i].GeneratorType == globals[x].GeneratorType) - { - found = true; - break; - } - } - - if (!found) - { - genList.Add(globals[x]); - } - } - } - - for (var x = 0; x < genList.Count; x++) - { - var value = (int)genList[x].GeneratorType; - if (value < 5 || value == 12 || value == 45 || value == 46 || value == 47 || value == 50 || - value == 54 || value == 57 || value == 58) - { - continue; - } - - if (value == 43 || value == 44) - { - byte lo_a; - byte hi_a; - byte lo_b; - byte hi_b; - lo_a = Platform.Platform.ToUInt8(region.Generators[value] & 0xFF); - hi_a = Platform.Platform.ToUInt8((region.Generators[value] >> 8) & 0xFF); - lo_b = Platform.Platform.ToUInt8(genList[x].AmountInt16 & 0xFF); - hi_b = Platform.Platform.ToUInt8((genList[x].AmountInt16 >> 8) & 0xFF); - - lo_a = (byte)Math.Max(lo_a, lo_b); - hi_a = Math.Min(hi_a, hi_b); - - if (lo_a > hi_a) - { - throw new Exception("Invalid sf2 region. The range generators do not intersect."); - } - - region.Generators[value] = Platform.Platform.ToInt16(lo_a | (hi_a << 8)); - } - else - { - region.Generators[value] = - Platform.Platform.ToInt16(region.Generators[value] + genList[x].AmountInt16); - } - } - } - } - - private void AssignPatchToBank(Patch.Patch patch, int bankNumber, int startRange, int endRange) - { - if (bankNumber < 0) - { - return; - } - - if (startRange > endRange) - { - var range = startRange; - startRange = endRange; - endRange = range; - } - - if (startRange < 0 || startRange >= BankSize) - { - throw new Exception("startRange out of range"); - } - - if (endRange < 0 || endRange >= BankSize) - { - throw new Exception("endRange out of range"); - } - - Patch.Patch[] patches; - if (_bank.ContainsKey(bankNumber)) - { - patches = _bank[bankNumber]; - } - else - { - patches = new Patch.Patch[BankSize]; - _bank[bankNumber] = patches; - } - - for (var x = startRange; x <= endRange; x++) - { - patches[x] = patch; - } - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/PcmData.cs b/Source/AlphaTab/Audio/Synth/Bank/PcmData.cs deleted file mode 100644 index 44fd689ed..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/PcmData.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Util; - -namespace AlphaTab.Audio.Synth.Bank -{ - internal abstract class PcmData - { - protected byte[] Data; - - public int Length { get; protected set; } - public int BytesPerSample { get; protected set; } - public int BitsPerSample => BytesPerSample * 8; - - protected PcmData(int bits, byte[] pcmData, bool isDataInLittleEndianFormat) - { - BytesPerSample = (byte)(bits / 8); - //if (pcmData.Length % BytesPerSample != 0) - // throw new Exception("Invalid PCM format. The PCM data was an invalid size."); - Data = pcmData; - Length = Data.Length / BytesPerSample; - if (!isDataInLittleEndianFormat) - { - SynthHelper.SwapEndianess(Data, bits); - } - } - - public abstract float this[int index] { get; } - - public static PcmData Create(int bits, byte[] pcmData, bool isDataInLittleEndianFormat) - { - switch (bits) - { - case 8: - return new PcmData8Bit(bits, pcmData, isDataInLittleEndianFormat); - case 16: - return new PcmData16Bit(bits, pcmData, isDataInLittleEndianFormat); - case 24: - return new PcmData24Bit(bits, pcmData, isDataInLittleEndianFormat); - case 32: - return new PcmData32Bit(bits, pcmData, isDataInLittleEndianFormat); - default: - throw new Exception("Invalid PCM format. " + bits + "bit pcm data is not supported."); - } - } - } - - internal class PcmData8Bit : PcmData - { - public PcmData8Bit(int bits, byte[] pcmData, bool isDataInLittleEndianFormat) : base(bits, - pcmData, - isDataInLittleEndianFormat) - { - } - - public override float this[int index] => Data[index] / 255f * 2f - 1f; - } - - internal class PcmData16Bit : PcmData - { - public PcmData16Bit(int bits, byte[] pcmData, bool isDataInLittleEndianFormat) : base(bits, - pcmData, - isDataInLittleEndianFormat) - { - } - - public override float this[int index] - { - get - { - index *= 2; - return (((Data[index] | (Data[index + 1] << 8)) << 16) >> 16) / 32768f; - } - } - } - - internal class PcmData24Bit : PcmData - { - public PcmData24Bit(int bits, byte[] pcmData, bool isDataInLittleEndianFormat) : base(bits, - pcmData, - isDataInLittleEndianFormat) - { - } - - public override float this[int index] - { - get - { - index *= 3; - return (((Data[index] | (Data[index + 1] << 8) | (Data[index + 2] << 16)) << 12) >> 12) / 8388608f; - } - } - } - - internal class PcmData32Bit : PcmData - { - public PcmData32Bit(int bits, byte[] pcmData, bool isDataInLittleEndianFormat) : base(bits, - pcmData, - isDataInLittleEndianFormat) - { - } - - public override float this[int index] - { - get - { - index *= 4; - return (Data[index] | (Data[index + 1] << 8) | (Data[index + 2] << 16) | (Data[index + 3] << 24)) / - 2147483648f; - } - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Bank/SampleDataAsset.cs b/Source/AlphaTab/Audio/Synth/Bank/SampleDataAsset.cs deleted file mode 100644 index f37906ad0..000000000 --- a/Source/AlphaTab/Audio/Synth/Bank/SampleDataAsset.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Sf2; - -namespace AlphaTab.Audio.Synth.Bank -{ - internal class SampleDataAsset - { - public string Name { get; set; } - public int Channels { get; set; } - public int SampleRate { get; set; } - public short RootKey { get; set; } - public short Tune { get; set; } - public float Start { get; set; } - public float End { get; set; } - public float LoopStart { get; set; } - public float LoopEnd { get; set; } - public PcmData SampleData { get; set; } - - public SampleDataAsset(SampleHeader sample, SoundFontSampleData sampleData) - { - Channels = 1; - - Name = sample.Name; - SampleRate = sample.SampleRate; - RootKey = sample.RootKey; - Tune = sample.Tune; - Start = sample.Start; - End = sample.End; - LoopStart = sample.StartLoop; - LoopEnd = sample.EndLoop; - if ((sample.SoundFontSampleLink & SoundFontSampleLink.OggVobis) != 0) - { - throw new Exception("Ogg Vobis encoded soundfonts not supported"); - } - - SampleData = PcmData.Create(sampleData.BitsPerSample, sampleData.SampleData, true); - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Ds/CircularSampleBuffer.cs b/Source/AlphaTab/Audio/Synth/Ds/CircularSampleBuffer.cs index b5c25ac5e..8b063db7e 100644 --- a/Source/AlphaTab/Audio/Synth/Ds/CircularSampleBuffer.cs +++ b/Source/AlphaTab/Audio/Synth/Ds/CircularSampleBuffer.cs @@ -3,11 +3,11 @@ namespace AlphaTab.Audio.Synth.Ds { /// - /// Represents a fixed size circular sample buffer that can be written to and read from. + /// Represents a fixed size circular sample buffer that can be written to and read from. /// public class CircularSampleBuffer { - private SampleArray _buffer; + private float[] _buffer; private int _writePosition; private int _readPosition; @@ -17,36 +17,36 @@ public class CircularSampleBuffer /// The size. public CircularSampleBuffer(int size) { - _buffer = new SampleArray(size); + _buffer = new float[size]; _writePosition = 0; _readPosition = 0; Count = 0; } /// - /// Gets the number of samples written to the buffer. + /// Gets the number of samples written to the buffer. /// public int Count { get; private set; } /// - /// Clears all samples written to this buffer. + /// Clears all samples written to this buffer. /// public void Clear() { _readPosition = 0; _writePosition = 0; Count = 0; - _buffer = new SampleArray(_buffer.Length); + _buffer = new float[_buffer.Length]; } /// - /// Writes the given samples to this buffer. + /// Writes the given samples to this buffer. /// /// The sample array to read from. /// /// /// - public int Write(SampleArray data, int offset, int count) + public int Write(float[] data, int offset, int count) { var samplesWritten = 0; if (count > _buffer.Length - Count) @@ -55,13 +55,13 @@ public int Write(SampleArray data, int offset, int count) } var writeToEnd = Math.Min(_buffer.Length - _writePosition, count); - SampleArray.Blit(data, offset, _buffer, _writePosition, writeToEnd); + Platform.Platform.ArrayCopy(data, offset, _buffer, _writePosition, writeToEnd); _writePosition += writeToEnd; _writePosition %= _buffer.Length; samplesWritten += writeToEnd; if (samplesWritten < count) { - SampleArray.Blit(data, offset + samplesWritten, _buffer, _writePosition, count - samplesWritten); + Platform.Platform.ArrayCopy(data, offset + samplesWritten, _buffer, _writePosition, count - samplesWritten); _writePosition += count - samplesWritten; samplesWritten = count; } @@ -71,13 +71,13 @@ public int Write(SampleArray data, int offset, int count) } /// - /// Reads the requested amount of samples from the buffer. + /// Reads the requested amount of samples from the buffer. /// /// The sample array to store the read elements. /// The offset within the destination buffer to put the items at. /// The number of items to read from this buffer. /// The number of items actually read from the buffer. - public int Read(SampleArray data, int offset, int count) + public int Read(float[] data, int offset, int count) { if (count > Count) { @@ -86,14 +86,14 @@ public int Read(SampleArray data, int offset, int count) var samplesRead = 0; var readToEnd = Math.Min(_buffer.Length - _readPosition, count); - SampleArray.Blit(_buffer, _readPosition, data, offset, readToEnd); + Platform.Platform.ArrayCopy(_buffer, _readPosition, data, offset, readToEnd); samplesRead += readToEnd; _readPosition += readToEnd; _readPosition %= _buffer.Length; if (samplesRead < count) { - SampleArray.Blit(_buffer, _readPosition, data, offset + samplesRead, count - samplesRead); + Platform.Platform.ArrayCopy(_buffer, _readPosition, data, offset + samplesRead, count - samplesRead); _readPosition += count - samplesRead; samplesRead = count; } diff --git a/Source/AlphaTab/Audio/Synth/IAlphaSynth.cs b/Source/AlphaTab/Audio/Synth/IAlphaSynth.cs index 0ac57f79a..f6c973a91 100644 --- a/Source/AlphaTab/Audio/Synth/IAlphaSynth.cs +++ b/Source/AlphaTab/Audio/Synth/IAlphaSynth.cs @@ -1,6 +1,5 @@ using System; using AlphaTab.Audio.Synth.Midi; -using AlphaTab.Audio.Synth.Synthesis; namespace AlphaTab.Audio.Synth { @@ -168,8 +167,8 @@ bool IsLooping /// Gets or sets the current and initial volume of the given channel. /// /// The channel number. - /// The volume of of the channel (0.0-3.0) - void SetChannelVolume(int channel, double volume); + /// The volume of of the channel (0.0-1.0) + void SetChannelVolume(int channel, float volume); /// /// Gets or sets the current and initial program of the given channel. diff --git a/Source/AlphaTab/Audio/Synth/ISynthOutput.cs b/Source/AlphaTab/Audio/Synth/ISynthOutput.cs index 291e7f9c9..38b68f95b 100644 --- a/Source/AlphaTab/Audio/Synth/ISynthOutput.cs +++ b/Source/AlphaTab/Audio/Synth/ISynthOutput.cs @@ -5,7 +5,7 @@ namespace AlphaTab.Audio.Synth { /// /// This is the base interface for output devices which can - /// request and playback audio samples. + /// request and playback audio samples. /// public interface ISynthOutput { @@ -20,13 +20,13 @@ public interface ISynthOutput void Open(); /// - /// Called when the sequencer finished the playback. - /// This tells the output not to request any samples anymore after the existing buffers are finished. + /// Called when the sequencer finished the playback. + /// This tells the output not to request any samples anymore after the existing buffers are finished. /// void SequencerFinished(); /// - /// Called when the output should start the playback. + /// Called when the output should start the playback. /// void Play(); @@ -39,10 +39,10 @@ public interface ISynthOutput /// Called when samples have been synthesized and should be added to the playback buffer. /// /// - void AddSamples(SampleArray samples); + void AddSamples(float[] samples); /// - /// Called when the samples in the output buffer should be reset. This is neeed for instance when seeking to another position. + /// Called when the samples in the output buffer should be reset. This is neeed for instance when seeking to another position. /// void ResetSamples(); @@ -52,22 +52,22 @@ public interface ISynthOutput event Action Ready; /// - /// Fired when a certain number of samples have been played. + /// Fired when a certain number of samples have been played. /// event Action SamplesPlayed; /// - /// Fired when the output needs more samples to be played. + /// Fired when the output needs more samples to be played. /// event Action SampleRequest; /// - /// Fired when the last samples after calling SequencerFinished have been played. + /// Fired when the last samples after calling SequencerFinished have been played. /// event Action Finished; /// - /// Activates the output component. + /// Activates the output component. /// void Activate(); } diff --git a/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs b/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs index 6f0ece6ca..096cf4d0d 100644 --- a/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs +++ b/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs @@ -1,29 +1,29 @@ using System; using AlphaTab.Audio.Synth.Midi; using AlphaTab.Audio.Synth.Midi.Event; -using AlphaTab.Audio.Synth.Synthesis; using AlphaTab.Collections; using AlphaTab.Util; +using AlphaTab.Audio.Synth.Synthesis; namespace AlphaTab.Audio.Synth { /// /// This sequencer dispatches midi events to the synthesizer based on the current - /// synthesize position. The sequencer does not consider the playback speed. + /// synthesize position. The sequencer does not consider the playback speed. /// internal class MidiFileSequencer { - private readonly Synthesizer _synthesizer; + private readonly TinySoundFont _synthesizer; private FastList _tempoChanges; - private FastDictionary _firstProgramEventPerChannel; + private readonly FastDictionary _firstProgramEventPerChannel; private FastList _synthData; private int _division; private int _eventIndex; /// - /// Note that this is not the actual playback position. It's the position where we are currently synthesizing at. - /// Depending on the buffer size of the output, this position is after the actual playback. + /// Note that this is not the actual playback position. It's the position where we are currently synthesizing at. + /// Depending on the buffer size of the output, this position is after the actual playback. /// private double _currentTime; @@ -50,17 +50,17 @@ public PlaybackRange PlaybackRange public bool IsLooping { get; set; } /// - /// Gets the duration of the song in ticks. + /// Gets the duration of the song in ticks. /// public int EndTick { get; private set; } /// - /// Gets the duration of the song in milliseconds. + /// Gets the duration of the song in milliseconds. /// public double EndTime => _endTime / PlaybackSpeed; /// - /// Gets or sets the playback speed. + /// Gets or sets the playback speed. /// public double PlaybackSpeed { @@ -68,7 +68,7 @@ public double PlaybackSpeed set; } - public MidiFileSequencer(Synthesizer synthesizer) + public MidiFileSequencer(TinySoundFont synthesizer) { _synthesizer = synthesizer; _firstProgramEventPerChannel = new FastDictionary(); @@ -111,8 +111,8 @@ public void Seek(double timePosition) _currentTime = 0; _eventIndex = 0; _synthesizer.NoteOffAll(true); - _synthesizer.ResetPrograms(); - _synthesizer.ResetSynthControls(); + _synthesizer.Reset(); + _synthesizer.SetupMetronomeChannel(); SilentProcess(timePosition); } @@ -150,7 +150,7 @@ public void LoadMidi(MidiFile midiFile) _eventIndex = 0; _currentTime = 0; - // build synth events. + // build synth events. _synthData = new FastList(); // Converts midi to milliseconds for easy sequencing @@ -236,18 +236,18 @@ public bool FillMidiEventQueue() private bool FillMidiEventQueueLimited(double maxMilliseconds) { - var millisecondsPerBuffer = - _synthesizer.MicroBufferSize / (double)_synthesizer.SampleRate * 1000 * PlaybackSpeed; + var millisecondsPerBuffer = TinySoundFont.MicroBufferSize / (double)_synthesizer.OutSampleRate * 1000 * PlaybackSpeed; if (maxMilliseconds > 0 && maxMilliseconds < millisecondsPerBuffer) { millisecondsPerBuffer = maxMilliseconds; } var anyEventsDispatched = false; - for (var i = 0; i < _synthesizer.MicroBufferCount; i++) + var endTime = InternalEndTime; + for (var i = 0; i < TinySoundFont.MicroBufferCount; i++) { _currentTime += millisecondsPerBuffer; - while (_eventIndex < _synthData.Count && _synthData[_eventIndex].Time < _currentTime) + while (_eventIndex < _synthData.Count && _synthData[_eventIndex].Time < _currentTime && _currentTime < endTime) { _synthesizer.DispatchEvent(i, _synthData[_eventIndex]); _eventIndex++; @@ -336,25 +336,30 @@ protected virtual void OnFinished() } + private double InternalEndTime => PlaybackRange == null ? _endTime : _playbackRangeEndTime; + public void CheckForStop() { - if (PlaybackRange == null && _currentTime >= _endTime) + if (_currentTime >= InternalEndTime) { - _currentTime = 0; - _eventIndex = 0; _synthesizer.NoteOffAll(true); - _synthesizer.ResetPrograms(); - _synthesizer.ResetSynthControls(); + _synthesizer.Reset(); + _synthesizer.SetupMetronomeChannel(); OnFinished(); } - else if (PlaybackRange != null && _currentTime >= _playbackRangeEndTime) + } + + public void Stop() + { + if (PlaybackRange == null) + { + _currentTime = 0; + _eventIndex = 0; + } + else if (PlaybackRange != null) { _currentTime = PlaybackRange.StartTick; _eventIndex = 0; - _synthesizer.NoteOffAll(true); - _synthesizer.ResetPrograms(); - _synthesizer.ResetSynthControls(); - OnFinished(); } } diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/PlaybackRange.cs b/Source/AlphaTab/Audio/Synth/PlaybackRange.cs similarity index 91% rename from Source/AlphaTab/Audio/Synth/Synthesis/PlaybackRange.cs rename to Source/AlphaTab/Audio/Synth/PlaybackRange.cs index de00de2bb..d7ab2e984 100644 --- a/Source/AlphaTab/Audio/Synth/Synthesis/PlaybackRange.cs +++ b/Source/AlphaTab/Audio/Synth/PlaybackRange.cs @@ -1,4 +1,4 @@ -namespace AlphaTab.Audio.Synth.Synthesis +namespace AlphaTab.Audio.Synth { /// /// Represents a range of the song that should be played. diff --git a/Source/AlphaTab/Audio/Synth/Sf2/Chunks/Chunk.cs b/Source/AlphaTab/Audio/Synth/Sf2/Chunks/Chunk.cs deleted file mode 100644 index de6db2ce9..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/Chunks/Chunk.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace AlphaTab.Audio.Synth.Sf2.Chunks -{ - internal class Chunk - { - public string Id { get; private set; } - public int Size { get; private set; } - - public Chunk(string id, int size) - { - Id = id; - Size = size; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/Chunks/GeneratorChunk.cs b/Source/AlphaTab/Audio/Synth/Sf2/Chunks/GeneratorChunk.cs deleted file mode 100644 index 950c81da9..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/Chunks/GeneratorChunk.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using AlphaTab.IO; - -namespace AlphaTab.Audio.Synth.Sf2.Chunks -{ - internal class GeneratorChunk : Chunk - { - public Generator[] Generators { get; set; } - - public GeneratorChunk(string id, int size, IReadable input) : base(id, size) - { - if (size % 4 != 0) - { - throw new Exception("Invalid SoundFont. The presetzone chunk was invalid"); - } - - Generators = new Generator[(int)(size / 4.0 - 1)]; - for (var x = 0; x < Generators.Length; x++) - { - Generators[x] = new Generator(input); - } - - new Generator(input); // terminal record - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/Chunks/InstrumentChunk.cs b/Source/AlphaTab/Audio/Synth/Sf2/Chunks/InstrumentChunk.cs deleted file mode 100644 index f04918b3f..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/Chunks/InstrumentChunk.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using AlphaTab.IO; - -namespace AlphaTab.Audio.Synth.Sf2.Chunks -{ - internal class InstrumentChunk : Chunk - { - private RawInstrument[] _rawInstruments; - - public InstrumentChunk(string id, int size, IReadable input) - : base(id, size) - { - if (size % 22 != 0) - { - throw new Exception("Invalid SoundFont. The preset chunk was invalid."); - } - - _rawInstruments = new RawInstrument[(int)(size / 22.0)]; - RawInstrument lastInstrument = null; - for (var x = 0; x < _rawInstruments.Length; x++) - { - var i = new RawInstrument(); - i.Name = input.Read8BitStringLength(20); - i.StartInstrumentZoneIndex = input.ReadUInt16LE(); - if (lastInstrument != null) - { - lastInstrument.EndInstrumentZoneIndex = Platform.Platform.ToUInt16(i.StartInstrumentZoneIndex - 1); - } - - _rawInstruments[x] = i; - lastInstrument = i; - } - } - - public Instrument[] ToInstruments(Zone[] zones) - { - var inst = new Instrument[_rawInstruments.Length - 1]; - for (var x = 0; x < inst.Length; x++) - { - var rawInst = _rawInstruments[x]; - var i = new Instrument(); - i.Name = rawInst.Name; - i.Zones = new Zone[rawInst.EndInstrumentZoneIndex - rawInst.StartInstrumentZoneIndex + 1]; - Platform.Platform.ArrayCopy(zones, rawInst.StartInstrumentZoneIndex, i.Zones, 0, i.Zones.Length); - inst[x] = i; - } - - return inst; - } - } - - internal class RawInstrument - { - public string Name { get; set; } - public int StartInstrumentZoneIndex { get; set; } - public int EndInstrumentZoneIndex { get; set; } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/Chunks/ModulatorChunk.cs b/Source/AlphaTab/Audio/Synth/Sf2/Chunks/ModulatorChunk.cs deleted file mode 100644 index 17fe11dde..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/Chunks/ModulatorChunk.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using AlphaTab.IO; - -namespace AlphaTab.Audio.Synth.Sf2.Chunks -{ - internal class ModulatorChunk : Chunk - { - public Modulator[] Modulators { get; set; } - - public ModulatorChunk(string id, int size, IReadable input) - : base(id, size) - { - if (size % 10 != 0) - { - throw new Exception("Invalid SoundFont. The presetzone chunk was invalid."); - } - - Modulators = new Modulator[size / 10 - 1]; - for (var x = 0; x < Modulators.Length; x++) - { - Modulators[x] = new Modulator(input); - } - - new Modulator(input); - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/Chunks/PresetHeaderChunk.cs b/Source/AlphaTab/Audio/Synth/Sf2/Chunks/PresetHeaderChunk.cs deleted file mode 100644 index 44c2ab386..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/Chunks/PresetHeaderChunk.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using AlphaTab.IO; - -namespace AlphaTab.Audio.Synth.Sf2.Chunks -{ - internal class PresetHeaderChunk : Chunk - { - private readonly RawPreset[] _rawPresets; - - public PresetHeaderChunk(string id, int size, IReadable input) - : base(id, size) - { - if (size % 38 != 0) - { - throw new Exception("Invalid SoundFont. The preset chunk was invalid."); - } - - _rawPresets = new RawPreset[(int)(size / 38.0)]; - RawPreset lastPreset = null; - for (var x = 0; x < _rawPresets.Length; x++) - { - var p = new RawPreset(); - p.Name = input.Read8BitStringLength(20); - p.PatchNumber = input.ReadUInt16LE(); - p.BankNumber = input.ReadUInt16LE(); - p.StartPresetZoneIndex = input.ReadUInt16LE(); - p.Library = input.ReadInt32LE(); - p.Genre = input.ReadInt32LE(); - p.Morphology = input.ReadInt32LE(); - if (lastPreset != null) - { - lastPreset.EndPresetZoneIndex = Platform.Platform.ToUInt16(p.StartPresetZoneIndex - 1); - } - - _rawPresets[x] = p; - lastPreset = p; - } - } - - public PresetHeader[] ToPresets(Zone[] presetZones) - { - var presets = new PresetHeader[_rawPresets.Length - 1]; - for (var x = 0; x < presets.Length; x++) - { - var rawPreset = _rawPresets[x]; - var p = new PresetHeader(); - p.BankNumber = rawPreset.BankNumber; - p.Genre = rawPreset.Genre; - p.Library = rawPreset.Library; - p.Morphology = rawPreset.Morphology; - p.Name = rawPreset.Name; - p.PatchNumber = rawPreset.PatchNumber; - p.Zones = new Zone[rawPreset.EndPresetZoneIndex - rawPreset.StartPresetZoneIndex + 1]; - Platform.Platform.ArrayCopy(presetZones, rawPreset.StartPresetZoneIndex, p.Zones, 0, p.Zones.Length); - presets[x] = p; - } - - return presets; - } - } - - internal class RawPreset - { - public string Name { get; set; } - public int PatchNumber { get; set; } - public int BankNumber { get; set; } - public int StartPresetZoneIndex { get; set; } - public int EndPresetZoneIndex { get; set; } - public int Library { get; set; } - public int Genre { get; set; } - public int Morphology { get; set; } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/Chunks/SampleHeaderChunk.cs b/Source/AlphaTab/Audio/Synth/Sf2/Chunks/SampleHeaderChunk.cs deleted file mode 100644 index 353869112..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/Chunks/SampleHeaderChunk.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using AlphaTab.IO; - -namespace AlphaTab.Audio.Synth.Sf2.Chunks -{ - internal class SampleHeaderChunk : Chunk - { - public SampleHeader[] SampleHeaders { get; set; } - - public SampleHeaderChunk(string id, int size, IReadable input) - : base(id, size) - { - if (size % 46 != 0) - { - throw new Exception("Invalid SoundFont. The sample header chunk was invalid."); - } - - SampleHeaders = new SampleHeader[(int)(size / 46.0 - 1)]; - - for (var x = 0; x < SampleHeaders.Length; x++) - { - SampleHeaders[x] = new SampleHeader(input); - } - - new SampleHeader(input); //read terminal record - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/Chunks/ZoneChunk.cs b/Source/AlphaTab/Audio/Synth/Sf2/Chunks/ZoneChunk.cs deleted file mode 100644 index ce4ed2169..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/Chunks/ZoneChunk.cs +++ /dev/null @@ -1,64 +0,0 @@ -using AlphaTab.IO; - -namespace AlphaTab.Audio.Synth.Sf2.Chunks -{ - internal class ZoneChunk : Chunk - { - private RawZoneData[] _zoneData; - - public ZoneChunk(string id, int size, IReadable input) - : base(id, size) - { - _zoneData = new RawZoneData[(int)(size / 4.0)]; - - RawZoneData lastZone = null; - for (var x = 0; x < _zoneData.Length; x++) - { - var z = new RawZoneData(); - z.GeneratorIndex = input.ReadUInt16LE(); - z.ModulatorIndex = input.ReadUInt16LE(); - if (lastZone != null) - { - lastZone.GeneratorCount = Platform.Platform.ToUInt16(z.GeneratorIndex - lastZone.GeneratorIndex); - lastZone.ModulatorCount = Platform.Platform.ToUInt16(z.ModulatorIndex - lastZone.ModulatorIndex); - } - - _zoneData[x] = z; - lastZone = z; - } - } - - public Zone[] ToZones(Modulator[] modulators, Generator[] generators) - { - var zones = new Zone[_zoneData.Length - 1]; - for (var x = 0; x < zones.Length; x++) - { - var rawZone = _zoneData[x]; - var zone = new Zone(); - zone.Generators = new Generator[rawZone.GeneratorCount]; - Platform.Platform.ArrayCopy(generators, - rawZone.GeneratorIndex, - zone.Generators, - 0, - rawZone.GeneratorCount); - zone.Modulators = new Modulator[rawZone.ModulatorCount]; - Platform.Platform.ArrayCopy(modulators, - rawZone.ModulatorIndex, - zone.Modulators, - 0, - rawZone.ModulatorCount); - zones[x] = zone; - } - - return zones; - } - } - - internal class RawZoneData - { - public int GeneratorIndex { get; set; } - public int ModulatorIndex { get; set; } - public int GeneratorCount { get; set; } - public int ModulatorCount { get; set; } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/ControllerSourceEnum.cs b/Source/AlphaTab/Audio/Synth/Sf2/ControllerSourceEnum.cs deleted file mode 100644 index b757469eb..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/ControllerSourceEnum.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal enum ControllerSourceEnum - { - NoController = 0, - NoteOnVelocity = 2, - NoteOnKeyNumber = 3, - PolyPressure = 10, - ChannelPressure = 13, - PitchWheel = 14, - PitchWheelSensitivity = 16, - Link = 127 - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/DirectionEnum.cs b/Source/AlphaTab/Audio/Synth/Sf2/DirectionEnum.cs deleted file mode 100644 index e874f422e..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/DirectionEnum.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal enum DirectionEnum - { - MinToMax = 0, - MaxToMin = 1 - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/Generator.cs b/Source/AlphaTab/Audio/Synth/Sf2/Generator.cs deleted file mode 100644 index e08904e86..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/Generator.cs +++ /dev/null @@ -1,37 +0,0 @@ -using AlphaTab.IO; - -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal class Generator - { - private ushort _rawAmount; - - public GeneratorEnum GeneratorType { get; set; } - - public short AmountInt16 - { - get => Platform.Platform.ToInt16(_rawAmount); - set => _rawAmount = Platform.Platform.ToUInt16(value); - } - - public short LowByteAmount - { - get => Platform.Platform.ToUInt8(_rawAmount & 0x00FF); - set => _rawAmount = Platform.Platform.ToUInt16((_rawAmount & 0xFF00) + Platform.Platform.ToUInt8(value)); - } - - public short HighByteAmount - { - get => Platform.Platform.ToUInt8((_rawAmount & 0xFF00) >> 8); - set => - _rawAmount = - Platform.Platform.ToUInt16((_rawAmount & 0x00FF) + (Platform.Platform.ToUInt8(value) << 8)); - } - - public Generator(IReadable input) - { - GeneratorType = (GeneratorEnum)input.ReadUInt16LE(); - _rawAmount = input.ReadUInt16LE(); - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/GeneratorEnum.cs b/Source/AlphaTab/Audio/Synth/Sf2/GeneratorEnum.cs deleted file mode 100644 index c9bea3788..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/GeneratorEnum.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal enum GeneratorEnum - { - StartAddressOffset = 0, - EndAddressOffset = 1, - StartLoopAddressOffset = 2, - EndLoopAddressOffset = 3, - StartAddressCoarseOffset = 4, - ModulationLFOToPitch = 5, - VibratoLFOToPitch = 6, - ModulationEnvelopeToPitch = 7, - InitialFilterCutoffFrequency = 8, - InitialFilterQ = 9, - ModulationLFOToFilterCutoffFrequency = 10, - ModulationEnvelopeToFilterCutoffFrequency = 11, - EndAddressCoarseOffset = 12, - ModulationLFOToVolume = 13, - Unused1 = 14, - ChorusEffectsSend = 15, - ReverbEffectsSend = 16, - Pan = 17, - Unused2 = 18, - Unused3 = 19, - Unused4 = 20, - DelayModulationLFO = 21, - FrequencyModulationLFO = 22, - DelayVibratoLFO = 23, - FrequencyVibratoLFO = 24, - DelayModulationEnvelope = 25, - AttackModulationEnvelope = 26, - HoldModulationEnvelope = 27, - DecayModulationEnvelope = 28, - SustainModulationEnvelope = 29, - ReleaseModulationEnvelope = 30, - KeyNumberToModulationEnvelopeHold = 31, - KeyNumberToModulationEnvelopeDecay = 32, - DelayVolumeEnvelope = 33, - AttackVolumeEnvelope = 34, - HoldVolumeEnvelope = 35, - DecayVolumeEnvelope = 36, - SustainVolumeEnvelope = 37, - ReleaseVolumeEnvelope = 38, - KeyNumberToVolumeEnvelopeHold = 39, - KeyNumberToVolumeEnvelopeDecay = 40, - Instrument = 41, - Reserved1 = 42, - KeyRange = 43, - VelocityRange = 44, - StartLoopAddressCoarseOffset = 45, - KeyNumber = 46, - Velocity = 47, - InitialAttenuation = 48, - Reserved2 = 49, - EndLoopAddressCoarseOffset = 50, - CoarseTune = 51, - FineTune = 52, - SampleId = 53, - SampleModes = 54, - Reserved3 = 55, - ScaleTuning = 56, - ExclusiveClass = 57, - OverridingRootKey = 58, - Unused5 = 59, - UnusedEnd = 60 - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/Instrument.cs b/Source/AlphaTab/Audio/Synth/Sf2/Instrument.cs deleted file mode 100644 index 13a34f763..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/Instrument.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal class Instrument - { - public string Name { get; set; } - public Zone[] Zones { get; set; } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/Modulator.cs b/Source/AlphaTab/Audio/Synth/Sf2/Modulator.cs deleted file mode 100644 index a2a3bb7aa..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/Modulator.cs +++ /dev/null @@ -1,22 +0,0 @@ -using AlphaTab.IO; - -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal class Modulator - { - public ModulatorType SourceModulationData { get; } - public int DestinationGenerator { get; } - public short Amount { get; } - public ModulatorType SourceModulationAmount { get; } - public int SourceTransform { get; } - - public Modulator(IReadable input) - { - SourceModulationData = new ModulatorType(input); - DestinationGenerator = input.ReadUInt16LE(); - Amount = input.ReadInt16LE(); - SourceModulationAmount = new ModulatorType(input); - SourceTransform = input.ReadUInt16LE(); - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/ModulatorType.cs b/Source/AlphaTab/Audio/Synth/Sf2/ModulatorType.cs deleted file mode 100644 index 7d366ccd4..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/ModulatorType.cs +++ /dev/null @@ -1,25 +0,0 @@ -using AlphaTab.Audio.Synth.Util; -using AlphaTab.IO; -using AlphaTab.Platform; - -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal class ModulatorType - { - public PolarityEnum Polarity { get; set; } - public DirectionEnum Direction { get; set; } - public int SourceType { get; set; } - public bool IsMidiContinuousController { get; private set; } - - public ModulatorType(IReadable input) - { - var raw = input.ReadUInt16LE(); - - Polarity = (raw & 0x0200) == 0x0200 ? PolarityEnum.Bipolar : PolarityEnum.Unipolar; - Direction = (raw & 0x0100) == 0x0100 ? DirectionEnum.MaxToMin : DirectionEnum.MinToMax; - - IsMidiContinuousController = ((raw & 0x0080) == 0x0080); - SourceType = ((raw & (0xFC00)) >> 10); - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/PolarityEnum.cs b/Source/AlphaTab/Audio/Synth/Sf2/PolarityEnum.cs deleted file mode 100644 index a1918399d..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/PolarityEnum.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal enum PolarityEnum - { - Unipolar = 0, - Bipolar = 1 - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/PresetHeader.cs b/Source/AlphaTab/Audio/Synth/Sf2/PresetHeader.cs deleted file mode 100644 index a6209e8c5..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/PresetHeader.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal class PresetHeader - { - public string Name { get; set; } - public int PatchNumber { get; set; } - public int BankNumber { get; set; } - public int Library { get; set; } - public int Genre { get; set; } - public int Morphology { get; set; } - public Zone[] Zones { get; set; } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/SampleHeader.cs b/Source/AlphaTab/Audio/Synth/Sf2/SampleHeader.cs deleted file mode 100644 index 7b7da2911..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/SampleHeader.cs +++ /dev/null @@ -1,32 +0,0 @@ -using AlphaTab.IO; - -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal class SampleHeader - { - public string Name { get; set; } - public int Start { get; private set; } - public int End { get; private set; } - public int StartLoop { get; private set; } - public int EndLoop { get; private set; } - public int SampleRate { get; private set; } - public byte RootKey { get; private set; } - public short Tune { get; private set; } - public ushort SampleLink { get; private set; } - public SoundFontSampleLink SoundFontSampleLink { get; private set; } - - public SampleHeader(IReadable input) - { - Name = input.Read8BitStringLength(20); - Start = input.ReadInt32LE(); - End = input.ReadInt32LE(); - StartLoop = input.ReadInt32LE(); - EndLoop = input.ReadInt32LE(); - SampleRate = input.ReadInt32LE(); - RootKey = (byte)input.ReadByte(); - Tune = Platform.Platform.ToInt16(input.ReadByte()); - SampleLink = input.ReadUInt16LE(); - SoundFontSampleLink = (SoundFontSampleLink)input.ReadUInt16LE(); - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/Sf2Region.cs b/Source/AlphaTab/Audio/Synth/Sf2/Sf2Region.cs deleted file mode 100644 index 5e0dc77c3..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/Sf2Region.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal class Sf2Region - { - public short[] Generators { get; set; } - - public Sf2Region() - { - Generators = new short[61]; - } - - public void ApplyDefaultValues() - { - Generators[(int)GeneratorEnum.StartAddressOffset] = 0; - Generators[(int)GeneratorEnum.EndAddressOffset] = 0; - Generators[(int)GeneratorEnum.StartLoopAddressOffset] = 0; - Generators[(int)GeneratorEnum.EndLoopAddressOffset] = 0; - Generators[(int)GeneratorEnum.StartAddressCoarseOffset] = 0; - Generators[(int)GeneratorEnum.ModulationLFOToPitch] = 0; - Generators[(int)GeneratorEnum.VibratoLFOToPitch] = 0; - Generators[(int)GeneratorEnum.ModulationEnvelopeToPitch] = 0; - Generators[(int)GeneratorEnum.InitialFilterCutoffFrequency] = 13500; - Generators[(int)GeneratorEnum.InitialFilterQ] = 0; - Generators[(int)GeneratorEnum.ModulationLFOToFilterCutoffFrequency] = 0; - Generators[(int)GeneratorEnum.ModulationEnvelopeToFilterCutoffFrequency] = 0; - Generators[(int)GeneratorEnum.EndAddressCoarseOffset] = 0; - Generators[(int)GeneratorEnum.ModulationLFOToVolume] = 0; - Generators[(int)GeneratorEnum.ChorusEffectsSend] = 0; - Generators[(int)GeneratorEnum.ReverbEffectsSend] = 0; - Generators[(int)GeneratorEnum.Pan] = 0; - Generators[(int)GeneratorEnum.DelayModulationLFO] = -12000; - Generators[(int)GeneratorEnum.FrequencyModulationLFO] = 0; - Generators[(int)GeneratorEnum.DelayVibratoLFO] = -12000; - Generators[(int)GeneratorEnum.FrequencyVibratoLFO] = 0; - Generators[(int)GeneratorEnum.DelayModulationEnvelope] = -12000; - Generators[(int)GeneratorEnum.AttackModulationEnvelope] = -12000; - Generators[(int)GeneratorEnum.HoldModulationEnvelope] = -12000; - Generators[(int)GeneratorEnum.DecayModulationEnvelope] = -12000; - Generators[(int)GeneratorEnum.SustainModulationEnvelope] = 0; - Generators[(int)GeneratorEnum.ReleaseModulationEnvelope] = -12000; - Generators[(int)GeneratorEnum.KeyNumberToModulationEnvelopeHold] = 0; - Generators[(int)GeneratorEnum.KeyNumberToModulationEnvelopeDecay] = 0; - Generators[(int)GeneratorEnum.DelayVolumeEnvelope] = -12000; - Generators[(int)GeneratorEnum.AttackVolumeEnvelope] = -12000; - Generators[(int)GeneratorEnum.HoldVolumeEnvelope] = -12000; - Generators[(int)GeneratorEnum.DecayVolumeEnvelope] = -12000; - Generators[(int)GeneratorEnum.SustainVolumeEnvelope] = 0; - Generators[(int)GeneratorEnum.ReleaseVolumeEnvelope] = -12000; - Generators[(int)GeneratorEnum.KeyNumberToVolumeEnvelopeHold] = 0; - Generators[(int)GeneratorEnum.KeyNumberToVolumeEnvelopeDecay] = 0; - Generators[(int)GeneratorEnum.KeyRange] = 0x7F00; - Generators[(int)GeneratorEnum.VelocityRange] = 0x7F00; - Generators[(int)GeneratorEnum.StartLoopAddressCoarseOffset] = 0; - Generators[(int)GeneratorEnum.KeyNumber] = -1; - Generators[(int)GeneratorEnum.Velocity] = -1; - Generators[(int)GeneratorEnum.InitialAttenuation] = 0; - Generators[(int)GeneratorEnum.EndLoopAddressCoarseOffset] = 0; - Generators[(int)GeneratorEnum.CoarseTune] = 0; - Generators[(int)GeneratorEnum.FineTune] = 0; - Generators[(int)GeneratorEnum.SampleModes] = 0; - Generators[(int)GeneratorEnum.ScaleTuning] = 100; - Generators[(int)GeneratorEnum.ExclusiveClass] = 0; - Generators[(int)GeneratorEnum.OverridingRootKey] = -1; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/SoundFont.cs b/Source/AlphaTab/Audio/Synth/Sf2/SoundFont.cs deleted file mode 100644 index df89a2f13..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/SoundFont.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using AlphaTab.IO; -using AlphaTab.Util; - -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal class SoundFont - { - public SoundFontInfo Info { get; set; } - public SoundFontSampleData SampleData { get; set; } - public SoundFontPresets Presets { get; set; } - - public void Load(IReadable input) - { - var id = input.Read8BitChars(4); - input.ReadInt32LE(); - if (id.ToLower() != "riff") - { - throw new Exception("Invalid soundfont. Could not find RIFF header."); - } - - id = input.Read8BitChars(4); - if (id.ToLower() != "sfbk") - { - throw new Exception("Invalid soundfont. Riff type is invalid."); - } - - Logger.Debug("SF2", "Reading info chunk"); - Info = new SoundFontInfo(input); - Logger.Debug("SF2", "Reading sampledata chunk"); - SampleData = new SoundFontSampleData(input); - Logger.Debug("SF2", "Reading preset chunk"); - Presets = new SoundFontPresets(input); - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/SoundFontInfo.cs b/Source/AlphaTab/Audio/Synth/Sf2/SoundFontInfo.cs deleted file mode 100644 index 574d045ac..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/SoundFontInfo.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using AlphaTab.IO; - -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal class SoundFontInfo - { - public short RomVersionMajor { get; set; } - public short RomVersionMinor { get; set; } - public short SfVersionMajor { get; set; } - public short SfVersionMinor { get; set; } - public string SoundEngine { get; set; } - public string BankName { get; set; } - public string DataRom { get; set; } - public string CreationDate { get; set; } - public string Author { get; set; } - public string TargetProduct { get; set; } - public string Copyright { get; set; } - public string Comments { get; set; } - public string Tools { get; set; } - - public SoundFontInfo(IReadable input) - { - Tools = ""; - Comments = ""; - Copyright = ""; - TargetProduct = ""; - Author = ""; - DataRom = ""; - CreationDate = ""; - BankName = ""; - SoundEngine = ""; - var id = input.Read8BitChars(4); - var size = input.ReadInt32LE(); - if (id.ToLower() != "list") - { - throw new Exception("Invalid soundfont. Could not find INFO LIST chunk."); - } - - var readTo = input.Position + size; - id = input.Read8BitChars(4); - if (id.ToLower() != "info") - { - throw new Exception("Invalid soundfont. The LIST chunk is not of type INFO."); - } - - while (input.Position < readTo) - { - id = input.Read8BitChars(4); - size = input.ReadInt32LE(); - switch (id.ToLower()) - { - case "ifil": - SfVersionMajor = input.ReadInt16LE(); - SfVersionMinor = input.ReadInt16LE(); - break; - case "isng": - SoundEngine = input.Read8BitStringLength(size); - break; - case "inam": - BankName = input.Read8BitStringLength(size); - break; - case "irom": - DataRom = input.Read8BitStringLength(size); - break; - case "iver": - RomVersionMajor = input.ReadInt16LE(); - RomVersionMinor = input.ReadInt16LE(); - break; - case "icrd": - CreationDate = input.Read8BitStringLength(size); - break; - case "ieng": - Author = input.Read8BitStringLength(size); - break; - case "iprd": - TargetProduct = input.Read8BitStringLength(size); - break; - case "icop": - Copyright = input.Read8BitStringLength(size); - break; - case "icmt": - Comments = input.Read8BitStringLength(size); - break; - case "isft": - Tools = input.Read8BitStringLength(size); - break; - default: - throw new Exception("Invalid soundfont. The Chunk: " + id + " was not expected."); - } - } - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/SoundFontPresets.cs b/Source/AlphaTab/Audio/Synth/Sf2/SoundFontPresets.cs deleted file mode 100644 index 6430d0483..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/SoundFontPresets.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Sf2.Chunks; -using AlphaTab.IO; - -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal class SoundFontPresets - { - public SampleHeader[] SampleHeaders { get; private set; } - public PresetHeader[] PresetHeaders { get; private set; } - public Instrument[] Instruments { get; private set; } - - public SoundFontPresets(IReadable input) - { - var id = input.Read8BitChars(4); - var size = input.ReadInt32LE(); - if (id.ToLower() != "list") - { - throw new Exception("Invalid soundfont. Could not find pdta LIST chunk."); - } - - var readTo = input.Position + size; - id = input.Read8BitChars(4); - if (id.ToLower() != "pdta") - { - throw new Exception("Invalid soundfont. The LIST chunk is not of type pdta."); - } - - Modulator[] presetModulators = null; - Generator[] presetGenerators = null; - Modulator[] instrumentModulators = null; - Generator[] instrumentGenerators = null; - - ZoneChunk pbag = null; - ZoneChunk ibag = null; - PresetHeaderChunk phdr = null; - InstrumentChunk inst = null; - - while (input.Position < readTo) - { - id = input.Read8BitChars(4); - size = input.ReadInt32LE(); - switch (id.ToLower()) - { - case "phdr": - phdr = new PresetHeaderChunk(id, size, input); - break; - case "pbag": - pbag = new ZoneChunk(id, size, input); - break; - case "pmod": - presetModulators = new ModulatorChunk(id, size, input).Modulators; - break; - case "pgen": - presetGenerators = new GeneratorChunk(id, size, input).Generators; - break; - case "inst": - inst = new InstrumentChunk(id, size, input); - break; - case "ibag": - ibag = new ZoneChunk(id, size, input); - break; - case "imod": - instrumentModulators = new ModulatorChunk(id, size, input).Modulators; - break; - case "igen": - instrumentGenerators = new GeneratorChunk(id, size, input).Generators; - break; - case "shdr": - SampleHeaders = new SampleHeaderChunk(id, size, input).SampleHeaders; - break; - default: - throw new Exception("Invalid soundfont. Unrecognized sub chunk: " + id); - } - } - - var pZones = pbag.ToZones(presetModulators, presetGenerators); - PresetHeaders = phdr.ToPresets(pZones); - var iZones = ibag.ToZones(instrumentModulators, instrumentGenerators); - Instruments = inst.ToInstruments(iZones); - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/SoundFontSampleData.cs b/Source/AlphaTab/Audio/Synth/Sf2/SoundFontSampleData.cs deleted file mode 100644 index 923d8d967..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/SoundFontSampleData.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using AlphaTab.IO; - -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal class SoundFontSampleData - { - public int BitsPerSample { get; set; } - public byte[] SampleData { get; set; } - - public SoundFontSampleData(IReadable input) - { - var id = input.Read8BitChars(4); - var size = input.ReadInt32LE(); - if (id.ToLower() != "list") - { - throw new Exception("Invalid soundfont. Could not find sdta LIST chunk."); - } - - var readTo = input.Position + size; - id = input.Read8BitChars(4); - if (id.ToLower() != "sdta") - { - throw new Exception("Invalid soundfont. The LIST chunk is not of type sdta."); - } - - BitsPerSample = 0; - byte[] rawSampleData = null; - while (input.Position < readTo) - { - var subId = input.Read8BitChars(4); - size = input.ReadInt32LE(); - switch (subId.ToLower()) - { - case "smpl": - BitsPerSample = 16; - rawSampleData = input.ReadByteArray(size); - break; - case "sm24": - if (rawSampleData == null || size != Math.Ceiling(SampleData.Length / 2.0)) - { - //ignore this chunk if wrong size or if it comes first - input.Skip(size); - } - else - { - BitsPerSample = 24; - for (var x = 0; x < SampleData.Length; x++) - { - var b = new byte[3]; - b[0] = (byte)input.ReadByte(); - b[1] = rawSampleData[2 * x]; - b[2] = rawSampleData[2 * x + 1]; - } - } - - if (size % 2 == 1) - { - if (input.ReadByte() != 0) - { - input.Position--; - } - } - - break; - default: - throw new Exception("Invalid soundfont. Unknown chunk id: " + subId + "."); - } - } - - if (BitsPerSample == 16) - { - SampleData = rawSampleData; - } - else if (BitsPerSample != 24) - { - throw new Exception("Only 16 and 24 bit samples are supported."); - } - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/SoundFontSampleLink.cs b/Source/AlphaTab/Audio/Synth/Sf2/SoundFontSampleLink.cs deleted file mode 100644 index 2fa1764f7..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/SoundFontSampleLink.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace AlphaTab.Audio.Synth.Sf2 -{ - [Flags] - internal enum SoundFontSampleLink - { - MonoSample = 1, - RightSample = 2, - LeftSample = 4, - LinkedSample = 8, - OggVobis = 0x10, - RomMonoSample = 0x8001, - RomRightSample = 0x8002, - RomLeftSample = 0x8004, - RomLinkedSample = 0x8008 - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/SourceTypeEnum.cs b/Source/AlphaTab/Audio/Synth/Sf2/SourceTypeEnum.cs deleted file mode 100644 index f0fe883f6..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/SourceTypeEnum.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal enum SourceTypeEnum - { - Linear = 0, - Concave = 1, - Convex = 2, - Switch = 3 - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/TransformEnum.cs b/Source/AlphaTab/Audio/Synth/Sf2/TransformEnum.cs deleted file mode 100644 index 0f4a0585f..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/TransformEnum.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal enum TransformEnum - { - Linear = 0, - AbsoluteValue = 2 - } -} diff --git a/Source/AlphaTab/Audio/Synth/Sf2/Zone.cs b/Source/AlphaTab/Audio/Synth/Sf2/Zone.cs deleted file mode 100644 index 7545d21ba..000000000 --- a/Source/AlphaTab/Audio/Synth/Sf2/Zone.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace AlphaTab.Audio.Synth.Sf2 -{ - internal class Zone - { - public Modulator[] Modulators { get; set; } - public Generator[] Generators { get; set; } - } -} diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/Hydra.cs b/Source/AlphaTab/Audio/Synth/SoundFont/Hydra.cs new file mode 100644 index 000000000..8082f39a7 --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/SoundFont/Hydra.cs @@ -0,0 +1,196 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using System; +using AlphaTab.Collections; +using AlphaTab.IO; + +namespace AlphaTab.Audio.Synth.SoundFont +{ + internal class Hydra + { + public FastList Phdrs { get; set; } + public FastList Pbags { get; set; } + public FastList Pmods { get; set; } + public FastList Pgens { get; set; } + public FastList Insts { get; set; } + public FastList Ibags { get; set; } + public FastList Imods { get; set; } + public FastList Igens { get; set; } + public FastList SHdrs { get; set; } + public float[] FontSamples { get; set; } + + public Hydra() + { + Phdrs = new FastList(); + Pbags = new FastList(); + Pmods = new FastList(); + Pgens = new FastList(); + Insts = new FastList(); + Ibags = new FastList(); + Imods = new FastList(); + Igens = new FastList(); + SHdrs = new FastList(); + } + + public void Load(IReadable readable) + { + var chunkHead = new RiffChunk(); + var chunkFastList = new RiffChunk(); + + if (!RiffChunk.Load(null, chunkHead, readable) || chunkHead.Id != "sfbk") + { + return; + } + + while (RiffChunk.Load(chunkHead, chunkFastList, readable)) + { + var chunk = new RiffChunk(); + if (chunkFastList.Id == "pdta") + { + while (RiffChunk.Load(chunkFastList, chunk, readable)) + { + switch (chunk.Id) + { + case "phdr": + for (uint i = 0, count = chunk.Size / HydraPhdr.SizeInFile; i < count; i++) + { + Phdrs.Add(HydraPhdr.Load(readable)); + } + + break; + case "pbag": + for (uint i = 0, count = chunk.Size / HydraPbag.SizeInFile; i < count; i++) + { + Pbags.Add(HydraPbag.Load(readable)); + } + + break; + case "pmod": + for (uint i = 0, count = chunk.Size / HydraPmod.SizeInFile; i < count; i++) + { + Pmods.Add(HydraPmod.Load(readable)); + } + + break; + case "pgen": + for (uint i = 0, count = chunk.Size / HydraPgen.SizeInFile; i < count; i++) + { + Pgens.Add(HydraPgen.Load(readable)); + } + + break; + case "inst": + for (uint i = 0, count = chunk.Size / HydraInst.SizeInFile; i < count; i++) + { + Insts.Add(HydraInst.Load(readable)); + } + + break; + case "ibag": + for (uint i = 0, count = chunk.Size / HydraIbag.SizeInFile; i < count; i++) + { + Ibags.Add(HydraIbag.Load(readable)); + } + + break; + case "imod": + for (uint i = 0, count = chunk.Size / HydraImod.SizeInFile; i < count; i++) + { + Imods.Add(HydraImod.Load(readable)); + } + + break; + case "igen": + for (uint i = 0, count = chunk.Size / HydraIgen.SizeInFile; i < count; i++) + { + Igens.Add(HydraIgen.Load(readable)); + } + + break; + case "shdr": + for (uint i = 0, count = chunk.Size / HydraShdr.SizeInFile; i < count; i++) + { + SHdrs.Add(HydraShdr.Load(readable)); + } + + break; + default: + readable.Position += (int)chunk.Size; + break; + } + } + } + else if (chunkFastList.Id == "sdta") + { + while (RiffChunk.Load(chunkFastList, chunk, readable)) + { + switch (chunk.Id) + { + case "smpl": + FontSamples = LoadSamples(chunk, readable); + break; + default: + readable.Position += (int)chunk.Size; + break; + } + } + } + else + { + readable.Position += (int)chunkFastList.Size; + } + } + } + + private static float[] LoadSamples(RiffChunk chunk, IReadable reader) + { + var samplesLeft = (int)(chunk.Size / 2); + var samples = new float[samplesLeft]; + var samplesPos = 0; + + var sampleBuffer = new byte[2048]; + var testBuffer = new short[sampleBuffer.Length / 2]; + while (samplesLeft > 0) + { + var samplesToRead = (int)Math.Min(samplesLeft, sampleBuffer.Length / 2); + reader.Read(sampleBuffer, 0, samplesToRead * 2); + for (var i = 0; i < samplesToRead; i++) + { + testBuffer[i] = Platform.Platform.ToInt16((sampleBuffer[(i * 2) + 1] << 8) | sampleBuffer[(i * 2)]); + samples[samplesPos + i] = testBuffer[i] / 32767f; + } + + samplesLeft -= samplesToRead; + samplesPos += samplesToRead; + } + + return samples; + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraGenAmount.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraGenAmount.cs new file mode 100644 index 000000000..3d4b21f73 --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraGenAmount.cs @@ -0,0 +1,56 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using AlphaTab.IO; + +namespace AlphaTab.Audio.Synth.SoundFont +{ + internal class HydraGenAmount + { + public ushort WordAmount { get; set; } + public short ShortAmount => (short)WordAmount; + public byte LowByteAmount + { + get => (byte)(WordAmount & 0x00FF); + set => WordAmount = (ushort)((WordAmount & 0xFF00) | value); + } + + public byte HighByteAmount + { + get => (byte)((WordAmount & 0xFF00) >> 8); + set => WordAmount = (ushort)((value & 0xFF00) | (WordAmount & 0xFF)); + } + + public static HydraGenAmount Load(IReadable reader) + { + var genAmount = new HydraGenAmount(); + genAmount.WordAmount = reader.ReadUInt16LE(); + return genAmount; + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraIbag.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraIbag.cs new file mode 100644 index 000000000..7b7de6aa7 --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraIbag.cs @@ -0,0 +1,48 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using AlphaTab.IO; + +namespace AlphaTab.Audio.Synth.SoundFont +{ + internal class HydraIbag + { + public const int SizeInFile = 4; + + public ushort InstGenNdx { get; set; } + public ushort InstModNdx { get; set; } + + public static HydraIbag Load(IReadable reader) + { + var ibag = new HydraIbag(); + ibag.InstGenNdx = reader.ReadUInt16LE(); + ibag.InstModNdx = reader.ReadUInt16LE(); + return ibag; + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraIgen.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraIgen.cs new file mode 100644 index 000000000..d2d1de0b0 --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraIgen.cs @@ -0,0 +1,48 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using AlphaTab.IO; + +namespace AlphaTab.Audio.Synth.SoundFont +{ + internal class HydraIgen + { + public const int SizeInFile = 4; + + public ushort GenOper { get; set; } + public HydraGenAmount GenAmount { get; set; } + + public static HydraIgen Load(IReadable reader) + { + var igen = new HydraIgen(); + igen.GenOper = reader.ReadUInt16LE(); + igen.GenAmount = HydraGenAmount.Load(reader); + return igen; + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraImod.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraImod.cs new file mode 100644 index 000000000..9f52086cc --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraImod.cs @@ -0,0 +1,54 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using AlphaTab.IO; + +namespace AlphaTab.Audio.Synth.SoundFont +{ + internal class HydraImod + { + public const int SizeInFile = 10; + + public ushort ModSrcOper { get; set; } + public ushort ModDestOper { get; set; } + public short ModAmount { get; set; } + public ushort ModAmtSrcOper { get; set; } + public ushort ModTransOper { get; set; } + + public static HydraImod Load(IReadable reader) + { + var imod = new HydraImod(); + imod.ModSrcOper = reader.ReadUInt16LE(); + imod.ModDestOper = reader.ReadUInt16LE(); + imod.ModAmount = reader.ReadInt16LE(); + imod.ModAmtSrcOper = reader.ReadUInt16LE(); + imod.ModTransOper = reader.ReadUInt16LE(); + return imod; + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraInst.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraInst.cs new file mode 100644 index 000000000..eb46f1f8c --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraInst.cs @@ -0,0 +1,48 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using AlphaTab.IO; + +namespace AlphaTab.Audio.Synth.SoundFont +{ + internal class HydraInst + { + public const int SizeInFile = 22; + + public string InstName { get; set; } + public ushort InstBagNdx { get; set; } + + public static HydraInst Load(IReadable reader) + { + var inst = new HydraInst(); + inst.InstName = reader.Read8BitStringLength(20); + inst.InstBagNdx = reader.ReadUInt16LE(); + return inst; + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraPbag.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPbag.cs new file mode 100644 index 000000000..459fb23e0 --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPbag.cs @@ -0,0 +1,48 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using AlphaTab.IO; + +namespace AlphaTab.Audio.Synth.SoundFont +{ + internal class HydraPbag + { + public const int SizeInFile = 4; + + public ushort GenNdx { get; set; } + public ushort ModNdx { get; set; } + + public static HydraPbag Load(IReadable reader) + { + var pbag = new HydraPbag(); + pbag.GenNdx = reader.ReadUInt16LE(); + pbag.ModNdx = reader.ReadUInt16LE(); + return pbag; + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraPgen.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPgen.cs new file mode 100644 index 000000000..c2e914596 --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPgen.cs @@ -0,0 +1,53 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using AlphaTab.IO; + +namespace AlphaTab.Audio.Synth.SoundFont +{ + internal class HydraPgen + { + public const int SizeInFile = 4; + + public const int GenInstrument = 41; + public const int GenKeyRange = 43; + public const int GenVelRange = 44; + public const int GenSampleId = 53; + + public ushort GenOper { get; set; } + public HydraGenAmount GenAmount { get; set; } + + public static HydraPgen Load(IReadable reader) + { + var pgen = new HydraPgen(); + pgen.GenOper = reader.ReadUInt16LE(); + pgen.GenAmount = HydraGenAmount.Load(reader); + return pgen; + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraPhdr.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPhdr.cs new file mode 100644 index 000000000..ae6d7fb74 --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPhdr.cs @@ -0,0 +1,60 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using AlphaTab.IO; + +namespace AlphaTab.Audio.Synth.SoundFont +{ + internal class HydraPhdr + { + public const int SizeInFile = 38; + + public string PresetName { get; set; } + public ushort Preset { get; set; } + public ushort Bank { get; set; } + public ushort PresetBagNdx { get; set; } + public uint Library { get; set; } + public uint Genre { get; set; } + public uint Morphology { get; set; } + + public static HydraPhdr Load(IReadable reader) + { + HydraPhdr phdr = new HydraPhdr(); + + phdr.PresetName = reader.Read8BitStringLength(20); + phdr.Preset = reader.ReadUInt16LE(); + phdr.Bank = reader.ReadUInt16LE(); + phdr.PresetBagNdx = reader.ReadUInt16LE(); + phdr.Library = reader.ReadUInt32LE(); + phdr.Genre = reader.ReadUInt32LE(); + phdr.Morphology = reader.ReadUInt32LE(); + + return phdr; + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraPmod.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPmod.cs new file mode 100644 index 000000000..92a02171c --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPmod.cs @@ -0,0 +1,55 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */using AlphaTab.IO; + +namespace AlphaTab.Audio.Synth.SoundFont +{ + internal class HydraPmod + { + public const int SizeInFile = 10; + + public ushort ModSrcOper { get; set; } + public ushort ModDestOper { get; set; } + public ushort ModAmount { get; set; } + public ushort ModAmtSrcOper { get; set; } + public ushort ModTransOper { get; set; } + + public static HydraPmod Load(IReadable reader) + { + var pmod = new HydraPmod(); + + pmod.ModSrcOper = reader.ReadUInt16LE(); + pmod.ModDestOper = reader.ReadUInt16LE(); + pmod.ModAmount = reader.ReadUInt16LE(); + pmod.ModAmtSrcOper = reader.ReadUInt16LE(); + pmod.ModTransOper = reader.ReadUInt16LE(); + + return pmod; + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraShdr.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraShdr.cs new file mode 100644 index 000000000..4ff1a0b8f --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraShdr.cs @@ -0,0 +1,65 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using AlphaTab.IO; +using AlphaTab.Platform; + +namespace AlphaTab.Audio.Synth.SoundFont +{ + internal class HydraShdr + { + public const int SizeInFile = 46; + + public string SampleName { get; set; } + public uint Start { get; set; } + public uint End { get; set; } + public uint StartLoop { get; set; } + public uint EndLoop { get; set; } + public uint SampleRate { get; set; } + public byte OriginalPitch { get; set; } + public sbyte PitchCorrection { get; set; } + public ushort SampleLink { get; set; } + public ushort SampleType { get; set; } + + public static HydraShdr Load(IReadable reader) + { + var shdr = new HydraShdr(); + shdr.SampleName = reader.Read8BitStringLength(20); + shdr.Start = reader.ReadUInt32LE(); + shdr.End = reader.ReadUInt32LE(); + shdr.StartLoop = reader.ReadUInt32LE(); + shdr.EndLoop = reader.ReadUInt32LE(); + shdr.SampleRate = reader.ReadUInt32LE(); + shdr.OriginalPitch = (byte)reader.ReadByte(); + shdr.PitchCorrection = reader.ReadSignedByte(); + shdr.SampleLink = reader.ReadUInt16LE(); + shdr.SampleType = reader.ReadUInt16LE(); + return shdr; + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/RiffChunk.cs b/Source/AlphaTab/Audio/Synth/SoundFont/RiffChunk.cs new file mode 100644 index 000000000..8f396e07b --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/SoundFont/RiffChunk.cs @@ -0,0 +1,94 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */using AlphaTab.IO; + +namespace AlphaTab.Audio.Synth.SoundFont +{ + internal class RiffChunk + { + public string Id { get; set; } + public uint Size { get; set; } + + public static bool Load(RiffChunk parent, RiffChunk chunk, IReadable stream) + { + if (parent != null && RiffChunk.HeaderSize > parent.Size) + { + return false; + } + + if (stream.Position + HeaderSize >= stream.Length) + { + return false; + } + + chunk.Id = stream.Read8BitStringLength(4); + if (chunk.Id[0] <= ' ' || chunk.Id[0] >= 'z') + { + return false; + } + chunk.Size = stream.ReadUInt32LE(); + + if (parent != null && HeaderSize + chunk.Size > parent.Size) + { + return false; + } + + if (parent != null) + { + parent.Size -= HeaderSize + chunk.Size; + } + + var isRiff = chunk.Id == "RIFF"; + var isList = chunk.Id == "LIST"; + + if (isRiff && parent != null) + { + // not allowed + return false; + } + + if (!isRiff && !isList) + { + // custom type without sub type + return true; + } + + // for lists unwrap the list type + chunk.Id = stream.Read8BitStringLength(4); + if (chunk.Id[0] <= ' ' || chunk.Id[0] >= 'z') + { + return false; + } + chunk.Size -= 4; + + return true; + } + + public const int HeaderSize = 4 /*FourCC*/ + 4 /*Size*/; + } +} diff --git a/Source/AlphaTab/Audio/Synth/SynthEvent.cs b/Source/AlphaTab/Audio/Synth/SynthEvent.cs new file mode 100644 index 000000000..accd33ce7 --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/SynthEvent.cs @@ -0,0 +1,25 @@ +using AlphaTab.Audio.Synth.Midi.Event; + +namespace AlphaTab.Audio.Synth.Synthesis +{ + internal class SynthEvent + { + public int EventIndex { get; set; } + public MidiEvent Event { get; set; } + public bool IsMetronome { get; set; } + public double Time { get; set; } + + public SynthEvent(int eventIndex, MidiEvent e) + { + EventIndex = eventIndex; + Event = e; + } + + public static SynthEvent NewMetronomeEvent(int eventIndex, int metronomeLength) + { + var x = new SynthEvent(eventIndex, null); + x.IsMetronome = true; + return x; + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/CCValue.cs b/Source/AlphaTab/Audio/Synth/Synthesis/CCValue.cs deleted file mode 100644 index 2e3f78197..000000000 --- a/Source/AlphaTab/Audio/Synth/Synthesis/CCValue.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace AlphaTab.Audio.Synth.Synthesis -{ - internal class CCValue - { - private byte _coarseValue; - private byte _fineValue; - private short _combined; - - public byte Coarse - { - get => _coarseValue; - set - { - _coarseValue = value; - UpdateCombined(); - } - } - - public byte Fine - { - get => _fineValue; - set - { - _fineValue = value; - UpdateCombined(); - } - } - - public short Combined - { - get => _combined; - set - { - _combined = value; - UpdateCoarseFinePair(); - } - } - - public CCValue(byte coarse, byte fine) - { - _coarseValue = coarse; - _fineValue = fine; - _combined = 0; - UpdateCombined(); - } - - public CCValue(short combined) - { - _coarseValue = 0; - _fineValue = 0; - _combined = combined; - UpdateCoarseFinePair(); - } - - private void UpdateCombined() - { - _combined = (short)((_coarseValue << 7) | _fineValue); - } - - private void UpdateCoarseFinePair() - { - _coarseValue = (byte)(_combined >> 7); - _fineValue = (byte)(_combined & 0x7F); - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Channel.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Channel.cs new file mode 100644 index 000000000..8c0a99e4d --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/Synthesis/Channel.cs @@ -0,0 +1,50 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace AlphaTab.Audio.Synth.Synthesis +{ + internal class Channel + { + public ushort PresetIndex { get; set; } + public ushort Bank { get; set; } + public ushort PitchWheel { get; set; } + public ushort MidiPan { get; set; } + public ushort MidiVolume { get; set; } + public ushort MidiExpression { get; set; } + public ushort MidiRpn { get; set; } + public ushort MidiData { get; set; } + public float PanOffset { get; set; } + public float GainDb { get; set; } + public float PitchRange { get; set; } + public float Tuning { get; set; } + + public float MixVolume { get; set; } + public bool Mute { get; set; } + public bool Solo { get; set; } + } +} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Channels.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Channels.cs new file mode 100644 index 000000000..46b308b0f --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/Synthesis/Channels.cs @@ -0,0 +1,75 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using System; +using AlphaTab.Collections; + +namespace AlphaTab.Audio.Synth.Synthesis +{ + internal class Channels + { + public int ActiveChannel { get; set; } + public FastList ChannelList { get; set; } + + public Channels() + { + ChannelList = new FastList(); + } + + public void SetupVoice(TinySoundFont tinySoundFont, Voice voice) + { + var c = ChannelList[ActiveChannel]; + var newpan = voice.Region.Pan + c.PanOffset; + voice.PlayingChannel = ActiveChannel; + voice.MixVolume = c.MixVolume; + voice.NoteGainDb += c.GainDb; + voice.CalcPitchRatio( + (c.PitchWheel == 8192 + ? c.Tuning + : ((c.PitchWheel / 16383.0f * c.PitchRange * 2.0f) - c.PitchRange + c.Tuning)), + tinySoundFont.OutSampleRate + ); + + if (newpan <= -0.5f) + { + voice.PanFactorLeft = 1.0f; + voice.PanFactorRight = 0.0f; + } + else if (newpan >= 0.5f) + { + voice.PanFactorLeft = 0.0f; + voice.PanFactorRight = 1.0f; + } + else + { + voice.PanFactorLeft = (float)Math.Sqrt(0.5f - newpan); + voice.PanFactorRight = (float)Math.Sqrt(0.5f + newpan); + } + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Envelope.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Envelope.cs new file mode 100644 index 000000000..3b9a3bbc1 --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/Synthesis/Envelope.cs @@ -0,0 +1,108 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +using AlphaTab.Audio.Synth.Util; + +namespace AlphaTab.Audio.Synth.Synthesis +{ + internal class Envelope + { + public float Delay { get; set; } + public float Attack { get; set; } + public float Hold { get; set; } + public float Decay { get; set; } + public float Sustain { get; set; } + public float Release { get; set; } + public float KeynumToHold { get; set; } + public float KeynumToDecay { get; set; } + + public Envelope() + { + } + + public Envelope(Envelope other) + { + Delay = other.Delay; + Attack = other.Attack; + Hold = other.Hold; + Decay = other.Decay; + Sustain = other.Sustain; + Release = other.Release; + KeynumToHold = other.KeynumToHold; + KeynumToDecay = other.KeynumToDecay; + } + + public void Clear() + { + Delay = 0; + Attack = 0; + Hold = 0; + Decay = 0; + Sustain = 0; + Release = 0; + KeynumToHold = 0; + KeynumToDecay = 0; + } + + public void EnvToSecs(bool sustainIsGain) + { + // EG times need to be converted from timecents to seconds. + // Pin very short EG segments. Timecents don't get to zero, and our EG is + // happier with zero values. + Delay = (Delay < -11950.0f ? 0.0f : SynthHelper.Timecents2Secs(Delay)); + Attack = (Attack < -11950.0f ? 0.0f : SynthHelper.Timecents2Secs(Attack)); + Release = (Release < -11950.0f ? 0.0f : SynthHelper.Timecents2Secs(Release)); + + // If we have dynamic hold or decay times depending on key number we need + // to keep the values in timecents so we can calculate it during startNote + if (KeynumToHold == 0) + { + Hold = (Hold < -11950.0f ? 0.0f : SynthHelper.Timecents2Secs(Hold)); + } + + if (KeynumToDecay == 0) + { + Decay = (Decay < -11950.0f ? 0.0f : SynthHelper.Timecents2Secs(Decay)); + } + + if (Sustain < 0.0f) + { + Sustain = 0.0f; + } + else if (sustainIsGain) + { + Sustain = SynthHelper.DecibelsToGain(-Sustain / 10.0f); + } + else + { + Sustain = 1.0f - (Sustain / 1000.0f); + } + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/LoopMode.cs b/Source/AlphaTab/Audio/Synth/Synthesis/LoopMode.cs new file mode 100644 index 000000000..169a6495d --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/Synthesis/LoopMode.cs @@ -0,0 +1,37 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace AlphaTab.Audio.Synth.Synthesis +{ + internal enum LoopMode + { + None, + Continuous, + Sustain + } +} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/OutputMode.cs b/Source/AlphaTab/Audio/Synth/Synthesis/OutputMode.cs new file mode 100644 index 000000000..bbf6e747e --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/Synthesis/OutputMode.cs @@ -0,0 +1,49 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace AlphaTab.Audio.Synth.Synthesis +{ + /// + /// Supported output modes by the render methods + /// + public enum OutputMode + { + /// + /// Two channels with single left/right samples one after another + /// + StereoInterleaved, + /// + /// Two channels with all samples for the left channel first then right + /// + StereoUnweaved, + /// + /// A single channel (stereo instruments are mixed into center) + /// + Mono + } +} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Preset.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Preset.cs new file mode 100644 index 000000000..d47f5e420 --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/Synthesis/Preset.cs @@ -0,0 +1,39 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace AlphaTab.Audio.Synth.Synthesis +{ + internal class Preset + { + + public string Name { get; set; } + public ushort PresetNumber { get; set; } + public ushort Bank { get; set; } + public Region[] Regions { get; set; } + } +} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Region.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Region.cs new file mode 100644 index 000000000..de6ae0855 --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/Synthesis/Region.cs @@ -0,0 +1,281 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using AlphaTab.Audio.Synth.SoundFont; + +namespace AlphaTab.Audio.Synth.Synthesis +{ + internal class Region + { + public LoopMode LoopMode { get; set; } + public uint SampleRate { get; set; } + public byte LoKey { get; set; } + public byte HiKey { get; set; } + public byte LoVel { get; set; } + public byte HiVel { get; set; } + public uint Group { get; set; } + public uint Offset { get; set; } + public uint End { get; set; } + public uint LoopStart { get; set; } + public uint LoopEnd { get; set; } + public int Transpose { get; set; } + public int Tune { get; set; } + public int PitchKeyCenter { get; set; } + public int PitchKeyTrack { get; set; } + public float Attenuation { get; set; } + public float Pan { get; set; } + public Envelope AmpEnv { get; set; } + public Envelope ModEnv { get; set; } + public int InitialFilterQ { get; set; } + public int InitialFilterFc { get; set; } + public int ModEnvToPitch { get; set; } + public int ModEnvToFilterFc { get; set; } + public int ModLfoToFilterFc { get; set; } + public int ModLfoToVolume { get; set; } + public float DelayModLFO { get; set; } + public int FreqModLFO { get; set; } + public int ModLfoToPitch { get; set; } + public float DelayVibLFO { get; set; } + public int FreqVibLFO { get; set; } + public int VibLfoToPitch { get; set; } + + public Region() + { + AmpEnv = new Envelope(); + ModEnv = new Envelope(); + } + + public Region(Region other) + { + LoopMode = other.LoopMode; + SampleRate = other.SampleRate; + LoKey = other.LoKey; + HiKey = other.HiKey; + LoVel = other.LoVel; + HiVel = other.HiVel; + Group = other.Group; + Offset = other.Offset; + End = other.End; + LoopStart = other.LoopStart; + LoopEnd = other.LoopEnd; + Transpose = other.Transpose; + Tune = other.Tune; + PitchKeyCenter = other.PitchKeyCenter; + PitchKeyTrack = other.PitchKeyTrack; + Attenuation = other.Attenuation; + Pan = other.Pan; + AmpEnv = new Envelope(other.AmpEnv); + ModEnv = new Envelope(other.ModEnv); + InitialFilterQ = other.InitialFilterQ; + InitialFilterFc = other.InitialFilterFc; + ModEnvToPitch = other.ModEnvToPitch; + ModEnvToFilterFc = other.ModEnvToFilterFc; + ModLfoToFilterFc = other.ModLfoToFilterFc; + ModLfoToVolume = other.ModLfoToVolume; + DelayModLFO = other.DelayModLFO; + FreqModLFO = other.FreqModLFO; + ModLfoToPitch = other.ModLfoToPitch; + DelayVibLFO = other.DelayVibLFO; + FreqVibLFO = other.FreqVibLFO; + VibLfoToPitch = other.VibLfoToPitch; + } + + public void Clear(bool forRelative) + { + LoopMode = 0; + SampleRate = 0; + LoKey = 0; + HiKey = 0; + LoVel = 0; + HiVel = 0; + Group = 0; + Offset = 0; + End = 0; + LoopStart = 0; + LoopEnd = 0; + Transpose = 0; + Tune = 0; + PitchKeyCenter = 0; + PitchKeyTrack = 0; + Attenuation = 0; + Pan = 0; + AmpEnv.Clear(); + ModEnv.Clear(); + InitialFilterQ = 0; + InitialFilterFc = 0; + ModEnvToPitch = 0; + ModEnvToFilterFc = 0; + ModLfoToFilterFc = 0; + ModLfoToVolume = 0; + DelayModLFO = 0; + FreqModLFO = 0; + ModLfoToPitch = 0; + DelayVibLFO = 0; + FreqVibLFO = 0; + VibLfoToPitch = 0; + + HiKey = HiVel = 127; + PitchKeyCenter = 60; // C4 + if (forRelative) + { + return; + } + + PitchKeyTrack = 100; + + PitchKeyCenter = -1; + + // SF2 defaults in timecents. + AmpEnv.Delay = AmpEnv.Attack = AmpEnv.Hold = AmpEnv.Decay = AmpEnv.Release = -12000.0f; + ModEnv.Delay = ModEnv.Attack = ModEnv.Hold = ModEnv.Decay = ModEnv.Release = -12000.0f; + + InitialFilterFc = 13500; + + DelayModLFO = -12000.0f; + DelayVibLFO = -12000.0f; + } + + private enum GenOperators + { + StartAddrsOffset, + EndAddrsOffset, + StartloopAddrsOffset, + EndloopAddrsOffset, + StartAddrsCoarseOffset, + ModLfoToPitch, + VibLfoToPitch, + ModEnvToPitch, + InitialFilterFc, + InitialFilterQ, + ModLfoToFilterFc, + ModEnvToFilterFc, + EndAddrsCoarseOffset, + ModLfoToVolume, + Unused1, + ChorusEffectsSend, + ReverbEffectsSend, + Pan, + Unused2, + Unused3, + Unused4, + DelayModLFO, + FreqModLFO, + DelayVibLFO, + FreqVibLFO, + DelayModEnv, + AttackModEnv, + HoldModEnv, + DecayModEnv, + SustainModEnv, + ReleaseModEnv, + KeynumToModEnvHold, + KeynumToModEnvDecay, + DelayVolEnv, + AttackVolEnv, + HoldVolEnv, + DecayVolEnv, + SustainVolEnv, + ReleaseVolEnv, + KeynumToVolEnvHold, + KeynumToVolEnvDecay, + Instrument, + Reserved1, + KeyRange, + VelRange, + StartloopAddrsCoarseOffset, + Keynum, + Velocity, + InitialAttenuation, + Reserved2, + EndloopAddrsCoarseOffset, + CoarseTune, + FineTune, + SampleID, + SampleModes, + Reserved3, + ScaleTuning, + ExclusiveClass, + OverridingRootKey, + Unused5, + EndOper + } + + internal void Operator(ushort genOper, HydraGenAmount amount) + { + switch ((GenOperators)genOper) + { + case GenOperators.StartAddrsOffset: Offset += (uint)amount.ShortAmount; break; + case GenOperators.EndAddrsOffset: End += (uint)amount.ShortAmount; break; + case GenOperators.StartloopAddrsOffset: LoopStart += (uint)amount.ShortAmount; break; + case GenOperators.EndloopAddrsOffset: LoopEnd += (uint)amount.ShortAmount; break; + case GenOperators.StartAddrsCoarseOffset: Offset += (uint)amount.ShortAmount * 32768; break; + case GenOperators.ModLfoToPitch: ModLfoToPitch = amount.ShortAmount; break; + case GenOperators.VibLfoToPitch: VibLfoToPitch = amount.ShortAmount; break; + case GenOperators.ModEnvToPitch: ModEnvToPitch = amount.ShortAmount; break; + case GenOperators.InitialFilterFc: InitialFilterFc = amount.ShortAmount; break; + case GenOperators.InitialFilterQ: InitialFilterQ = amount.ShortAmount; break; + case GenOperators.ModLfoToFilterFc: ModLfoToFilterFc = amount.ShortAmount; break; + case GenOperators.ModEnvToFilterFc: ModEnvToFilterFc = amount.ShortAmount; break; + case GenOperators.EndAddrsCoarseOffset: End += (uint)amount.ShortAmount * 32768; break; + case GenOperators.ModLfoToVolume: ModLfoToVolume = amount.ShortAmount; break; + case GenOperators.Pan: Pan = amount.ShortAmount / 1000.0f; break; + case GenOperators.DelayModLFO: DelayModLFO = amount.ShortAmount; break; + case GenOperators.FreqModLFO: FreqModLFO = amount.ShortAmount; break; + case GenOperators.DelayVibLFO: DelayVibLFO = amount.ShortAmount; break; + case GenOperators.FreqVibLFO: FreqVibLFO = amount.ShortAmount; break; + case GenOperators.DelayModEnv: ModEnv.Delay = amount.ShortAmount; break; + case GenOperators.AttackModEnv: ModEnv.Attack = amount.ShortAmount; break; + case GenOperators.HoldModEnv: ModEnv.Hold = amount.ShortAmount; break; + case GenOperators.DecayModEnv: ModEnv.Decay = amount.ShortAmount; break; + case GenOperators.SustainModEnv: ModEnv.Sustain = amount.ShortAmount; break; + case GenOperators.ReleaseModEnv: ModEnv.Release = amount.ShortAmount; break; + case GenOperators.KeynumToModEnvHold: ModEnv.KeynumToHold = amount.ShortAmount; break; + case GenOperators.KeynumToModEnvDecay: ModEnv.KeynumToDecay = amount.ShortAmount; break; + case GenOperators.DelayVolEnv: AmpEnv.Delay = amount.ShortAmount; break; + case GenOperators.AttackVolEnv: AmpEnv.Attack = amount.ShortAmount; break; + case GenOperators.HoldVolEnv: AmpEnv.Hold = amount.ShortAmount; break; + case GenOperators.DecayVolEnv: AmpEnv.Decay = amount.ShortAmount; break; + case GenOperators.SustainVolEnv: AmpEnv.Sustain = amount.ShortAmount; break; + case GenOperators.ReleaseVolEnv: AmpEnv.Release = amount.ShortAmount; break; + case GenOperators.KeynumToVolEnvHold: AmpEnv.KeynumToHold = amount.ShortAmount; break; + case GenOperators.KeynumToVolEnvDecay: AmpEnv.KeynumToDecay = amount.ShortAmount; break; + case GenOperators.KeyRange: LoKey = amount.LowByteAmount; HiKey = amount.HighByteAmount; break; + case GenOperators.VelRange: LoVel = amount.LowByteAmount; HiVel = amount.HighByteAmount; break; + case GenOperators.StartloopAddrsCoarseOffset: LoopStart += (uint)amount.ShortAmount * 32768; break; + case GenOperators.InitialAttenuation: Attenuation += amount.ShortAmount * 0.1f; break; + case GenOperators.EndloopAddrsCoarseOffset: LoopEnd += (uint)amount.ShortAmount * 32768; break; + case GenOperators.CoarseTune: Transpose += amount.ShortAmount; break; + case GenOperators.FineTune: Tune += amount.ShortAmount; break; + case GenOperators.SampleModes: LoopMode = ((amount.WordAmount & 3) == 3 ? LoopMode.Sustain : ((amount.WordAmount & 3) == 1 ? LoopMode.Continuous : LoopMode.None)); break; + case GenOperators.ScaleTuning: PitchKeyTrack = amount.ShortAmount; break; + case GenOperators.ExclusiveClass: Group = amount.WordAmount; break; + case GenOperators.OverridingRootKey: PitchKeyCenter = amount.ShortAmount; break; + } + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/SynthParameters.cs b/Source/AlphaTab/Audio/Synth/Synthesis/SynthParameters.cs deleted file mode 100644 index 87087fcee..000000000 --- a/Source/AlphaTab/Audio/Synth/Synthesis/SynthParameters.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Bank.Components; -using AlphaTab.Audio.Synth.Util; - -namespace AlphaTab.Audio.Synth.Synthesis -{ - /// - /// Parameters for a single synth channel including its program, bank, and cc list. - /// - internal class SynthParameters - { - /// - /// program number - /// - public byte Program { get; set; } - - /// - /// bank number - /// - public byte BankSelect { get; set; } - - /// - /// channel pressure event - /// - public byte ChannelAfterTouch { get; set; } - - /// - /// (vol) pan positions controlling both right and left output levels - /// - public CCValue Pan { get; set; } - - /// - /// (vol) channel volume controller - /// - public CCValue Volume { get; set; } - - /// - /// (vol) expression controller - /// - public CCValue Expression { get; set; } - - /// - /// (pitch) mod wheel pitch modifier in partial cents ie. 22.3 - /// - public CCValue ModRange { get; set; } - - /// - /// (pitch) pitch bend including both semitones and cents - /// - public CCValue PitchBend { get; set; } - - /// - /// controls max and min pitch bend range semitones - /// - public byte PitchBendRangeCoarse { get; set; } - - /// - /// controls max and min pitch bend range cents - /// - public byte PitchBendRangeFine { get; set; } - - /// - /// (pitch) transposition in semitones - /// - public short MasterCoarseTune { get; set; } - - /// - /// (pitch) transposition in cents - /// - public CCValue MasterFineTune { get; set; } - - /// - /// hold pedal status (true) for active - /// - public bool HoldPedal { get; set; } - - /// - /// legato pedal status (true) for active - /// - public bool LegatoPedal { get; set; } - - /// - /// registered parameter number - /// - public CCValue Rpn { get; set; } - - public Synthesizer Synth { get; set; } - - - //These are updated whenever a midi event that affects them is recieved. - - public float CurrentVolume { get; set; } - public int CurrentPitch { get; set; } //in cents - public int CurrentMod { get; set; } //in cents - public PanComponent CurrentPan { get; set; } - public float MixVolume { get; set; } - - public SynthParameters(Synthesizer synth) - { - Synth = synth; - - Pan = new CCValue(0); - Volume = new CCValue(0); - Expression = new CCValue(0); - ModRange = new CCValue(0); - PitchBend = new CCValue(0); - MasterFineTune = new CCValue(0); - Rpn = new CCValue(0); - - MixVolume = 1; - - CurrentPan = new PanComponent(); - - ResetControllers(); - } - - /// - /// Resets all of the channel's controllers to initial first power on values. Not the same as CC-121. - /// - public void ResetControllers() - { - Program = 0; - BankSelect = 0; - ChannelAfterTouch = 0; //Reset Channel Pressure to 0 - Pan.Combined = 0x2000; - Volume.Fine = 0; - Volume.Coarse = 100; //Reset Vol Positions back to 90/127 (GM spec) - Expression.Combined = 0x3FFF; //Reset Expression positions back to 127/127 - ModRange.Combined = 0; - PitchBend.Combined = 0x2000; - PitchBendRangeCoarse = 2; //Reset pitch wheel to +-2 semitones (GM spec) - PitchBendRangeFine = 0; - MasterCoarseTune = 0; - MasterFineTune.Combined = 0x2000; //Reset fine tune - HoldPedal = false; - LegatoPedal = false; - Rpn.Combined = 0x3FFF; //Reset rpn - UpdateCurrentPan(); - UpdateCurrentPitch(); - UpdateCurrentVolumeFromExpression(); - } - - public void UpdateCurrentPitch() - { - CurrentPitch = (int)((PitchBend.Combined - 8192.0) / 8192.0 * - (100 * PitchBendRangeCoarse + PitchBendRangeFine)); - } - - public void UpdateCurrentMod() - { - CurrentMod = (int)(SynthConstants.DefaultModDepth * (ModRange.Combined / 16383.0)); - } - - public void UpdateCurrentPan() - { - var value = SynthConstants.HalfPi * (Pan.Combined / 16383.0); - CurrentPan.Left = (float)Math.Cos(value); - CurrentPan.Right = (float)Math.Sin(value); - } - - public void UpdateCurrentVolumeFromVolume() - { - CurrentVolume = Volume.Combined / 16383f; - CurrentVolume *= CurrentVolume; - } - - public void UpdateCurrentVolumeFromExpression() - { - CurrentVolume = Expression.Combined / 16383f; - CurrentVolume *= CurrentVolume; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Synthesizer.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Synthesizer.cs deleted file mode 100644 index ac0d3ca67..000000000 --- a/Source/AlphaTab/Audio/Synth/Synthesis/Synthesizer.cs +++ /dev/null @@ -1,714 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Bank; -using AlphaTab.Audio.Synth.Bank.Patch; -using AlphaTab.Audio.Synth.Ds; -using AlphaTab.Audio.Synth.Midi; -using AlphaTab.Audio.Synth.Midi.Event; -using AlphaTab.Audio.Synth.Util; -using AlphaTab.Collections; -using AlphaTab.Util; - -namespace AlphaTab.Audio.Synth.Synthesis -{ - internal class SynthEvent - { - public int EventIndex { get; set; } - public MidiEvent Event { get; set; } - public bool IsMetronome { get; set; } - public double Time { get; set; } - - public SynthEvent(int eventIndex, MidiEvent e) - { - EventIndex = eventIndex; - Event = e; - } - - public static SynthEvent NewMetronomeEvent(int eventIndex, int metronomeLength) - { - var x = new SynthEvent(eventIndex, null); - x.IsMetronome = true; - return x; - } - } - - internal class Synthesizer - { - private readonly VoiceManager _voiceManager; - private readonly SynthParameters[] _synthChannels; - - private readonly Patch[] _layerList; - private readonly LinkedList _midiEventQueue; - private readonly int[] _midiEventCounts; - - private int _metronomeChannel; - private FastDictionary _mutedChannels; - private FastDictionary _soloChannels; - private bool _isAnySolo; - - - public int ActiveVoices => _voiceManager.ActiveVoices.Length; - - public int FreeVoices => _voiceManager.FreeVoices.Length; - - - /// - /// The size of the individual sub buffers in samples - /// - public int MicroBufferSize { get; private set; } - - /// - /// The number of sub buffers - /// - public int MicroBufferCount { get; private set; } - - /// - /// Gets or sets the overall buffer of samples consisting of multiple microbuffers. - /// - public SampleArray SampleBuffer { get; set; } - - /// - /// The patch bank that holds all of the currently loaded instrument patches - /// - public PatchBank SoundBank { get; private set; } - - /// - /// The number of samples per second produced per channel - /// - public int SampleRate { get; private set; } - - /// - /// The master volume - /// - public float MasterVolume { get; set; } - - - /// - /// The metronome volume - /// - public float MetronomeVolume - { - get => _synthChannels[_metronomeChannel].MixVolume; - set => _synthChannels[_metronomeChannel].MixVolume = value; - } - - public Synthesizer(int sampleRate, int audioChannels, int bufferSize, int bufferCount, int polyphony) - { - var MinSampleRate = 8000; - var MaxSampleRate = 96000; - // - // Setup synth parameters - MasterVolume = 1; - - SampleRate = SynthHelper.ClampI(sampleRate, MinSampleRate, MaxSampleRate); - MicroBufferSize = SynthHelper.ClampI(bufferSize, - (int)(SynthConstants.MinBufferSize * sampleRate), - (int)(SynthConstants.MaxBufferSize * sampleRate)); - MicroBufferSize = (int)(Math.Ceiling(MicroBufferSize / (double)SynthConstants.DefaultBlockSize) * - SynthConstants.DefaultBlockSize); //ensure multiple of block size - MicroBufferCount = Math.Max(1, bufferCount); - SampleBuffer = new SampleArray(MicroBufferSize * MicroBufferCount * audioChannels); - - // Setup Controllers - _synthChannels = new SynthParameters[SynthConstants.DefaultChannelCount]; - for (var x = 0; x < _synthChannels.Length; x++) - { - _synthChannels[x] = new SynthParameters(this); - } - - // setup metronome channel - _metronomeChannel = _synthChannels.Length - 1; - - // Create synth voices - _voiceManager = - new VoiceManager( - SynthHelper.ClampI(polyphony, SynthConstants.MinPolyphony, SynthConstants.MaxPolyphony)); - - // Create midi containers - _midiEventQueue = new LinkedList(); - _midiEventCounts = new int[MicroBufferCount]; - _layerList = new Patch[15]; - - _mutedChannels = new FastDictionary(); - _soloChannels = new FastDictionary(); - - ResetSynthControls(); - } - - public void LoadBank(PatchBank bank) - { - UnloadBank(); - SoundBank = bank; - } - - public void UnloadBank() - { - if (SoundBank != null) - { - NoteOffAll(true); - _voiceManager.UnloadPatches(); - SoundBank = null; - } - } - - public void ResetSynthControls() - { - foreach (var parameters in _synthChannels) - { - parameters.ResetControllers(); - } - - _synthChannels[MidiHelper.DrumChannel].BankSelect = PatchBank.DrumBank; - ReleaseAllHoldPedals(); - - _synthChannels[_metronomeChannel].Volume.Coarse = 128; - _synthChannels[_metronomeChannel].UpdateCurrentVolumeFromVolume(); - _synthChannels[_metronomeChannel].BankSelect = PatchBank.DrumBank; - //_synthChannels[_metronomeChannel].MixVolume = 0; - } - - public void ResetPrograms() - { - foreach (var parameters in _synthChannels) - { - parameters.Program = 0; - } - } - - public void Synthesize() - { - SampleBuffer.Clear(); - FillWorkingBuffer(false); - } - - public void SynthesizeSilent() - { - SampleBuffer.Clear(); - FillWorkingBuffer(true); - } - - private void FillWorkingBuffer(bool silent) - { - /*Break the process loop into sections representing the smallest timeframe before the midi controls need to be updated - the bigger the timeframe the more efficent the process is, but playback quality will be reduced.*/ - var sampleIndex = 0; - var anySolo = _isAnySolo; - for (var x = 0; x < MicroBufferCount; x++) - { - if (_midiEventQueue.Length > 0) - { - for (var i = 0; i < _midiEventCounts[x]; i++) - { - var m = _midiEventQueue.RemoveLast(); - if (m.IsMetronome) - { - NoteOff(_metronomeChannel, 37); - NoteOn(_metronomeChannel, 37, 95); - } - else - { - ProcessMidiMessage(m.Event); - } - } - } - - //voice processing loop - var node = _voiceManager.ActiveVoices.First; //node used to traverse the active voices - while (node != null) - { - var channel = node.Value.VoiceParams.Channel; - // channel is muted if it is either explicitley muted, or another channel is set to solo but not this one. - var isChannelMuted = _mutedChannels.ContainsKey(channel) || - anySolo && !_soloChannels.ContainsKey(channel); - - if (silent) - { - node.Value.ProcessSilent(sampleIndex, sampleIndex + MicroBufferSize * 2); - } - else - { - node.Value.Process(sampleIndex, sampleIndex + MicroBufferSize * 2, isChannelMuted); - } - - //if an active voice has stopped remove it from the list - if (node.Value.VoiceParams.State == VoiceStateEnum.Stopped) - { - var delnode = node; //node used to remove inactive voices - node = node.Next; - _voiceManager.RemoveVoiceFromRegistry(delnode.Value); - _voiceManager.ActiveVoices.Remove(delnode); - _voiceManager.FreeVoices.AddFirst(delnode.Value); - } - else - { - node = node.Next; - } - } - - sampleIndex += MicroBufferSize * SynthConstants.AudioChannels; - } - - Platform.Platform.ClearIntArray(_midiEventCounts); - } - - #region Midi Handling - - public void NoteOn(int channel, int note, int velocity) - { - // Get the correct instrument depending if it is a drum or not - var sChan = _synthChannels[channel]; - var inst = SoundBank.GetPatchByNumber(sChan.BankSelect, sChan.Program); - if (inst == null) - { - return; - } - - // A NoteOn can trigger multiple voices via layers - int layerCount; - if (inst is MultiPatch) - { - layerCount = ((MultiPatch)inst).FindPatches(channel, note, velocity, _layerList); - } - else - { - layerCount = 1; - _layerList[0] = inst; - } - - // If a key with the same note value exists, stop it - if (_voiceManager.Registry[channel][note] != null) - { - var node = _voiceManager.Registry[channel][note]; - while (node != null) - { - node.Value.Stop(); - node = node.Next; - } - - _voiceManager.RemoveFromRegistry(channel, note); - } - - // Check exclusive groups - for (var x = 0; x < layerCount; x++) - { - var notseen = true; - for (var i = x - 1; i >= 0; i--) - { - if (_layerList[x].ExclusiveGroupTarget == _layerList[i].ExclusiveGroupTarget) - { - notseen = false; - break; - } - } - - if (_layerList[x].ExclusiveGroupTarget != 0 && notseen) - { - var node = _voiceManager.ActiveVoices.First; - while (node != null) - { - if (_layerList[x].ExclusiveGroupTarget == node.Value.Patch.ExclusiveGroup) - { - node.Value.Stop(); - _voiceManager.RemoveVoiceFromRegistry(node.Value); - } - - node = node.Next; - } - } - } - - // Assign a voice to each layer - for (var x = 0; x < layerCount; x++) - { - var voice = _voiceManager.GetFreeVoice(); - if (voice == null) // out of voices and skipping is enabled - { - break; - } - - voice.Configure(channel, note, velocity, _layerList[x], _synthChannels[channel]); - _voiceManager.AddToRegistry(voice); - _voiceManager.ActiveVoices.AddLast(voice); - voice.Start(); - } - - // Clear layer list - for (var x = 0; x < layerCount; x++) - { - _layerList[x] = null; - } - } - - public void NoteOff(int channel, int note) - { - if (_synthChannels[channel].HoldPedal) - { - var node = _voiceManager.Registry[channel][note]; - while (node != null) - { - node.Value.VoiceParams.NoteOffPending = true; - node = node.Next; - } - } - else - { - var node = _voiceManager.Registry[channel][note]; - while (node != null) - { - node.Value.Stop(); - node = node.Next; - } - - _voiceManager.RemoveFromRegistry(channel, note); - } - } - - public void NoteOffAll(bool immediate) - { - var node = _voiceManager.ActiveVoices.First; - if (immediate) - { - //if immediate ignore hold pedals and clear the entire registry - _voiceManager.ClearRegistry(); - while (node != null) - { - node.Value.StopImmediately(); - var delnode = node; - node = node.Next; - _voiceManager.ActiveVoices.Remove(delnode); - _voiceManager.FreeVoices.AddFirst(delnode.Value); - } - } - else - { - //otherwise we have to check for hold pedals and double check the registry before removing the voice - while (node != null) - { - var voiceParams = node.Value.VoiceParams; - if (voiceParams.State == VoiceStateEnum.Playing) - { - //if hold pedal is enabled do not stop the voice - if (_synthChannels[voiceParams.Channel].HoldPedal) - { - voiceParams.NoteOffPending = true; - } - else - { - node.Value.Stop(); - _voiceManager.RemoveVoiceFromRegistry(node.Value); - } - } - - node = node.Next; - } - } - } - - public void NoteOffAllChannel(int channel, bool immediate) - { - var node = _voiceManager.ActiveVoices.First; - while (node != null) - { - if (channel == node.Value.VoiceParams.Channel) - { - if (immediate) - { - node.Value.StopImmediately(); - var delnode = node; - node = node.Next; - _voiceManager.ActiveVoices.Remove(delnode); - _voiceManager.FreeVoices.AddFirst(delnode.Value); - } - else - { - //if hold pedal is enabled do not stop the voice - if (_synthChannels[channel].HoldPedal) - { - node.Value.VoiceParams.NoteOffPending = true; - } - else - { - node.Value.Stop(); - } - - node = node.Next; - } - } - } - } - - public void ProcessMidiMessage(MidiEvent e) - { - Logger.Debug("Midi", "Processing midi " + e.Command); - var command = e.Command; - var channel = e.Channel; - var data1 = e.Data1; - var data2 = e.Data2; - switch (command) - { - case MidiEventType.NoteOff: - NoteOff(channel, data1); - break; - case MidiEventType.NoteOn: - if (data2 == 0) - { - NoteOff(channel, data1); - } - else - { - NoteOn(channel, data1, data2); - } - - break; - case MidiEventType.NoteAftertouch: - //synth uses channel after touch instead - break; - case MidiEventType.Controller: - switch ((ControllerType)data1) - { - case ControllerType.BankSelectCoarse: //Bank select coarse - if (channel == MidiHelper.DrumChannel) - { - data2 += PatchBank.DrumBank; - } - - if (SoundBank.IsBankLoaded(data2)) - { - _synthChannels[channel].BankSelect = (byte)data2; - } - else - { - _synthChannels[channel].BankSelect = - (byte)(channel == MidiHelper.DrumChannel ? PatchBank.DrumBank : 0); - } - - break; - case ControllerType.ModulationCoarse: //Modulation wheel coarse - _synthChannels[channel].ModRange.Coarse = (byte)data2; - _synthChannels[channel].UpdateCurrentMod(); - break; - case ControllerType.ModulationFine: //Modulation wheel fine - _synthChannels[channel].ModRange.Fine = (byte)data2; - _synthChannels[channel].UpdateCurrentMod(); - break; - case ControllerType.VolumeCoarse: //Channel volume coarse - _synthChannels[channel].Volume.Coarse = (byte)data2; - _synthChannels[channel].UpdateCurrentVolumeFromVolume(); - break; - case ControllerType.VolumeFine: //Channel volume fine - _synthChannels[channel].Volume.Fine = (byte)data2; - _synthChannels[channel].UpdateCurrentVolumeFromVolume(); - break; - case ControllerType.PanCoarse: //Pan coarse - _synthChannels[channel].Pan.Coarse = (byte)data2; - _synthChannels[channel].UpdateCurrentPan(); - break; - case ControllerType.PanFine: //Pan fine - _synthChannels[channel].Pan.Fine = (byte)data2; - _synthChannels[channel].UpdateCurrentPan(); - break; - case ControllerType.ExpressionControllerCoarse: //Expression coarse - _synthChannels[channel].Expression.Coarse = (byte)data2; - _synthChannels[channel].UpdateCurrentVolumeFromExpression(); - break; - case ControllerType.ExpressionControllerFine: //Expression fine - _synthChannels[channel].Expression.Fine = (byte)data2; - _synthChannels[channel].UpdateCurrentVolumeFromExpression(); - break; - case ControllerType.HoldPedal: //Hold pedal - if (_synthChannels[channel].HoldPedal && !(data2 > 63) - ) //if hold pedal is released stop any voices with pending release tags - { - ReleaseHoldPedal(channel); - } - - _synthChannels[channel].HoldPedal = data2 > 63; - break; - case ControllerType.LegatoPedal: //Legato Pedal - _synthChannels[channel].LegatoPedal = data2 > 63; - break; - case ControllerType.NonRegisteredParameterCourse - : //NRPN Coarse Select //fix for invalid DataEntry after unsupported NRPN events - _synthChannels[channel].Rpn.Combined = 0x3FFF; //todo implement NRPN - break; - case ControllerType.NonRegisteredParameterFine - : //NRPN Fine Select //fix for invalid DataEntry after unsupported NRPN events - _synthChannels[channel].Rpn.Combined = 0x3FFF; //todo implement NRPN - break; - case ControllerType.RegisteredParameterCourse: //RPN Coarse Select - _synthChannels[channel].Rpn.Coarse = (byte)data2; - break; - case ControllerType.RegisteredParameterFine: //RPN Fine Select - _synthChannels[channel].Rpn.Fine = (byte)data2; - break; - case ControllerType.AllNotesOff: //Note Off All - NoteOffAll(false); - break; - case ControllerType.DataEntryCoarse: //DataEntry Coarse - switch (_synthChannels[channel].Rpn.Combined) - { - case 0: //change semitone, pitchwheel - _synthChannels[channel].PitchBendRangeCoarse = (byte)data2; - _synthChannels[channel].UpdateCurrentPitch(); - break; - case 1: //master fine tune coarse - _synthChannels[channel].MasterFineTune.Coarse = (byte)data2; - break; - case 2: //master coarse tune coarse - _synthChannels[channel].MasterCoarseTune = (short)(data2 - 64); - break; - } - - break; - case ControllerType.DataEntryFine: //DataEntry Fine - switch (_synthChannels[channel].Rpn.Combined) - { - case 0: //change cents, pitchwheel - _synthChannels[channel].PitchBendRangeFine = (byte)data2; - _synthChannels[channel].UpdateCurrentPitch(); - break; - case 1: //master fine tune fine - _synthChannels[channel].MasterFineTune.Fine = (byte)data2; - break; - } - - break; - case ControllerType.ResetControllers: //Reset All - _synthChannels[channel].Expression.Combined = 0x3FFF; - _synthChannels[channel].ModRange.Combined = 0; - if (_synthChannels[channel].HoldPedal) - { - ReleaseHoldPedal(channel); - } - - _synthChannels[channel].HoldPedal = false; - _synthChannels[channel].LegatoPedal = false; - _synthChannels[channel].Rpn.Combined = 0x3FFF; - _synthChannels[channel].PitchBend.Combined = 0x2000; - _synthChannels[channel].ChannelAfterTouch = 0; - _synthChannels[channel].UpdateCurrentPitch(); //because pitchBend was reset - _synthChannels[channel].UpdateCurrentVolumeFromExpression(); //because expression was reset - break; - default: - return; - } - - break; - case MidiEventType.ProgramChange: //Program Change - _synthChannels[channel].Program = (byte)data1; - break; - case MidiEventType.ChannelAftertouch: //Channel Aftertouch - _synthChannels[channel].ChannelAfterTouch = (byte)data2; - break; - case MidiEventType.PitchBend: //Pitch Bend - _synthChannels[channel].PitchBend.Coarse = (byte)data2; - _synthChannels[channel].PitchBend.Fine = (byte)data1; - _synthChannels[channel].UpdateCurrentPitch(); - break; - } - - OnMidiEventProcessed(e); - } - - public event Action MidiEventProcessed; - - private void OnMidiEventProcessed(MidiEvent e) - { - var handler = MidiEventProcessed; - if (handler != null) - { - handler(e); - } - } - - private void ReleaseAllHoldPedals() - { - var node = _voiceManager.ActiveVoices.First; - while (node != null) - { - if (node.Value.VoiceParams.NoteOffPending) - { - node.Value.Stop(); - _voiceManager.RemoveVoiceFromRegistry(node.Value); - } - - node = node.Next; - } - } - - private void ReleaseHoldPedal(int channel) - { - var node = _voiceManager.ActiveVoices.First; - while (node != null) - { - if (node.Value.VoiceParams.Channel == channel && node.Value.VoiceParams.NoteOffPending) - { - node.Value.Stop(); - _voiceManager.RemoveVoiceFromRegistry(node.Value); - } - - node = node.Next; - } - } - - #endregion - - public void DispatchEvent(int i, SynthEvent synthEvent) - { - _midiEventQueue.AddFirst(synthEvent); - _midiEventCounts[i]++; - } - - public void SetChannelMute(int channel, bool mute) - { - if (mute) - { - _mutedChannels[channel] = true; - } - else - { - _mutedChannels.Remove(channel); - } - } - - public void ResetChannelStates() - { - _mutedChannels = new FastDictionary(); - _soloChannels = new FastDictionary(); - _isAnySolo = false; - } - - public void SetChannelSolo(int channel, bool solo) - { - if (solo) - { - _soloChannels[channel] = true; - } - else - { - _soloChannels.Remove(channel); - } - - _isAnySolo = _soloChannels.Count > 0; - } - - public void SetChannelProgram(int channel, byte program) - { - if (channel < 0 || channel >= _synthChannels.Length) - { - return; - } - - _synthChannels[channel].Program = (byte)program; - } - - public void SetChannelVolume(int channel, double volume) - { - if (channel < 0 || channel >= _synthChannels.Length) - { - return; - } - - _synthChannels[channel].MixVolume = (float)volume; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.AlphaTab.cs b/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.AlphaTab.cs new file mode 100644 index 000000000..b567ea806 --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.AlphaTab.cs @@ -0,0 +1,185 @@ +// This file contains alphaTab specific extensions to the TinySoundFont audio synthesis +using AlphaTab.Audio.Synth.Ds; +using AlphaTab.Audio.Synth.Midi.Event; +using AlphaTab.Audio.Synth.Util; +using AlphaTab.Collections; +using AlphaTab.Util; + +namespace AlphaTab.Audio.Synth.Synthesis +{ + internal partial class TinySoundFont + { + public const int MicroBufferCount = 32; // 4069 samples in total + public const int MicroBufferSize = 64; // 64 stereo samples + + private readonly LinkedList _midiEventQueue = new LinkedList(); + private readonly int[] _midiEventCounts = new int[MicroBufferCount]; + private FastDictionary _mutedChannels = new FastDictionary(); + private FastDictionary _soloChannels = new FastDictionary(); + private bool _isAnySolo; + + public float[] Synthesize() + { + return FillWorkingBuffer(false); + } + + public void SynthesizeSilent() + { + FillWorkingBuffer(true); + } + + public float ChannelGetMixVolume(int channel) + { + return (_channels != null && channel < _channels.ChannelList.Count + ? _channels.ChannelList[channel].MixVolume + : 1.0f); + } + + public void ChannelSetMixVolume(int channel, float volume) + { + var c = ChannelInit(channel); + foreach (var v in _voices) + { + if (v.PlayingChannel == channel && v.PlayingPreset != -1) + { + v.MixVolume = volume; + } + } + + c.MixVolume = volume; + } + + public void ChannelSetMute(int channel, bool mute) + { + if (mute) + { + _mutedChannels[channel] = true; + } + else + { + _mutedChannels.Remove(channel); + } + } + + public void ChannelSetSolo(int channel, bool solo) + { + if (solo) + { + _soloChannels[channel] = true; + } + else + { + _soloChannels.Remove(channel); + } + + _isAnySolo = _soloChannels.Count > 0; + } + + public void ResetChannelStates() + { + _mutedChannels = new FastDictionary(); + _soloChannels = new FastDictionary(); + _isAnySolo = false; + } + + public void DispatchEvent(int i, SynthEvent synthEvent) + { + _midiEventQueue.AddFirst(synthEvent); + _midiEventCounts[i]++; + } + + private float[] FillWorkingBuffer(bool silent) + { + /*Break the process loop into sections representing the smallest timeframe before the midi controls need to be updated + the bigger the timeframe the more efficent the process is, but playback quality will be reduced.*/ + var buffer = new float[MicroBufferSize * MicroBufferCount * SynthConstants.AudioChannels]; + var bufferPos = 0; + var anySolo = _isAnySolo; + + // process in micro-buffers + for (var x = 0; x < MicroBufferCount; x++) + { + // process events for first microbuffer + if (_midiEventQueue.Length > 0) + { + for (var i = 0; i < _midiEventCounts[x]; i++) + { + var m = _midiEventQueue.RemoveLast(); + if (m.IsMetronome) + { + ChannelNoteOff(SynthConstants.MetronomeChannel, 33); + ChannelNoteOn(SynthConstants.MetronomeChannel, 33, 95 / 127f); + } + else + { + ProcessMidiMessage(m.Event); + } + } + } + + // voice processing loop + foreach (var voice in _voices) + { + if (voice.PlayingPreset != -1) + { + var channel = voice.PlayingChannel; + // channel is muted if it is either explicitley muted, or another channel is set to solo but not this one. + var isChannelMuted = _mutedChannels.ContainsKey(channel) || + anySolo && !_soloChannels.ContainsKey(channel); + + if (silent) + { + voice.Kill(); + } + else + { + voice.Render(this, buffer, bufferPos, MicroBufferSize, isChannelMuted); + } + } + } + + bufferPos += MicroBufferSize * SynthConstants.AudioChannels; + } + + Platform.Platform.ClearIntArray(_midiEventCounts); + return buffer; + } + + private void ProcessMidiMessage(MidiEvent e) + { + Logger.Debug("Midi", "Processing midi " + e.Command); + var command = e.Command; + var channel = e.Channel; + var data1 = e.Data1; + var data2 = e.Data2; + switch (command) + { + case MidiEventType.NoteOff: + ChannelNoteOff(channel, data1); + break; + case MidiEventType.NoteOn: + ChannelNoteOn(channel, data1, data2 / 127f); + break; + case MidiEventType.NoteAftertouch: + break; + case MidiEventType.Controller: + ChannelMidiControl(channel, data1, data2); + break; + case MidiEventType.ProgramChange: + ChannelSetPresetNumber(channel, data1, channel == 9); + break; + case MidiEventType.ChannelAftertouch: + break; + case MidiEventType.PitchBend: + ChannelSetPitchWheel(channel, (short)(data1 | (data2 << 8))); + break; + } + } + + public void SetupMetronomeChannel() + { + ChannelSetVolume(SynthConstants.MetronomeChannel, 1); + ChannelSetPresetNumber(SynthConstants.MetronomeChannel, 0, true); + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs b/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs new file mode 100644 index 000000000..63f6d8301 --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs @@ -0,0 +1,1301 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using System; +using AlphaTab.Audio.Synth.SoundFont; +using AlphaTab.Audio.Synth.Util; +using AlphaTab.Collections; + +// ReSharper disable UnusedMember.Global + +namespace AlphaTab.Audio.Synth.Synthesis +{ + /// + /// This is a tiny soundfont based synthesizer. + /// + /// + /// NOT YET IMPLEMENTED + /// - Support for ChorusEffectsSend and ReverbEffectsSend generators + /// - Better low-pass filter without lowering performance too much + /// - Support for modulators + /// + internal partial class TinySoundFont + { + private Preset[] _presets; + private readonly FastList _voices; + private Channels _channels; + private uint _voicePlayIndex; + + internal float[] FontSamples { get; set; } + + /// + /// Returns the number of presets in the loaded SoundFont + /// + public int PresetCount => _presets.Length; + + /// + /// Gets the currently configured output mode. + /// + /// + public OutputMode OutputMode { get; private set; } + + /// + /// Gets the currently configured sample rate. + /// + /// + public float OutSampleRate { get; private set; } + + /// + /// Gets the currently configured global gain in DB. + /// + public float GlobalGainDb { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public TinySoundFont(float sampleRate) + { + OutSampleRate = sampleRate; + OutputMode = OutputMode.StereoInterleaved; + _voices = new FastList(); + } + + /// + /// Stop all playing notes immediatly and reset all channel parameters + /// + public void Reset() + { + foreach (var v in _voices) + { + if (v.PlayingPreset != -1 && + (v.AmpEnv.Segment < VoiceEnvelopeSegment.Release || v.AmpEnv.Parameters.Release != 0)) + { + v.EndQuick(OutSampleRate); + } + } + + _channels = null; + } + + /// + /// Setup the parameters for the voice render methods + /// + /// if mono or stereo and how stereo channel data is ordered + /// the number of samples per second (output frequency) + /// volume gain in decibels (>0 means higher, <0 means lower) + public void SetOutput(OutputMode outputMode, int sampleRate, float globalGainDb) + { + OutputMode = outputMode; + OutSampleRate = sampleRate >= 1 ? sampleRate : 44100.0f; + GlobalGainDb = globalGainDb; + } + + /// + /// Start playing a note + /// + /// preset index >= 0 and < + /// note value between 0 and 127 (60 being middle C) + /// velocity as a float between 0.0 (equal to note off) and 1.0 (full) + public void NoteOn(int presetIndex, int key, float vel) + { + var midiVelocity = (int)(vel * 127); + + if (presetIndex < 0 || presetIndex >= _presets.Length) + { + return; + } + + if (vel <= 0.0f) + { + NoteOff(presetIndex, key); + return; + } + + // Play all matching regions. + var voicePlayIndex = _voicePlayIndex++; + foreach (var region in _presets[presetIndex].Regions) + { + if (key < region.LoKey || key > region.HiKey || midiVelocity < region.LoVel || + midiVelocity > region.HiVel) + { + continue; + } + + Voice voice = null; + if (region.Group != 0) + { + foreach (var v in _voices) + { + if (v.PlayingPreset == presetIndex && v.Region.Group == region.Group) + { + v.EndQuick(OutSampleRate); + } + else if (v.PlayingPreset == -1 && voice == null) + { + voice = v; + } + } + } + else + { + foreach (var v in _voices) + { + if (v.PlayingPreset == -1) + { + voice = v; + } + } + } + + if (voice == null) + { + for (var i = 0; i < 4; i++) + { + var newVoice = new Voice + { + PlayingPreset = -1 + }; + _voices.Add(newVoice); + } + + voice = _voices[_voices.Count - 4]; + } + + voice.Region = region; + voice.PlayingPreset = presetIndex; + voice.PlayingKey = key; + voice.PlayIndex = voicePlayIndex; + voice.NoteGainDb = GlobalGainDb - region.Attenuation - SynthHelper.GainToDecibels(1.0f / vel); + + if (_channels != null) + { + _channels.SetupVoice(this, voice); + } + else + { + voice.CalcPitchRatio(0, OutSampleRate); + // The SFZ spec is silent about the pan curve, but a 3dB pan law seems common. This sqrt() curve matches what Dimension LE does; Alchemy Free seems closer to sin(adjustedPan * pi/2). + voice.PanFactorLeft = (float)Math.Sqrt(0.5f - region.Pan); + voice.PanFactorRight = (float)Math.Sqrt(0.5f + region.Pan); + } + + // Offset/end. + voice.SourceSamplePosition = region.Offset; + + // Loop. + var doLoop = (region.LoopMode != LoopMode.None && region.LoopStart < region.LoopEnd); + voice.LoopStart = (doLoop ? region.LoopStart : 0); + voice.LoopEnd = (doLoop ? region.LoopEnd : 0); + + // Setup envelopes. + voice.AmpEnv.Setup(region.AmpEnv, key, midiVelocity, true, OutSampleRate); + voice.ModEnv.Setup(region.ModEnv, key, midiVelocity, false, OutSampleRate); + + // Setup lowpass filter. + var filterQDB = region.InitialFilterQ / 10.0f; + voice.LowPass.QInv = 1.0 / Math.Pow(10.0, (filterQDB / 20.0)); + voice.LowPass.Z1 = voice.LowPass.Z2 = 0; + voice.LowPass.Active = (region.InitialFilterFc <= 13500); + if (voice.LowPass.Active) + { + voice.LowPass.Setup(SynthHelper.Cents2Hertz(region.InitialFilterFc) / OutSampleRate); + } + + // Setup LFO filters. + voice.ModLfo.Setup(region.DelayModLFO, region.FreqModLFO, OutSampleRate); + voice.VibLfo.Setup(region.DelayVibLFO, region.FreqVibLFO, OutSampleRate); + } + } + + /// + /// Start playing a note + /// + /// instrument bank number (alternative to preset_index) + /// preset number (alternative to preset_index) + /// note value between 0 and 127 (60 being middle C) + /// velocity as a float between 0.0 (equal to note off) and 1.0 (full) + /// returns false if preset does not exist, otherwise true + public bool BankNoteOn(int bank, int presetNumber, int key, float vel) + { + var presetIndex = GetPresetIndex(bank, presetNumber); + if (presetIndex == -1) + { + return false; + } + + NoteOn(presetIndex, key, vel); + return true; + } + + /// + /// Stop playing a note + /// + /// + /// + public void NoteOff(int presetIndex, int key) + { + Voice matchFirst = null; + Voice matchLast = null; + var matches = new FastList(); + foreach (var v in _voices) + { + if (v.PlayingPreset != presetIndex || v.PlayingKey != key || + v.AmpEnv.Segment >= VoiceEnvelopeSegment.Release) + { + continue; + } + else if (matchFirst == null || v.PlayIndex < matchFirst.PlayIndex) + { + matchFirst = v; + matchLast = v; + matches.Add(v); + } + else if (v.PlayIndex == matchFirst.PlayIndex) + { + matchLast = v; + matches.Add(v); + } + } + + if (matchFirst == null) + { + return; + } + + foreach (var v in matches) + { + if (v != matchFirst && v != matchLast && + (v.PlayIndex != matchFirst.PlayIndex || v.PlayingPreset != presetIndex || v.PlayingKey != key || + v.AmpEnv.Segment >= VoiceEnvelopeSegment.Release)) + { + continue; + } + + v.End(OutSampleRate); + } + } + + /// + /// Stop playing a note + /// + /// + /// + /// + /// returns false if preset does not exist, otherwise true + public bool BankNoteOff(int bank, int presetNumber, int key) + { + var presetIndex = GetPresetIndex(bank, presetNumber); + if (presetIndex == -1) + { + return false; + } + + NoteOff(presetIndex, key); + return true; + } + + /// + /// Stop playing all notes (end with sustain and release) + /// + public void NoteOffAll(bool immediate) + { + foreach (var voice in _voices) + { + if (voice.PlayingPreset != -1 && voice.AmpEnv.Segment < VoiceEnvelopeSegment.Release) + { + if (immediate) + { + voice.EndQuick(OutSampleRate); + } + else + { + voice.End(OutSampleRate); + } + } + } + } + + /// + /// Returns the number of active voices + /// + public int ActiveVoiceCount + { + get + { + var count = 0; + foreach (var v in _voices) + { + if (v.PlayingPreset != -1) + { + count++; + } + } + + return count; + } + } + + private Channel ChannelInit(int channel) + { + if (_channels != null && channel < _channels.ChannelList.Count) + { + return _channels.ChannelList[channel]; + } + + if (_channels == null) + { + _channels = new Channels(); + } + + for (var i = _channels.ChannelList.Count; i <= channel; i++) + { + var c = new Channel(); + c.PresetIndex = c.Bank = 0; + c.PitchWheel = c.MidiPan = 8192; + c.MidiVolume = c.MidiExpression = 16383; + c.MidiRpn = 0xFFFF; + c.MidiData = 0; + c.PanOffset = 0.0f; + c.GainDb = 0.0f; + c.PitchRange = 2.0f; + c.Tuning = 0.0f; + c.MixVolume = 1; + _channels.ChannelList.Add(c); + } + + return _channels.ChannelList[channel]; + } + + /// + /// Returns the preset index from a bank and preset number, or -1 if it does not exist in the loaded SoundFont + /// + /// + /// + /// + private int GetPresetIndex(int bank, int presetNumber) + { + for (var i = 0; i < _presets.Length; i++) + { + var preset = _presets[i]; + if (preset.PresetNumber == presetNumber && preset.Bank == bank) + { + return i; + } + } + + return -1; + } + + /// + /// Returns the name of a preset index >= 0 and < GetPresetName() + /// + /// + /// + public string GetPresetName(int presetIndex) + { + return (presetIndex < 0 || presetIndex >= _presets.Length ? null : _presets[presetIndex].Name); + } + + /// + /// Returns the name of a preset by bank and preset number + /// + /// + /// + /// + public string BankGetPresetName(int bank, int presetNumber) + { + return GetPresetName(GetPresetIndex(bank, presetNumber)); + } + + #region Higher level channel based functions + + /// + /// Start playing a note on a channel + /// + /// channel number + /// note value between 0 and 127 (60 being middle C) + /// velocity as a float between 0.0 (equal to note off) and 1.0 (full) + public void ChannelNoteOn(int channel, int key, float vel) + { + if (_channels == null || channel > _channels.ChannelList.Count) + { + return; + } + + _channels.ActiveChannel = channel; + NoteOn(_channels.ChannelList[channel].PresetIndex, key, vel); + } + + /// + /// Stop playing notes on a channel + /// + /// channel number + /// note value between 0 and 127 (60 being middle C) + public void ChannelNoteOff(int channel, int key) + { + var matches = new FastList(); + Voice matchFirst = null; + Voice matchLast = null; + foreach (var v in _voices) + { + //Find the first and last entry in the voices list with matching channel, key and look up the smallest play index + if (v.PlayingPreset == -1 || v.PlayingChannel != channel || v.PlayingKey != key || + v.AmpEnv.Segment >= VoiceEnvelopeSegment.Release) + { + continue; + } + + if (matchFirst == null || v.PlayIndex < matchFirst.PlayIndex) + { + matchFirst = matchLast = v; + matches.Add(v); + } + else if (v.PlayIndex == matchFirst.PlayIndex) + { + matchLast = v; + matches.Add(v); + } + } + + if (matchFirst == null) + { + return; + } + + foreach (var v in matches) + { + //Stop all voices with matching channel, key and the smallest play index which was enumerated above + if (v != matchFirst && v != matchLast && + (v.PlayIndex != matchFirst.PlayIndex || v.PlayingPreset == -1 || v.PlayingChannel != channel || + v.PlayingKey != key || v.AmpEnv.Segment >= VoiceEnvelopeSegment.Release)) + { + continue; + } + + v.End(OutSampleRate); + } + } + + /// + /// Stop playing all notes on a channel with sustain and release. + /// + /// channel number + public void ChannelNoteOffAll(int channel) + { + foreach (var v in _voices) + { + if (v.PlayingPreset != -1 && v.PlayingChannel == channel && + v.AmpEnv.Segment < VoiceEnvelopeSegment.Release) + { + v.End(OutSampleRate); + } + } + } + + /// + /// Stop playing all notes on a channel immediately + /// + /// channel number + public void ChannelSoundsOffAll(int channel) + { + foreach (var v in _voices) + { + if (v.PlayingPreset != -1 && v.PlayingChannel == channel && + (v.AmpEnv.Segment < VoiceEnvelopeSegment.Release || v.AmpEnv.Parameters.Release == 0)) + { + v.EndQuick(OutSampleRate); + } + } + } + + /// + /// + /// + /// channel number + /// preset index >= 0 and < + public void ChannelSetPresetIndex(int channel, int presetIndex) + { + ChannelInit(channel).PresetIndex = (ushort)presetIndex; + } + + /// + /// + /// + /// channel number + /// preset number (alternative to preset_index) + /// false for normal channels, otherwise apply MIDI drum channel rules + /// return false if preset does not exist, otherwise true + public bool ChannelSetPresetNumber(int channel, int presetNumber, bool midiDrums = false) + { + var c = ChannelInit(channel); + int presetIndex; + if (midiDrums) + { + presetIndex = GetPresetIndex(128 | (c.Bank & 0x7FFF), presetNumber); + if (presetIndex == -1) + { + presetIndex = GetPresetIndex(128, presetNumber); + } + + if (presetIndex == -1) + { + presetIndex = GetPresetIndex(128, 0); + } + + if (presetIndex == -1) + { + presetIndex = GetPresetIndex(c.Bank & 0x7FF, presetNumber); + } + } + else + { + presetIndex = GetPresetIndex(c.Bank & 0x7FF, presetNumber); + } + + if (presetIndex == -1) + { + presetIndex = GetPresetIndex(0, presetNumber); + } + + if (presetIndex != -1) + { + c.PresetIndex = (ushort)presetIndex; + return true; + } + + return false; + } + + /// + /// + /// + /// channel number + /// instrument bank number (alternative to preset_index) + public void ChannelSetBank(int channel, int bank) + { + ChannelInit(channel).Bank = (ushort)bank; + } + + /// + /// + /// + /// channel number + /// instrument bank number (alternative to preset_index) + /// preset number (alternative to preset_index) + /// return false if preset does not exist, otherwise true + public bool ChannelSetBankPreset(int channel, int bank, int presetNumber) + { + var c = ChannelInit(channel); + var presetIndex = GetPresetIndex(bank, presetNumber); + if (presetIndex == -1) + { + return false; + } + + c.PresetIndex = (ushort)presetIndex; + c.Bank = (ushort)bank; + return true; + } + + /// + /// + /// + /// channel number + /// stereo panning value from 0.0 (left) to 1.0 (right) (default 0.5 center) + public void ChannelSetPan(int channel, float pan) + { + foreach (var v in _voices) + { + if (v.PlayingChannel == channel && v.PlayingPreset != -1) + { + var newPan = v.Region.Pan + pan - 0.5f; + if (newPan <= -0.5f) + { + v.PanFactorLeft = 1; + v.PanFactorRight = 0; + } + else if (newPan >= 0.5f) + { + v.PanFactorLeft = 0; + v.PanFactorRight = 1; + } + else + { + v.PanFactorLeft = (float)Math.Sqrt(0.5f - newPan); + v.PanFactorRight = (float)Math.Sqrt(0.5f + newPan); + } + } + } + + ChannelInit(channel).PanOffset = pan - 0.5f; + } + + /// + /// + /// + /// channel number + /// linear volume scale factor (default 1.0 full) + public void ChannelSetVolume(int channel, float volume) + { + var c = ChannelInit(channel); + var gainDb = SynthHelper.GainToDecibels(volume); + var gainDBChange = gainDb - c.GainDb; + if (gainDBChange == 0) + { + return; + } + + foreach (var v in _voices) + { + if (v.PlayingChannel == channel && v.PlayingPreset != -1) + { + v.NoteGainDb += gainDBChange; + } + } + + c.GainDb = gainDb; + } + + /// + /// + /// + /// channel number + /// pitch wheel position 0 to 16383 (default 8192 unpitched) + public void ChannelSetPitchWheel(int channel, int pitchWheel) + { + var c = ChannelInit(channel); + if (c.PitchWheel == pitchWheel) + { + return; + } + + c.PitchWheel = (ushort)pitchWheel; + ChannelApplyPitch(channel, c); + } + + private void ChannelApplyPitch(int channel, Channel c) + { + var pitchShift = c.PitchWheel == 8192 + ? c.Tuning + : ((c.PitchWheel / 16383.0f * c.PitchRange * 2f) - c.PitchRange + c.Tuning); + foreach (var v in _voices) + { + if (v.PlayingChannel == channel && v.PlayingPreset != -1) + { + v.CalcPitchRatio(pitchShift, OutSampleRate); + } + } + } + + /// + /// + /// + /// channel number + /// range of the pitch wheel in semitones (default 2.0, total +/- 2 semitones) + public void ChannelSetPitchRange(int channel, float pitchRange) + { + var c = ChannelInit(channel); + if (c.PitchRange == pitchRange) + { + return; + } + + c.PitchRange = pitchRange; + if (c.PitchWheel != 8192) + { + ChannelApplyPitch(channel, c); + } + } + + /// + /// + /// + /// channel number + /// tuning of all playing voices in semitones (default 0.0, standard (A440) tuning) + public void ChannelSetTuning(int channel, float tuning) + { + var c = ChannelInit(channel); + if (c.Tuning == tuning) + { + return; + } + + c.Tuning = tuning; + ChannelApplyPitch(channel, c); + } + + /// + /// Apply a MIDI control change to the channel (not all controllers are supported!) + /// + /// + /// + /// + public void ChannelMidiControl(int channel, int controller, int controlValue) + { + var c = ChannelInit(channel); + switch (controller) + { + case 5: /*Portamento_Time_MSB*/ + case 96: /*DATA_BUTTON_INCREMENT*/ + case 97: /*DATA_BUTTON_DECREMENT*/ + case 64: /*HOLD_PEDAL*/ + case 65: /*Portamento*/ + case 66: /*SostenutoPedal */ + case 122: /*LocalKeyboard */ + case 124: /*OmniModeOff */ + case 125: /*OmniModeon */ + case 126: /*MonoMode */ + case 127: /*PolyMode*/ + return; + + case 38 /*DATA_ENTRY_LSB*/: + c.MidiData = (ushort)((c.MidiData & 0x3F80) | controlValue); + if (c.MidiRpn == 0) + { + ChannelSetPitchRange(channel, (c.MidiData >> 7) + 0.01f * (c.MidiData & 0x7F)); + } + else if (c.MidiRpn == 1) + { + ChannelSetTuning(channel, (int)c.Tuning + (c.MidiData - 8192.0f) / 8192.0f); //fine tune + } + else if (c.MidiRpn == 2 && controller == 6) + { + ChannelSetTuning(channel, (controlValue - 64.0f) + (c.Tuning - (int)c.Tuning)); //coarse tune + } + + return; + + case 7 /*VOLUME_MSB*/: + c.MidiVolume = (ushort)((c.MidiVolume & 0x7F) | (controlValue << 7)); + //Raising to the power of 3 seems to result in a decent sounding volume curve for MIDI + ChannelSetVolume(channel, + (float)Math.Pow((c.MidiVolume / 16383.0f) * (c.MidiExpression / 16383.0f), 3.0f)); + return; + case 39 /*VOLUME_LSB*/: + c.MidiVolume = (ushort)((c.MidiVolume & 0x3F80) | controlValue); + //Raising to the power of 3 seems to result in a decent sounding volume curve for MIDI + ChannelSetVolume(channel, + (float)Math.Pow((c.MidiVolume / 16383.0f) * (c.MidiExpression / 16383.0f), 3.0f)); + return; + case 11 /*EXPRESSION_MSB*/: + c.MidiExpression = (ushort)((c.MidiExpression & 0x7F) | (controlValue << 7)); + //Raising to the power of 3 seems to result in a decent sounding volume curve for MIDI + ChannelSetVolume(channel, + (float)Math.Pow((c.MidiVolume / 16383.0f) * (c.MidiExpression / 16383.0f), 3.0f)); + return; + case 43 /*EXPRESSION_LSB*/: + c.MidiExpression = (ushort)((c.MidiExpression & 0x3F80) | controlValue); + //Raising to the power of 3 seems to result in a decent sounding volume curve for MIDI + ChannelSetVolume(channel, + (float)Math.Pow((c.MidiVolume / 16383.0f) * (c.MidiExpression / 16383.0f), 3.0f)); + return; + case 10 /*PAN_MSB*/: + c.MidiPan = (ushort)((c.MidiPan & 0x7F) | (controlValue << 7)); + ChannelSetPan(channel, c.MidiPan / 16383.0f); + return; + case 42 /*PAN_LSB*/: + c.MidiPan = (ushort)((c.MidiPan & 0x3F80) | controlValue); + ChannelSetPan(channel, c.MidiPan / 16383.0f); + return; + case 6 /*DATA_ENTRY_MSB*/: + c.MidiData = (ushort)((c.MidiData & 0x7F) | (controlValue << 7)); + if (c.MidiRpn == 0) + { + ChannelSetPitchRange(channel, (c.MidiData >> 7) + 0.01f * (c.MidiData & 0x7F)); + } + else if (c.MidiRpn == 1) + { + ChannelSetTuning(channel, (int)c.Tuning + (c.MidiData - 8192.0f) / 8192.0f); //fine tune + } + else if (c.MidiRpn == 2 && controller == 6) + { + ChannelSetTuning(channel, (controlValue - 64.0f) + (c.Tuning - (int)c.Tuning)); //coarse tune + } + + return; + case 0 /*BANK_SELECT_MSB*/: + c.Bank = (ushort)(0x8000 | controlValue); + return; //bank select MSB alone acts like LSB + case 32 /*BANK_SELECT_LSB*/: + c.Bank = (ushort)(((c.Bank & 0x8000) != 0 ? ((c.Bank & 0x7F) << 7) : 0) | controlValue); + return; + case 101 /*RPN_MSB*/: + c.MidiRpn = (ushort)(((c.MidiRpn == 0xFFFF ? 0 : c.MidiRpn) & 0x7F) | (controlValue << 7)); + // TODO + return; + case 100 /*RPN_LSB*/: + c.MidiRpn = (ushort)(((c.MidiRpn == 0xFFFF ? 0 : c.MidiRpn) & 0x3F80) | controlValue); + // TODO + return; + case 98 /*NRPN_LSB*/: + c.MidiRpn = 0xFFFF; + // TODO + return; + case 99 /*NRPN_MSB*/: + c.MidiRpn = 0xFFFF; + // TODO + return; + case 120 /*ALL_SOUND_OFF*/: + ChannelSoundsOffAll(channel); + return; + case 123 /*ALL_NOTES_OFF*/: + ChannelNoteOffAll(channel); + return; + case 121 /*ALL_CTRL_OFF*/: + c.MidiVolume = c.MidiExpression = 16383; + c.MidiPan = 8192; + c.Bank = 0; + ChannelSetVolume(channel, 1); + ChannelSetPan(channel, 0.5f); + ChannelSetPitchRange(channel, 2); + // TODO + return; + } + } + + /// + /// Gets the current preset index of the given channel. + /// + /// The channel index + /// The current preset index of the given channel. + public int ChannelGetPresetIndex(int channel) + { + return (_channels != null && channel < _channels.ChannelList.Count + ? _channels.ChannelList[channel].PresetIndex + : 0); + } + + /// + /// Gets the current bank of the given channel. + /// + /// The channel index + /// The current bank of the given channel. + public int ChannelGetPresetBank(int channel) + { + return (_channels != null && channel < _channels.ChannelList.Count + ? (_channels.ChannelList[channel].Bank & 0x7FFF) + : 0); + } + + /// + /// Gets the current pan of the given channel. + /// + /// The channel index + /// The current pan of the given channel. + public float ChannelGetPan(int channel) + { + return (_channels != null && channel < _channels.ChannelList.Count + ? _channels.ChannelList[channel].PanOffset - 0.5f + : 0.5f); + } + + /// + /// Gets the current volume of the given channel. + /// + /// The channel index + /// The current volune of the given channel. + public float ChannelGetVolume(int channel) + { + return (_channels != null && channel < _channels.ChannelList.Count + ? SynthHelper.DecibelsToGain(_channels.ChannelList[channel].GainDb) + : 1.0f); + } + + /// + /// Gets the current pitch wheel of the given channel. + /// + /// The channel index + /// The current pitch wheel of the given channel. + public int ChannelGetPitchWheel(int channel) + { + return (_channels != null && channel < _channels.ChannelList.Count + ? _channels.ChannelList[channel].PitchWheel + : 8192); + } + + /// + /// Gets the current pitch range of the given channel. + /// + /// The channel index + /// The current pitch range of the given channel. + public float ChannelGetPitchRange(int channel) + { + return (_channels != null && channel < _channels.ChannelList.Count + ? _channels.ChannelList[channel].PitchRange + : 2.0f); + } + + /// + /// Gets the current tuning of the given channel. + /// + /// The channel index + /// The current tuning of the given channel. + public float ChannelGetTuning(int channel) + { + return (_channels != null && channel < _channels.ChannelList.Count + ? _channels.ChannelList[channel].Tuning + : 0.0f); + } + + #endregion + + #region Loading + + public void LoadPresets(Hydra hydra) + { + _presets = new Preset[hydra.Phdrs.Count - 1]; + FontSamples = hydra.FontSamples; + + for (var phdrIndex = 0; phdrIndex < hydra.Phdrs.Count - 1; phdrIndex++) + { + var sortedIndex = 0; + var phdr = hydra.Phdrs[phdrIndex]; + + for (var otherPhdrIndex = 0; otherPhdrIndex < hydra.Phdrs.Count; otherPhdrIndex++) + { + var otherPhdr = hydra.Phdrs[otherPhdrIndex]; + if (otherPhdrIndex == phdrIndex || otherPhdr.Bank > phdr.Bank) + { + continue; + } + else if (otherPhdr.Bank < phdr.Bank) + { + sortedIndex++; + } + else if (otherPhdr.Preset > phdr.Preset) + { + continue; + } + else if (otherPhdr.Preset < phdr.Preset) + { + sortedIndex++; + } + else if (otherPhdrIndex < phdrIndex) + { + sortedIndex++; + } + } + + var regionIndex = 0; + + var preset = _presets[sortedIndex] = new Preset(); + preset.Name = phdr.PresetName; + preset.Bank = phdr.Bank; + preset.PresetNumber = phdr.Preset; + var regionNum = 0; + + for (int pbagIndex = phdr.PresetBagNdx; + pbagIndex < hydra.Phdrs[phdrIndex + 1].PresetBagNdx; + pbagIndex++) + { + var pbag = hydra.Pbags[pbagIndex]; + byte plokey = 0, phikey = 127, plovel = 0, phivel = 127; + + for (int pgenIndex = pbag.GenNdx; pgenIndex < hydra.Pbags[pbagIndex + 1].GenNdx; pgenIndex++) + { + var pgen = hydra.Pgens[pgenIndex]; + + if (pgen.GenOper == HydraPgen.GenKeyRange) + { + plokey = pgen.GenAmount.LowByteAmount; + phikey = pgen.GenAmount.HighByteAmount; + continue; + } + + + if (pgen.GenOper == HydraPgen.GenVelRange) + { + plovel = pgen.GenAmount.LowByteAmount; + phivel = pgen.GenAmount.HighByteAmount; + continue; + } + + if (pgen.GenOper != HydraPgen.GenInstrument) + { + continue; + } + + if (pgen.GenAmount.WordAmount >= hydra.Insts.Count) + { + continue; + } + + var pinst = hydra.Insts[pgen.GenAmount.WordAmount]; + for (int ibagIndex = pinst.InstBagNdx; + ibagIndex < hydra.Insts[pgen.GenAmount.WordAmount + 1].InstBagNdx; + ibagIndex++) + { + var ibag = hydra.Ibags[ibagIndex]; + + byte ilokey = 0, ihikey = 127, ilovel = 0, ihivel = 127; + for (int igenIndex = ibag.InstGenNdx; + igenIndex < hydra.Ibags[ibagIndex + 1].InstGenNdx; + igenIndex++) + { + var igen = hydra.Igens[igenIndex]; + if (igen.GenOper == HydraPgen.GenKeyRange) + { + ilokey = igen.GenAmount.LowByteAmount; + ihikey = igen.GenAmount.HighByteAmount; + continue; + } + + + if (igen.GenOper == HydraPgen.GenVelRange) + { + ilovel = igen.GenAmount.LowByteAmount; + ihivel = igen.GenAmount.HighByteAmount; + continue; + } + + if (igen.GenOper == HydraPgen.GenSampleId && ihikey >= plokey && ilokey <= phikey && + ihivel >= plovel && ilovel <= phivel) + { + regionNum++; + } + } + } + } + } + + preset.Regions = new Region[regionNum]; + + var globalRegion = new Region(); + globalRegion.Clear(true); + + // Zones. + for (int pbagIndex = phdr.PresetBagNdx; + pbagIndex < hydra.Phdrs[phdrIndex + 1].PresetBagNdx; + pbagIndex++) + { + var pbag = hydra.Pbags[pbagIndex]; + + var presetRegion = new Region(globalRegion); + var hadGenInstrument = false; + + // Generators. + for (int pgenIndex = pbag.GenNdx; pgenIndex < hydra.Pbags[pbagIndex + 1].GenNdx; pgenIndex++) + { + var pgen = hydra.Pgens[pgenIndex]; + + // Instrument. + if (pgen.GenOper == HydraPgen.GenInstrument) + { + var whichInst = pgen.GenAmount.WordAmount; + if (whichInst >= hydra.Insts.Count) + { + continue; + } + + var instRegion = new Region(); + instRegion.Clear(false); + + // Generators + var inst = hydra.Insts[whichInst]; + for (int ibagIndex = inst.InstBagNdx; + ibagIndex < hydra.Insts[whichInst + 1].InstBagNdx; + ibagIndex++) + { + var ibag = hydra.Ibags[ibagIndex]; + var zoneRegion = new Region(instRegion); + var hadSampleId = false; + + for (int igenIndex = ibag.InstGenNdx; + igenIndex < hydra.Ibags[ibagIndex + 1].InstGenNdx; + igenIndex++) + { + var igen = hydra.Igens[igenIndex]; + + if (igen.GenOper == HydraPgen.GenSampleId) + { + //preset region key and vel ranges are a filter for the zone regions + if (zoneRegion.HiKey < presetRegion.LoKey || + zoneRegion.LoKey > presetRegion.HiKey) + { + continue; + } + + if (zoneRegion.HiVel < presetRegion.LoVel || + zoneRegion.LoVel > presetRegion.HiVel) + { + continue; + } + + if (presetRegion.LoKey > zoneRegion.LoKey) + { + zoneRegion.LoKey = presetRegion.LoKey; + } + + if (presetRegion.HiKey < zoneRegion.HiKey) + { + zoneRegion.HiKey = presetRegion.HiKey; + } + + if (presetRegion.LoVel > zoneRegion.LoVel) + { + zoneRegion.LoVel = presetRegion.LoVel; + } + + if (presetRegion.HiVel < zoneRegion.HiVel) + { + zoneRegion.HiVel = presetRegion.HiVel; + } + + //sum regions + zoneRegion.Offset += presetRegion.Offset; + zoneRegion.End += presetRegion.End; + zoneRegion.LoopStart += presetRegion.LoopStart; + zoneRegion.LoopEnd += presetRegion.LoopEnd; + zoneRegion.Transpose += presetRegion.Transpose; + zoneRegion.Tune += presetRegion.Tune; + zoneRegion.PitchKeyTrack += presetRegion.PitchKeyTrack; + zoneRegion.Attenuation += presetRegion.Attenuation; + zoneRegion.Pan += presetRegion.Pan; + zoneRegion.AmpEnv.Delay += presetRegion.AmpEnv.Delay; + zoneRegion.AmpEnv.Attack += presetRegion.AmpEnv.Attack; + zoneRegion.AmpEnv.Hold += presetRegion.AmpEnv.Hold; + zoneRegion.AmpEnv.Decay += presetRegion.AmpEnv.Decay; + zoneRegion.AmpEnv.Sustain += presetRegion.AmpEnv.Sustain; + zoneRegion.AmpEnv.Release += presetRegion.AmpEnv.Release; + zoneRegion.ModEnv.Delay += presetRegion.ModEnv.Delay; + zoneRegion.ModEnv.Attack += presetRegion.ModEnv.Attack; + zoneRegion.ModEnv.Hold += presetRegion.ModEnv.Hold; + zoneRegion.ModEnv.Decay += presetRegion.ModEnv.Decay; + zoneRegion.ModEnv.Sustain += presetRegion.ModEnv.Sustain; + zoneRegion.ModEnv.Release += presetRegion.ModEnv.Release; + zoneRegion.InitialFilterQ += presetRegion.InitialFilterQ; + zoneRegion.InitialFilterFc += presetRegion.InitialFilterFc; + zoneRegion.ModEnvToPitch += presetRegion.ModEnvToPitch; + zoneRegion.ModEnvToFilterFc += presetRegion.ModEnvToFilterFc; + zoneRegion.DelayModLFO += presetRegion.DelayModLFO; + zoneRegion.FreqModLFO += presetRegion.FreqModLFO; + zoneRegion.ModLfoToPitch += presetRegion.ModLfoToPitch; + zoneRegion.ModLfoToFilterFc += presetRegion.ModLfoToFilterFc; + zoneRegion.ModLfoToVolume += presetRegion.ModLfoToVolume; + zoneRegion.DelayVibLFO += presetRegion.DelayVibLFO; + zoneRegion.FreqVibLFO += presetRegion.FreqVibLFO; + zoneRegion.VibLfoToPitch += presetRegion.VibLfoToPitch; + + // EG times need to be converted from timecents to seconds. + zoneRegion.AmpEnv.EnvToSecs(true); + zoneRegion.ModEnv.EnvToSecs(false); + + // LFO times need to be converted from timecents to seconds. + zoneRegion.DelayModLFO = (zoneRegion.DelayModLFO < -11950.0f + ? 0.0f + : SynthHelper.Timecents2Secs(zoneRegion.DelayModLFO)); + zoneRegion.DelayVibLFO = (zoneRegion.DelayVibLFO < -11950.0f + ? 0.0f + : SynthHelper.Timecents2Secs(zoneRegion.DelayVibLFO)); + + // Pin values to their ranges. + if (zoneRegion.Pan < -0.5f) + { + zoneRegion.Pan = -0.5f; + } + else if (zoneRegion.Pan > 0.5f) + { + zoneRegion.Pan = 0.5f; + } + + if (zoneRegion.InitialFilterQ < 1500 || zoneRegion.InitialFilterQ > 13500) + { + zoneRegion.InitialFilterQ = 0; + } + + var shdr = hydra.SHdrs[igen.GenAmount.WordAmount]; + zoneRegion.Offset += shdr.Start; + zoneRegion.End += shdr.End; + zoneRegion.LoopStart += shdr.StartLoop; + zoneRegion.LoopEnd += shdr.EndLoop; + if (shdr.EndLoop > 0) + { + zoneRegion.LoopEnd -= 1; + } + + if (zoneRegion.PitchKeyCenter == -1) + { + zoneRegion.PitchKeyCenter = shdr.OriginalPitch; + } + + zoneRegion.Tune += shdr.PitchCorrection; + zoneRegion.SampleRate = shdr.SampleRate; + if (zoneRegion.End != 0 && zoneRegion.End < FontSamples.Length) + { + zoneRegion.End++; + } + else + { + zoneRegion.End = (uint)FontSamples.Length; + } + + preset.Regions[regionIndex] = new Region(zoneRegion); + regionIndex++; + + hadSampleId = true; + } + else + { + zoneRegion.Operator(igen.GenOper, igen.GenAmount); + } + } + + // Handle instrument's global zone. + if (ibag == hydra.Ibags[inst.InstBagNdx] && !hadSampleId) + { + instRegion = new Region(zoneRegion); + } + + // Modulators (TODO) + //if (ibag->instModNdx < ibag[1].instModNdx) addUnsupportedOpcode("any modulator"); + } + + hadGenInstrument = true; + } + else + { + presetRegion.Operator(pgen.GenOper, pgen.GenAmount); + } + } + + + // Modulators (TODO) + //if (pbag->modNdx < pbag[1].modNdx) addUnsupportedOpcode("any modulator"); + + // Handle preset's global zone. + if (pbag == hydra.Pbags[phdr.PresetBagNdx] && !hadGenInstrument) + { + globalRegion = presetRegion; + } + } + } + } + + #endregion + } +} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Voice.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Voice.cs index 1efa5822b..4282011e9 100644 --- a/Source/AlphaTab/Audio/Synth/Synthesis/Voice.cs +++ b/Source/AlphaTab/Audio/Synth/Synthesis/Voice.cs @@ -1,85 +1,322 @@ -using AlphaTab.Audio.Synth.Bank.Patch; +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +using AlphaTab.Audio.Synth.Util; namespace AlphaTab.Audio.Synth.Synthesis { - internal enum VoiceStateEnum + internal partial class Voice { - Stopped = 0, - Stopping = 1, - Playing = 2 - } + /// + /// The lower this block size is the more accurate the effects are. + /// Increasing the value significantly lowers the CPU usage of the voice rendering. + /// If LFO affects the low-pass filter it can be hearable even as low as 8. + /// + private const int RenderEffectSampleBLock = 64; - internal class Voice - { - public Patch Patch { get; private set; } - public VoiceParameters VoiceParams { get; private set; } + public int PlayingPreset { get; set; } + public int PlayingKey { get; set; } + public int PlayingChannel { get; set; } + + public Region Region { get; set; } + + public double PitchInputTimecents { get; set; } + public double PitchOutputFactor { get; set; } + public double SourceSamplePosition { get; set; } + + public float NoteGainDb { get; set; } + public float PanFactorLeft { get; set; } + public float PanFactorRight { get; set; } + + public uint PlayIndex { get; set; } + public uint LoopStart { get; set; } + public uint LoopEnd { get; set; } + + public VoiceEnvelope AmpEnv { get; set; } + public VoiceEnvelope ModEnv { get; set; } + + public VoiceLowPass LowPass { get; set; } + public VoiceLfo ModLfo { get; set; } + public VoiceLfo VibLfo { get; set; } + + public float MixVolume { get; set; } + public bool Mute { get; set; } public Voice() { - VoiceParams = new VoiceParameters(); + AmpEnv = new VoiceEnvelope(); + ModEnv = new VoiceEnvelope(); + LowPass = new VoiceLowPass(); + ModLfo = new VoiceLfo(); + VibLfo = new VoiceLfo(); } - public void Start() + public void CalcPitchRatio(float pitchShift, float outSampleRate) { - if (VoiceParams.State != VoiceStateEnum.Stopped) - { - return; - } - - if (Patch.Start(VoiceParams)) - { - VoiceParams.State = VoiceStateEnum.Playing; - } + var note = PlayingKey + Region.Transpose + Region.Tune / 100.0; + var adjustedPitch = Region.PitchKeyCenter + (note - Region.PitchKeyCenter) * (Region.PitchKeyTrack / 100.0); + if (pitchShift != 0) adjustedPitch += pitchShift; + PitchInputTimecents = adjustedPitch * 100.0; + PitchOutputFactor = Region.SampleRate / (SynthHelper.Timecents2Secs(Region.PitchKeyCenter * 100.0) * outSampleRate); } - public void Stop() + public void End(float outSampleRate) { - if (VoiceParams.State != VoiceStateEnum.Playing) + AmpEnv.NextSegment(VoiceEnvelopeSegment.Sustain, outSampleRate); + ModEnv.NextSegment(VoiceEnvelopeSegment.Sustain, outSampleRate); + if (Region.LoopMode == LoopMode.Sustain) { - return; + // Continue playing, but stop looping. + LoopEnd = LoopStart; } - - VoiceParams.State = VoiceStateEnum.Stopping; - Patch.Stop(VoiceParams); } - public void StopImmediately() + public void EndQuick(float outSampleRate) { - VoiceParams.State = VoiceStateEnum.Stopped; + AmpEnv.Parameters.Release = 0.0f; + AmpEnv.NextSegment(VoiceEnvelopeSegment.Sustain, outSampleRate); + ModEnv.Parameters.Release = 0.0f; + ModEnv.NextSegment(VoiceEnvelopeSegment.Sustain, outSampleRate); } - public void Process(int startIndex, int endIndex, bool isMuted) + public void Render(TinySoundFont f, float[] outputBuffer, int offset, int numSamples, bool isMuted) { - //do not process if the voice is stopped - if (VoiceParams.State == VoiceStateEnum.Stopped) + // https://github.com/FluidSynth/fluidsynth/blob/5fe56b32b27c9aa52932eee5fdabe57dc54b6115/src/rvoice/fluid_rvoice.c#L304 + var region = Region; + var input = f.FontSamples; + var outL = 0; + var outR = f.OutputMode == OutputMode.StereoUnweaved ? numSamples : -1; + + // Cache some values, to give them at least some chance of ending up in registers. + var updateModEnv = (region.ModEnvToPitch != 0 || region.ModEnvToFilterFc != 0); + var updateModLFO = (ModLfo.Delta > 0 && (region.ModLfoToPitch != 0 || region.ModLfoToFilterFc != 0 || region.ModLfoToVolume != 0)); + var updateVibLFO = (VibLfo.Delta > 0 && (region.VibLfoToPitch != 0)); + var isLooping = (LoopStart < LoopEnd); + int tmpLoopStart = (int)LoopStart, tmpLoopEnd = (int)LoopEnd; + double tmpSampleEndDbl = (double)region.End, tmpLoopEndDbl = (double)tmpLoopEnd + 1.0; + var tmpSourceSamplePosition = SourceSamplePosition; + + var tmpLowpass = new VoiceLowPass(LowPass); + + var dynamicLowpass = (region.ModLfoToFilterFc != 0 || region.ModEnvToFilterFc != 0); + float tmpSampleRate, tmpInitialFilterFc, tmpModLfoToFilterFc, tmpModEnvToFilterFc; + + var dynamicPitchRatio = (region.ModLfoToPitch != 0 || region.ModEnvToPitch != 0 || region.VibLfoToPitch != 0); + double pitchRatio; + float tmpModLfoToPitch, tmpVibLfoToPitch, tmpModEnvToPitch; + + var dynamicGain = (region.ModLfoToVolume != 0); + float noteGain = 0, tmpModLfoToVolume; + + if (dynamicLowpass) { - return; + tmpSampleRate = f.OutSampleRate; + tmpInitialFilterFc = (float)region.InitialFilterFc; + tmpModLfoToFilterFc = (float)region.ModLfoToFilterFc; + tmpModEnvToFilterFc = (float)region.ModEnvToFilterFc; + } + else + { + tmpSampleRate = 0; + tmpInitialFilterFc = 0; + tmpModLfoToFilterFc = 0; + tmpModEnvToFilterFc = 0; } - //process using the patch's algorithm - Patch.Process(VoiceParams, startIndex, endIndex, isMuted, false); - } + if (dynamicPitchRatio) + { + pitchRatio = 0; + tmpModLfoToPitch = (float)region.ModLfoToPitch; + tmpVibLfoToPitch = (float)region.VibLfoToPitch; + tmpModEnvToPitch = (float)region.ModEnvToPitch; + } + else + { + pitchRatio = SynthHelper.Timecents2Secs(PitchInputTimecents) * PitchOutputFactor; + tmpModLfoToPitch = 0; + tmpVibLfoToPitch = 0; + tmpModEnvToPitch = 0; + } - public void ProcessSilent(int startIndex, int endIndex) - { - //do not process if the voice is stopped - if (VoiceParams.State == VoiceStateEnum.Stopped) + if (dynamicGain) + { + tmpModLfoToVolume = (float)region.ModLfoToVolume * 0.1f; + } + else { - return; + noteGain = SynthHelper.DecibelsToGain(NoteGainDb); + tmpModLfoToVolume = 0; } - //process using the patch's algorithm - Patch.Process(VoiceParams, startIndex, endIndex, true, true); + while (numSamples > 0) + { + float gainMono, gainLeft, gainRight; + var blockSamples = (numSamples > RenderEffectSampleBLock ? RenderEffectSampleBLock : numSamples); + numSamples -= blockSamples; + + if (dynamicLowpass) + { + var fres = tmpInitialFilterFc + ModLfo.Level * tmpModLfoToFilterFc + ModEnv.Level * tmpModEnvToFilterFc; + tmpLowpass.Active = (fres <= 13500.0f); + if (tmpLowpass.Active) + { + tmpLowpass.Setup(SynthHelper.Cents2Hertz(fres) / tmpSampleRate); + } + } + + if (dynamicPitchRatio) + pitchRatio = SynthHelper.Timecents2Secs(PitchInputTimecents + (ModLfo.Level * tmpModLfoToPitch + VibLfo.Level * tmpVibLfoToPitch + ModEnv.Level * tmpModEnvToPitch)) * PitchOutputFactor; + + if (dynamicGain) + noteGain = SynthHelper.DecibelsToGain(NoteGainDb + (ModLfo.Level * tmpModLfoToVolume)); + + gainMono = noteGain * AmpEnv.Level; + + if (isMuted) + { + gainMono = 0; + } + else + { + gainMono *= MixVolume; + } + + // Update EG. + AmpEnv.Process(blockSamples, f.OutSampleRate); + if (updateModEnv) + { + ModEnv.Process(blockSamples, f.OutSampleRate); + } + + // Update LFOs. + if (updateModLFO) + { + ModLfo.Process(blockSamples); + } + + if (updateVibLFO) + { + VibLfo.Process(blockSamples); + } + + switch (f.OutputMode) + { + case OutputMode.StereoInterleaved: + gainLeft = gainMono * PanFactorLeft; + gainRight = gainMono * PanFactorRight; + while (blockSamples-- > 0 && tmpSourceSamplePosition < tmpSampleEndDbl) + { + var pos = (int)tmpSourceSamplePosition; + var nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1); + + // Simple linear interpolation. + + // TODO: check for interpolation mode on voice + // https://github.com/FluidSynth/fluidsynth/blob/5fe56b32b27c9aa52932eee5fdabe57dc54b6115/src/rvoice/fluid_rvoice.c#L434 + float alpha = (float)(tmpSourceSamplePosition - pos), val = (input[pos] * (1.0f - alpha) + input[nextPos] * alpha); + + // Low-pass filter. + if (tmpLowpass.Active) val = tmpLowpass.Process(val); + + outputBuffer[offset + outL] += val * gainLeft; + outL++; + outputBuffer[offset + outL] += val * gainRight; + outL++; + + // Next sample. + tmpSourceSamplePosition += pitchRatio; + if (tmpSourceSamplePosition >= tmpLoopEndDbl && isLooping) tmpSourceSamplePosition -= (tmpLoopEnd - tmpLoopStart + 1.0); + } + break; + case OutputMode.StereoUnweaved: + gainLeft = gainMono * PanFactorLeft; + gainRight = gainMono * PanFactorRight; + while (blockSamples-- > 0 && tmpSourceSamplePosition < tmpSampleEndDbl) + { + var pos = (int)tmpSourceSamplePosition; + var nextPos = (int)(pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1); + + // Simple linear interpolation. + float alpha = (float)(tmpSourceSamplePosition - pos), val = (input[pos] * (1.0f - alpha) + input[nextPos] * alpha); + + // Low-pass filter. + if (tmpLowpass.Active) val = tmpLowpass.Process(val); + + outputBuffer[offset + outL] += val * gainLeft; + outL++; + outputBuffer[offset + outR] += val * gainRight; + outR++; + + // Next sample. + tmpSourceSamplePosition += pitchRatio; + if (tmpSourceSamplePosition >= tmpLoopEndDbl && isLooping) tmpSourceSamplePosition -= (tmpLoopEnd - tmpLoopStart + 1.0); + } + break; + case OutputMode.Mono: + while (blockSamples-- > 0 && tmpSourceSamplePosition < tmpSampleEndDbl) + { + int pos = (int)tmpSourceSamplePosition; + int nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1); + + // Simple linear interpolation. + float alpha = (float)(tmpSourceSamplePosition - pos), val = (input[pos] * (1.0f - alpha) + input[nextPos] * alpha); + + // Low-pass filter. + if (tmpLowpass.Active) val = tmpLowpass.Process(val); + + outputBuffer[offset + outL] = val * gainMono; + outL++; + + // Next sample. + tmpSourceSamplePosition += pitchRatio; + if (tmpSourceSamplePosition >= tmpLoopEndDbl && isLooping) tmpSourceSamplePosition -= (tmpLoopEnd - tmpLoopStart + 1.0); + } + break; + } + + if (tmpSourceSamplePosition >= tmpSampleEndDbl || AmpEnv.Segment == VoiceEnvelopeSegment.Done) + { + Kill(); + return; + } + } + + SourceSamplePosition = tmpSourceSamplePosition; + if (tmpLowpass.Active || dynamicLowpass) + { + LowPass = tmpLowpass; + } } - public void Configure(int channel, int note, int velocity, Patch patch, SynthParameters synthParams) + public void Kill() { - VoiceParams.Reset(); - VoiceParams.Channel = channel; - VoiceParams.Note = note; - VoiceParams.Velocity = velocity; - VoiceParams.SynthParams = synthParams; - Patch = patch; + PlayingPreset = -1; } } } diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelope.cs b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelope.cs new file mode 100644 index 000000000..5e09fb3ce --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelope.cs @@ -0,0 +1,215 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using System; +using AlphaTab.Audio.Synth.Util; + +namespace AlphaTab.Audio.Synth.Synthesis +{ + internal class VoiceEnvelope + { + private const float FastReleaseTime = 0.01f; + public float Level { get; set; } + public float Slope { get; set; } + + public int SamplesUntilNextSegment { get; set; } + + public VoiceEnvelopeSegment Segment { get; set; } + public short MidiVelocity { get; set; } + + public Envelope Parameters { get; set; } + + public bool SegmentIsExponential { get; set; } + public bool IsAmpEnv { get; set; } + + public void NextSegment(VoiceEnvelopeSegment activeSegment, float outSampleRate) + { + while (true) // if segment is handled, method will return + { + switch (activeSegment) + { + case VoiceEnvelopeSegment.None: + SamplesUntilNextSegment = (int)(Parameters.Delay * outSampleRate); + if (SamplesUntilNextSegment > 0) + { + Segment = VoiceEnvelopeSegment.Delay; + SegmentIsExponential = false; + Level = 0.0f; + Slope = 0.0f; + return; + } + activeSegment = VoiceEnvelopeSegment.Delay; + break; + + case VoiceEnvelopeSegment.Delay: + SamplesUntilNextSegment = (int)(Parameters.Attack * outSampleRate); + if (SamplesUntilNextSegment > 0) + { + if (!this.IsAmpEnv) + { + //mod env attack duration scales with velocity (velocity of 1 is full duration, max velocity is 0.125 times duration) + SamplesUntilNextSegment = + (int)(Parameters.Attack * ((145 - this.MidiVelocity) / 144.0f) * outSampleRate); + } + + Segment = VoiceEnvelopeSegment.Attack; + SegmentIsExponential = false; + Level = 0.0f; + Slope = 1.0f / SamplesUntilNextSegment; + return; + } + + activeSegment = VoiceEnvelopeSegment.Attack; + break; + + case VoiceEnvelopeSegment.Attack: + SamplesUntilNextSegment = (int)(Parameters.Hold * outSampleRate); + if (SamplesUntilNextSegment > 0) + { + Segment = VoiceEnvelopeSegment.Hold; + SegmentIsExponential = false; + Level = 1.0f; + Slope = 0.0f; + return; + } + + activeSegment = VoiceEnvelopeSegment.Hold; + break; + + case VoiceEnvelopeSegment.Hold: + SamplesUntilNextSegment = (int)(Parameters.Decay * outSampleRate); + if (SamplesUntilNextSegment > 0) + { + Segment = VoiceEnvelopeSegment.Decay; + Level = 1.0f; + if (this.IsAmpEnv) + { + // I don't truly understand this; just following what LinuxSampler does. + var mysterySlope = (float)(-9.226 / SamplesUntilNextSegment); + Slope = (float)Math.Exp(mysterySlope); + SegmentIsExponential = true; + if (Parameters.Sustain > 0.0f) + { + // Again, this is following LinuxSampler's example, which is similar to + // SF2-style decay, where "decay" specifies the time it would take to + // get to zero, not to the sustain level. The SFZ spec is not that + // specific about what "decay" means, so perhaps it's really supposed + // to specify the time to reach the sustain level. + SamplesUntilNextSegment = (int)(Math.Log(Parameters.Sustain) / mysterySlope); + } + } + else + { + Slope = (float)(-1.0 / SamplesUntilNextSegment); + SamplesUntilNextSegment = + (int)(Parameters.Decay * (1.0f - Parameters.Sustain) * outSampleRate); + SegmentIsExponential = false; + } + + return; + } + + activeSegment = VoiceEnvelopeSegment.Decay; + break; + + case VoiceEnvelopeSegment.Decay: + Segment = VoiceEnvelopeSegment.Sustain; + Level = Parameters.Sustain; + Slope = 0.0f; + SamplesUntilNextSegment = 0x7FFFFFFF; + SegmentIsExponential = false; + return; + case VoiceEnvelopeSegment.Sustain: + Segment = VoiceEnvelopeSegment.Release; + SamplesUntilNextSegment = + (int)((Parameters.Release <= 0 ? FastReleaseTime : Parameters.Release) * outSampleRate); + if (this.IsAmpEnv) + { + // I don't truly understand this; just following what LinuxSampler does. + var mysterySlope = (float)(-9.226 / SamplesUntilNextSegment); + Slope = (float)Math.Exp(mysterySlope); + SegmentIsExponential = true; + } + else + { + Slope = -Level / SamplesUntilNextSegment; + SegmentIsExponential = false; + } + + return; + case VoiceEnvelopeSegment.Release: + default: + Segment = VoiceEnvelopeSegment.Done; + SegmentIsExponential = false; + Level = Slope = 0.0f; + SamplesUntilNextSegment = 0x7FFFFFF; + return; + } + } + } + + public void Setup( + Envelope newParameters, + int midiNoteNumber, + int midiVelocity, + bool isAmpEnv, + float outSampleRate) + { + Parameters = new Envelope(newParameters); + if (Parameters.KeynumToHold > 0) + { + Parameters.Hold += Parameters.KeynumToHold * (60.0f - midiNoteNumber); + Parameters.Hold = Parameters.Hold < -10000.0f ? 0.0f : SynthHelper.Timecents2Secs(Parameters.Hold); + } + + if (Parameters.KeynumToDecay > 0) + { + Parameters.Decay += Parameters.KeynumToDecay * (60.0f - midiNoteNumber); + Parameters.Decay = Parameters.Decay < -10000.0f ? 0.0f : SynthHelper.Timecents2Secs(Parameters.Decay); + } + + MidiVelocity = (short)midiVelocity; + IsAmpEnv = isAmpEnv; + NextSegment(VoiceEnvelopeSegment.None, outSampleRate); + } + + public void Process(int numSamples, float outSampleRate) + { + if (Slope > 0) + { + if (SegmentIsExponential) Level *= (float)Math.Pow(Slope, numSamples); + else Level += (Slope * numSamples); + } + + if ((SamplesUntilNextSegment -= numSamples) <= 0) + { + NextSegment(Segment, outSampleRate); + } + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelopeSegment.cs b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelopeSegment.cs new file mode 100644 index 000000000..0e6969ee8 --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelopeSegment.cs @@ -0,0 +1,41 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace AlphaTab.Audio.Synth.Synthesis +{ + internal enum VoiceEnvelopeSegment + { + None, + Delay, + Attack, + Hold, + Decay, + Sustain, + Release, Done + } +} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLfo.cs b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLfo.cs new file mode 100644 index 000000000..927f3c5ce --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLfo.cs @@ -0,0 +1,67 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +using AlphaTab.Audio.Synth.Util; + +namespace AlphaTab.Audio.Synth.Synthesis +{ + internal class VoiceLfo + { + public int SamplesUntil { get; set; } + public float Level { get; set; } + public float Delta { get; set; } + + public void Setup(float delay, int freqCents, float outSampleRate) + { + SamplesUntil = (int)(delay * outSampleRate); + Delta = (4.0f * SynthHelper.Cents2Hertz(freqCents) / outSampleRate); + Level = 0; + } + + public void Process(int blockSamples) + { + if (SamplesUntil > blockSamples) + { + SamplesUntil -= blockSamples; + return; + } + Level += Delta * blockSamples; + if (Level > 1.0f) + { + Delta = -Delta; + Level = 2.0f - Level; + } + else if (Level < -1.0f) + { + Delta = -Delta; + Level = -2.0f - Level; + } + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLowPass.cs b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLowPass.cs new file mode 100644 index 000000000..f71e6a49f --- /dev/null +++ b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLowPass.cs @@ -0,0 +1,79 @@ +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using System; + +namespace AlphaTab.Audio.Synth.Synthesis +{ + internal class VoiceLowPass + { + public double QInv { get; set; } + public double A0 { get; set; } + public double A1 { get; set; } + public double B1 { get; set; } + public double B2 { get; set; } + public double Z1 { get; set; } + public double Z2 { get; set; } + public bool Active { get; set; } + + public VoiceLowPass() + { + } + + public VoiceLowPass(VoiceLowPass other) + { + QInv = other.QInv; + A0 = other.A0; + A1 = other.A1; + B1 = other.B1; + B2 = other.B2; + Z1 = other.Z1; + Z2 = other.Z2; + Active = other.Active; + } + + public void Setup(float fc) + { + // Lowpass filter from http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ + double k = Math.Tan(Math.PI * fc), KK = k * k; + var norm = 1 / (1 + k * QInv + KK); + A0 = KK * norm; + A1 = 2 * A0; + B1 = 2 * (KK - 1) * norm; + B2 = (1 - k * QInv + KK) * norm; + } + + public float Process(float input) + { + var output = input * A0 + Z1; + Z1 = input * A1 + Z2 -B1 * output; + Z2 = input * A0 - B2 * output; + return (float)output; + } + } +} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceManager.cs b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceManager.cs deleted file mode 100644 index a075d466a..000000000 --- a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceManager.cs +++ /dev/null @@ -1,174 +0,0 @@ -using AlphaTab.Audio.Synth.Ds; -using AlphaTab.Audio.Synth.Util; - -namespace AlphaTab.Audio.Synth.Synthesis -{ - internal class VoiceNode - { - public Voice Value { get; set; } - public VoiceNode Next { get; set; } - } - - internal class VoiceManager - { - private Voice[] _voicePool; - private LinkedList _vNodes; - - public int Polyphony { get; set; } - public LinkedList FreeVoices { get; set; } - public LinkedList ActiveVoices { get; set; } - public VoiceNode[][] Registry { get; set; } - - public VoiceManager(int voiceCount) - { - Polyphony = voiceCount; - - _voicePool = new Voice[voiceCount]; - _vNodes = new LinkedList(); - FreeVoices = new LinkedList(); - ActiveVoices = new LinkedList(); - - for (var i = 0; i < voiceCount; i++) - { - var v = new Voice(); - _voicePool[i] = v; - _vNodes.AddLast(new VoiceNode()); - FreeVoices.AddLast(v); - } - - Registry = new VoiceNode[SynthConstants.DefaultChannelCount][]; - for (var i = 0; i < Registry.Length; i++) - { - Registry[i] = new VoiceNode[SynthConstants.DefaultKeyCount]; - } - } - - public Voice GetFreeVoice() - { - if (FreeVoices.Length > 0) - { - var voice = FreeVoices.First.Value; - FreeVoices.RemoveFirst(); - return voice; - } - - return StealQuietestVoice(); - } - - public void AddToRegistry(Voice voice) - { - var node = _vNodes.RemoveLast(); - node.Value = voice; - node.Next = Registry[voice.VoiceParams.Channel][voice.VoiceParams.Note]; - Registry[voice.VoiceParams.Channel][voice.VoiceParams.Note] = node; - } - - public void RemoveFromRegistry(int channel, int note) - { - var node = Registry[channel][note]; - while (node != null) - { - _vNodes.AddLast(node); - node = node.Next; - } - - Registry[channel][note] = null; - } - - public void RemoveVoiceFromRegistry(Voice voice) - { - var node = Registry[voice.VoiceParams.Channel][voice.VoiceParams.Note]; - if (node == null) - { - return; - } - - if (node.Value == voice) - { - Registry[voice.VoiceParams.Channel][voice.VoiceParams.Note] = node.Next; - _vNodes.AddLast(node); - } - else - { - var node2 = node; - node = node.Next; - while (node != null) - { - if (node.Value == voice) - { - node2.Next = node.Next; - _vNodes.AddLast(node); - return; - } - - node2 = node; - node = node.Next; - } - } - } - - public void ClearRegistry() - { - var node = ActiveVoices.First; - while (node != null) - { - var vnode = Registry[node.Value.VoiceParams.Channel][node.Value.VoiceParams.Note]; - while (vnode != null) - { - _vNodes.AddLast(vnode); - vnode = vnode.Next; - } - - Registry[node.Value.VoiceParams.Channel][node.Value.VoiceParams.Note] = null; - node = node.Next; - } - } - - public void UnloadPatches() - { - foreach (var v in _voicePool) - { - v.Configure(0, 0, 0, null, null); - var current = _vNodes.First; - while (current != null) - { - current.Value.Value = null; - current = current.Next; - } - } - } - - private Voice StealQuietestVoice() - { - var voiceVolume = 1000.0; - LinkedListNode quietest = null; - var node = ActiveVoices.First; - while (node != null) - { - if (node.Value.VoiceParams.State != VoiceStateEnum.Playing) - { - var volume = node.Value.VoiceParams.CombinedVolume; - if (volume < voiceVolume) - { - quietest = node; - voiceVolume = volume; - } - } - - node = node.Next; - } - - if (quietest == null) - { - quietest = ActiveVoices.First; - } - - //check and remove from registry - RemoveVoiceFromRegistry(quietest.Value); - ActiveVoices.Remove(quietest); - //stop voice if it is not already - quietest.Value.VoiceParams.State = VoiceStateEnum.Stopped; - return quietest.Value; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceParameters.cs b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceParameters.cs deleted file mode 100644 index a10b4bde6..000000000 --- a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceParameters.cs +++ /dev/null @@ -1,111 +0,0 @@ -using AlphaTab.Audio.Synth.Bank.Components; -using AlphaTab.Audio.Synth.Bank.Components.Generators; -using AlphaTab.Audio.Synth.Ds; -using AlphaTab.Audio.Synth.Util; -using AlphaTab.Utils; - -namespace AlphaTab.Audio.Synth.Synthesis -{ - internal class VoiceParameters - { - private float _mix1; - private float _mix2; - - public int Channel { get; set; } - public int Note { get; set; } - public int Velocity { get; set; } - public bool NoteOffPending { get; set; } - public VoiceStateEnum State { get; set; } - public int PitchOffset { get; set; } - public float VolOffset { get; set; } - public SampleArray BlockBuffer { get; set; } - - public UnionData[] PData { get; set; } - public SynthParameters SynthParams { get; set; } - public GeneratorParameters[] GeneratorParams { get; set; } - public Envelope[] Envelopes { get; set; } - public Filter[] Filters { get; set; } - public Lfo[] Lfos { get; set; } - - public float CombinedVolume => _mix1 + _mix2; - - public VoiceParameters() - { - BlockBuffer = new SampleArray(SynthConstants.DefaultBlockSize); - //create default number of each component - PData = new UnionData[SynthConstants.MaxVoiceComponents]; - GeneratorParams = new GeneratorParameters[SynthConstants.MaxVoiceComponents]; - Envelopes = new Envelope[SynthConstants.MaxVoiceComponents]; - Filters = new Filter[SynthConstants.MaxVoiceComponents]; - Lfos = new Lfo[SynthConstants.MaxVoiceComponents]; - //initialize each component - for (var x = 0; x < SynthConstants.MaxVoiceComponents; x++) - { - GeneratorParams[x] = new GeneratorParameters(); - Envelopes[x] = new Envelope(); - Filters[x] = new Filter(); - Lfos[x] = new Lfo(); - } - } - - public void Reset() - { - NoteOffPending = false; - PitchOffset = 0; - VolOffset = 0; - for (var i = 0; i < PData.Length; i++) - { - PData[i] = new UnionData(); - } - - _mix1 = 0; - _mix2 = 0; - } - - public void MixMonoToMonoInterp(int startIndex, float volume) - { - var inc = (volume - _mix1) / SynthConstants.DefaultBlockSize; - for (var i = 0; i < BlockBuffer.Length; i++) - { - _mix1 += inc; - SynthParams.Synth.SampleBuffer[startIndex + i] += BlockBuffer[i] * _mix1; - } - - _mix1 = volume; - } - - public void MixMonoToStereoInterp(int startIndex, float leftVol, float rightVol) - { - var incL = (leftVol - _mix1) / SynthConstants.DefaultBlockSize; - var incR = (rightVol - _mix2) / SynthConstants.DefaultBlockSize; - for (var i = 0; i < BlockBuffer.Length; i++) - { - _mix1 += incL; - _mix2 += incR; - SynthParams.Synth.SampleBuffer[startIndex] += BlockBuffer[i] * _mix1; - SynthParams.Synth.SampleBuffer[startIndex + 1] += BlockBuffer[i] * _mix2; - startIndex += 2; - } - - _mix1 = leftVol; - _mix2 = rightVol; - } - - public void MixStereoToStereoInterp(int startIndex, float leftVol, float rightVol) - { - var incL = (leftVol - _mix1) / SynthConstants.DefaultBlockSize; - var incR = (rightVol - _mix2) / SynthConstants.DefaultBlockSize; - for (var i = 0; i < BlockBuffer.Length; i++) - { - _mix1 += incL; - _mix2 += incR; - SynthParams.Synth.SampleBuffer[startIndex + i] += BlockBuffer[i] * _mix1; - i++; - SynthParams.Synth.SampleBuffer[startIndex + i] += BlockBuffer[i] * _mix2; - } - - _mix1 = leftVol; - _mix2 = rightVol; - } - } -} diff --git a/Source/AlphaTab/Audio/Synth/Util/SynthConstants.cs b/Source/AlphaTab/Audio/Synth/Util/SynthConstants.cs index 61a664277..c7f1e46f8 100644 --- a/Source/AlphaTab/Audio/Synth/Util/SynthConstants.cs +++ b/Source/AlphaTab/Audio/Synth/Util/SynthConstants.cs @@ -1,33 +1,43 @@ -namespace AlphaTab.Audio.Synth.Util +// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT, +// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + +// C# port for alphaTab: (C) 2019 by Daniel Kuschny +// Licensed under: MPL-2.0 + +/* + * LICENSE (MIT) + * + * Copyright (C) 2017, 2018 Bernhard Schelling + * Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace AlphaTab.Audio.Synth.Util { internal static class SynthConstants { - public const int AudioChannels = 2; - - public const double Pi = 3.14159265358979; - public const double TwoPi = 2.0 * Pi; - public const double HalfPi = Pi / 2.0; - public const double InverseSqrtOfTwo = 0.707106781186; - public const float DefaultLfoFrequency = 8.0f; - public const int DefaultModDepth = 100; - public const int DefaultPolyphony = 40; - public const int MinPolyphony = 5; - public const int MaxPolyphony = 250; - public const int DefaultBlockSize = 64; - public const double MaxBufferSize = 0.05; - public const double MinBufferSize = 0.001; - public const double DenormLimit = 1e-38; - public const double NonAudible = 1e-5; - public const int SincWidth = 16; - public const int SincResolution = 64; - public const int MaxVoiceComponents = 4; + public const int DrumBank = 128; public const int DefaultChannelCount = 16 + 1 /*metronome*/; - public const int DefaultKeyCount = 128; + public const int MetronomeChannel = DefaultChannelCount - 1; - public const float DefaultMixGain = 0.35f; + public const int AudioChannels = 2; - public const float MinVolume = 0; - public const float MaxVolume = 10; + public const float MinVolume = 0f; + public const float MaxVolume = 1f; public const byte MinProgram = 0; public const byte MaxProgram = 127; diff --git a/Source/AlphaTab/Audio/Synth/Util/SynthHelper.cs b/Source/AlphaTab/Audio/Synth/Util/SynthHelper.cs index 911384ee4..37452fa82 100644 --- a/Source/AlphaTab/Audio/Synth/Util/SynthHelper.cs +++ b/Source/AlphaTab/Audio/Synth/Util/SynthHelper.cs @@ -2,51 +2,34 @@ namespace AlphaTab.Audio.Synth.Util { - internal class SynthHelper + internal static class SynthHelper { - public static void SwapEndianess(byte[] data, int bits) + public static float Timecents2Secs(float timecents) { - bits /= 8; //get bytes per sample - var swapArray = new byte[bits]; - for (var x = 0; x < data.Length; x += bits) - { - Platform.Platform.BlockCopy(data, x, swapArray, 0, bits); - Platform.Platform.Reverse(swapArray); - Platform.Platform.BlockCopy(swapArray, 0, data, x, bits); - } + return (float)Math.Pow(2f, timecents / 1200f); } - public static byte ClampB(byte value, byte min, byte max) + public static double Timecents2Secs(double timecents) { - if (value <= min) - { - return min; - } - - if (value >= max) - { - return max; - } - - return value; + return Math.Pow(2.0, timecents / 1200.0); } - public static double ClampD(double value, double min, double max) + public static float DecibelsToGain(float db) { - if (value <= min) - { - return min; - } - - if (value >= max) - { - return max; - } + return db > -100f ? (float)Math.Pow(10.0f, db * 0.05f) : 0; + } - return value; + public static float GainToDecibels(float gain) + { + return (gain <= .00001f ? -100f : (float)(20.0 * Math.Log10(gain))); } - public static float ClampF(float value, float min, float max) + public static float Cents2Hertz(float cents) + { + return 8.176f * (float)Math.Pow(2.0f, cents / 1200.0f); + } + + public static byte ClampB(byte value, byte min, byte max) { if (value <= min) { @@ -61,7 +44,7 @@ public static float ClampF(float value, float min, float max) return value; } - public static int ClampI(int value, int min, int max) + public static double ClampD(double value, double min, double max) { if (value <= min) { @@ -76,7 +59,7 @@ public static int ClampI(int value, int min, int max) return value; } - public static short ClampS(short value, short min, short max) + public static float ClampF(float value, float min, float max) { if (value <= min) { @@ -90,73 +73,5 @@ public static short ClampS(short value, short min, short max) return value; } - - public static double NearestPowerOfTwo(double value) - { - return Math.Pow(2, Math.Round(Math.Log(value, 2))); - } - - public static double SamplesFromTime(int sampleRate, double seconds) - { - return sampleRate * seconds; - } - - public static double TimeFromSamples(int sampleRate, int samples) - { - return samples / (double)sampleRate; - } - - public static double DBtoLinear(double dBvalue) - { - return Math.Pow(10.0, dBvalue / 20.0); - } - - public static double LineartoDB(double linearvalue) - { - return 20.0 * Math.Log10(linearvalue); - } - - //Midi Note and Frequency Conversions - public static double FrequencyToKey(double frequency, int rootkey) - { - return 12.0 * Math.Log(frequency / 440.0, 2.0) + rootkey; - } - - public static double KeyToFrequency(double key, int rootkey) - { - return Math.Pow(2.0, (key - rootkey) / 12.0) * 440.0; - } - - public static double SemitoneToPitch(int key) - { - //does not return a frequency, only the 2^(1/12) value. - if (key < -127) - { - key = -127; - } - else if (key > 127) - { - key = 127; - } - - return Tables.SemitoneTable(127 + key); - } - - public static double CentsToPitch(int cents) - { - //does not return a frequency, only the 2^(1/12) value. - var key = cents / 100; - cents -= key * 100; - if (key < -127) - { - key = -127; - } - else if (key > 127) - { - key = 127; - } - - return Tables.SemitoneTable(127 + key) * Tables.CentTable(100 + cents); - } } } diff --git a/Source/AlphaTab/Audio/Synth/Util/Tables.cs b/Source/AlphaTab/Audio/Synth/Util/Tables.cs deleted file mode 100644 index 14586f58e..000000000 --- a/Source/AlphaTab/Audio/Synth/Util/Tables.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using AlphaTab.Audio.Synth.Ds; - -namespace AlphaTab.Audio.Synth.Util -{ - internal class Tables - { - private static bool _isInitialized; - - private static SampleArray[] _envelopeTables; - private static SampleArray _semitoneTable; - private static SampleArray _centTable; - private static SampleArray _sincTable; - - public static SampleArray EnvelopeTables(int index) - { - if (!_isInitialized) - { - Init(); - } - - return _envelopeTables[index]; - } - - public static float SemitoneTable(int index) - { - if (!_isInitialized) - { - Init(); - } - - return _semitoneTable[index]; - } - - public static float CentTable(int index) - { - if (!_isInitialized) - { - Init(); - } - - return _centTable[index]; - } - - public static float SincTable(int index) - { - if (!_isInitialized) - { - Init(); - } - - return _sincTable[index]; - } - - private static void Init() - { - var EnvelopeSize = 64; - var ExponentialCoeff = .09f; - _envelopeTables = new SampleArray[4]; - _envelopeTables[0] = RemoveDenormals(CreateSustainTable(EnvelopeSize)); - _envelopeTables[1] = RemoveDenormals(CreateLinearTable(EnvelopeSize)); - _envelopeTables[2] = RemoveDenormals(CreateExponentialTable(EnvelopeSize, ExponentialCoeff)); - _envelopeTables[3] = RemoveDenormals(CreateSineTable(EnvelopeSize)); - _centTable = CreateCentTable(); - _semitoneTable = CreateSemitoneTable(); - _sincTable = CreateSincTable(SynthConstants.SincWidth, SynthConstants.SincResolution, .43f, HammingWindow); - _isInitialized = true; - } - - private static SampleArray CreateCentTable() - { - //-100 to 100 cents - var cents = new SampleArray(201); - for (var x = 0; x < cents.Length; x++) - { - cents[x] = (float)Math.Pow(2.0, (x - 100.0) / 1200.0); - } - - return cents; - } - - private static SampleArray CreateSemitoneTable() - { - //-127 to 127 semitones - var table = new SampleArray(255); - for (var x = 0; x < table.Length; x++) - { - table[x] = (float)Math.Pow(2.0, (x - 127.0) / 12.0); - } - - return table; - } - - private static SampleArray CreateSustainTable(int size) - { - var table = new SampleArray(size); - for (var x = 0; x < size; x++) - { - table[x] = 1; - } - - return table; - } - - private static SampleArray CreateLinearTable(int size) - { - var table = new SampleArray(size); - for (var x = 0; x < size; x++) - { - table[x] = x / (float)(size - 1); - } - - return table; - } - - private static SampleArray CreateExponentialTable(int size, float coeff) - { - coeff = SynthHelper.ClampF(coeff, .001f, .9f); - var graph = new SampleArray(size); - var val = 0.0; - for (var x = 0; x < size; x++) - { - graph[x] = (float)val; - val += coeff * (1 / 0.63 - val); - } - - for (var x = 0; x < size; x++) - { - graph[x] = graph[x] / graph[graph.Length - 1]; - } - - return graph; - } - - private static SampleArray CreateSineTable(int size) - { - var graph = new SampleArray(size); - var inc = (float)(3.0 * Math.PI / 2.0) / (size - 1); - var phase = 0.0; - for (var x = 0; x < size; x++) - { - graph[x] = (float)Math.Abs(Math.Sin(phase)); - phase += inc; - } - - return graph; - } - - private static SampleArray RemoveDenormals(SampleArray data) - { - for (var x = 0; x < data.Length; x++) - { - if (Math.Abs(data[x]) < SynthConstants.DenormLimit) - { - data[x] = 0; - } - } - - return data; - } - - private static float HammingWindow(float i, int size) - { - return (float)(0.54 - 0.46 * Math.Cos(SynthConstants.TwoPi * i / size)); - } - - private static SampleArray CreateSincTable( - int windowSize, - int resolution, - float cornerRatio, - Func windowFunction) - { - var subWindow = windowSize / 2 + 1; - var table = new SampleArray(subWindow * resolution); - var gain = 2.0 * cornerRatio; - for (var x = 0; x < subWindow; x++) - { - for (var y = 0; y < resolution; y++) - { - var a = x + y / (float)resolution; - var sinc = SynthConstants.TwoPi * cornerRatio * a; - if (Math.Abs(sinc) > 0.00001) - { - sinc = Math.Sin(sinc) / sinc; - } - else - { - sinc = 1.0; - } - - table[x * SynthConstants.SincResolution + y] = (float)(gain * sinc * windowFunction(a, windowSize)); - } - } - - return table; - } - } -} diff --git a/Source/AlphaTab/Importer/GpifParser.cs b/Source/AlphaTab/Importer/GpifParser.cs index 652788dfc..7d3a88e1f 100644 --- a/Source/AlphaTab/Importer/GpifParser.cs +++ b/Source/AlphaTab/Importer/GpifParser.cs @@ -62,7 +62,7 @@ private FastList private FastDictionary _voiceById; // contains voices by their id private FastDictionary - _beatsOfVoice; // contains ids of beats stored in a voice (key = voice id) + _beatsOfVoice; // contains ids of beats stored in a voice (key = voice id) private FastDictionary _rhythmOfBeat; // contains ids of rhythm used by a beat (key = beat id) private FastDictionary _beatById; // contains beats by their id @@ -135,10 +135,10 @@ private void ParseDom(XmlDocument dom) return; } - // the XML uses IDs for referring elements within the + // the XML uses IDs for referring elements within the // model. Therefore we do the parsing in 2 steps: // - at first we read all model elements and store them by ID in a lookup table - // - after that we need to join up the information. + // - after that we need to join up the information. if (root.LocalName == "GPIF") { Score = new Score(); @@ -189,7 +189,7 @@ private void ParseDom(XmlDocument dom) // // ... - // + // private void ParseScoreNode(XmlNode element) { @@ -252,7 +252,7 @@ private void ParseScoreNode(XmlNode element) // // ... - // + // private void ParseMasterTrackNode(XmlNode node) { @@ -364,7 +364,7 @@ private void ParseAutomation(XmlNode node, FastDictionary... - // + // private void ParseTracksNode(XmlNode node) { @@ -432,6 +432,7 @@ private void ParseTrack(XmlNode node) break; case "GeneralMidi": case "MidiConnection": + case "MIDISettings": ParseGeneralMidi(track, c); break; case "Sounds": @@ -950,7 +951,7 @@ private void ParseTranspose(Track track, XmlNode node) // // ... - // + // private void ParseMasterBarsNode(XmlNode node) { @@ -1007,7 +1008,7 @@ private void ParseMasterBar(XmlNode node) } break; - // TODO case "Directions": // Coda segno etc. + // TODO case "Directions": // Coda segno etc. case "AlternateEndings": var alternateEndings = c.InnerText.Split(' '); var i = 0; @@ -1140,7 +1141,7 @@ private void ParseFermata(MasterBar masterBar, XmlNode node) // // ... - // + // private void ParseBars(XmlNode node) { @@ -1235,7 +1236,7 @@ private void ParseBar(XmlNode node) // // ... - // + // private void ParseVoices(XmlNode node) { @@ -1276,7 +1277,7 @@ private void ParseVoice(XmlNode node) // // ... - // + // private void ParseBeats(XmlNode node) { @@ -1681,7 +1682,7 @@ private void ParseBeatProperties(XmlNode node, Beat beat) // // ... - // + // private void ParseNotes(XmlNode node) { @@ -1904,9 +1905,9 @@ private void ParseNoteProperties(XmlNode node, Note note, string noteId) } break; - // case "Element": - // case "Variation": - // case "Tone": + // case "Element": + // case "Variation": + // case "Tone": case "Octave": note.Octave = Platform.Platform.ParseInt(c.FindChildElement("Number").InnerText); break; @@ -1962,7 +1963,7 @@ private void ParseNoteProperties(XmlNode node, Note note, string noteId) } // NOTE: If we directly cast the expression of value to (int) it is 3 instead of 4, strange compiler - // optimizations happening here: + // optimizations happening here: // (int)(Platform.ParseFloat(GetValue(c.FindChildElement("Float")))* BendPointValueFactor) => (int)(100f * 0.04f) => 3 // (Platform.ParseFloat(GetValue(c.FindChildElement("Float")))* BendPointValueFactor) => (100f * 0.04f) => 4.0 bendDestination.Value = @@ -1989,7 +1990,7 @@ private void ParseNoteProperties(XmlNode node, Note note, string noteId) break; case "HopoDestination": - // NOTE: gets automatically calculated + // NOTE: gets automatically calculated // if (FindChildElement(node, "Enable") != null) // note.isHammerPullDestination = true; break; diff --git a/Tools/header.js b/Tools/header.js index 379eabd0b..48f384ddb 100644 --- a/Tools/header.js +++ b/Tools/header.js @@ -6,5 +6,11 @@ * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ + * + * SoundFont loading and Audio Synthesis based on TinySoundFont (licensed under MIT) + * Copyright (C) 2017, 2018 Bernhard Schelling (https://github.com/schellingb/TinySoundFont) + * + * TinySoundFont is based on SFZero (licensed under MIT) + * Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + */ {{source}} \ No newline at end of file