From 8a5494799029d0fe87a50ccdeccba4c9a1f11215 Mon Sep 17 00:00:00 2001
From: Daniel Kuschny
Date: Tue, 30 Jul 2019 11:11:22 +0200
Subject: [PATCH 01/11] Replaced old synth with TinySoundFont
---
Phase/Mscorlib/system/CsMath.hx | 1 +
.../Collections/SampleArray.cs | 64 -
.../Collections/SampleArray.cs | 45 -
.../JavaScript/AlphaSynthFlashOutput.cs | 5 +-
.../JavaScript/AlphaSynthWebAudioOutput.cs | 4 +-
.../JavaScript/AlphaSynthWebWorkerApi.cs | 7 +-
.../JavaScript/AlphaSynthWorkerSynthOutput.cs | 6 +-
.../Platform/JavaScript/JQueryAlphaTab.cs | 1 -
.../AlphaTab.JavaScript/Platform/Platform.cs | 7 +
Source/AlphaTab.Test.Js/test/karma.conf.js | 2 +-
Source/AlphaTab.Test/Audio/AlphaSynthTests.cs | 2 +-
Source/AlphaTab/AlphaTab.Shared.projitems | 86 +-
Source/AlphaTab/AlphaTabApi.cs | 47 +-
Source/AlphaTab/Audio/Synth/AlphaSynth.cs | 40 +-
.../AlphaTab/Audio/Synth/Bank/AssetManager.cs | 42 -
.../Audio/Synth/Bank/Components/Envelope.cs | 206 ---
.../Audio/Synth/Bank/Components/Filter.cs | 238 ---
.../Generators/DefaultGenerators.cs | 12 -
.../Bank/Components/Generators/Generator.cs | 114 --
.../Generators/GeneratorParameters.cs | 67 -
.../Components/Generators/SampleGenerator.cs | 98 --
.../Components/Generators/SawGenerator.cs | 49 -
.../Components/Generators/SineGenerator.cs | 50 -
.../Components/Generators/SquareGenerator.cs | 50 -
.../Generators/TriangleGenerator.cs | 49 -
.../Generators/WhiteNoiseGenerator.cs | 48 -
.../Audio/Synth/Bank/Components/Lfo.cs | 87 --
.../Synth/Bank/Components/PanComponent.cs | 43 -
.../Bank/Descriptors/EnvelopeDescriptor.cs | 50 -
.../Bank/Descriptors/FilterDescriptor.cs | 24 -
.../Bank/Descriptors/GeneratorDescriptor.cs | 48 -
.../Synth/Bank/Descriptors/LfoDescriptor.cs | 21 -
.../Audio/Synth/Bank/Patch/MultiPatch.cs | 225 ---
.../AlphaTab/Audio/Synth/Bank/Patch/Patch.cs | 29 -
.../Audio/Synth/Bank/Patch/Sf2Patch.cs | 482 -------
.../AlphaTab/Audio/Synth/Bank/PatchAsset.cs | 14 -
Source/AlphaTab/Audio/Synth/Bank/PatchBank.cs | 341 -----
Source/AlphaTab/Audio/Synth/Bank/PcmData.cs | 112 --
.../Audio/Synth/Bank/SampleDataAsset.cs | 39 -
.../Audio/Synth/Ds/CircularSampleBuffer.cs | 28 +-
Source/AlphaTab/Audio/Synth/IAlphaSynth.cs | 33 +-
Source/AlphaTab/Audio/Synth/ISynthOutput.cs | 20 +-
.../AlphaTab/Audio/Synth/MidiFileSequencer.cs | 36 +-
.../Synth/{Synthesis => }/PlaybackRange.cs | 2 +-
.../AlphaTab/Audio/Synth/Sf2/Chunks/Chunk.cs | 14 -
.../Audio/Synth/Sf2/Chunks/GeneratorChunk.cs | 26 -
.../Audio/Synth/Sf2/Chunks/InstrumentChunk.cs | 58 -
.../Audio/Synth/Sf2/Chunks/ModulatorChunk.cs | 27 -
.../Synth/Sf2/Chunks/PresetHeaderChunk.cs | 73 -
.../Synth/Sf2/Chunks/SampleHeaderChunk.cs | 28 -
.../Audio/Synth/Sf2/Chunks/ZoneChunk.cs | 64 -
.../Audio/Synth/Sf2/ControllerSourceEnum.cs | 14 -
.../AlphaTab/Audio/Synth/Sf2/DirectionEnum.cs | 8 -
Source/AlphaTab/Audio/Synth/Sf2/Generator.cs | 37 -
.../AlphaTab/Audio/Synth/Sf2/GeneratorEnum.cs | 67 -
Source/AlphaTab/Audio/Synth/Sf2/Instrument.cs | 8 -
Source/AlphaTab/Audio/Synth/Sf2/Modulator.cs | 22 -
.../AlphaTab/Audio/Synth/Sf2/ModulatorType.cs | 25 -
.../AlphaTab/Audio/Synth/Sf2/PolarityEnum.cs | 8 -
.../AlphaTab/Audio/Synth/Sf2/PresetHeader.cs | 13 -
.../AlphaTab/Audio/Synth/Sf2/SampleHeader.cs | 32 -
Source/AlphaTab/Audio/Synth/Sf2/Sf2Region.cs | 66 -
Source/AlphaTab/Audio/Synth/Sf2/SoundFont.cs | 36 -
.../AlphaTab/Audio/Synth/Sf2/SoundFontInfo.cs | 94 --
.../Audio/Synth/Sf2/SoundFontPresets.cs | 83 --
.../Audio/Synth/Sf2/SoundFontSampleData.cs | 81 --
.../Audio/Synth/Sf2/SoundFontSampleLink.cs | 18 -
.../Audio/Synth/Sf2/SourceTypeEnum.cs | 10 -
.../AlphaTab/Audio/Synth/Sf2/TransformEnum.cs | 8 -
Source/AlphaTab/Audio/Synth/Sf2/Zone.cs | 8 -
.../AlphaTab/Audio/Synth/SoundFont/Hydra.cs | 169 +++
.../Audio/Synth/SoundFont/HydraGenAmount.cs | 28 +
.../Audio/Synth/SoundFont/HydraIbag.cs | 20 +
.../Audio/Synth/SoundFont/HydraIgen.cs | 20 +
.../Audio/Synth/SoundFont/HydraImod.cs | 27 +
.../Audio/Synth/SoundFont/HydraInst.cs | 20 +
.../Audio/Synth/SoundFont/HydraPbag.cs | 20 +
.../Audio/Synth/SoundFont/HydraPgen.cs | 25 +
.../Audio/Synth/SoundFont/HydraPhdr.cs | 32 +
.../Audio/Synth/SoundFont/HydraPmod.cs | 28 +
.../Audio/Synth/SoundFont/HydraShdr.cs | 39 +
.../Audio/Synth/SoundFont/RiffChunk.cs | 67 +
Source/AlphaTab/Audio/Synth/SynthEvent.cs | 25 +
.../AlphaTab/Audio/Synth/Synthesis/CCValue.cs | 66 -
.../AlphaTab/Audio/Synth/Synthesis/Channel.cs | 22 +
.../Audio/Synth/Synthesis/Channels.cs | 47 +
.../Audio/Synth/Synthesis/Envelope.cs | 77 +
.../Audio/Synth/Synthesis/LoopMode.cs | 9 +
.../Audio/Synth/Synthesis/OutputMode.cs | 21 +
.../AlphaTab/Audio/Synth/Synthesis/Preset.cs | 11 +
.../AlphaTab/Audio/Synth/Synthesis/Region.cs | 253 ++++
.../Audio/Synth/Synthesis/SynthParameters.cs | 173 ---
.../Audio/Synth/Synthesis/Synthesizer.cs | 714 ---------
.../Synth/Synthesis/TinySoundFont.AlphaTab.cs | 187 +++
.../Audio/Synth/Synthesis/TinySoundFont.cs | 1271 +++++++++++++++++
.../AlphaTab/Audio/Synth/Synthesis/Voice.cs | 301 +++-
.../Audio/Synth/Synthesis/VoiceEnvelope.cs | 167 +++
.../Synth/Synthesis/VoiceEnvelopeSegment.cs | 13 +
.../Audio/Synth/Synthesis/VoiceLfo.cs | 36 +
.../Audio/Synth/Synthesis/VoiceLowPass.cs | 51 +
.../Audio/Synth/Synthesis/VoiceManager.cs | 174 ---
.../Audio/Synth/Synthesis/VoiceParameters.cs | 111 --
.../Audio/Synth/Util/SynthConstants.cs | 8 +-
.../AlphaTab/Audio/Synth/Util/SynthHelper.cs | 80 --
Source/AlphaTab/Audio/Synth/Util/Tables.cs | 198 ---
Source/AlphaTab/Audio/Synth/Util/Utils.cs | 32 +
106 files changed, 3119 insertions(+), 5325 deletions(-)
delete mode 100644 Source/AlphaTab.CSharp/Collections/SampleArray.cs
delete mode 100644 Source/AlphaTab.JavaScript/Collections/SampleArray.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/AssetManager.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Components/Envelope.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Components/Filter.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Components/Generators/DefaultGenerators.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Components/Generators/Generator.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Components/Generators/GeneratorParameters.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SampleGenerator.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SawGenerator.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SineGenerator.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Components/Generators/SquareGenerator.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Components/Generators/TriangleGenerator.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Components/Generators/WhiteNoiseGenerator.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Components/Lfo.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Components/PanComponent.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Descriptors/EnvelopeDescriptor.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Descriptors/FilterDescriptor.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Descriptors/GeneratorDescriptor.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Descriptors/LfoDescriptor.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Patch/MultiPatch.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Patch/Patch.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/Patch/Sf2Patch.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/PatchAsset.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/PatchBank.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/PcmData.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Bank/SampleDataAsset.cs
rename Source/AlphaTab/Audio/Synth/{Synthesis => }/PlaybackRange.cs (91%)
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/Chunks/Chunk.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/Chunks/GeneratorChunk.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/Chunks/InstrumentChunk.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/Chunks/ModulatorChunk.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/Chunks/PresetHeaderChunk.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/Chunks/SampleHeaderChunk.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/Chunks/ZoneChunk.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/ControllerSourceEnum.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/DirectionEnum.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/Generator.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/GeneratorEnum.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/Instrument.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/Modulator.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/ModulatorType.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/PolarityEnum.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/PresetHeader.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/SampleHeader.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/Sf2Region.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/SoundFont.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/SoundFontInfo.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/SoundFontPresets.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/SoundFontSampleData.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/SoundFontSampleLink.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/SourceTypeEnum.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/TransformEnum.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Sf2/Zone.cs
create mode 100644 Source/AlphaTab/Audio/Synth/SoundFont/Hydra.cs
create mode 100644 Source/AlphaTab/Audio/Synth/SoundFont/HydraGenAmount.cs
create mode 100644 Source/AlphaTab/Audio/Synth/SoundFont/HydraIbag.cs
create mode 100644 Source/AlphaTab/Audio/Synth/SoundFont/HydraIgen.cs
create mode 100644 Source/AlphaTab/Audio/Synth/SoundFont/HydraImod.cs
create mode 100644 Source/AlphaTab/Audio/Synth/SoundFont/HydraInst.cs
create mode 100644 Source/AlphaTab/Audio/Synth/SoundFont/HydraPbag.cs
create mode 100644 Source/AlphaTab/Audio/Synth/SoundFont/HydraPgen.cs
create mode 100644 Source/AlphaTab/Audio/Synth/SoundFont/HydraPhdr.cs
create mode 100644 Source/AlphaTab/Audio/Synth/SoundFont/HydraPmod.cs
create mode 100644 Source/AlphaTab/Audio/Synth/SoundFont/HydraShdr.cs
create mode 100644 Source/AlphaTab/Audio/Synth/SoundFont/RiffChunk.cs
create mode 100644 Source/AlphaTab/Audio/Synth/SynthEvent.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/CCValue.cs
create mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/Channel.cs
create mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/Channels.cs
create mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/Envelope.cs
create mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/LoopMode.cs
create mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/OutputMode.cs
create mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/Preset.cs
create mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/Region.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/SynthParameters.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/Synthesizer.cs
create mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.AlphaTab.cs
create mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs
create mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelope.cs
create mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelopeSegment.cs
create mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/VoiceLfo.cs
create mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/VoiceLowPass.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/VoiceManager.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Synthesis/VoiceParameters.cs
delete mode 100644 Source/AlphaTab/Audio/Synth/Util/Tables.cs
create mode 100644 Source/AlphaTab/Audio/Synth/Util/Utils.cs
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/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.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..df5a87f0a 100644
--- a/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWebAudioOutput.cs
+++ b/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWebAudioOutput.cs
@@ -142,7 +142,7 @@ public void SequencerFinished()
_finished = true;
}
- public void AddSamples(SampleArray f)
+ public void AddSamples(float[] f)
{
_circularBuffer.Write(f, 0, f.Length);
}
@@ -183,7 +183,7 @@ private void GenerateSound(AudioProcessingEvent e)
_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 ba2a9eb3a..32051723e 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;
@@ -208,7 +207,7 @@ public AlphaSynthWebWorkerApi(ISynthOutput player, string alphaSynthScriptFile,
}
catch
{
- // fallback to blob worker
+ // fallback to blob worker
try
{
HaxeString script = "importScripts('" + alphaSynthScriptFile + "')";
@@ -362,9 +361,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/JavaScript/JQueryAlphaTab.cs b/Source/AlphaTab.JavaScript/Platform/JavaScript/JQueryAlphaTab.cs
index 02f26735d..495977bb6 100644
--- a/Source/AlphaTab.JavaScript/Platform/JavaScript/JQueryAlphaTab.cs
+++ b/Source/AlphaTab.JavaScript/Platform/JavaScript/JQueryAlphaTab.cs
@@ -1,6 +1,5 @@
using System;
using AlphaTab.Audio.Synth;
-using AlphaTab.Audio.Synth.Synthesis;
using AlphaTab.Collections;
using AlphaTab.Haxe.jQuery;
using AlphaTab.Haxe.Js;
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/karma.conf.js b/Source/AlphaTab.Test.Js/test/karma.conf.js
index 6fa8e1a6e..f1da284d1 100644
--- a/Source/AlphaTab.Test.Js/test/karma.conf.js
+++ b/Source/AlphaTab.Test.Js/test/karma.conf.js
@@ -62,7 +62,7 @@ module.exports = function(config) {
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
- browsers: ['ChromeHeadless'],
+ browsers: ['Chrome'],
// Continuous Integration mode
diff --git a/Source/AlphaTab.Test/Audio/AlphaSynthTests.cs b/Source/AlphaTab.Test/Audio/AlphaSynthTests.cs
index bb01bd676..c476ebe8b 100644
--- a/Source/AlphaTab.Test/Audio/AlphaSynthTests.cs
+++ b/Source/AlphaTab.Test/Audio/AlphaSynthTests.cs
@@ -144,7 +144,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/AlphaTab.Shared.projitems b/Source/AlphaTab/AlphaTab.Shared.projitems
index 7f4d95fe0..b9b5a12d1 100644
--- a/Source/AlphaTab/AlphaTab.Shared.projitems
+++ b/Source/AlphaTab/AlphaTab.Shared.projitems
@@ -19,30 +19,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -56,44 +32,38 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
-
+
diff --git a/Source/AlphaTab/AlphaTabApi.cs b/Source/AlphaTab/AlphaTabApi.cs
index bbac7f089..0973bcf47 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;
@@ -36,27 +35,27 @@ public class AlphaTabApi
public IContainer Container { get; }
///
- /// Gets the UI container that will hold all rendered results.
+ /// Gets the UI container that will hold all rendered results.
///
public IContainer CanvasElement { get; }
///
- /// Gets the score renderer used for rendering the music sheet. This is the low-level API responsible for the actual rendering chain.
+ /// Gets the score renderer used for rendering the music sheet. This is the low-level API responsible for the actual rendering chain.
///
public IScoreRenderer Renderer { get; }
///
- /// Gets a value indicating whether auto-sizing is active and the music sheet will be re-rendered on resize.
+ /// Gets a value indicating whether auto-sizing is active and the music sheet will be re-rendered on resize.
///
public bool AutoSize { get; }
///
- /// Gets the score holding all information about the song being rendered.
+ /// Gets the score holding all information about the song being rendered.
///
public Score Score { get; private set; }
///
- /// Gets the indexes of the tracks that should be rendered of the currently set score.
+ /// Gets the indexes of the tracks that should be rendered of the currently set score.
///
public int[] TrackIndexes { get; private set; }
@@ -167,7 +166,7 @@ public AlphaTabApi(IUiFacade uiFacade, TSettings settings)
}
///
- /// Destroys the alphaTab control and restores the initial state of the UI.
+ /// Destroys the alphaTab control and restores the initial state of the UI.
///
public virtual void Destroy()
{
@@ -209,7 +208,7 @@ protected Track[] TrackIndexesToTracks(int[] trackIndexes)
#region Rendering
///
- /// Applies any changes that were done to the settings object and informs the about any new values to consider.
+ /// Applies any changes that were done to the settings object and informs the about any new values to consider.
///
public void UpdateSettings()
{
@@ -263,7 +262,7 @@ private void AppendRenderResult(RenderFinishedEventArgs result)
}
///
- /// Initiates a rendering of the given tracks.
+ /// Initiates a rendering of the given tracks.
///
/// The data model holding the song information.
/// The indexes of the tracks to render.
@@ -284,7 +283,7 @@ public void RenderTracks(Score score, int[] tracks, bool invalidate = true)
}
///
- /// Tells alphaTab to render the given alphaTex.
+ /// Tells alphaTab to render the given alphaTex.
///
/// The alphaTex code to render.
/// If set, the given tracks will be rendered, otherwise the first track only will be rendered.
@@ -313,7 +312,7 @@ public void Tex(string contents, int[] tracks = null)
}
///
- /// Performs any necessary steps that are needed after a new score was loaded/set.
+ /// Performs any necessary steps that are needed after a new score was loaded/set.
///
/// The score that was loaded.
/// If set to true, a rerendering will be initiated as part of this call.
@@ -364,7 +363,7 @@ public void Render()
private MidiTickLookup _tickCache;
///
- /// Gets the alphaSynth player used for playback. This is the low-level API to the Midi synthesizer used for playback.
+ /// Gets the alphaSynth player used for playback. This is the low-level API to the Midi synthesizer used for playback.
///
public IAlphaSynth Player { get; private set; }
@@ -440,7 +439,7 @@ private void LoadMidiForScore()
}
///
- /// Changes the volume of th given tracks.
+ /// Changes the volume of th given tracks.
///
/// The tracks for which the volume should be changed.
/// The volume to set for all tracks in percent (0-1)
@@ -459,7 +458,7 @@ public void ChangeTrackVolume(Track[] tracks, float volume)
}
///
- /// Changes the given tracks to be played solo or not.
+ /// Changes the given tracks to be played solo or not.
///
/// The list of tracks to play solo or not.
/// If set to true, the tracks will be added to the solo list. If false, they are removed.
@@ -481,7 +480,7 @@ public void ChangeTrackSolo(Track[] tracks, bool solo)
}
///
- /// Changes the given tracks to be muted or not.
+ /// Changes the given tracks to be muted or not.
///
/// The list of track to mute or unmute.
/// If set to true, the tracks will be muted. If false they are unmuted.
@@ -500,7 +499,7 @@ public void ChangeTrackMute(Track[] tracks, bool mute)
}
///
- /// Starts the playback of the current song.
+ /// Starts the playback of the current song.
///
public void Play()
{
@@ -526,7 +525,7 @@ public void Pause()
}
///
- /// Toggles between play/pause depending on the current player state.
+ /// Toggles between play/pause depending on the current player state.
///
public void PlayPause()
{
@@ -719,7 +718,7 @@ private void CursorUpdateBeat(Beat beat, Beat nextBeat, double duration, bool st
if (nextBeat != null)
{
// if we are moving within the same bar or to the next bar
- // transition to the next beat, otherwise transition to the end of the bar.
+ // transition to the next beat, otherwise transition to the end of the bar.
if (nextBeat.Voice.Bar.Index == beat.Voice.Bar.Index ||
nextBeat.Voice.Bar.Index == beat.Voice.Bar.Index + 1)
{
@@ -812,7 +811,7 @@ private void CursorUpdateBeat(Beat beat, Beat nextBeat, double duration, bool st
}
///
- /// This event is fired whenever a new beat is played.
+ /// This event is fired whenever a new beat is played.
///
public event Action PlayedBeatChanged;
@@ -906,7 +905,7 @@ private void SetupClickHandling()
CursorUpdateBeat(_selectionStart.Beat, null, 0, false);
Player.TickPosition = realMasterBarStart + _selectionStart.Beat.PlaybackStart;
- // set playback range
+ // set playback range
if (_selectionEnd != null && _selectionStart.Beat != _selectionEnd.Beat)
{
var realMasterBarEnd = tickCache.GetMasterBarStart(_selectionEnd.Beat.Voice.Bar.MasterBar);
@@ -984,7 +983,7 @@ private void CursorSelectRange(SelectionInfo startBeat, SelectionInfo endBeat)
if (startBeat.Bounds.BarBounds.MasterBarBounds.StaveGroupBounds !=
endBeat.Bounds.BarBounds.MasterBarBounds.StaveGroupBounds)
{
- // from the startbeat to the end of the staff,
+ // from the startbeat to the end of the staff,
// then fill all staffs until the end-beat staff
// then from staff-start to the end beat (or to end of bar if it's the last beat)
@@ -1037,7 +1036,7 @@ private void CursorSelectRange(SelectionInfo startBeat, SelectionInfo endBeat)
#region Events
///
- /// This event is fired whenever a new song is loaded.
+ /// This event is fired whenever a new song is loaded.
///
public event Action Loaded;
@@ -1069,7 +1068,7 @@ private void OnResize(ResizeEventArgs obj)
}
///
- /// This event is fired when the rendering of the whole music sheet is finished.
+ /// This event is fired when the rendering of the whole music sheet is finished.
///
public event Action RenderFinished;
@@ -1085,7 +1084,7 @@ private void OnRenderFinished(RenderFinishedEventArgs e)
}
///
- /// This event is fired when the rendering of the whole music sheet is finished, and all handlers of ran.
+ /// This event is fired when the rendering of the whole music sheet is finished, and all handlers of ran.
///
public event Action PostRenderFinished;
diff --git a/Source/AlphaTab/Audio/Synth/AlphaSynth.cs b/Source/AlphaTab/Audio/Synth/AlphaSynth.cs
index e877c0724..ad254ea84 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);
}
}
@@ -173,8 +173,7 @@ public AlphaSynth(ISynthOutput output)
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;
@@ -251,10 +250,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();
@@ -307,7 +305,7 @@ public void LoadMidiFile(MidiFile midiFile)
///
public void SetChannelMute(int channel, bool mute)
{
- _synthesizer.SetChannelMute(channel, mute);
+ _synthesizer.ChannelSetMute(channel, mute);
}
///
@@ -319,14 +317,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);
}
///
@@ -334,12 +332,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);
}
@@ -354,7 +352,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 27ad71329..d89b00ef6 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
{
@@ -34,7 +33,7 @@ PlayerState State
}
///
- /// Gets or sets the loging level.
+ /// Gets or sets the loging level.
///
AlphaTab.Util.LogLevel LogLevel
{
@@ -89,7 +88,7 @@ double TimePosition
///
/// Gets or sets the range of the song that should be played. Set this to null
- /// to play the whole song.
+ /// to play the whole song.
///
PlaybackRange PlaybackRange
{
@@ -98,7 +97,7 @@ PlaybackRange PlaybackRange
}
///
- /// Gets or sets whether the playback should automatically restart after it finished.
+ /// Gets or sets whether the playback should automatically restart after it finished.
///
bool IsLooping
{
@@ -113,7 +112,7 @@ bool IsLooping
///
/// Starts the playback if possible
- ///
+ ///
void Play();
///
@@ -138,7 +137,7 @@ bool IsLooping
void LoadSoundFont(byte[] data);
///
- /// Loads the given midi file structure.
+ /// Loads the given midi file structure.
///
///
void LoadMidiFile(MidiFile midi);
@@ -167,8 +166,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.
@@ -178,47 +177,47 @@ bool IsLooping
void SetChannelProgram(int channel, byte program);
///
- /// This event is fired when the player is ready to be interacted with.
+ /// This event is fired when the player is ready to be interacted with.
///
event Action Ready;
///
- /// This event is fired when all required data for playback is loaded and ready.
+ /// This event is fired when all required data for playback is loaded and ready.
///
event Action ReadyForPlayback;
///
- /// This event is fired when the playback of the whole song finished.
+ /// This event is fired when the playback of the whole song finished.
///
event Action Finished;
///
- /// This event is fired when the SoundFont needed for playback was loaded.
+ /// This event is fired when the SoundFont needed for playback was loaded.
///
event Action SoundFontLoaded;
///
- /// This event is fired when the loading of the SoundFont failed.
+ /// This event is fired when the loading of the SoundFont failed.
///
event Action SoundFontLoadFailed;
///
- /// This event is fired when the Midi file needed for playback was loaded.
+ /// This event is fired when the Midi file needed for playback was loaded.
///
event Action MidiLoaded;
///
- /// This event is fired when the loading of the Midi file failed.
+ /// This event is fired when the loading of the Midi file failed.
///
event Action MidiLoadFailed;
///
- /// This event is fired when the playback state changed.
+ /// This event is fired when the playback state changed.
///
event Action StateChanged;
///
- /// This event is fired when the current playback position of the song changed.
+ /// This event is fired when the current playback position of the song changed.
///
event Action PositionChanged;
}
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..98b6f80dd 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,7 @@ public void Seek(double timePosition)
_currentTime = 0;
_eventIndex = 0;
_synthesizer.NoteOffAll(true);
- _synthesizer.ResetPrograms();
- _synthesizer.ResetSynthControls();
+ _synthesizer.Reset();
SilentProcess(timePosition);
}
@@ -150,7 +149,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,15 +235,14 @@ 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++)
+ for (var i = 0; i < TinySoundFont.MicroBufferCount; i++)
{
_currentTime += millisecondsPerBuffer;
while (_eventIndex < _synthData.Count && _synthData[_eventIndex].Time < _currentTime)
@@ -343,8 +341,7 @@ public void CheckForStop()
_currentTime = 0;
_eventIndex = 0;
_synthesizer.NoteOffAll(true);
- _synthesizer.ResetPrograms();
- _synthesizer.ResetSynthControls();
+ _synthesizer.Reset();
OnFinished();
}
else if (PlaybackRange != null && _currentTime >= _playbackRangeEndTime)
@@ -352,8 +349,7 @@ public void CheckForStop()
_currentTime = PlaybackRange.StartTick;
_eventIndex = 0;
_synthesizer.NoteOffAll(true);
- _synthesizer.ResetPrograms();
- _synthesizer.ResetSynthControls();
+ _synthesizer.Reset();
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..550d7d2db
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/Hydra.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Collections.Generic;
+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..4767aec7e
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraGenAmount.cs
@@ -0,0 +1,28 @@
+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..d88a74d9c
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraIbag.cs
@@ -0,0 +1,20 @@
+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..03d64085b
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraIgen.cs
@@ -0,0 +1,20 @@
+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..3cbd07461
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraImod.cs
@@ -0,0 +1,27 @@
+using System.IO;
+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..e453ba308
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraInst.cs
@@ -0,0 +1,20 @@
+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..dad6a42f1
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPbag.cs
@@ -0,0 +1,20 @@
+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..a79115623
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPgen.cs
@@ -0,0 +1,25 @@
+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..b3db0c9b1
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPhdr.cs
@@ -0,0 +1,32 @@
+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..f55eddc8b
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPmod.cs
@@ -0,0 +1,28 @@
+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..cf7372e09
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraShdr.cs
@@ -0,0 +1,39 @@
+using System;
+using System.IO;
+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..74c6158a2
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/RiffChunk.cs
@@ -0,0 +1,67 @@
+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..575c97932
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/Channel.cs
@@ -0,0 +1,22 @@
+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..ed1f602fc
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/Channels.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+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.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..b343a51fe
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/Envelope.cs
@@ -0,0 +1,77 @@
+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 : Utils.Timecents2Secs(Delay));
+ Attack = (Attack < -11950.0f ? 0.0f : Utils.Timecents2Secs(Attack));
+ Release = (Release < -11950.0f ? 0.0f : Utils.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 : Utils.Timecents2Secs(Hold));
+ }
+
+ if (KeynumToDecay == 0)
+ {
+ Decay = (Decay < -11950.0f ? 0.0f : Utils.Timecents2Secs(Decay));
+ }
+
+ if (Sustain < 0.0f)
+ {
+ Sustain = 0.0f;
+ }
+ else if (sustainIsGain)
+ {
+ Sustain = Utils.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..c6bbf5a3f
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/LoopMode.cs
@@ -0,0 +1,9 @@
+namespace AlphaTab.Audio.Synth.Synthesis
+{
+ internal enum LoopMode
+ {
+ None,
+ Continuous,
+ Sustain
+ }
+}
\ No newline at end of file
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/OutputMode.cs b/Source/AlphaTab/Audio/Synth/Synthesis/OutputMode.cs
new file mode 100644
index 000000000..d7daaac2a
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/OutputMode.cs
@@ -0,0 +1,21 @@
+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
+ }
+}
\ No newline at end of file
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Preset.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Preset.cs
new file mode 100644
index 000000000..85109deb9
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/Preset.cs
@@ -0,0 +1,11 @@
+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; }
+ }
+}
\ No newline at end of file
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Region.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Region.cs
new file mode 100644
index 000000000..a8704a12e
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/Region.cs
@@ -0,0 +1,253 @@
+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..730f7070b
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.AlphaTab.cs
@@ -0,0 +1,187 @@
+using AlphaTab.Audio.Synth.Ds;
+using AlphaTab.Audio.Synth.Midi.Event;
+using AlphaTab.Audio.Synth.Util;
+using AlphaTab.Collections;
+using AlphaTab.Platform;
+using AlphaTab.Util;
+
+namespace AlphaTab.Audio.Synth.Synthesis
+{
+ internal partial class TinySoundFont
+ {
+ public const int MicroBufferCount = 3;
+ public const int MicroBufferSize = 448;
+
+ private readonly LinkedList _midiEventQueue = new LinkedList();
+ private readonly int[] _midiEventCounts = new int[MicroBufferCount];
+ private FastDictionary _mutedChannels;
+ private FastDictionary _soloChannels;
+ private bool _isAnySolo;
+ private float[] _sampleBuffer = new float[MicroBufferSize * MicroBufferCount * SynthConstants.AudioChannels];
+
+ public float[] SampleBuffer => _sampleBuffer;
+
+ public void Synthesize()
+ {
+ _sampleBuffer = new float[_sampleBuffer.Length];
+ FillWorkingBuffer(false);
+ }
+
+ public void SynthesizeSilent()
+ {
+ _sampleBuffer = new float[_sampleBuffer.Length];
+ 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 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(SynthConstants.MetronomeChannel, 37);
+ NoteOn(SynthConstants.MetronomeChannel, 37, 95);
+ }
+ else
+ {
+ ProcessMidiMessage(m.Event);
+ }
+ }
+ }
+
+ //voice processing loop
+ var sampleCount = MicroBufferSize * SynthConstants.AudioChannels;
+ 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, _sampleBuffer, sampleIndex, sampleCount, isChannelMuted);
+ }
+ }
+ }
+
+ sampleIndex += sampleCount;
+ }
+
+ Platform.Platform.ClearIntArray(_midiEventCounts);
+ }
+
+ 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:
+ if (data2 == 0)
+ {
+ ChannelNoteOff(channel, data1);
+ }
+ else
+ {
+ ChannelNoteOn(channel, data1, data2);
+ }
+ 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;
+ }
+ }
+ }
+}
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs b/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs
new file mode 100644
index 000000000..a128d6e2c
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs
@@ -0,0 +1,1271 @@
+using System;
+using AlphaTab.Audio.Synth.SoundFont;
+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 - Utils.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(Utils.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;
+ _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 = Utils.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
+ ? Utils.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
+ : Utils.Timecents2Secs(zoneRegion.DelayModLFO));
+ zoneRegion.DelayVibLFO = (zoneRegion.DelayVibLFO < -11950.0f
+ ? 0.0f
+ : Utils.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..56eb8e76d 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/Voice.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/Voice.cs
@@ -1,85 +1,282 @@
-using AlphaTab.Audio.Synth.Bank.Patch;
-
-namespace AlphaTab.Audio.Synth.Synthesis
+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 / (Utils.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 = Utils.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)
{
- return;
+ tmpModLfoToVolume = (float)region.ModLfoToVolume * 0.1f;
}
+ else
+ {
+ noteGain = Utils.DecibelsToGain(NoteGainDb);
+ tmpModLfoToVolume = 0;
+ }
+
+ 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(Utils.Cents2Hertz(fres) / tmpSampleRate);
+ }
+ }
+
+ if (dynamicPitchRatio)
+ pitchRatio = Utils.Timecents2Secs(PitchInputTimecents + (ModLfo.Level * tmpModLfoToPitch + VibLfo.Level * tmpVibLfoToPitch + ModEnv.Level * tmpModEnvToPitch)) * PitchOutputFactor;
+
+ if (dynamicGain)
+ noteGain = Utils.DecibelsToGain(NoteGainDb + (ModLfo.Level * tmpModLfoToVolume));
+
+ gainMono = noteGain * AmpEnv.Level;
+
+ if (isMuted)
+ {
+ gainMono = 0;
+ }
+
+ // 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);
- //process using the patch's algorithm
- Patch.Process(VoiceParams, startIndex, endIndex, true, true);
+ // Low-pass filter.
+ if (tmpLowpass.Active) val = tmpLowpass.Process(val);
+
+ outputBuffer[offset + (outL++)] += val * gainLeft;
+ outputBuffer[offset + (outL++)] += val * gainRight;
+
+ // 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;
+ outputBuffer[offset + (outR++)] += val * gainRight;
+
+ // 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;
+
+ // 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..cdcf9dd12
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelope.cs
@@ -0,0 +1,167 @@
+using System;
+
+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 VoiceEnvelope()
+ {
+ }
+
+ public void NextSegment(VoiceEnvelopeSegment activeSegment, float outSampleRate)
+ {
+ 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;
+ }
+ goto case VoiceEnvelopeSegment.Delay;
+
+ 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;
+ }
+ goto case VoiceEnvelopeSegment.Attack;
+
+ case VoiceEnvelopeSegment.Attack:
+ SamplesUntilNextSegment = (int)(Parameters.Hold * outSampleRate);
+ if (SamplesUntilNextSegment > 0)
+ {
+ Segment = VoiceEnvelopeSegment.Hold;
+ SegmentIsExponential = false;
+ Level = 1.0f;
+ Slope = 0.0f;
+ return;
+ }
+ goto case VoiceEnvelopeSegment.Hold;
+
+ 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;
+ }
+ goto case VoiceEnvelopeSegment.Decay;
+
+ 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;
+ break;
+ }
+ }
+
+ 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 : Utils.Timecents2Secs(Parameters.Hold);
+ }
+ if (Parameters.KeynumToDecay > 0)
+ {
+ Parameters.Decay += Parameters.KeynumToDecay * (60.0f - midiNoteNumber);
+ Parameters.Decay = Parameters.Decay < -10000.0f ? 0.0f : Utils.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..9140e4384
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelopeSegment.cs
@@ -0,0 +1,13 @@
+namespace AlphaTab.Audio.Synth.Synthesis
+{
+ internal enum VoiceEnvelopeSegment
+ {
+ None,
+ Delay,
+ Attack,
+ Hold,
+ Decay,
+ Sustain,
+ Release, Done
+ }
+}
\ No newline at end of file
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLfo.cs b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLfo.cs
new file mode 100644
index 000000000..a690fb3d2
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLfo.cs
@@ -0,0 +1,36 @@
+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 * Utils.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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLowPass.cs b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLowPass.cs
new file mode 100644
index 000000000..41fdfa631
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLowPass.cs
@@ -0,0 +1,51 @@
+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..c684e0577 100644
--- a/Source/AlphaTab/Audio/Synth/Util/SynthConstants.cs
+++ b/Source/AlphaTab/Audio/Synth/Util/SynthConstants.cs
@@ -2,6 +2,9 @@
{
internal static class SynthConstants
{
+ public const int DefaultChannelCount = 16 + 1 /*metronome*/;
+ public const int MetronomeChannel = DefaultChannelCount - 1;
+
public const int AudioChannels = 2;
public const double Pi = 3.14159265358979;
@@ -21,13 +24,12 @@ internal static class SynthConstants
public const int SincWidth = 16;
public const int SincResolution = 64;
public const int MaxVoiceComponents = 4;
- public const int DefaultChannelCount = 16 + 1 /*metronome*/;
public const int DefaultKeyCount = 128;
public const float DefaultMixGain = 0.35f;
- 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..3d8e04323 100644
--- a/Source/AlphaTab/Audio/Synth/Util/SynthHelper.cs
+++ b/Source/AlphaTab/Audio/Synth/Util/SynthHelper.cs
@@ -4,18 +4,6 @@ namespace AlphaTab.Audio.Synth.Util
{
internal class SynthHelper
{
- public static void SwapEndianess(byte[] data, int bits)
- {
- 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);
- }
- }
-
public static byte ClampB(byte value, byte min, byte max)
{
if (value <= min)
@@ -90,73 +78,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/Audio/Synth/Util/Utils.cs b/Source/AlphaTab/Audio/Synth/Util/Utils.cs
new file mode 100644
index 000000000..5b50dae2b
--- /dev/null
+++ b/Source/AlphaTab/Audio/Synth/Util/Utils.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace AlphaTab.Audio.Synth.Synthesis
+{
+ class Utils
+ {
+ public static float Timecents2Secs(float timecents)
+ {
+ return (float)Math.Pow(2f, timecents / 1200f);
+ }
+
+ public static double Timecents2Secs(double timecents)
+ {
+ return Math.Pow(2.0, timecents / 1200.0);
+ }
+
+ public static float DecibelsToGain(float db)
+ {
+ return db > -100f ? (float) Math.Pow(10.0f, db * 0.05f) : 0;
+ }
+
+ public static float GainToDecibels(float gain)
+ {
+ return (gain <= .00001f ? -100f : (float)(20.0 * Math.Log10(gain)));
+ }
+
+ public static float Cents2Hertz(float cents)
+ {
+ return 8.176f * (float)Math.Pow(2.0f, cents / 1200.0f);
+ }
+ }
+}
\ No newline at end of file
From 7f25ce70bf3af961c56fd234f7b6ef197f44d5eb Mon Sep 17 00:00:00 2001
From: Daniel Kuschny
Date: Tue, 30 Jul 2019 13:59:18 +0200
Subject: [PATCH 02/11] Fixed some issues on synthesis due to compiler
behavior, added copyright notice and thanks to readme for TinySoundFont and
SFZero.
---
README.md | 2 +
.../NAudioSynthOutput.cs | 6 +-
.../CSharp/AlphaSynthWorkerApiBase.cs | 2 +-
.../Platform/CSharp/Wpf/NAudioSynthOutput.cs | 4 +-
Source/AlphaTab/AlphaTab.Shared.projitems | 1 -
.../Audio/Generator/MidiFileGenerator.cs | 2 +-
Source/AlphaTab/Audio/Synth/AlphaSynth.cs | 4 +-
.../AlphaTab/Audio/Synth/SoundFont/Hydra.cs | 31 ++-
.../Audio/Synth/SoundFont/HydraGenAmount.cs | 30 ++-
.../Audio/Synth/SoundFont/HydraIbag.cs | 30 ++-
.../Audio/Synth/SoundFont/HydraIgen.cs | 30 ++-
.../Audio/Synth/SoundFont/HydraImod.cs | 29 ++-
.../Audio/Synth/SoundFont/HydraInst.cs | 30 ++-
.../Audio/Synth/SoundFont/HydraPbag.cs | 30 ++-
.../Audio/Synth/SoundFont/HydraPgen.cs | 30 ++-
.../Audio/Synth/SoundFont/HydraPhdr.cs | 30 ++-
.../Audio/Synth/SoundFont/HydraPmod.cs | 29 ++-
.../Audio/Synth/SoundFont/HydraShdr.cs | 30 ++-
.../Audio/Synth/SoundFont/RiffChunk.cs | 29 ++-
.../AlphaTab/Audio/Synth/Synthesis/Channel.cs | 30 ++-
.../Audio/Synth/Synthesis/Channels.cs | 30 ++-
.../Audio/Synth/Synthesis/Envelope.cs | 45 +++-
.../Audio/Synth/Synthesis/LoopMode.cs | 32 ++-
.../Audio/Synth/Synthesis/OutputMode.cs | 32 ++-
.../AlphaTab/Audio/Synth/Synthesis/Preset.cs | 34 ++-
.../AlphaTab/Audio/Synth/Synthesis/Region.cs | 30 ++-
.../Synth/Synthesis/TinySoundFont.AlphaTab.cs | 44 ++--
.../Audio/Synth/Synthesis/TinySoundFont.cs | 43 +++-
.../AlphaTab/Audio/Synth/Synthesis/Voice.cs | 60 ++++-
.../Audio/Synth/Synthesis/VoiceEnvelope.cs | 238 +++++++++++-------
.../Synth/Synthesis/VoiceEnvelopeSegment.cs | 32 ++-
.../Audio/Synth/Synthesis/VoiceLfo.cs | 37 ++-
.../Audio/Synth/Synthesis/VoiceLowPass.cs | 30 ++-
.../Audio/Synth/Util/SynthConstants.cs | 51 ++--
.../AlphaTab/Audio/Synth/Util/SynthHelper.cs | 47 ++--
Source/AlphaTab/Audio/Synth/Util/Utils.cs | 32 ---
36 files changed, 959 insertions(+), 267 deletions(-)
delete mode 100644 Source/AlphaTab/Audio/Synth/Util/Utils.cs
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/Samples/CSharp/AlphaTab.Samples.Player/NAudioSynthOutput.cs b/Samples/CSharp/AlphaTab.Samples.Player/NAudioSynthOutput.cs
index 8d166cda1..cac622053 100644
--- a/Samples/CSharp/AlphaTab.Samples.Player/NAudioSynthOutput.cs
+++ b/Samples/CSharp/AlphaTab.Samples.Player/NAudioSynthOutput.cs
@@ -62,7 +62,7 @@ public void SequencerFinished()
_finished = true;
}
- public void AddSamples(SampleArray f)
+ public void AddSamples(float[] f)
{
_circularBuffer.Write(f, 0, f.Length);
}
@@ -97,7 +97,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++)
@@ -127,4 +127,4 @@ public void Activate()
public event Action Finished;
}
}
-#endif
\ No newline at end of file
+#endif
diff --git a/Source/AlphaTab.CSharp/Platform/CSharp/AlphaSynthWorkerApiBase.cs b/Source/AlphaTab.CSharp/Platform/CSharp/AlphaSynthWorkerApiBase.cs
index b8db70b96..55914d16a 100644
--- a/Source/AlphaTab.CSharp/Platform/CSharp/AlphaSynthWorkerApiBase.cs
+++ b/Source/AlphaTab.CSharp/Platform/CSharp/AlphaSynthWorkerApiBase.cs
@@ -140,7 +140,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 33093bb3a..d8c5b1bd2 100644
--- a/Source/AlphaTab.CSharp/Platform/CSharp/Wpf/NAudioSynthOutput.cs
+++ b/Source/AlphaTab.CSharp/Platform/CSharp/Wpf/NAudioSynthOutput.cs
@@ -65,7 +65,7 @@ public void SequencerFinished()
_finished = true;
}
- public void AddSamples(SampleArray f)
+ public void AddSamples(float[] f)
{
_circularBuffer.Write(f, 0, f.Length);
}
@@ -100,7 +100,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/AlphaTab.Shared.projitems b/Source/AlphaTab/AlphaTab.Shared.projitems
index b9b5a12d1..c8109414c 100644
--- a/Source/AlphaTab/AlphaTab.Shared.projitems
+++ b/Source/AlphaTab/AlphaTab.Shared.projitems
@@ -63,7 +63,6 @@
-
diff --git a/Source/AlphaTab/Audio/Generator/MidiFileGenerator.cs b/Source/AlphaTab/Audio/Generator/MidiFileGenerator.cs
index 1828eab74..cd91ffe03 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 ad254ea84..37f9aa254 100644
--- a/Source/AlphaTab/Audio/Synth/AlphaSynth.cs
+++ b/Source/AlphaTab/Audio/Synth/AlphaSynth.cs
@@ -164,9 +164,9 @@ 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();
};
diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/Hydra.cs b/Source/AlphaTab/Audio/Synth/SoundFont/Hydra.cs
index 550d7d2db..8082f39a7 100644
--- a/Source/AlphaTab/Audio/Synth/SoundFont/Hydra.cs
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/Hydra.cs
@@ -1,5 +1,32 @@
-using System;
-using System.Collections.Generic;
+// 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;
diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraGenAmount.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraGenAmount.cs
index 4767aec7e..3d4b21f73 100644
--- a/Source/AlphaTab/Audio/Synth/SoundFont/HydraGenAmount.cs
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraGenAmount.cs
@@ -1,4 +1,32 @@
-using AlphaTab.IO;
+// 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
{
diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraIbag.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraIbag.cs
index d88a74d9c..7b7de6aa7 100644
--- a/Source/AlphaTab/Audio/Synth/SoundFont/HydraIbag.cs
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraIbag.cs
@@ -1,4 +1,32 @@
-using AlphaTab.IO;
+// 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
{
diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraIgen.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraIgen.cs
index 03d64085b..d2d1de0b0 100644
--- a/Source/AlphaTab/Audio/Synth/SoundFont/HydraIgen.cs
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraIgen.cs
@@ -1,4 +1,32 @@
-using AlphaTab.IO;
+// 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
{
diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraImod.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraImod.cs
index 3cbd07461..9f52086cc 100644
--- a/Source/AlphaTab/Audio/Synth/SoundFont/HydraImod.cs
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraImod.cs
@@ -1,4 +1,31 @@
-using System.IO;
+// 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
diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraInst.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraInst.cs
index e453ba308..eb46f1f8c 100644
--- a/Source/AlphaTab/Audio/Synth/SoundFont/HydraInst.cs
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraInst.cs
@@ -1,4 +1,32 @@
-using AlphaTab.IO;
+// 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
{
diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraPbag.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPbag.cs
index dad6a42f1..459fb23e0 100644
--- a/Source/AlphaTab/Audio/Synth/SoundFont/HydraPbag.cs
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPbag.cs
@@ -1,4 +1,32 @@
-using AlphaTab.IO;
+// 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
{
diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraPgen.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPgen.cs
index a79115623..c2e914596 100644
--- a/Source/AlphaTab/Audio/Synth/SoundFont/HydraPgen.cs
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPgen.cs
@@ -1,4 +1,32 @@
-using AlphaTab.IO;
+// 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
{
diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraPhdr.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPhdr.cs
index b3db0c9b1..ae6d7fb74 100644
--- a/Source/AlphaTab/Audio/Synth/SoundFont/HydraPhdr.cs
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPhdr.cs
@@ -1,4 +1,32 @@
-using AlphaTab.IO;
+// 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
{
diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraPmod.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPmod.cs
index f55eddc8b..92a02171c 100644
--- a/Source/AlphaTab/Audio/Synth/SoundFont/HydraPmod.cs
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraPmod.cs
@@ -1,4 +1,31 @@
-using AlphaTab.IO;
+// 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
{
diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/HydraShdr.cs b/Source/AlphaTab/Audio/Synth/SoundFont/HydraShdr.cs
index cf7372e09..4ff1a0b8f 100644
--- a/Source/AlphaTab/Audio/Synth/SoundFont/HydraShdr.cs
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/HydraShdr.cs
@@ -1,5 +1,31 @@
-using System;
-using System.IO;
+// 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;
diff --git a/Source/AlphaTab/Audio/Synth/SoundFont/RiffChunk.cs b/Source/AlphaTab/Audio/Synth/SoundFont/RiffChunk.cs
index 74c6158a2..8f396e07b 100644
--- a/Source/AlphaTab/Audio/Synth/SoundFont/RiffChunk.cs
+++ b/Source/AlphaTab/Audio/Synth/SoundFont/RiffChunk.cs
@@ -1,4 +1,31 @@
-using AlphaTab.IO;
+// 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
{
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Channel.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Channel.cs
index 575c97932..8c0a99e4d 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/Channel.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/Channel.cs
@@ -1,4 +1,32 @@
-namespace AlphaTab.Audio.Synth.Synthesis
+// 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
{
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Channels.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Channels.cs
index ed1f602fc..ebb3c92ec 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/Channels.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/Channels.cs
@@ -1,4 +1,32 @@
-using System;
+// 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 System.Collections.Generic;
using AlphaTab.Collections;
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Envelope.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Envelope.cs
index b343a51fe..3b9a3bbc1 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/Envelope.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/Envelope.cs
@@ -1,4 +1,35 @@
-namespace AlphaTab.Audio.Synth.Synthesis
+// 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
{
@@ -44,20 +75,20 @@ 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 : Utils.Timecents2Secs(Delay));
- Attack = (Attack < -11950.0f ? 0.0f : Utils.Timecents2Secs(Attack));
- Release = (Release < -11950.0f ? 0.0f : Utils.Timecents2Secs(Release));
+ 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 : Utils.Timecents2Secs(Hold));
+ Hold = (Hold < -11950.0f ? 0.0f : SynthHelper.Timecents2Secs(Hold));
}
if (KeynumToDecay == 0)
{
- Decay = (Decay < -11950.0f ? 0.0f : Utils.Timecents2Secs(Decay));
+ Decay = (Decay < -11950.0f ? 0.0f : SynthHelper.Timecents2Secs(Decay));
}
if (Sustain < 0.0f)
@@ -66,7 +97,7 @@ public void EnvToSecs(bool sustainIsGain)
}
else if (sustainIsGain)
{
- Sustain = Utils.DecibelsToGain(-Sustain / 10.0f);
+ Sustain = SynthHelper.DecibelsToGain(-Sustain / 10.0f);
}
else
{
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/LoopMode.cs b/Source/AlphaTab/Audio/Synth/Synthesis/LoopMode.cs
index c6bbf5a3f..169a6495d 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/LoopMode.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/LoopMode.cs
@@ -1,4 +1,32 @@
-namespace AlphaTab.Audio.Synth.Synthesis
+// 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
{
@@ -6,4 +34,4 @@ internal enum LoopMode
Continuous,
Sustain
}
-}
\ No newline at end of file
+}
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/OutputMode.cs b/Source/AlphaTab/Audio/Synth/Synthesis/OutputMode.cs
index d7daaac2a..bbf6e747e 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/OutputMode.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/OutputMode.cs
@@ -1,4 +1,32 @@
-namespace AlphaTab.Audio.Synth.Synthesis
+// 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
@@ -18,4 +46,4 @@ public enum OutputMode
///
Mono
}
-}
\ No newline at end of file
+}
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Preset.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Preset.cs
index 85109deb9..d47f5e420 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/Preset.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/Preset.cs
@@ -1,11 +1,39 @@
-namespace AlphaTab.Audio.Synth.Synthesis
+// 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; }
}
-}
\ No newline at end of file
+}
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Region.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Region.cs
index a8704a12e..de6ae0855 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/Region.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/Region.cs
@@ -1,4 +1,32 @@
-using AlphaTab.Audio.Synth.SoundFont;
+// 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
{
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.AlphaTab.cs b/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.AlphaTab.cs
index 730f7070b..bec29d465 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.AlphaTab.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.AlphaTab.cs
@@ -1,35 +1,30 @@
+// 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.Platform;
using AlphaTab.Util;
namespace AlphaTab.Audio.Synth.Synthesis
{
internal partial class TinySoundFont
{
- public const int MicroBufferCount = 3;
- public const int MicroBufferSize = 448;
+ 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;
- private FastDictionary _soloChannels;
+ private FastDictionary _mutedChannels = new FastDictionary();
+ private FastDictionary _soloChannels = new FastDictionary();
private bool _isAnySolo;
- private float[] _sampleBuffer = new float[MicroBufferSize * MicroBufferCount * SynthConstants.AudioChannels];
- public float[] SampleBuffer => _sampleBuffer;
-
- public void Synthesize()
+ public float[] Synthesize()
{
- _sampleBuffer = new float[_sampleBuffer.Length];
- FillWorkingBuffer(false);
+ return FillWorkingBuffer(false);
}
public void SynthesizeSilent()
{
- _sampleBuffer = new float[_sampleBuffer.Length];
FillWorkingBuffer(true);
}
@@ -93,14 +88,18 @@ public void DispatchEvent(int i, SynthEvent synthEvent)
_midiEventCounts[i]++;
}
- private void FillWorkingBuffer(bool silent)
+ 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 sampleIndex = 0;
+ 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++)
@@ -118,8 +117,7 @@ private void FillWorkingBuffer(bool silent)
}
}
- //voice processing loop
- var sampleCount = MicroBufferSize * SynthConstants.AudioChannels;
+ // voice processing loop
foreach (var voice in _voices)
{
if (voice.PlayingPreset != -1)
@@ -135,15 +133,16 @@ private void FillWorkingBuffer(bool silent)
}
else
{
- voice.Render(this, _sampleBuffer, sampleIndex, sampleCount, isChannelMuted);
+ voice.Render(this, buffer, bufferPos, MicroBufferSize, isChannelMuted);
}
}
}
- sampleIndex += sampleCount;
+ bufferPos += MicroBufferSize * SynthConstants.AudioChannels;
}
Platform.Platform.ClearIntArray(_midiEventCounts);
+ return buffer;
}
private void ProcessMidiMessage(MidiEvent e)
@@ -159,14 +158,7 @@ private void ProcessMidiMessage(MidiEvent e)
ChannelNoteOff(channel, data1);
break;
case MidiEventType.NoteOn:
- if (data2 == 0)
- {
- ChannelNoteOff(channel, data1);
- }
- else
- {
- ChannelNoteOn(channel, data1, data2);
- }
+ ChannelNoteOn(channel, data1, data2 / 127f);
break;
case MidiEventType.NoteAftertouch:
break;
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs b/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs
index a128d6e2c..05fa02371 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs
@@ -1,5 +1,34 @@
-using System;
+// 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
@@ -161,7 +190,7 @@ public void NoteOn(int presetIndex, int key, float vel)
voice.PlayingPreset = presetIndex;
voice.PlayingKey = key;
voice.PlayIndex = voicePlayIndex;
- voice.NoteGainDb = GlobalGainDb - region.Attenuation - Utils.GainToDecibels(1.0f / vel);
+ voice.NoteGainDb = GlobalGainDb - region.Attenuation - SynthHelper.GainToDecibels(1.0f / vel);
if (_channels != null)
{
@@ -194,7 +223,7 @@ public void NoteOn(int presetIndex, int key, float vel)
voice.LowPass.Active = (region.InitialFilterFc <= 13500);
if (voice.LowPass.Active)
{
- voice.LowPass.Setup(Utils.Cents2Hertz(region.InitialFilterFc) / OutSampleRate);
+ voice.LowPass.Setup(SynthHelper.Cents2Hertz(region.InitialFilterFc) / OutSampleRate);
}
// Setup LFO filters.
@@ -633,7 +662,7 @@ public void ChannelSetPan(int channel, float pan)
public void ChannelSetVolume(int channel, float volume)
{
var c = ChannelInit(channel);
- var gainDb = Utils.GainToDecibels(volume);
+ var gainDb = SynthHelper.GainToDecibels(volume);
var gainDBChange = gainDb - c.GainDb;
if (gainDBChange == 0)
{
@@ -892,7 +921,7 @@ public float ChannelGetPan(int channel)
public float ChannelGetVolume(int channel)
{
return (_channels != null && channel < _channels.ChannelList.Count
- ? Utils.DecibelsToGain(_channels.ChannelList[channel].GainDb)
+ ? SynthHelper.DecibelsToGain(_channels.ChannelList[channel].GainDb)
: 1.0f);
}
@@ -1178,10 +1207,10 @@ public void LoadPresets(Hydra hydra)
// LFO times need to be converted from timecents to seconds.
zoneRegion.DelayModLFO = (zoneRegion.DelayModLFO < -11950.0f
? 0.0f
- : Utils.Timecents2Secs(zoneRegion.DelayModLFO));
+ : SynthHelper.Timecents2Secs(zoneRegion.DelayModLFO));
zoneRegion.DelayVibLFO = (zoneRegion.DelayVibLFO < -11950.0f
? 0.0f
- : Utils.Timecents2Secs(zoneRegion.DelayVibLFO));
+ : SynthHelper.Timecents2Secs(zoneRegion.DelayVibLFO));
// Pin values to their ranges.
if (zoneRegion.Pan < -0.5f)
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Voice.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Voice.cs
index 56eb8e76d..9e392667c 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/Voice.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/Voice.cs
@@ -1,4 +1,35 @@
-namespace AlphaTab.Audio.Synth.Synthesis
+// 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 partial class Voice
{
@@ -52,7 +83,7 @@ public void CalcPitchRatio(float pitchShift, float outSampleRate)
var adjustedPitch = Region.PitchKeyCenter + (note - Region.PitchKeyCenter) * (Region.PitchKeyTrack / 100.0);
if (pitchShift != 0) adjustedPitch += pitchShift;
PitchInputTimecents = adjustedPitch * 100.0;
- PitchOutputFactor = Region.SampleRate / (Utils.Timecents2Secs(Region.PitchKeyCenter * 100.0) * outSampleRate);
+ PitchOutputFactor = Region.SampleRate / (SynthHelper.Timecents2Secs(Region.PitchKeyCenter * 100.0) * outSampleRate);
}
public void End(float outSampleRate)
@@ -127,7 +158,7 @@ public void Render(TinySoundFont f, float[] outputBuffer, int offset, int numSam
}
else
{
- pitchRatio = Utils.Timecents2Secs(PitchInputTimecents) * PitchOutputFactor;
+ pitchRatio = SynthHelper.Timecents2Secs(PitchInputTimecents) * PitchOutputFactor;
tmpModLfoToPitch = 0;
tmpVibLfoToPitch = 0;
tmpModEnvToPitch = 0;
@@ -139,7 +170,7 @@ public void Render(TinySoundFont f, float[] outputBuffer, int offset, int numSam
}
else
{
- noteGain = Utils.DecibelsToGain(NoteGainDb);
+ noteGain = SynthHelper.DecibelsToGain(NoteGainDb);
tmpModLfoToVolume = 0;
}
@@ -155,15 +186,15 @@ public void Render(TinySoundFont f, float[] outputBuffer, int offset, int numSam
tmpLowpass.Active = (fres <= 13500.0f);
if (tmpLowpass.Active)
{
- tmpLowpass.Setup(Utils.Cents2Hertz(fres) / tmpSampleRate);
+ tmpLowpass.Setup(SynthHelper.Cents2Hertz(fres) / tmpSampleRate);
}
}
if (dynamicPitchRatio)
- pitchRatio = Utils.Timecents2Secs(PitchInputTimecents + (ModLfo.Level * tmpModLfoToPitch + VibLfo.Level * tmpVibLfoToPitch + ModEnv.Level * tmpModEnvToPitch)) * PitchOutputFactor;
+ pitchRatio = SynthHelper.Timecents2Secs(PitchInputTimecents + (ModLfo.Level * tmpModLfoToPitch + VibLfo.Level * tmpVibLfoToPitch + ModEnv.Level * tmpModEnvToPitch)) * PitchOutputFactor;
if (dynamicGain)
- noteGain = Utils.DecibelsToGain(NoteGainDb + (ModLfo.Level * tmpModLfoToVolume));
+ noteGain = SynthHelper.DecibelsToGain(NoteGainDb + (ModLfo.Level * tmpModLfoToVolume));
gainMono = noteGain * AmpEnv.Level;
@@ -209,8 +240,10 @@ public void Render(TinySoundFont f, float[] outputBuffer, int offset, int numSam
// Low-pass filter.
if (tmpLowpass.Active) val = tmpLowpass.Process(val);
- outputBuffer[offset + (outL++)] += val * gainLeft;
- outputBuffer[offset + (outL++)] += val * gainRight;
+ outputBuffer[offset + outL] += val * gainLeft;
+ outL++;
+ outputBuffer[offset + outL] += val * gainRight;
+ outL++;
// Next sample.
tmpSourceSamplePosition += pitchRatio;
@@ -231,8 +264,10 @@ public void Render(TinySoundFont f, float[] outputBuffer, int offset, int numSam
// Low-pass filter.
if (tmpLowpass.Active) val = tmpLowpass.Process(val);
- outputBuffer[offset + (outL++)] += val * gainLeft;
- outputBuffer[offset + (outR++)] += val * gainRight;
+ outputBuffer[offset + outL] += val * gainLeft;
+ outL++;
+ outputBuffer[offset + outR] += val * gainRight;
+ outR++;
// Next sample.
tmpSourceSamplePosition += pitchRatio;
@@ -251,7 +286,8 @@ public void Render(TinySoundFont f, float[] outputBuffer, int offset, int numSam
// Low-pass filter.
if (tmpLowpass.Active) val = tmpLowpass.Process(val);
- outputBuffer[offset + (outL++)] = val * gainMono;
+ outputBuffer[offset + outL] = val * gainMono;
+ outL++;
// Next sample.
tmpSourceSamplePosition += pitchRatio;
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelope.cs b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelope.cs
index cdcf9dd12..5e09fb3ce 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelope.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelope.cs
@@ -1,4 +1,33 @@
-using System;
+// 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
{
@@ -18,133 +47,152 @@ internal class VoiceEnvelope
public bool SegmentIsExponential { get; set; }
public bool IsAmpEnv { get; set; }
- public VoiceEnvelope()
- {
- }
-
public void NextSegment(VoiceEnvelopeSegment activeSegment, float outSampleRate)
{
- switch (activeSegment)
+ while (true) // if segment is handled, method will return
{
- case VoiceEnvelopeSegment.None:
- SamplesUntilNextSegment = (int)(Parameters.Delay * outSampleRate);
- if (SamplesUntilNextSegment > 0)
- {
- Segment = VoiceEnvelopeSegment.Delay;
- SegmentIsExponential = false;
- Level = 0.0f;
- Slope = 0.0f;
- return;
- }
- goto case VoiceEnvelopeSegment.Delay;
-
- case VoiceEnvelopeSegment.Delay:
- SamplesUntilNextSegment = (int)(Parameters.Attack * outSampleRate);
- if (SamplesUntilNextSegment > 0)
- {
- if (!this.IsAmpEnv)
+ switch (activeSegment)
+ {
+ case VoiceEnvelopeSegment.None:
+ SamplesUntilNextSegment = (int)(Parameters.Delay * outSampleRate);
+ if (SamplesUntilNextSegment > 0)
{
- //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.Delay;
+ SegmentIsExponential = false;
+ Level = 0.0f;
+ Slope = 0.0f;
+ return;
}
- Segment = VoiceEnvelopeSegment.Attack;
- SegmentIsExponential = false;
- Level = 0.0f;
- Slope = 1.0f / SamplesUntilNextSegment;
- return;
- }
- goto case VoiceEnvelopeSegment.Attack;
-
- case VoiceEnvelopeSegment.Attack:
- SamplesUntilNextSegment = (int)(Parameters.Hold * outSampleRate);
- if (SamplesUntilNextSegment > 0)
- {
- Segment = VoiceEnvelopeSegment.Hold;
- SegmentIsExponential = false;
- Level = 1.0f;
+ 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;
- }
- goto case VoiceEnvelopeSegment.Hold;
-
- case VoiceEnvelopeSegment.Hold:
- SamplesUntilNextSegment = (int)(Parameters.Decay * outSampleRate);
- if (SamplesUntilNextSegment > 0)
- {
- Segment = VoiceEnvelopeSegment.Decay;
- Level = 1.0f;
+ 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;
- 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);
+ Slope = -Level / SamplesUntilNextSegment;
SegmentIsExponential = false;
}
+
return;
- }
- goto case VoiceEnvelopeSegment.Decay;
-
- 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;
+ case VoiceEnvelopeSegment.Release:
+ default:
+ Segment = VoiceEnvelopeSegment.Done;
SegmentIsExponential = false;
- }
- return;
- case VoiceEnvelopeSegment.Release:
- default:
- Segment = VoiceEnvelopeSegment.Done;
- SegmentIsExponential = false;
- Level = Slope = 0.0f;
- SamplesUntilNextSegment = 0x7FFFFFF;
- break;
+ Level = Slope = 0.0f;
+ SamplesUntilNextSegment = 0x7FFFFFF;
+ return;
+ }
}
}
- public void Setup(Envelope newParameters, int midiNoteNumber, int midiVelocity, bool isAmpEnv, float outSampleRate)
+ 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 : Utils.Timecents2Secs(Parameters.Hold);
+ 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 : Utils.Timecents2Secs(Parameters.Decay);
+ Parameters.Decay = Parameters.Decay < -10000.0f ? 0.0f : SynthHelper.Timecents2Secs(Parameters.Decay);
}
+
MidiVelocity = (short)midiVelocity;
IsAmpEnv = isAmpEnv;
NextSegment(VoiceEnvelopeSegment.None, outSampleRate);
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelopeSegment.cs b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelopeSegment.cs
index 9140e4384..0e6969ee8 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelopeSegment.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceEnvelopeSegment.cs
@@ -1,4 +1,32 @@
-namespace AlphaTab.Audio.Synth.Synthesis
+// 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
{
@@ -10,4 +38,4 @@ internal enum VoiceEnvelopeSegment
Sustain,
Release, Done
}
-}
\ No newline at end of file
+}
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLfo.cs b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLfo.cs
index a690fb3d2..927f3c5ce 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLfo.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLfo.cs
@@ -1,4 +1,35 @@
-namespace AlphaTab.Audio.Synth.Synthesis
+// 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
{
@@ -9,7 +40,7 @@ internal class VoiceLfo
public void Setup(float delay, int freqCents, float outSampleRate)
{
SamplesUntil = (int)(delay * outSampleRate);
- Delta = (4.0f * Utils.Cents2Hertz(freqCents) / outSampleRate);
+ Delta = (4.0f * SynthHelper.Cents2Hertz(freqCents) / outSampleRate);
Level = 0;
}
@@ -33,4 +64,4 @@ public void Process(int blockSamples)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLowPass.cs b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLowPass.cs
index 41fdfa631..f71e6a49f 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLowPass.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/VoiceLowPass.cs
@@ -1,4 +1,32 @@
-using System;
+// 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
{
diff --git a/Source/AlphaTab/Audio/Synth/Util/SynthConstants.cs b/Source/AlphaTab/Audio/Synth/Util/SynthConstants.cs
index c684e0577..b28f21f1f 100644
--- a/Source/AlphaTab/Audio/Synth/Util/SynthConstants.cs
+++ b/Source/AlphaTab/Audio/Synth/Util/SynthConstants.cs
@@ -1,4 +1,32 @@
-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
{
@@ -7,27 +35,6 @@ 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 DefaultKeyCount = 128;
-
- public const float DefaultMixGain = 0.35f;
-
public const float MinVolume = 0f;
public const float MaxVolume = 1f;
diff --git a/Source/AlphaTab/Audio/Synth/Util/SynthHelper.cs b/Source/AlphaTab/Audio/Synth/Util/SynthHelper.cs
index 3d8e04323..37452fa82 100644
--- a/Source/AlphaTab/Audio/Synth/Util/SynthHelper.cs
+++ b/Source/AlphaTab/Audio/Synth/Util/SynthHelper.cs
@@ -2,39 +2,34 @@
namespace AlphaTab.Audio.Synth.Util
{
- internal class SynthHelper
+ internal static class SynthHelper
{
- public static byte ClampB(byte value, byte min, byte max)
+ public static float Timecents2Secs(float timecents)
{
- if (value <= min)
- {
- return min;
- }
-
- if (value >= max)
- {
- return max;
- }
-
- return value;
+ return (float)Math.Pow(2f, timecents / 1200f);
}
- public static double ClampD(double value, double min, double max)
+ public static double Timecents2Secs(double timecents)
{
- if (value <= min)
- {
- return min;
- }
+ return Math.Pow(2.0, timecents / 1200.0);
+ }
- if (value >= max)
- {
- return max;
- }
+ public static float DecibelsToGain(float db)
+ {
+ 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)
{
@@ -49,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)
{
@@ -64,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)
{
diff --git a/Source/AlphaTab/Audio/Synth/Util/Utils.cs b/Source/AlphaTab/Audio/Synth/Util/Utils.cs
deleted file mode 100644
index 5b50dae2b..000000000
--- a/Source/AlphaTab/Audio/Synth/Util/Utils.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System;
-
-namespace AlphaTab.Audio.Synth.Synthesis
-{
- class Utils
- {
- public static float Timecents2Secs(float timecents)
- {
- return (float)Math.Pow(2f, timecents / 1200f);
- }
-
- public static double Timecents2Secs(double timecents)
- {
- return Math.Pow(2.0, timecents / 1200.0);
- }
-
- public static float DecibelsToGain(float db)
- {
- return db > -100f ? (float) Math.Pow(10.0f, db * 0.05f) : 0;
- }
-
- public static float GainToDecibels(float gain)
- {
- return (gain <= .00001f ? -100f : (float)(20.0 * Math.Log10(gain)));
- }
-
- public static float Cents2Hertz(float cents)
- {
- return 8.176f * (float)Math.Pow(2.0f, cents / 1200.0f);
- }
- }
-}
\ No newline at end of file
From 64f8f5fea36dd371ec280d6ca66587e33ac153ec Mon Sep 17 00:00:00 2001
From: Daniel Kuschny
Date: Tue, 30 Jul 2019 14:33:41 +0200
Subject: [PATCH 03/11] Consider mix volume correctly on synthesis, ensure
track volumes are correctly initialized.
---
Source/AlphaTab/AlphaTabApi.cs | 9 +++++++++
Source/AlphaTab/Audio/Synth/Synthesis/Channels.cs | 2 +-
Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs | 1 +
Source/AlphaTab/Audio/Synth/Synthesis/Voice.cs | 4 ++++
4 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/Source/AlphaTab/AlphaTabApi.cs b/Source/AlphaTab/AlphaTabApi.cs
index 0973bcf47..23eab1ae7 100644
--- a/Source/AlphaTab/AlphaTabApi.cs
+++ b/Source/AlphaTab/AlphaTabApi.cs
@@ -383,6 +383,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/Synth/Synthesis/Channels.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Channels.cs
index ebb3c92ec..46b308b0f 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/Channels.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/Channels.cs
@@ -27,7 +27,6 @@
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
-using System.Collections.Generic;
using AlphaTab.Collections;
namespace AlphaTab.Audio.Synth.Synthesis
@@ -47,6 +46,7 @@ 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
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs b/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs
index 05fa02371..63f6d8301 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.cs
@@ -384,6 +384,7 @@ private Channel ChannelInit(int channel)
c.GainDb = 0.0f;
c.PitchRange = 2.0f;
c.Tuning = 0.0f;
+ c.MixVolume = 1;
_channels.ChannelList.Add(c);
}
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/Voice.cs b/Source/AlphaTab/Audio/Synth/Synthesis/Voice.cs
index 9e392667c..4282011e9 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/Voice.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/Voice.cs
@@ -202,6 +202,10 @@ public void Render(TinySoundFont f, float[] outputBuffer, int offset, int numSam
{
gainMono = 0;
}
+ else
+ {
+ gainMono *= MixVolume;
+ }
// Update EG.
AmpEnv.Process(blockSamples, f.OutSampleRate);
From bf79729e9ee902685d2bbc0303f2e5b935d8cbb9 Mon Sep 17 00:00:00 2001
From: Daniel Kuschny
Date: Tue, 30 Jul 2019 14:37:22 +0200
Subject: [PATCH 04/11] Added TSF and SFZero info in alphaTab output license
header
---
Tools/header.js | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
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
From e374525eb3d5aeb8af53bab05ff047bc30691f57 Mon Sep 17 00:00:00 2001
From: Daniel Kuschny
Date: Tue, 30 Jul 2019 14:57:36 +0200
Subject: [PATCH 05/11] Fixed compilation issues
---
.../test/alphaTab.tests.specs.js | 4 --
Source/AlphaTab.Test.Js/test/karma.conf.js | 2 +-
Source/AlphaTab.Test/Audio/AlphaSynthTests.cs | 49 +------------------
3 files changed, 2 insertions(+), 53 deletions(-)
diff --git a/Source/AlphaTab.Test.Js/test/alphaTab.tests.specs.js b/Source/AlphaTab.Test.Js/test/alphaTab.tests.specs.js
index 401a517d7..23f65b37a 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();
diff --git a/Source/AlphaTab.Test.Js/test/karma.conf.js b/Source/AlphaTab.Test.Js/test/karma.conf.js
index f1da284d1..6fa8e1a6e 100644
--- a/Source/AlphaTab.Test.Js/test/karma.conf.js
+++ b/Source/AlphaTab.Test.Js/test/karma.conf.js
@@ -62,7 +62,7 @@ module.exports = function(config) {
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
- browsers: ['Chrome'],
+ browsers: ['ChromeHeadless'],
// Continuous Integration mode
diff --git a/Source/AlphaTab.Test/Audio/AlphaSynthTests.cs b/Source/AlphaTab.Test/Audio/AlphaSynthTests.cs
index c476ebe8b..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()
{
From 12bc4786572d1ad347b36e8e6ff1f540b0ec561c Mon Sep 17 00:00:00 2001
From: Daniel Kuschny
Date: Tue, 30 Jul 2019 15:07:15 +0200
Subject: [PATCH 06/11] Fixed tests
---
.../Audio/MidiFileGeneratorTest.cs | 128 +++++++++---------
1 file changed, 64 insertions(+), 64 deletions(-)
diff --git a/Source/AlphaTab.Test/Audio/MidiFileGeneratorTest.cs b/Source/AlphaTab.Test/Audio/MidiFileGeneratorTest.cs
index a548ea433..4b3cb1bcb 100644
--- a/Source/AlphaTab.Test/Audio/MidiFileGeneratorTest.cs
+++ b/Source/AlphaTab.Test/Audio/MidiFileGeneratorTest.cs
@@ -85,26 +85,26 @@ public void TestBend()
new FlatMidiEventGenerator.TimeSignatureEvent { Tick = 0, Numerator = 4, Denominator = 4 },
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 },
+ // bend effect
+ 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
@@ -209,40 +209,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
@@ -306,41 +306,41 @@ public void TestBendMultiPoint()
new FlatMidiEventGenerator.TimeSignatureEvent { Tick = 0, Numerator = 4, Denominator = 4 },
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
+ // bend effect
+ 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
+ // end of track
new FlatMidiEventGenerator.TrackEndEvent { Tick = 3840, Track = 0 } // 3840 = end of bar
};
From 1068faf772d907a120cd3c87bbe4b481eda20db2 Mon Sep 17 00:00:00 2001
From: Daniel Kuschny
Date: Tue, 30 Jul 2019 16:36:00 +0200
Subject: [PATCH 07/11] Corrected behavior where initial midi events were
re-generated after sequencer finish.
---
.../JavaScript/AlphaSynthWebAudioOutput.cs | 6 ------
Source/AlphaTab/Audio/Synth/AlphaSynth.cs | 1 +
.../AlphaTab/Audio/Synth/MidiFileSequencer.cs | 18 ++++++++++++++----
3 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWebAudioOutput.cs b/Source/AlphaTab.JavaScript/Platform/JavaScript/AlphaSynthWebAudioOutput.cs
index df5a87f0a..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()
@@ -180,9 +177,6 @@ private void GenerateSound(AudioProcessingEvent e)
}
else
{
- _contextTimeOnGenerate = _context.CurrentTime;
- _samplesGenerated = left.Length;
-
var buffer = new float[samples];
_circularBuffer.Read(buffer, 0, buffer.Length);
diff --git a/Source/AlphaTab/Audio/Synth/AlphaSynth.cs b/Source/AlphaTab/Audio/Synth/AlphaSynth.cs
index 37f9aa254..8654baf5d 100644
--- a/Source/AlphaTab/Audio/Synth/AlphaSynth.cs
+++ b/Source/AlphaTab/Audio/Synth/AlphaSynth.cs
@@ -236,6 +236,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));
diff --git a/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs b/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs
index 98b6f80dd..43d1a8935 100644
--- a/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs
+++ b/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs
@@ -338,22 +338,32 @@ public void CheckForStop()
{
if (PlaybackRange == null && _currentTime >= _endTime)
{
- _currentTime = 0;
- _eventIndex = 0;
_synthesizer.NoteOffAll(true);
_synthesizer.Reset();
OnFinished();
}
else if (PlaybackRange != null && _currentTime >= _playbackRangeEndTime)
{
- _currentTime = PlaybackRange.StartTick;
- _eventIndex = 0;
_synthesizer.NoteOffAll(true);
_synthesizer.Reset();
OnFinished();
}
}
+ public void Stop()
+ {
+ if (PlaybackRange == null)
+ {
+ _currentTime = 0;
+ _eventIndex = 0;
+ }
+ else if (PlaybackRange != null && _currentTime >= _playbackRangeEndTime)
+ {
+ _currentTime = PlaybackRange.StartTick;
+ _eventIndex = 0;
+ }
+ }
+
public void SetChannelProgram(int channel, byte program)
{
if (_firstProgramEventPerChannel.ContainsKey(channel))
From 96bcdb358ea55d548d974d3d73564d61e723885a Mon Sep 17 00:00:00 2001
From: Daniel Kuschny
Date: Tue, 30 Jul 2019 16:38:14 +0200
Subject: [PATCH 08/11] Always execute stop to ensure seek to start
---
Source/AlphaTab/Audio/Synth/AlphaSynth.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Source/AlphaTab/Audio/Synth/AlphaSynth.cs b/Source/AlphaTab/Audio/Synth/AlphaSynth.cs
index 8654baf5d..0fceb5871 100644
--- a/Source/AlphaTab/Audio/Synth/AlphaSynth.cs
+++ b/Source/AlphaTab/Audio/Synth/AlphaSynth.cs
@@ -228,7 +228,7 @@ public void PlayPause()
///
public void Stop()
{
- if (State == PlayerState.Paused || !IsReadyForPlayback)
+ if (!IsReadyForPlayback)
{
return;
}
From c73562a0d530ae996901c3f1dd137e7858f0250b Mon Sep 17 00:00:00 2001
From: Daniel Kuschny
Date: Tue, 30 Jul 2019 16:43:01 +0200
Subject: [PATCH 09/11] Ensure midi event dispatching does not exceed playback
range
---
Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs | 15 ++++++---------
1 file changed, 6 insertions(+), 9 deletions(-)
diff --git a/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs b/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs
index 43d1a8935..266faa29d 100644
--- a/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs
+++ b/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs
@@ -242,10 +242,11 @@ private bool FillMidiEventQueueLimited(double maxMilliseconds)
}
var anyEventsDispatched = false;
+ 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++;
@@ -334,15 +335,11 @@ protected virtual void OnFinished()
}
+ private double InternalEndTime => PlaybackRange == null ? _endTime : _playbackRangeEndTime;
+
public void CheckForStop()
{
- if (PlaybackRange == null && _currentTime >= _endTime)
- {
- _synthesizer.NoteOffAll(true);
- _synthesizer.Reset();
- OnFinished();
- }
- else if (PlaybackRange != null && _currentTime >= _playbackRangeEndTime)
+ if (_currentTime >= InternalEndTime)
{
_synthesizer.NoteOffAll(true);
_synthesizer.Reset();
@@ -357,7 +354,7 @@ public void Stop()
_currentTime = 0;
_eventIndex = 0;
}
- else if (PlaybackRange != null && _currentTime >= _playbackRangeEndTime)
+ else if (PlaybackRange != null)
{
_currentTime = PlaybackRange.StartTick;
_eventIndex = 0;
From efd2c15be2524865afcc99c0b849be227f48fb73 Mon Sep 17 00:00:00 2001
From: Daniel Kuschny
Date: Tue, 30 Jul 2019 17:37:11 +0200
Subject: [PATCH 10/11] Fixed metronome
---
Source/AlphaTab/Audio/Synth/AlphaSynth.cs | 2 ++
Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs | 2 ++
.../Audio/Synth/Synthesis/TinySoundFont.AlphaTab.cs | 10 ++++++++--
Source/AlphaTab/Audio/Synth/Util/SynthConstants.cs | 1 +
4 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/Source/AlphaTab/Audio/Synth/AlphaSynth.cs b/Source/AlphaTab/Audio/Synth/AlphaSynth.cs
index 0fceb5871..f6d47dea8 100644
--- a/Source/AlphaTab/Audio/Synth/AlphaSynth.cs
+++ b/Source/AlphaTab/Audio/Synth/AlphaSynth.cs
@@ -190,6 +190,7 @@ public void Play()
}
Output.Activate();
+ _synthesizer.SetupMetronomeChannel();
Logger.Debug("AlphaSynth", "Starting playback");
State = PlayerState.Playing;
@@ -271,6 +272,7 @@ private void CheckReadyForPlayback()
{
if (IsReadyForPlayback)
{
+ _synthesizer.SetupMetronomeChannel();
OnReadyForPlayback();
}
}
diff --git a/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs b/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs
index 266faa29d..096cf4d0d 100644
--- a/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs
+++ b/Source/AlphaTab/Audio/Synth/MidiFileSequencer.cs
@@ -112,6 +112,7 @@ public void Seek(double timePosition)
_eventIndex = 0;
_synthesizer.NoteOffAll(true);
_synthesizer.Reset();
+ _synthesizer.SetupMetronomeChannel();
SilentProcess(timePosition);
}
@@ -343,6 +344,7 @@ public void CheckForStop()
{
_synthesizer.NoteOffAll(true);
_synthesizer.Reset();
+ _synthesizer.SetupMetronomeChannel();
OnFinished();
}
}
diff --git a/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.AlphaTab.cs b/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.AlphaTab.cs
index bec29d465..b567ea806 100644
--- a/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.AlphaTab.cs
+++ b/Source/AlphaTab/Audio/Synth/Synthesis/TinySoundFont.AlphaTab.cs
@@ -107,8 +107,8 @@ private float[] FillWorkingBuffer(bool silent)
var m = _midiEventQueue.RemoveLast();
if (m.IsMetronome)
{
- NoteOff(SynthConstants.MetronomeChannel, 37);
- NoteOn(SynthConstants.MetronomeChannel, 37, 95);
+ ChannelNoteOff(SynthConstants.MetronomeChannel, 33);
+ ChannelNoteOn(SynthConstants.MetronomeChannel, 33, 95 / 127f);
}
else
{
@@ -175,5 +175,11 @@ private void ProcessMidiMessage(MidiEvent e)
break;
}
}
+
+ public void SetupMetronomeChannel()
+ {
+ ChannelSetVolume(SynthConstants.MetronomeChannel, 1);
+ ChannelSetPresetNumber(SynthConstants.MetronomeChannel, 0, true);
+ }
}
}
diff --git a/Source/AlphaTab/Audio/Synth/Util/SynthConstants.cs b/Source/AlphaTab/Audio/Synth/Util/SynthConstants.cs
index b28f21f1f..c7f1e46f8 100644
--- a/Source/AlphaTab/Audio/Synth/Util/SynthConstants.cs
+++ b/Source/AlphaTab/Audio/Synth/Util/SynthConstants.cs
@@ -30,6 +30,7 @@ namespace AlphaTab.Audio.Synth.Util
{
internal static class SynthConstants
{
+ public const int DrumBank = 128;
public const int DefaultChannelCount = 16 + 1 /*metronome*/;
public const int MetronomeChannel = DefaultChannelCount - 1;
From c7bc061eee2d452028e29af1ccd13a43a0977c70 Mon Sep 17 00:00:00 2001
From: Daniel Kuschny
Date: Tue, 30 Jul 2019 17:38:01 +0200
Subject: [PATCH 11/11] Fixed issue on GP7 files that midi settings are not
correctly loaded.
---
Source/AlphaTab/Importer/GpifParser.cs | 35 +++++++++++++-------------
1 file changed, 18 insertions(+), 17 deletions(-)
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;