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