Decompiled source of BigDoom v1.0.0
BigWalkDoom.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
#define DEBUG using System; using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using HarmonyLib; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.Injection; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using ManagedDoom; using ManagedDoom.Audio; using ManagedDoom.Unity; using ManagedDoom.UserInput; using ManagedDoom.Video; using MeltySynth; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("BigWalkDoom")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("BigWalkDoom")] [assembly: AssemblyTitle("BigWalkDoom")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace MeltySynth { internal static class ArrayMath { public static void MultiplyAdd(float a, float[] x, float[] destination) { for (int i = 0; i < destination.Length; i++) { destination[i] += a * x[i]; } } public static void MultiplyAdd(float a, float step, float[] x, float[] destination) { for (int i = 0; i < destination.Length; i++) { destination[i] += a * x[i]; a += step; } } } public static class AudioRendererEx { public static void RenderInterleaved(this IAudioRenderer renderer, Span<float> destination) { if (renderer == null) { throw new ArgumentNullException("renderer"); } if (destination.Length % 2 != 0) { throw new ArgumentException("The length of the destination buffer must be even.", "destination"); } int num = destination.Length / 2; int length = destination.Length; float[] array = ArrayPool<float>.Shared.Rent(length); try { Span<float> left = new Span<float>(array, 0, num); Span<float> right = new Span<float>(array, num, num); renderer.Render(left, right); int num2 = 0; for (int i = 0; i < num; i++) { destination[num2++] = left[i]; destination[num2++] = right[i]; } } finally { ArrayPool<float>.Shared.Return(array); } } public static void RenderMono(this IAudioRenderer renderer, Span<float> destination) { if (renderer == null) { throw new ArgumentNullException("renderer"); } int length = destination.Length; int minimumLength = 2 * destination.Length; float[] array = ArrayPool<float>.Shared.Rent(minimumLength); try { Span<float> left = new Span<float>(array, 0, length); Span<float> right = new Span<float>(array, length, length); renderer.Render(left, right); for (int i = 0; i < length; i++) { destination[i] = (left[i] + right[i]) / 2f; } } finally { ArrayPool<float>.Shared.Return(array); } } public static void RenderInt16(this IAudioRenderer renderer, Span<short> left, Span<short> right) { if (renderer == null) { throw new ArgumentNullException("renderer"); } if (left.Length != right.Length) { throw new ArgumentException("The output buffers for the left and right must be the same length."); } int length = left.Length; int minimumLength = 2 * left.Length; float[] array = ArrayPool<float>.Shared.Rent(minimumLength); try { Span<float> left2 = new Span<float>(array, 0, length); Span<float> right2 = new Span<float>(array, length, length); renderer.Render(left2, right2); for (int i = 0; i < length; i++) { float num = 32768f * left2[i]; if (num < -32768f) { num = -32768f; } else if (num > 32767f) { num = 32767f; } left[i] = (short)num; } for (int j = 0; j < length; j++) { float num2 = 32768f * right2[j]; if (num2 < -32768f) { num2 = -32768f; } else if (num2 > 32767f) { num2 = 32767f; } right[j] = (short)num2; } } finally { ArrayPool<float>.Shared.Return(array); } } public static void RenderInterleavedInt16(this IAudioRenderer renderer, Span<short> destination) { if (renderer == null) { throw new ArgumentNullException("renderer"); } if (destination.Length % 2 != 0) { throw new ArgumentException("The length of the destination buffer must be even.", "destination"); } int num = destination.Length / 2; int length = destination.Length; float[] array = ArrayPool<float>.Shared.Rent(length); try { Span<float> left = new Span<float>(array, 0, num); Span<float> right = new Span<float>(array, num, num); renderer.Render(left, right); int num2 = 0; for (int i = 0; i < num; i++) { int num3 = (int)(32768f * left[i]); if (num3 < -32768) { num3 = -32768; } else if (num3 > 32767) { num3 = 32767; } int num4 = (int)(32768f * right[i]); if (num4 < -32768) { num4 = -32768; } else if (num4 > 32767) { num4 = 32767; } destination[num2++] = (short)num3; destination[num2++] = (short)num4; } } finally { ArrayPool<float>.Shared.Return(array); } } public static void RenderMonoInt16(this IAudioRenderer renderer, Span<short> destination) { if (renderer == null) { throw new ArgumentNullException("renderer"); } int length = destination.Length; int minimumLength = 2 * destination.Length; float[] array = ArrayPool<float>.Shared.Rent(minimumLength); try { Span<float> left = new Span<float>(array, 0, length); Span<float> right = new Span<float>(array, length, length); renderer.Render(left, right); for (int i = 0; i < length; i++) { int num = (int)(16384f * (left[i] + right[i])); if (num < -32768) { num = -32768; } else if (num > 32767) { num = 32767; } destination[i] = (short)num; } } finally { ArrayPool<float>.Shared.Return(array); } } } internal static class BinaryReaderEx { public static string ReadFourCC(this BinaryReader reader) { byte[] array = reader.ReadBytes(4); for (int i = 0; i < array.Length; i++) { byte b = array[i]; if (32 > b || b > 126) { array[i] = 63; } } return Encoding.ASCII.GetString(array, 0, array.Length); } public static string ReadFixedLengthString(this BinaryReader reader, int length) { byte[] array = reader.ReadBytes(length); int i; for (i = 0; i < array.Length && array[i] != 0; i++) { } return Encoding.ASCII.GetString(array, 0, i); } public static short ReadInt16BigEndian(this BinaryReader reader) { short num = reader.ReadInt16(); int num2 = 0xFF & num; int num3 = 0xFF & (num >> 8); return (short)((num2 << 8) | num3); } public static int ReadInt32BigEndian(this BinaryReader reader) { int num = reader.ReadInt32(); int num2 = 0xFF & num; int num3 = 0xFF & (num >> 8); int num4 = 0xFF & (num >> 16); int num5 = 0xFF & (num >> 24); return (num2 << 24) | (num3 << 16) | (num4 << 8) | num5; } public static int ReadIntVariableLength(this BinaryReader reader) { int num = 0; int num2 = 0; while (true) { byte b = reader.ReadByte(); num = (num << 7) | (b & 0x7F); if ((b & 0x80) == 0) { break; } num2++; if (num2 == 4) { throw new InvalidDataException("The length of the value must be equal to or less than 4."); } } return num; } } internal sealed class BiQuadFilter { private static readonly float resonancePeakOffset = 1f - 1f / Mathf.Sqrt(2f); private readonly Synthesizer synthesizer; private bool active; private float a0; private float a1; private float a2; private float a3; private float a4; private float x1; private float x2; private float y1; private float y2; internal BiQuadFilter(Synthesizer synthesizer) { this.synthesizer = synthesizer; } public void ClearBuffer() { x1 = 0f; x2 = 0f; y1 = 0f; y2 = 0f; } public void SetLowPassFilter(float cutoffFrequency, float resonance) { if (cutoffFrequency < 0.499f * (float)synthesizer.SampleRate) { active = true; float num = resonance - resonancePeakOffset / (1f + 6f * (resonance - 1f)); float num2 = (float)Math.PI * 2f * cutoffFrequency / (float)synthesizer.SampleRate; float num3 = Mathf.Cos(num2); float num4 = Mathf.Sin(num2) / (2f * num); float b = (1f - num3) / 2f; float b2 = 1f - num3; float b3 = (1f - num3) / 2f; float num5 = 1f + num4; float num6 = -2f * num3; float num7 = 1f - num4; SetCoefficients(num5, num6, num7, b, b2, b3); } else { active = false; } } public void Process(float[] block) { if (active) { for (int i = 0; i < block.Length; i++) { float num = block[i]; float num2 = a0 * num + a1 * x1 + a2 * x2 - a3 * y1 - a4 * y2; x2 = x1; x1 = num; y2 = y1; y1 = num2; block[i] = num2; } } else { x2 = block[^2]; x1 = block[^1]; y2 = x2; y1 = x1; } } private void SetCoefficients(float a0, float a1, float a2, float b0, float b1, float b2) { this.a0 = b0 / a0; this.a1 = b1 / a0; this.a2 = b2 / a0; a3 = a1 / a0; a4 = a2 / a0; } } internal sealed class Channel { private readonly Synthesizer synthesizer; private readonly bool isPercussionChannel; private int bankNumber; private int patchNumber; private short modulation; private short volume; private short pan; private short expression; private bool holdPedal; private byte reverbSend; private byte chorusSend; private short rpn; private short pitchBendRange; private short coarseTune; private short fineTune; private float pitchBend; public bool IsPercussionChannel => isPercussionChannel; public int BankNumber => bankNumber; public int PatchNumber => patchNumber; public float Modulation => 0.003051944f * (float)modulation; public float Volume => 6.103888E-05f * (float)volume; public float Pan => 0.006103888f * (float)pan - 50f; public float Expression => 6.103888E-05f * (float)expression; public bool HoldPedal => holdPedal; public float ReverbSend => 1f / 127f * (float)(int)reverbSend; public float ChorusSend => 1f / 127f * (float)(int)chorusSend; public float PitchBendRange => (float)(pitchBendRange >> 7) + 0.01f * (float)(pitchBendRange & 0x7F); public float Tune => (float)coarseTune + 0.00012207031f * (float)(fineTune - 8192); public float PitchBend => PitchBendRange * pitchBend; internal Channel(Synthesizer synthesizer, bool isPercussionChannel) { this.synthesizer = synthesizer; this.isPercussionChannel = isPercussionChannel; Reset(); } public void Reset() { bankNumber = (isPercussionChannel ? 128 : 0); patchNumber = 0; modulation = 0; volume = 12800; pan = 8192; expression = 16256; holdPedal = false; reverbSend = 40; chorusSend = 0; rpn = -1; pitchBendRange = 256; coarseTune = 0; fineTune = 8192; pitchBend = 0f; } public void ResetAllControllers() { modulation = 0; expression = 16256; holdPedal = false; rpn = -1; pitchBend = 0f; } public void SetBank(int value) { bankNumber = value; if (isPercussionChannel) { bankNumber += 128; } } public void SetPatch(int value) { patchNumber = value; } public void SetModulationCoarse(int value) { modulation = (short)((modulation & 0x7F) | (value << 7)); } public void SetModulationFine(int value) { modulation = (short)((modulation & 0xFF80) | value); } public void SetVolumeCoarse(int value) { volume = (short)((volume & 0x7F) | (value << 7)); } public void SetVolumeFine(int value) { volume = (short)((volume & 0xFF80) | value); } public void SetPanCoarse(int value) { pan = (short)((pan & 0x7F) | (value << 7)); } public void SetPanFine(int value) { pan = (short)((pan & 0xFF80) | value); } public void SetExpressionCoarse(int value) { expression = (short)((expression & 0x7F) | (value << 7)); } public void SetExpressionFine(int value) { expression = (short)((expression & 0xFF80) | value); } public void SetHoldPedal(int value) { holdPedal = value >= 64; } public void SetReverbSend(int value) { reverbSend = (byte)value; } public void SetChorusSend(int value) { chorusSend = (byte)value; } public void SetRpnCoarse(int value) { rpn = (short)((rpn & 0x7F) | (value << 7)); } public void SetRpnFine(int value) { rpn = (short)((rpn & 0xFF80) | value); } public void DataEntryCoarse(int value) { switch (rpn) { case 0: pitchBendRange = (short)((pitchBendRange & 0x7F) | (value << 7)); break; case 1: fineTune = (short)((fineTune & 0x7F) | (value << 7)); break; case 2: coarseTune = (short)(value - 64); break; } } public void DataEntryFine(int value) { switch (rpn) { case 0: pitchBendRange = (short)((pitchBendRange & 0xFF80) | value); break; case 1: fineTune = (short)((fineTune & 0xFF80) | value); break; } } public void SetPitchBend(int value1, int value2) { pitchBend = 0.00012207031f * (float)((value1 | (value2 << 7)) - 8192); } } internal sealed class Chorus { private readonly float[] bufferL; private readonly float[] bufferR; private readonly float[] delayTable; private int bufferIndexL; private int bufferIndexR; private int delayTableIndexL; private int delayTableIndexR; internal Chorus(int sampleRate, double delay, double depth, double frequency) { bufferL = new float[(int)((double)sampleRate * (delay + depth)) + 2]; bufferR = new float[(int)((double)sampleRate * (delay + depth)) + 2]; delayTable = new float[(int)Math.Round((double)sampleRate / frequency)]; for (int i = 0; i < delayTable.Length; i++) { double a = Math.PI * 2.0 * (double)i / (double)delayTable.Length; delayTable[i] = (float)((double)sampleRate * (delay + depth * Math.Sin(a))); } bufferIndexL = 0; bufferIndexR = 0; delayTableIndexL = 0; delayTableIndexR = delayTable.Length / 4; } public void Process(float[] inputLeft, float[] inputRight, float[] outputLeft, float[] outputRight) { for (int i = 0; i < outputLeft.Length; i++) { double num = (double)bufferIndexL - (double)delayTable[delayTableIndexL]; if (num < 0.0) { num += (double)bufferL.Length; } int num2 = (int)num; int num3 = num2 + 1; if (num3 == bufferL.Length) { num3 = 0; } double num4 = bufferL[num2]; double num5 = bufferL[num3]; double num6 = num - (double)num2; outputLeft[i] = (float)(num4 + num6 * (num5 - num4)); bufferL[bufferIndexL] = inputLeft[i]; bufferIndexL++; if (bufferIndexL == bufferL.Length) { bufferIndexL = 0; } delayTableIndexL++; if (delayTableIndexL == delayTable.Length) { delayTableIndexL = 0; } } for (int j = 0; j < outputRight.Length; j++) { double num7 = (double)bufferIndexR - (double)delayTable[delayTableIndexR]; if (num7 < 0.0) { num7 += (double)bufferR.Length; } int num8 = (int)num7; int num9 = num8 + 1; if (num9 == bufferR.Length) { num9 = 0; } double num10 = bufferR[num8]; double num11 = bufferR[num9]; double num12 = num7 - (double)num8; outputRight[j] = (float)(num10 + num12 * (num11 - num10)); bufferR[bufferIndexR] = inputRight[j]; bufferIndexR++; if (bufferIndexR == bufferR.Length) { bufferIndexR = 0; } delayTableIndexR++; if (delayTableIndexR == delayTable.Length) { delayTableIndexR = 0; } } } public void Mute() { Array.Clear(bufferL, 0, bufferL.Length); Array.Clear(bufferR, 0, bufferR.Length); } } internal struct Generator { private readonly GeneratorType type; private readonly ushort value; public GeneratorType Type => type; public ushort Value => value; private Generator(BinaryReader reader) { type = (GeneratorType)reader.ReadUInt16(); value = reader.ReadUInt16(); } internal static Generator[] ReadFromChunk(BinaryReader reader, int size) { if (size % 4 != 0) { throw new InvalidDataException("The generator list is invalid."); } Generator[] array = new Generator[size / 4 - 1]; for (int i = 0; i < array.Length; i++) { array[i] = new Generator(reader); } new Generator(reader); return array; } } internal enum GeneratorType : ushort { StartAddressOffset, EndAddressOffset, StartLoopAddressOffset, EndLoopAddressOffset, StartAddressCoarseOffset, ModulationLfoToPitch, VibratoLfoToPitch, ModulationEnvelopeToPitch, InitialFilterCutoffFrequency, InitialFilterQ, ModulationLfoToFilterCutoffFrequency, ModulationEnvelopeToFilterCutoffFrequency, EndAddressCoarseOffset, ModulationLfoToVolume, Unused1, ChorusEffectsSend, ReverbEffectsSend, Pan, Unused2, Unused3, Unused4, DelayModulationLfo, FrequencyModulationLfo, DelayVibratoLfo, FrequencyVibratoLfo, DelayModulationEnvelope, AttackModulationEnvelope, HoldModulationEnvelope, DecayModulationEnvelope, SustainModulationEnvelope, ReleaseModulationEnvelope, KeyNumberToModulationEnvelopeHold, KeyNumberToModulationEnvelopeDecay, DelayVolumeEnvelope, AttackVolumeEnvelope, HoldVolumeEnvelope, DecayVolumeEnvelope, SustainVolumeEnvelope, ReleaseVolumeEnvelope, KeyNumberToVolumeEnvelopeHold, KeyNumberToVolumeEnvelopeDecay, Instrument, Reserved1, KeyRange, VelocityRange, StartLoopAddressCoarseOffset, KeyNumber, Velocity, InitialAttenuation, Reserved2, EndLoopAddressCoarseOffset, CoarseTune, FineTune, SampleID, SampleModes, Reserved3, ScaleTuning, ExclusiveClass, OverridingRootKey, Unused5, UnusedEnd } public interface IAudioRenderer { void Render(Span<float> left, Span<float> right); } public sealed class Instrument { internal static readonly Instrument Default = new Instrument(); private readonly string name; private readonly InstrumentRegion[] regions; public string Name => name; public IReadOnlyList<InstrumentRegion> Regions => regions; internal InstrumentRegion[] RegionArray => regions; private Instrument() { name = "Default"; regions = Array.Empty<InstrumentRegion>(); } private Instrument(InstrumentInfo info, Zone[] zones, SampleHeader[] samples) { name = info.Name; int num = info.ZoneEndIndex - info.ZoneStartIndex + 1; if (num <= 0) { throw new InvalidDataException("The instrument '" + info.Name + "' has no zone."); } Span<Zone> zones2 = new Span<Zone>(zones, info.ZoneStartIndex, num); regions = InstrumentRegion.Create(this, zones2, samples); } internal static Instrument[] Create(InstrumentInfo[] infos, Zone[] zones, SampleHeader[] samples) { if (infos.Length <= 1) { throw new InvalidDataException("No valid instrument was found."); } Instrument[] array = new Instrument[infos.Length - 1]; for (int i = 0; i < array.Length; i++) { array[i] = new Instrument(infos[i], zones, samples); } return array; } public override string ToString() { return name; } } internal sealed class InstrumentInfo { private string name; private int zoneStartIndex; private int zoneEndIndex; public string Name => name; public int ZoneStartIndex => zoneStartIndex; public int ZoneEndIndex => zoneEndIndex; private InstrumentInfo(BinaryReader reader) { name = reader.ReadFixedLengthString(20); zoneStartIndex = reader.ReadUInt16(); } internal static InstrumentInfo[] ReadFromChunk(BinaryReader reader, int size) { if (size % 22 != 0) { throw new InvalidDataException("The instrument list is invalid."); } int num = size / 22; InstrumentInfo[] array = new InstrumentInfo[num]; for (int i = 0; i < num; i++) { array[i] = new InstrumentInfo(reader); } for (int j = 0; j < num - 1; j++) { array[j].zoneEndIndex = array[j + 1].zoneStartIndex - 1; } return array; } } public sealed class InstrumentRegion { internal static readonly InstrumentRegion Default = new InstrumentRegion(); private readonly short[] gs; private readonly SampleHeader sample; internal short this[GeneratorType generatortType] => gs[(uint)generatortType]; public SampleHeader Sample => sample; public int SampleStart => sample.Start + StartAddressOffset; public int SampleEnd => sample.End + EndAddressOffset; public int SampleStartLoop => sample.StartLoop + StartLoopAddressOffset; public int SampleEndLoop => sample.EndLoop + EndLoopAddressOffset; public int StartAddressOffset => 32768 * this[GeneratorType.StartAddressCoarseOffset] + this[GeneratorType.StartAddressOffset]; public int EndAddressOffset => 32768 * this[GeneratorType.EndAddressCoarseOffset] + this[GeneratorType.EndAddressOffset]; public int StartLoopAddressOffset => 32768 * this[GeneratorType.StartLoopAddressCoarseOffset] + this[GeneratorType.StartLoopAddressOffset]; public int EndLoopAddressOffset => 32768 * this[GeneratorType.EndLoopAddressCoarseOffset] + this[GeneratorType.EndLoopAddressOffset]; public int ModulationLfoToPitch => this[GeneratorType.ModulationLfoToPitch]; public int VibratoLfoToPitch => this[GeneratorType.VibratoLfoToPitch]; public int ModulationEnvelopeToPitch => this[GeneratorType.ModulationEnvelopeToPitch]; public float InitialFilterCutoffFrequency => SoundFontMath.CentsToHertz(this[GeneratorType.InitialFilterCutoffFrequency]); public float InitialFilterQ => 0.1f * (float)this[GeneratorType.InitialFilterQ]; public int ModulationLfoToFilterCutoffFrequency => this[GeneratorType.ModulationLfoToFilterCutoffFrequency]; public int ModulationEnvelopeToFilterCutoffFrequency => this[GeneratorType.ModulationEnvelopeToFilterCutoffFrequency]; public float ModulationLfoToVolume => 0.1f * (float)this[GeneratorType.ModulationLfoToVolume]; public float ChorusEffectsSend => 0.1f * (float)this[GeneratorType.ChorusEffectsSend]; public float ReverbEffectsSend => 0.1f * (float)this[GeneratorType.ReverbEffectsSend]; public float Pan => 0.1f * (float)this[GeneratorType.Pan]; public float DelayModulationLfo => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayModulationLfo]); public float FrequencyModulationLfo => SoundFontMath.CentsToHertz(this[GeneratorType.FrequencyModulationLfo]); public float DelayVibratoLfo => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayVibratoLfo]); public float FrequencyVibratoLfo => SoundFontMath.CentsToHertz(this[GeneratorType.FrequencyVibratoLfo]); public float DelayModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayModulationEnvelope]); public float AttackModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.AttackModulationEnvelope]); public float HoldModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.HoldModulationEnvelope]); public float DecayModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DecayModulationEnvelope]); public float SustainModulationEnvelope => 0.1f * (float)this[GeneratorType.SustainModulationEnvelope]; public float ReleaseModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.ReleaseModulationEnvelope]); public int KeyNumberToModulationEnvelopeHold => this[GeneratorType.KeyNumberToModulationEnvelopeHold]; public int KeyNumberToModulationEnvelopeDecay => this[GeneratorType.KeyNumberToModulationEnvelopeDecay]; public float DelayVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayVolumeEnvelope]); public float AttackVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.AttackVolumeEnvelope]); public float HoldVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.HoldVolumeEnvelope]); public float DecayVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DecayVolumeEnvelope]); public float SustainVolumeEnvelope => 0.1f * (float)this[GeneratorType.SustainVolumeEnvelope]; public float ReleaseVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.ReleaseVolumeEnvelope]); public int KeyNumberToVolumeEnvelopeHold => this[GeneratorType.KeyNumberToVolumeEnvelopeHold]; public int KeyNumberToVolumeEnvelopeDecay => this[GeneratorType.KeyNumberToVolumeEnvelopeDecay]; public int KeyRangeStart => this[GeneratorType.KeyRange] & 0xFF; public int KeyRangeEnd => (this[GeneratorType.KeyRange] >> 8) & 0xFF; public int VelocityRangeStart => this[GeneratorType.VelocityRange] & 0xFF; public int VelocityRangeEnd => (this[GeneratorType.VelocityRange] >> 8) & 0xFF; public float InitialAttenuation => 0.1f * (float)this[GeneratorType.InitialAttenuation]; public int CoarseTune => this[GeneratorType.CoarseTune]; public int FineTune => this[GeneratorType.FineTune] + sample.PitchCorrection; public LoopMode SampleModes => (LoopMode)((this[GeneratorType.SampleModes] != 2) ? this[GeneratorType.SampleModes] : 0); public int ScaleTuning => this[GeneratorType.ScaleTuning]; public int ExclusiveClass => this[GeneratorType.ExclusiveClass]; public int RootKey => (this[GeneratorType.OverridingRootKey] != -1) ? this[GeneratorType.OverridingRootKey] : sample.OriginalPitch; private InstrumentRegion() { gs = new short[61]; gs[8] = 13500; gs[21] = -12000; gs[23] = -12000; gs[25] = -12000; gs[26] = -12000; gs[27] = -12000; gs[28] = -12000; gs[30] = -12000; gs[33] = -12000; gs[34] = -12000; gs[35] = -12000; gs[36] = -12000; gs[38] = -12000; gs[43] = 32512; gs[44] = 32512; gs[46] = -1; gs[47] = -1; gs[56] = 100; gs[58] = -1; sample = SampleHeader.Default; } private InstrumentRegion(Instrument instrument, Zone global, Zone local, SampleHeader[] samples) : this() { foreach (Generator generator in global.Generators) { SetParameter(generator); } foreach (Generator generator2 in local.Generators) { SetParameter(generator2); } short num = gs[53]; if (0 > num || num >= samples.Length) { throw new InvalidDataException($"The instrument '{instrument.Name}' contains an invalid sample ID '{num}'."); } sample = samples[num]; } internal static InstrumentRegion[] Create(Instrument instrument, Span<Zone> zones, SampleHeader[] samples) { if (zones[0].Generators.Count == 0 || zones[0].Generators.Last().Type != GeneratorType.SampleID) { Zone global = zones[0]; InstrumentRegion[] array = new InstrumentRegion[zones.Length - 1]; for (int i = 0; i < array.Length; i++) { array[i] = new InstrumentRegion(instrument, global, zones[i + 1], samples); } return array; } InstrumentRegion[] array2 = new InstrumentRegion[zones.Length]; for (int j = 0; j < array2.Length; j++) { array2[j] = new InstrumentRegion(instrument, Zone.Empty, zones[j], samples); } return array2; } private void SetParameter(Generator generator) { int type = (int)generator.Type; if (0 <= type && type < gs.Length) { gs[type] = (short)generator.Value; } } public bool Contains(int key, int velocity) { bool flag = KeyRangeStart <= key && key <= KeyRangeEnd; bool flag2 = VelocityRangeStart <= velocity && velocity <= VelocityRangeEnd; return flag && flag2; } public override string ToString() { return $"{sample.Name} (Key: {KeyRangeStart}-{KeyRangeEnd}, Velocity: {VelocityRangeStart}-{VelocityRangeEnd})"; } } internal sealed class Lfo { private readonly Synthesizer synthesizer; private bool active; private double delay; private double period; private int processedSampleCount; private float value; public float Value => value; internal Lfo(Synthesizer synthesizer) { this.synthesizer = synthesizer; } public void Start(float delay, float frequency) { if ((double)frequency > 0.001) { active = true; this.delay = delay; period = 1.0 / (double)frequency; processedSampleCount = 0; value = 0f; } else { active = false; value = 0f; } } public void Process() { if (!active) { return; } processedSampleCount += synthesizer.BlockSize; double num = (double)processedSampleCount / (double)synthesizer.SampleRate; if (num < delay) { value = 0f; return; } double num2 = (num - delay) % period / period; if (num2 < 0.25) { value = (float)(4.0 * num2); } else if (num2 < 0.75) { value = (float)(4.0 * (0.5 - num2)); } else { value = (float)(4.0 * (num2 - 1.0)); } } } public enum LoopMode { NoLoop = 0, Continuous = 1, LoopUntilNoteOff = 3 } public sealed class MidiFile { internal struct Message { private byte channel; private byte command; private byte data1; private byte data2; public MessageType Type => channel switch { 252 => MessageType.TempoChange, 253 => MessageType.LoopStart, 254 => MessageType.LoopEnd, byte.MaxValue => MessageType.EndOfTrack, _ => MessageType.Normal, }; public byte Channel => channel; public byte Command => command; public byte Data1 => data1; public byte Data2 => data2; public double Tempo => 60000000.0 / (double)((command << 16) | (data1 << 8) | data2); private Message(byte channel, byte command, byte data1, byte data2) { this.channel = channel; this.command = command; this.data1 = data1; this.data2 = data2; } public static Message Common(byte status, byte data1) { byte b = (byte)(status & 0xF); byte b2 = (byte)(status & 0xF0); byte b3 = 0; return new Message(b, b2, data1, b3); } public static Message Common(byte status, byte data1, byte data2, MidiFileLoopType loopType) { byte b = (byte)(status & 0xF); byte b2 = (byte)(status & 0xF0); if (b2 == 176) { switch (loopType) { case MidiFileLoopType.RpgMaker: if (data1 == 111) { return LoopStart(); } break; case MidiFileLoopType.IncredibleMachine: switch (data1) { case 110: return LoopStart(); case 111: return LoopEnd(); } break; case MidiFileLoopType.FinalFantasy: switch (data1) { case 116: return LoopStart(); case 117: return LoopEnd(); } break; } } return new Message(b, b2, data1, data2); } public static Message TempoChange(int tempo) { byte b = (byte)(tempo >> 16); byte b2 = (byte)(tempo >> 8); byte b3 = (byte)tempo; return new Message(252, b, b2, b3); } public static Message LoopStart() { return new Message(253, 0, 0, 0); } public static Message LoopEnd() { return new Message(254, 0, 0, 0); } public static Message EndOfTrack() { return new Message(byte.MaxValue, 0, 0, 0); } public override string ToString() { return channel switch { 252 => "Tempo: " + Tempo, 253 => "LoopStart", 254 => "LoopEnd", byte.MaxValue => "EndOfTrack", _ => "CH" + channel + ": " + command.ToString("X2") + ", " + data1.ToString("X2") + ", " + data2.ToString("X2"), }; } } internal enum MessageType { Normal = 0, TempoChange = 252, LoopStart = 253, LoopEnd = 254, EndOfTrack = 255 } private Message[] messages; private TimeSpan[] times; public TimeSpan Length => times.Last(); internal Message[] Messages => messages; internal TimeSpan[] Times => times; public MidiFile(Stream stream) { if (stream == null) { throw new ArgumentNullException("stream"); } Load(stream, 0, MidiFileLoopType.None); Debug.Assert(messages != null); Debug.Assert(times != null); } public MidiFile(Stream stream, int loopPoint) { if (stream == null) { throw new ArgumentNullException("stream"); } if (loopPoint < 0) { throw new ArgumentException("The loop point must be a non-negative value.", "loopPoint"); } Load(stream, loopPoint, MidiFileLoopType.None); Debug.Assert(messages != null); Debug.Assert(times != null); } public MidiFile(Stream stream, MidiFileLoopType loopType) { if (stream == null) { throw new ArgumentNullException("stream"); } Load(stream, 0, loopType); Debug.Assert(messages != null); Debug.Assert(times != null); } public MidiFile(string path) { if (path == null) { throw new ArgumentNullException("path"); } using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read)) { Load(stream, 0, MidiFileLoopType.None); } Debug.Assert(messages != null); Debug.Assert(times != null); } public MidiFile(string path, int loopPoint) { if (path == null) { throw new ArgumentNullException("path"); } if (loopPoint < 0) { throw new ArgumentException("The loop point must be a non-negative value.", "loopPoint"); } using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read)) { Load(stream, loopPoint, MidiFileLoopType.None); } Debug.Assert(messages != null); Debug.Assert(times != null); } public MidiFile(string path, MidiFileLoopType loopType) { if (path == null) { throw new ArgumentNullException("path"); } using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read)) { Load(stream, 0, loopType); } Debug.Assert(messages != null); Debug.Assert(times != null); } internal static TimeSpan GetTimeSpanFromSeconds(double value) { return new TimeSpan((long)(10000000.0 * value)); } private void Load(Stream stream, int loopPoint, MidiFileLoopType loopType) { using BinaryReader reader = new BinaryReader(stream, Encoding.ASCII, leaveOpen: true); string text = reader.ReadFourCC(); if (text != "MThd") { throw new InvalidDataException("The chunk type must be 'MThd', but was '" + text + "'."); } int num = reader.ReadInt32BigEndian(); if (num != 6) { throw new InvalidDataException("The MThd chunk has invalid data."); } short num2 = reader.ReadInt16BigEndian(); if (num2 != 0 && num2 != 1) { throw new NotSupportedException($"The format {num2} is not supported."); } short num3 = reader.ReadInt16BigEndian(); short resolution = reader.ReadInt16BigEndian(); List<Message>[] array = new List<Message>[num3]; List<int>[] array2 = new List<int>[num3]; for (int i = 0; i < num3; i++) { int num4 = i; List<int>[] array3 = array2; int num5 = i; (List<Message>, List<int>) tuple = ReadTrack(reader, loopType); array[num4] = tuple.Item1; array3[num5] = tuple.Item2; } if (loopPoint != 0) { List<int> list = array2[0]; List<Message> list2 = array[0]; if (loopPoint <= list.Last()) { for (int j = 0; j < list.Count; j++) { if (list[j] >= loopPoint) { list.Insert(j, loopPoint); list2.Insert(j, Message.LoopStart()); break; } } } else { list.Add(loopPoint); list2.Add(Message.LoopStart()); } } (messages, times) = MergeTracks(array, array2, resolution); } private static (List<Message>, List<int>) ReadTrack(BinaryReader reader, MidiFileLoopType loopType) { string text = reader.ReadFourCC(); if (text != "MTrk") { throw new InvalidDataException("The chunk type must be 'MTrk', but was '" + text + "'."); } reader.ReadInt32BigEndian(); List<Message> list = new List<Message>(); List<int> list2 = new List<int>(); int num = 0; byte b = 0; while (true) { int num2 = reader.ReadIntVariableLength(); byte b2 = reader.ReadByte(); try { num = checked(num + num2); } catch (OverflowException) { throw new NotSupportedException("Long MIDI file is not supported."); } if ((b2 & 0x80) == 0) { int num3 = b & 0xF0; if (num3 == 192 || num3 == 208) { list.Add(Message.Common(b, b2)); list2.Add(num); } else { byte data = reader.ReadByte(); list.Add(Message.Common(b, b2, data, loopType)); list2.Add(num); } continue; } switch (b2) { case 240: DiscardData(reader); break; case 247: DiscardData(reader); break; case byte.MaxValue: switch (reader.ReadByte()) { case 47: reader.ReadByte(); list.Add(Message.EndOfTrack()); list2.Add(num); return (list, list2); case 81: list.Add(Message.TempoChange(ReadTempo(reader))); list2.Add(num); break; default: DiscardData(reader); break; } break; default: { int num4 = b2 & 0xF0; if (num4 == 192 || num4 == 208) { byte data2 = reader.ReadByte(); list.Add(Message.Common(b2, data2)); list2.Add(num); } else { byte data3 = reader.ReadByte(); byte data4 = reader.ReadByte(); list.Add(Message.Common(b2, data3, data4, loopType)); list2.Add(num); } break; } } b = b2; } } private static (Message[], TimeSpan[]) MergeTracks(List<Message>[] messageLists, List<int>[] tickLists, int resolution) { List<Message> list = new List<Message>(); List<TimeSpan> list2 = new List<TimeSpan>(); int[] array = new int[messageLists.Length]; int num = 0; TimeSpan zero = TimeSpan.Zero; double num2 = 120.0; while (true) { int num3 = int.MaxValue; int num4 = -1; for (int i = 0; i < tickLists.Length; i++) { if (array[i] < tickLists[i].Count) { int num5 = tickLists[i][array[i]]; if (num5 < num3) { num3 = num5; num4 = i; } } } if (num4 == -1) { break; } int num6 = tickLists[num4][array[num4]]; int num7 = num6 - num; TimeSpan timeSpanFromSeconds = GetTimeSpanFromSeconds(60.0 / ((double)resolution * num2) * (double)num7); num += num7; zero += timeSpanFromSeconds; Message item = messageLists[num4][array[num4]]; if (item.Type == MessageType.TempoChange) { num2 = item.Tempo; } else { list.Add(item); list2.Add(zero); } array[num4]++; } return (list.ToArray(), list2.ToArray()); } private static int ReadTempo(BinaryReader reader) { int num = reader.ReadIntVariableLength(); if (num != 3) { throw new InvalidDataException("Failed to read the tempo value."); } byte b = reader.ReadByte(); byte b2 = reader.ReadByte(); byte b3 = reader.ReadByte(); return (b << 16) | (b2 << 8) | b3; } private static void DiscardData(BinaryReader reader) { int num = reader.ReadIntVariableLength(); reader.BaseStream.Position += num; } } public enum MidiFileLoopType { None, RpgMaker, IncredibleMachine, FinalFantasy } public sealed class MidiFileSequencer : IAudioRenderer { private readonly Synthesizer synthesizer; private float speed; private MidiFile midiFile; private bool loop; private int blockWrote; private TimeSpan currentTime; private int msgIndex; private int loopIndex; public TimeSpan Position => currentTime; public bool EndOfSequence { get { if (midiFile == null) { return true; } return msgIndex == midiFile.Messages.Length; } } public float Speed { get { return speed; } set { if (value > 0f) { speed = value; return; } throw new ArgumentOutOfRangeException("The playback speed must be a positive value."); } } public MidiFileSequencer(Synthesizer synthesizer) { if (synthesizer == null) { throw new ArgumentNullException("synthesizer"); } this.synthesizer = synthesizer; speed = 1f; } public void Play(MidiFile midiFile, bool loop) { if (midiFile == null) { throw new ArgumentNullException("midiFile"); } this.midiFile = midiFile; this.loop = loop; blockWrote = synthesizer.BlockSize; currentTime = TimeSpan.Zero; msgIndex = 0; loopIndex = 0; synthesizer.Reset(); } public void Stop() { midiFile = null; synthesizer.Reset(); } public void Render(Span<float> left, Span<float> right) { if (left.Length != right.Length) { throw new ArgumentException("The output buffers for the left and right must be the same length."); } int num; for (int i = 0; i < left.Length; i += num) { if (blockWrote == synthesizer.BlockSize) { ProcessEvents(); blockWrote = 0; currentTime += MidiFile.GetTimeSpanFromSeconds((double)speed * (double)synthesizer.BlockSize / (double)synthesizer.SampleRate); } int val = synthesizer.BlockSize - blockWrote; int val2 = left.Length - i; num = Math.Min(val, val2); synthesizer.Render(left.Slice(i, num), right.Slice(i, num)); blockWrote += num; } } private void ProcessEvents() { if (midiFile == null) { return; } while (msgIndex < midiFile.Messages.Length) { TimeSpan timeSpan = midiFile.Times[msgIndex]; MidiFile.Message message = midiFile.Messages[msgIndex]; if (!(timeSpan <= currentTime)) { break; } if (message.Type == MidiFile.MessageType.Normal) { synthesizer.ProcessMidiMessage(message.Channel, message.Command, message.Data1, message.Data2); } else if (loop) { if (message.Type == MidiFile.MessageType.LoopStart) { loopIndex = msgIndex; } else if (message.Type == MidiFile.MessageType.LoopEnd) { currentTime = midiFile.Times[loopIndex]; msgIndex = loopIndex; synthesizer.NoteOffAll(immediate: false); } } msgIndex++; } if (msgIndex == midiFile.Messages.Length && loop) { currentTime = midiFile.Times[loopIndex]; msgIndex = loopIndex; synthesizer.NoteOffAll(immediate: false); } } } internal sealed class ModulationEnvelope { private enum Stage { Delay, Attack, Hold, Decay, Release } private readonly Synthesizer synthesizer; private double attackSlope; private double decaySlope; private double releaseSlope; private double attackStartTime; private double holdStartTime; private double decayStartTime; private double decayEndTime; private double releaseEndTime; private float sustainLevel; private float releaseLevel; private int processedSampleCount; private Stage stage; private float value; public float Value => value; internal ModulationEnvelope(Synthesizer synthesizer) { this.synthesizer = synthesizer; } public void Start(float delay, float attack, float hold, float decay, float sustain, float release) { attackSlope = 1f / attack; decaySlope = 1f / decay; releaseSlope = 1f / release; attackStartTime = delay; holdStartTime = attackStartTime + (double)attack; decayStartTime = holdStartTime + (double)hold; decayEndTime = decayStartTime + (double)decay; releaseEndTime = release; sustainLevel = SoundFontMath.Clamp(sustain, 0f, 1f); releaseLevel = 0f; processedSampleCount = 0; stage = Stage.Delay; value = 0f; Process(0); } public void Release() { stage = Stage.Release; releaseEndTime += (double)processedSampleCount / (double)synthesizer.SampleRate; releaseLevel = value; } public bool Process() { return Process(synthesizer.BlockSize); } private bool Process(int sampleCount) { processedSampleCount += sampleCount; double num = (double)processedSampleCount / (double)synthesizer.SampleRate; while (stage <= Stage.Hold && !(num < stage switch { Stage.Delay => attackStartTime, Stage.Attack => holdStartTime, Stage.Hold => decayStartTime, _ => throw new InvalidOperationException("Invalid envelope stage."), })) { stage++; } switch (stage) { case Stage.Delay: value = 0f; return true; case Stage.Attack: value = (float)(attackSlope * (num - attackStartTime)); return true; case Stage.Hold: value = 1f; return true; case Stage.Decay: value = Math.Max((float)(decaySlope * (decayEndTime - num)), sustainLevel); return value > SoundFontMath.NonAudible; case Stage.Release: value = Math.Max((float)((double)releaseLevel * releaseSlope * (releaseEndTime - num)), 0f); return value > SoundFontMath.NonAudible; default: throw new InvalidOperationException("Invalid envelope stage."); } } } internal static class Modulator { internal static void DiscardData(BinaryReader reader, int size) { if (size % 10 != 0) { throw new InvalidDataException("The modulator list is invalid."); } reader.BaseStream.Position += size; } } internal sealed class Oscillator { private const int fracBits = 24; private const long fracUnit = 16777216L; private const float fpToSample = 1.8189894E-12f; private readonly Synthesizer synthesizer; private short[] data; private LoopMode loopMode; private int sampleRate; private int start; private int end; private int startLoop; private int endLoop; private int rootKey; private float tune; private float pitchChangeScale; private float sampleRateRatio; private bool looping; private long position_fp; internal Oscillator(Synthesizer synthesizer) { this.synthesizer = synthesizer; } public void Start(short[] data, LoopMode loopMode, int sampleRate, int start, int end, int startLoop, int endLoop, int rootKey, int coarseTune, int fineTune, int scaleTuning) { this.data = data; this.loopMode = loopMode; this.sampleRate = sampleRate; this.start = start; this.end = end; this.startLoop = startLoop; this.endLoop = endLoop; this.rootKey = rootKey; tune = (float)coarseTune + 0.01f * (float)fineTune; pitchChangeScale = 0.01f * (float)scaleTuning; sampleRateRatio = (float)sampleRate / (float)synthesizer.SampleRate; if (loopMode == LoopMode.NoLoop) { looping = false; } else { looping = true; } position_fp = (long)start << 24; } public void Release() { if (loopMode == LoopMode.LoopUntilNoteOff) { looping = false; } } public bool Process(float[] block, float pitch) { float num = pitchChangeScale * (pitch - (float)rootKey) + tune; float num2 = sampleRateRatio * Mathf.Pow(2f, num / 12f); return FillBlock(block, num2); } internal bool FillBlock(float[] block, double pitchRatio) { long pitchRatio_fp = (long)(16777216.0 * pitchRatio); if (looping) { return FillBlock_Continuous(block, pitchRatio_fp); } return FillBlock_NoLoop(block, pitchRatio_fp); } private bool FillBlock_NoLoop(float[] block, long pitchRatio_fp) { for (int i = 0; i < block.Length; i++) { long num = position_fp >> 24; if (num >= end) { if (i > 0) { Array.Clear(block, i, block.Length - i); return true; } return false; } short num2 = data[num]; short num3 = data[num + 1]; long num4 = position_fp & 0xFFFFFF; block[i] = 1.8189894E-12f * (float)(((long)num2 << 24) + num4 * (num3 - num2)); position_fp += pitchRatio_fp; } return true; } private bool FillBlock_Continuous(float[] block, long pitchRatio_fp) { long num = (long)endLoop << 24; long num2 = endLoop - startLoop; long num3 = num2 << 24; for (int i = 0; i < block.Length; i++) { if (position_fp >= num) { position_fp -= num3; } long num4 = position_fp >> 24; long num5 = num4 + 1; if (num5 >= endLoop) { num5 -= num2; } short num6 = data[num4]; short num7 = data[num5]; long num8 = position_fp & 0xFFFFFF; block[i] = 1.8189894E-12f * (float)(((long)num6 << 24) + num8 * (num7 - num6)); position_fp += pitchRatio_fp; } return true; } } public sealed class Preset { internal static readonly Preset Default = new Preset(); private readonly string name; private readonly int patchNumber; private readonly int bankNumber; private readonly int library; private readonly int genre; private readonly int morphology; private readonly PresetRegion[] regions; public string Name => name; public int PatchNumber => patchNumber; public int BankNumber => bankNumber; public IReadOnlyList<PresetRegion> Regions => regions; internal PresetRegion[] RegionArray => regions; private Preset() { name = "Default"; regions = Array.Empty<PresetRegion>(); } private Preset(PresetInfo info, Zone[] zones, Instrument[] instruments) { name = info.Name; patchNumber = info.PatchNumber; bankNumber = info.BankNumber; library = info.Library; genre = info.Genre; morphology = info.Morphology; int num = info.ZoneEndIndex - info.ZoneStartIndex + 1; if (num <= 0) { throw new InvalidDataException("The preset '" + info.Name + "' has no zone."); } Span<Zone> zones2 = new Span<Zone>(zones, info.ZoneStartIndex, num); regions = PresetRegion.Create(this, zones2, instruments); } internal static Preset[] Create(PresetInfo[] infos, Zone[] zones, Instrument[] instruments) { if (infos.Length <= 1) { throw new InvalidDataException("No valid preset was found."); } Preset[] array = new Preset[infos.Length - 1]; for (int i = 0; i < array.Length; i++) { array[i] = new Preset(infos[i], zones, instruments); } return array; } public override string ToString() { return name; } } internal sealed class PresetInfo { private string name; private int patchNumber; private int bankNumber; private int zoneStartIndex; private int zoneEndIndex; private int library; private int genre; private int morphology; public string Name => name; public int PatchNumber => patchNumber; public int BankNumber => bankNumber; public int ZoneStartIndex => zoneStartIndex; public int ZoneEndIndex => zoneEndIndex; public int Library => library; public int Genre => genre; public int Morphology => morphology; private PresetInfo(BinaryReader reader) { name = reader.ReadFixedLengthString(20); patchNumber = reader.ReadUInt16(); bankNumber = reader.ReadUInt16(); zoneStartIndex = reader.ReadUInt16(); library = reader.ReadInt32(); genre = reader.ReadInt32(); morphology = reader.ReadInt32(); } internal static PresetInfo[] ReadFromChunk(BinaryReader reader, int size) { if (size % 38 != 0) { throw new InvalidDataException("The preset list is invalid."); } int num = size / 38; PresetInfo[] array = new PresetInfo[num]; for (int i = 0; i < num; i++) { array[i] = new PresetInfo(reader); } for (int j = 0; j < num - 1; j++) { array[j].zoneEndIndex = array[j + 1].zoneStartIndex - 1; } return array; } } public sealed class PresetRegion { internal static readonly PresetRegion Default = new PresetRegion(); private readonly short[] gs; private readonly Instrument instrument; internal short this[GeneratorType generatortType] => gs[(uint)generatortType]; public Instrument Instrument => instrument; public int ModulationLfoToPitch => this[GeneratorType.ModulationLfoToPitch]; public int VibratoLfoToPitch => this[GeneratorType.VibratoLfoToPitch]; public int ModulationEnvelopeToPitch => this[GeneratorType.ModulationEnvelopeToPitch]; public float InitialFilterCutoffFrequency => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.InitialFilterCutoffFrequency]); public float InitialFilterQ => 0.1f * (float)this[GeneratorType.InitialFilterQ]; public int ModulationLfoToFilterCutoffFrequency => this[GeneratorType.ModulationLfoToFilterCutoffFrequency]; public int ModulationEnvelopeToFilterCutoffFrequency => this[GeneratorType.ModulationEnvelopeToFilterCutoffFrequency]; public float ModulationLfoToVolume => 0.1f * (float)this[GeneratorType.ModulationLfoToVolume]; public float ChorusEffectsSend => 0.1f * (float)this[GeneratorType.ChorusEffectsSend]; public float ReverbEffectsSend => 0.1f * (float)this[GeneratorType.ReverbEffectsSend]; public float Pan => 0.1f * (float)this[GeneratorType.Pan]; public float DelayModulationLfo => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.DelayModulationLfo]); public float FrequencyModulationLfo => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.FrequencyModulationLfo]); public float DelayVibratoLfo => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.DelayVibratoLfo]); public float FrequencyVibratoLfo => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.FrequencyVibratoLfo]); public float DelayModulationEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.DelayModulationEnvelope]); public float AttackModulationEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.AttackModulationEnvelope]); public float HoldModulationEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.HoldModulationEnvelope]); public float DecayModulationEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.DecayModulationEnvelope]); public float SustainModulationEnvelope => 0.1f * (float)this[GeneratorType.SustainModulationEnvelope]; public float ReleaseModulationEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.ReleaseModulationEnvelope]); public int KeyNumberToModulationEnvelopeHold => this[GeneratorType.KeyNumberToModulationEnvelopeHold]; public int KeyNumberToModulationEnvelopeDecay => this[GeneratorType.KeyNumberToModulationEnvelopeDecay]; public float DelayVolumeEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.DelayVolumeEnvelope]); public float AttackVolumeEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.AttackVolumeEnvelope]); public float HoldVolumeEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.HoldVolumeEnvelope]); public float DecayVolumeEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.DecayVolumeEnvelope]); public float SustainVolumeEnvelope => 0.1f * (float)this[GeneratorType.SustainVolumeEnvelope]; public float ReleaseVolumeEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.ReleaseVolumeEnvelope]); public int KeyNumberToVolumeEnvelopeHold => this[GeneratorType.KeyNumberToVolumeEnvelopeHold]; public int KeyNumberToVolumeEnvelopeDecay => this[GeneratorType.KeyNumberToVolumeEnvelopeDecay]; public int KeyRangeStart => this[GeneratorType.KeyRange] & 0xFF; public int KeyRangeEnd => (this[GeneratorType.KeyRange] >> 8) & 0xFF; public int VelocityRangeStart => this[GeneratorType.VelocityRange] & 0xFF; public int VelocityRangeEnd => (this[GeneratorType.VelocityRange] >> 8) & 0xFF; public float InitialAttenuation => 0.1f * (float)this[GeneratorType.InitialAttenuation]; public int CoarseTune => this[GeneratorType.CoarseTune]; public int FineTune => this[GeneratorType.FineTune]; public int ScaleTuning => this[GeneratorType.ScaleTuning]; private PresetRegion() { gs = new short[61]; gs[43] = 32512; gs[44] = 32512; instrument = Instrument.Default; } private PresetRegion(Preset preset, Zone global, Zone local, Instrument[] instruments) : this() { foreach (Generator generator in global.Generators) { SetParameter(generator); } foreach (Generator generator2 in local.Generators) { SetParameter(generator2); } short num = gs[41]; if (0 > num || num >= instruments.Length) { throw new InvalidDataException($"The preset '{preset.Name}' contains an invalid instrument ID '{num}'."); } instrument = instruments[num]; } internal static PresetRegion[] Create(Preset preset, Span<Zone> zones, Instrument[] instruments) { if (zones[0].Generators.Count == 0 || zones[0].Generators.Last().Type != GeneratorType.Instrument) { Zone global = zones[0]; PresetRegion[] array = new PresetRegion[zones.Length - 1]; for (int i = 0; i < array.Length; i++) { array[i] = new PresetRegion(preset, global, zones[i + 1], instruments); } return array; } PresetRegion[] array2 = new PresetRegion[zones.Length]; for (int j = 0; j < array2.Length; j++) { array2[j] = new PresetRegion(preset, Zone.Empty, zones[j], instruments); } return array2; } private void SetParameter(Generator generator) { int type = (int)generator.Type; if (0 <= type && type < gs.Length) { gs[type] = (short)generator.Value; } } public bool Contains(int key, int velocity) { bool flag = KeyRangeStart <= key && key <= KeyRangeEnd; bool flag2 = VelocityRangeStart <= velocity && velocity <= VelocityRangeEnd; return flag && flag2; } public override string ToString() { return $"{instrument.Name} (Key: {KeyRangeStart}-{KeyRangeEnd}, Velocity: {VelocityRangeStart}-{VelocityRangeEnd})"; } } internal static class RegionEx { public static void Start(this Oscillator oscillator, short[] data, InstrumentRegion region) { oscillator.Start(data, new RegionPair(PresetRegion.Default, region)); } public static void Start(this Oscillator oscillator, short[] data, RegionPair region) { int sampleRate = region.Instrument.Sample.SampleRate; LoopMode sampleModes = region.SampleModes; int sampleStart = region.SampleStart; int sampleEnd = region.SampleEnd; int sampleStartLoop = region.SampleStartLoop; int sampleEndLoop = region.SampleEndLoop; int rootKey = region.RootKey; int coarseTune = region.CoarseTune; int fineTune = region.FineTune; int scaleTuning = region.ScaleTuning; oscillator.Start(data, sampleModes, sampleRate, sampleStart, sampleEnd, sampleStartLoop, sampleEndLoop, rootKey, coarseTune, fineTune, scaleTuning); } public static void Start(this VolumeEnvelope envelope, InstrumentRegion region, int key, int velocity) { envelope.Start(new RegionPair(PresetRegion.Default, region), key, velocity); } public static void Start(this VolumeEnvelope envelope, RegionPair region, int key, int velocity) { float delayVolumeEnvelope = region.DelayVolumeEnvelope; float attackVolumeEnvelope = region.AttackVolumeEnvelope; float hold = region.HoldVolumeEnvelope * SoundFontMath.KeyNumberToMultiplyingFactor(region.KeyNumberToVolumeEnvelopeHold, key); float decay = region.DecayVolumeEnvelope * SoundFontMath.KeyNumberToMultiplyingFactor(region.KeyNumberToVolumeEnvelopeDecay, key); float sustain = SoundFontMath.DecibelsToLinear(0f - region.SustainVolumeEnvelope); float release = Math.Max(region.ReleaseVolumeEnvelope, 0.01f); envelope.Start(delayVolumeEnvelope, attackVolumeEnvelope, hold, decay, sustain, release); } public static void Start(this ModulationEnvelope envelope, InstrumentRegion region, int key, int velocity) { envelope.Start(new RegionPair(PresetRegion.Default, region), key, velocity); } public static void Start(this ModulationEnvelope envelope, RegionPair region, int key, int velocity) { float delayModulationEnvelope = region.DelayModulationEnvelope; float attack = region.AttackModulationEnvelope * ((float)(145 - velocity) / 144f); float hold = region.HoldModulationEnvelope * SoundFontMath.KeyNumberToMultiplyingFactor(region.KeyNumberToModulationEnvelopeHold, key); float decay = region.DecayModulationEnvelope * SoundFontMath.KeyNumberToMultiplyingFactor(region.KeyNumberToModulationEnvelopeDecay, key); float sustain = 1f - region.SustainModulationEnvelope / 100f; float releaseModulationEnvelope = region.ReleaseModulationEnvelope; envelope.Start(delayModulationEnvelope, attack, hold, decay, sustain, releaseModulationEnvelope); } public static void StartVibrato(this Lfo lfo, InstrumentRegion region, int key, int velocity) { lfo.StartVibrato(new RegionPair(PresetRegion.Default, region), key, velocity); } public static void StartVibrato(this Lfo lfo, RegionPair region, int key, int velocity) { lfo.Start(region.DelayVibratoLfo, region.FrequencyVibratoLfo); } public static void StartModulation(this Lfo lfo, InstrumentRegion region, int key, int velocity) { lfo.StartModulation(new RegionPair(PresetRegion.Default, region), key, velocity); } public static void StartModulation(this Lfo lfo, RegionPair region, int key, int velocity) { lfo.Start(region.DelayModulationLfo, region.FrequencyModulationLfo); } } internal struct RegionPair { private readonly PresetRegion preset; private readonly InstrumentRegion instrument; private int this[GeneratorType generatortType] => instrument[generatortType] + preset[generatortType]; public PresetRegion Preset => preset; public InstrumentRegion Instrument => instrument; public int SampleStart => instrument.SampleStart; public int SampleEnd => instrument.SampleEnd; public int SampleStartLoop => instrument.SampleStartLoop; public int SampleEndLoop => instrument.SampleEndLoop; public int StartAddressOffset => instrument.StartAddressOffset; public int EndAddressOffset => instrument.EndAddressOffset; public int StartLoopAddressOffset => instrument.StartLoopAddressOffset; public int EndLoopAddressOffset => instrument.EndLoopAddressOffset; public int ModulationLfoToPitch => this[GeneratorType.ModulationLfoToPitch]; public int VibratoLfoToPitch => this[GeneratorType.VibratoLfoToPitch]; public int ModulationEnvelopeToPitch => this[GeneratorType.ModulationEnvelopeToPitch]; public float InitialFilterCutoffFrequency => SoundFontMath.CentsToHertz(this[GeneratorType.InitialFilterCutoffFrequency]); public float InitialFilterQ => 0.1f * (float)this[GeneratorType.InitialFilterQ]; public int ModulationLfoToFilterCutoffFrequency => this[GeneratorType.ModulationLfoToFilterCutoffFrequency]; public int ModulationEnvelopeToFilterCutoffFrequency => this[GeneratorType.ModulationEnvelopeToFilterCutoffFrequency]; public float ModulationLfoToVolume => 0.1f * (float)this[GeneratorType.ModulationLfoToVolume]; public float ChorusEffectsSend => 0.1f * (float)this[GeneratorType.ChorusEffectsSend]; public float ReverbEffectsSend => 0.1f * (float)this[GeneratorType.ReverbEffectsSend]; public float Pan => 0.1f * (float)this[GeneratorType.Pan]; public float DelayModulationLfo => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayModulationLfo]); public float FrequencyModulationLfo => SoundFontMath.CentsToHertz(this[GeneratorType.FrequencyModulationLfo]); public float DelayVibratoLfo => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayVibratoLfo]); public float FrequencyVibratoLfo => SoundFontMath.CentsToHertz(this[GeneratorType.FrequencyVibratoLfo]); public float DelayModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayModulationEnvelope]); public float AttackModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.AttackModulationEnvelope]); public float HoldModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.HoldModulationEnvelope]); public float DecayModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DecayModulationEnvelope]); public float SustainModulationEnvelope => 0.1f * (float)this[GeneratorType.SustainModulationEnvelope]; public float ReleaseModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.ReleaseModulationEnvelope]); public int KeyNumberToModulationEnvelopeHold => this[GeneratorType.KeyNumberToModulationEnvelopeHold]; public int KeyNumberToModulationEnvelopeDecay => this[GeneratorType.KeyNumberToModulationEnvelopeDecay]; public float DelayVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayVolumeEnvelope]); public float AttackVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.AttackVolumeEnvelope]); public float HoldVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.HoldVolumeEnvelope]); public float DecayVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DecayVolumeEnvelope]); public float SustainVolumeEnvelope => 0.1f * (float)this[GeneratorType.SustainVolumeEnvelope]; public float ReleaseVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.ReleaseVolumeEnvelope]); public int KeyNumberToVolumeEnvelopeHold => this[GeneratorType.KeyNumberToVolumeEnvelopeHold]; public int KeyNumberToVolumeEnvelopeDecay => this[GeneratorType.KeyNumberToVolumeEnvelopeDecay]; public float InitialAttenuation => 0.1f * (float)this[GeneratorType.InitialAttenuation]; public int CoarseTune => this[GeneratorType.CoarseTune]; public int FineTune => this[GeneratorType.FineTune] + instrument.Sample.PitchCorrection; public LoopMode SampleModes => instrument.SampleModes; public int ScaleTuning => this[GeneratorType.ScaleTuning]; public int ExclusiveClass => instrument.ExclusiveClass; public int RootKey => instrument.RootKey; internal RegionPair(PresetRegion preset, InstrumentRegion instrument) { this.preset = preset; this.instrument = instrument; } } internal sealed class Reverb { internal sealed class CombFilter { private readonly float[] buffer; private int bufferIndex; private float filterStore; private float feedback; private float damp1; private float damp2; public float Feedback { get { return feedback; } set { feedback = value; } } public float Damp { get { return damp1; } set { damp1 = value; damp2 = 1f - value; } } public CombFilter(int bufferSize) { buffer = new float[bufferSize]; bufferIndex = 0; filterStore = 0f; feedback = 0f; damp1 = 0f; damp2 = 0f; } public void Mute() { Array.Clear(buffer, 0, buffer.Length); filterStore = 0f; } public void Process(float[] inputBlock, float[] outputBlock) { int num; for (int i = 0; i < outputBlock.Length; i += num) { if (bufferIndex == buffer.Length) { bufferIndex = 0; } int val = buffer.Length - bufferIndex; int val2 = outputBlock.Length - i; num = Math.Min(val, val2); for (int j = 0; j < num; j++) { int num2 = i + j; int num3 = bufferIndex + j; float num4 = inputBlock[num2]; float num5 = buffer[num3]; if (Mathf.Abs(num5) < 1E-06f) { num5 = 0f; } filterStore = num5 * damp2 + filterStore * damp1; if (Mathf.Abs(filterStore) < 1E-06f) { filterStore = 0f; } buffer[num3] = num4 + filterStore * feedback; outputBlock[num2] += num5; } bufferIndex += num; } } } internal sealed class AllPassFilter { private readonly float[] buffer; private int bufferIndex; private float feedback; public float Feedback { get { return feedback; } set { feedback = value; } } public AllPassFilter(int bufferSize) { buffer = new float[bufferSize]; bufferIndex = 0; feedback = 0f; } public void Mute() { Array.Clear(buffer, 0, buffer.Length); } public void Process(float[] block) { int num; for (int i = 0; i < block.Length; i += num) { if (bufferIndex == buffer.Length) { bufferIndex = 0; } int val = buffer.Length - bufferIndex; int val2 = block.Length - i; num = Math.Min(val, val2); for (int j = 0; j < num; j++) { int num2 = i + j; int num3 = bufferIndex + j; float num4 = block[num2]; float num5 = buffer[num3]; if (Mathf.Abs(num5) < 1E-06f) { num5 = 0f; } block[num2] = num5 - num4; buffer[num3] = num4 + num5 * feedback; } bufferIndex += num; } } } private const float fixedGain = 0.015f; private const float scaleWet = 3f; private const float scaleDamp = 0.4f; private const float scaleRoom = 0.28f; private const float offsetRoom = 0.7f; private const float initialRoom = 0.5f; private const float initialDamp = 0.5f; private const float initialWet = 1f / 3f; private const float initialWidth = 1f; private const int stereoSpread = 23; private const int cfTuningL1 = 1116; private const int cfTuningR1 = 1139; private const int cfTuningL2 = 1188; private const int cfTuningR2 = 1211; private const int cfTuningL3 = 1277; private const int cfTuningR3 = 1300; private const int cfTuningL4 = 1356; private const int cfTuningR4 = 1379; private const int cfTuningL5 = 1422; private const int cfTuningR5 = 1445; private const int cfTuningL6 = 1491; private const int cfTuningR6 = 1514; private const int cfTuningL7 = 1557; private const int cfTuningR7 = 1580; private const int cfTuningL8 = 1617; private const int cfTuningR8 = 1640; private const int apfTuningL1 = 556; private const int apfTuningR1 = 579; private const int apfTuningL2 = 441; private const int apfTuningR2 = 464; private const int apfTuningL3 = 341; private const int apfTuningR3 = 364; private const int apfTuningL4 = 225; private const int apfTuningR4 = 248; private readonly CombFilter[] cfsL; private readonly CombFilter[] cfsR; private readonly AllPassFilter[] apfsL; private readonly AllPassFilter[] apfsR; private float gain; private float roomSize; private float roomSize1; private float damp; private float damp1; private float wet; private float wet1; private float wet2; private float width; public float InputGain => gain; public float RoomSize { get { return (roomSize - 0.7f) / 0.28f; } set { roomSize = value * 0.28f + 0.7f; Update(); } } public float Damp { get { return damp / 0.4f; } set { damp = value * 0.4f; Update(); } } public float Wet { get { return wet / 3f; } set { wet = value * 3f; Update(); } } public float Width { get { return width; } set { width = value; Update(); } } internal Reverb(int sampleRate) { cfsL = new CombFilter[8] { new CombFilter(ScaleTuning(sampleRate, 1116)), new CombFilter(ScaleTuning(sampleRate, 1188)), new CombFilter(ScaleTuning(sampleRate, 1277)), new CombFilter(ScaleTuning(sampleRate, 1356)), new CombFilter(ScaleTuning(sampleRate, 1422)), new CombFilter(ScaleTuning(sampleRate, 1491)), new CombFilter(ScaleTuning(sampleRate, 1557)), new CombFilter(ScaleTuning(sampleRate, 1617)) }; cfsR = new CombFilter[8] { new CombFilter(ScaleTuning(sampleRate, 1139)), new CombFilter(ScaleTuning(sampleRate, 1211)), new CombFilter(ScaleTuning(sampleRate, 1300)), new CombFilter(ScaleTuning(sampleRate, 1379)), new CombFilter(ScaleTuning(sampleRate, 1445)), new CombFilter(ScaleTuning(sampleRate, 1514)), new CombFilter(ScaleTuning(sampleRate, 1580)), new CombFilter(ScaleTuning(sampleRate, 1640)) }; apfsL = new AllPassFilter[4] { new AllPassFilter(ScaleTuning(sampleRate, 556)), new AllPassFilter(ScaleTuning(sampleRate, 441)), new AllPassFilter(ScaleTuning(sampleRate, 341)), new AllPassFilter(ScaleTuning(sampleRate, 225)) }; apfsR = new AllPassFilter[4] { new AllPassFilter(ScaleTuning(sampleRate, 579)), new AllPassFilter(ScaleTuning(sampleRate, 464)), new AllPassFilter(ScaleTuning(sampleRate, 364)), new AllPassFilter(ScaleTuning(sampleRate, 248)) }; AllPassFilter[] array = apfsL; foreach (AllPassFilter allPassFilter in array) { allPassFilter.Feedback = 0.5f; } AllPassFilter[] array2 = apfsR; foreach (AllPassFilter allPassFilter2 in array2) { allPassFilter2.Feedback = 0.5f; } Wet = 1f / 3f; RoomSize = 0.5f; Damp = 0.5f; Width = 1f; } private int ScaleTuning(int sampleRate, int tuning) { return (int)Math.Round((double)sampleRate / 44100.0 * (double)tuning); } public void Process(float[] input, float[] outputLeft, float[] outputRight) { Array.Clear(outputLeft, 0, outputLeft.Length); Array.Clear(outputRight, 0, outputRight.Length); CombFilter[] array = cfsL; foreach (CombFilter combFilter in array) { combFilter.Process(input, outputLeft); } AllPassFilter[] array2 = apfsL; foreach (AllPassFilter allPassFilter in array2) { allPassFilter.Process(outputLeft); } CombFilter[] array3 = cfsR; foreach (CombFilter combFilter2 in array3) { combFilter2.Process(input, outputRight); } AllPassFilter[] array4 = apfsR; foreach (AllPassFilter allPassFilter2 in array4) { allPassFilter2.Process(outputRight); } if ((double)(1f - wet1) > 0.001 || (double)wet2 > 0.001) { for (int m = 0; m < input.Length; m++) { float num = outputLeft[m]; float num2 = outputRight[m]; outputLeft[m] = num * wet1 + num2 * wet2; outputRight[m] = num2 * wet1 + num * wet2; } } } public void Mute() { CombFilter[] array = cfsL; foreach (CombFilter combFilter in array) { combFilter.Mute(); } CombFilter[] array2 = cfsR; foreach (CombFilter combFilter2 in array2) { combFilter2.Mute(); } AllPassFilter[] array3 = apfsL; foreach (AllPassFilter allPassFilter in array3) { allPassFilter.Mute(); } AllPassFilter[] array4 = apfsR; foreach (AllPassFilter allPassFilter2 in array4) { allPassFilter2.Mute(); } } private void Update() { wet1 = wet * (width / 2f + 0.5f); wet2 = wet * ((1f - width) / 2f); roomSize1 = roomSize; damp1 = damp; gain = 0.015f; CombFilter[] array = cfsL; foreach (CombFilter combFilter in array) { combFilter.Feedback = roomSize1; combFilter.Damp = damp1; } CombFilter[] array2 = cfsR; foreach (CombFilter combFilter2 in array2) { combFilter2.Feedback = roomSize1; combFilter2.Damp = damp1; } } } public sealed class SampleHeader { internal static readonly SampleHeader Default = new SampleHeader(); private readonly string name; private readonly int start; private readonly int end; private readonly int startLoop; private readonly int endLoop; private readonly int sampleRate; private readonly byte originalPitch; private readonly sbyte pitchCorrection; private readonly ushort link; private readonly SampleType type; public string Name => name; public int Start => start; public int End => end; public int StartLoop => startLoop; public int EndLoop => endLoop; public int SampleRate => sampleRate; public byte OriginalPitch => originalPitch; public sbyte PitchCorrection => pitchCorrection; private SampleHeader() { name = "Default"; } private SampleHeader(BinaryReader reader) { name = reader.ReadFixedLengthString(20); start = reader.ReadInt32(); end = reader.ReadInt32(); startLoop = reader.ReadInt32(); endLoop = reader.ReadInt32(); sampleRate = reader.ReadInt32(); originalPitch = reader.ReadByte(); pitchCorrection = reader.ReadSByte(); link = reader.ReadUInt16(); type = (SampleType)reader.ReadUInt16(); } internal static SampleHeader[] ReadFromChunk(BinaryReader reader, int size) { if (size % 46 != 0) { throw new InvalidDataException("The sample header list is invalid."); } SampleHeader[] array = new SampleHeader[size / 46 - 1]; for (int i = 0; i < array.Length; i++) { array[i] = new SampleHeader(reader); } new SampleHeader(reader); return array; } public override string ToString() { return name; } } internal enum SampleType { Mono = 1, Right = 2, Left = 4, Linked = 8, RomMono = 32769, RomRight = 32770, RomLeft = 32772, RomLinked = 32776 } public sealed class SoundFont { private SoundFontInfo info; private int bitsPerSample; private short[] waveData; private SampleHeader[] sampleHeaders; private Preset[] presets; private Instrument[] instruments; public SoundFontInfo Info => info; public int BitsPerSample => bitsPerSample; public ReadOnlySpan<short> WaveData => waveData; public IReadOnlyList<SampleHeader> SampleHeaders => sampleHeaders; public IReadOnlyList<Preset> Presets => presets; public IReadOnlyList<Instrument> Instruments => instruments; internal short[] WaveDataArray => waveData; internal SampleHeader[] SampleHeaderArray => sampleHeaders; internal Preset[] PresetArray => presets; internal Instrument[] InstrumentArray => instruments; public SoundFont(Stream stream) { if (stream == null) { throw new ArgumentNullException("stream"); } Load(stream); Debug.Assert(info != null); Debug.Assert(waveData != null); Debug.Assert(sampleHeaders != null); Debug.Assert(presets != null); Debug.Assert(instruments != null); } public SoundFont(string path) { if (path == null) { throw new ArgumentNullException("path"); } using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read)) { Load(stream); } Debug.Assert(info != null); Debug.Assert(waveData != null); Debug.Assert(sampleHeaders != null); Debug.Assert(presets != null); Debug.Assert(instruments != null); } private void Load(Stream stream) { using (BinaryReader binaryReader = new BinaryReader(stream, Encoding.ASCII, leaveOpen: true)) { string text = binaryReader.ReadFourCC(); if (text != "RIFF") { throw new InvalidDataException("The RIFF chunk was not found."); } int num = binaryReader.ReadInt32(); string text2 = binaryReader.ReadFourCC(); if (text2 != "sfbk") { throw new InvalidDataException("The type of the RIFF chunk must be 'sfbk', but was '" + text2 + "'."); } info = new SoundFontInfo(binaryReader); SoundFontSampleData soundFontSampleData = new SoundFontSampleData(binaryReader); bitsPerSample = soundFontSampleData.BitsPerSample; waveData = soundFontSampleData.Samples; SoundFontParameters soundFontParameters = new SoundFontParameters(binaryReader); sampleHeaders = soundFontParameters.SampleHeaders; presets = soundFontParameters.Presets; instruments = soundFontParameters.Instruments; } CheckSamples(); CheckRegions(); } public override string ToString() { return info.BankName; } private void CheckSamples() { int num = waveData.Length - 4; SampleHeader[] array = sampleHeaders; foreach (SampleHeader sampleHeader in array) { if (0 > sampleHeader.Start || sampleHeader.Start >= num) { throw new InvalidDataException("The start position of the sample '" + sampleHeader.Name + "' is out of range."); } if (0 > sampleHeader.StartLoop || sampleHeader.StartLoop >= num) { throw new InvalidDataException("The loop start position of the sample '" + sampleHeader.Name + "' is out of range."); } if (0 >= sampleHeader.End || sampleHeader.End > num) { throw new InvalidDataException("The end position of the sample '" + sampleHeader.Name + "' is out of range."); } if (0 > sampleHeader.EndLoop || sampleHeader.EndLoop > num) { throw new InvalidDataException("The loop end position of the sample '" + sampleHeader.Name + "' is out of range."); } } } private void CheckRegions() { int num = waveData.Length - 4; Instrument[] array = instruments; foreach (Instrument instrument in array) { InstrumentRegion[] regionArray = instrument.RegionArray; foreach (InstrumentRegion instrumentRegion in regionArray) { if (0 > instrumentRegion.SampleStart || instrumentRegion.SampleStart >= num) { throw new InvalidDataException($"The start position of the sample '{instrumentRegion.Sample.Name}' in the instrument '{instrument.Name}' is out of range."); } if (0 > instrumentRegion.SampleStartLoop || instrumentRegion.SampleStartLoop >= num) { throw new InvalidDataException($"The loop start position of the sample '{instrumentRegion.Sample.Name}' in the instrument '{instrument.Name}' is out of range."); } if (0 >= instrumentRegion.SampleEnd || instrumentRegion.SampleEnd > num) { throw new InvalidDataException($"The end position of the sample '{instrumentRegion.Sample.Name}' in the instrument '{instrument.Name}' is out of range."); } if (0 > instrumentRegion.SampleEndLoop || instrumentRegion.SampleEndLoop > num) { throw new InvalidDataException($"The loop end position of the sample '{instrumentRegion.Sample.Name}' in the instrument '{instrument.Name}' is out of range."); } LoopMode sampleModes = instrumentRegion.SampleModes; LoopMode loopMode = sampleModes; if ((uint)loopMode > 1u && loopMode != LoopMode.LoopUntilNoteOff) { throw new InvalidDataException($"The sample '{instrumentRegion.Sample.Name}' in the instrument '{instrument.Name}' has an invalid loop mode."); } } } } } public sealed class SoundFontInfo { private readonly SoundFontVersion version = default(SoundFontVersion); private readonly string targetSoundEngine = string.Empty; private readonly string bankName = string.Empty; private readonly string romName = string.Empty; private readonly SoundFontVersion romVersion = default(SoundFontVersion); private readonly string creationDate = string.Empty; private readonly string author = string.Empty; private readonly string targetProduct = string.Empty; private readonly string copyright = string.Empty; private readonly string comments = string.Empty; private readonly string tools = string.Empty; public SoundFontVersion Version => version; public string TargetSoundEngine => targetSoundEngine; public string BankName => bankName; public string RomName => romName; public SoundFontVersion RomVersion => romVersion; public string CeationDate => creationDate; public string Author => author; public string TargetProduct => targetProduct; public string Copyright => copyright; public string Comments => comments; public string Tools => tools; internal SoundFontInfo(BinaryReader reader) { string text = reader.ReadFourCC(); if (text != "LIST") { throw new InvalidDataException("The LIST chunk was not found."); } long num = reader.BaseStream.Position + reader.ReadInt32(); string text2 = reader.ReadFourCC(); if (text2 != "INFO") { throw new InvalidDataException("The type of the LIST chunk must be 'INFO', but was '" + text2 + "'."); } while (reader.BaseStream.Position < num) { string text3 = reader.ReadFourCC(); int length = reader.ReadInt32(); switch (text3) { case "ifil": version = new SoundFontVersion(reader.ReadInt16(), reader.ReadInt16()); break; case "isng": targetSoundEngine = reader.ReadFixedLengthString(length); break; case "INAM": bankName = reader.ReadFixedLengthString(length); break; case "irom": romName = reader.ReadFixedLengthString(length); break; case "iver": romVersion = new SoundFontVersion(reader.ReadInt16(), reader.ReadInt16()); break; case "ICRD": creationDate = reader.ReadFixedLengthString(length); break; case "IENG": author = reader.ReadFixedLengthString(length); break; case "IPRD": targetProduct = reader.ReadFixedLengthString(length); break; case "ICOP": copyright = reader.ReadFixedLengthString(length); break; case "ICMT": comments = reader.ReadFixedLengthString(length); break; case "ISFT": tools = reader.ReadFixedLengthString(length); break; default: throw new InvalidDataException("The INFO list contains an unknown ID '" + text3 + "'."); } } } public override string ToString() { return bankName; } } internal static class SoundFontMath { public const float HalfPi = (float)Math.PI / 2f; public static readonly float NonAudible = 0.001f; private static readonly double logNonAudible = Math.Log(0.001); public static float TimecentsToSeconds(float x) { return Mathf.Pow(2f, 0.00083333335f * x); } public static float CentsToHertz(float x) { return 8.176f * Mathf.Pow(2f, 0.00083333335f * x); } public static float CentsToMultiplyingFactor(float x) { return Mathf.Pow(2f, 0.00083333335f * x); } public static float DecibelsToLinear(float x) { return Mathf.Pow(10f, 0.05f * x); } public static float LinearToDecibels(float x) { return 20f * Mathf.Log10(x); } public static float KeyNumberToMultiplyingFactor(int cents, int key) { return TimecentsToSeconds(cents * (60 - key)); } public static double ExpCutoff(double x) { if (x < logNonAudible) { return 0.0; } return Math.Exp(x); } public static float Clamp(float value, float min, float max) { if (value < min) { return min; } if (value > max) { return max; } return value; } } internal sealed class SoundFontParameters { private readonly SampleHeader[] sampleHeaders; private readonly Preset[] presets; private readonly Instrument[] instruments; public SampleHeader[] SampleHeaders => sampleHeaders; public Preset[] Presets => presets; public Instrument[] Instruments => instruments; internal SoundFontParameters(BinaryReader reader) { string text = reader.ReadFourCC(); if (text != "LIST") { throw new InvalidDataException("The LIST chunk was not found."); } long num = reader.BaseStream.Position + reader.ReadInt32(); string text2 = reader.ReadFourCC(); if (text2 != "pdta") { throw new InvalidDataException("The type of the LIST chunk must be 'pdta', but was '" + text2 + "'."); } PresetInfo[] array = null; ZoneInfo[] array2 = null; Generator[] array3 = null; InstrumentInfo[] array4 = null; ZoneInfo[] array5 = null; Generator[] array6 = null; while (reader.BaseStream.Position < num) { string text3 = reader.ReadFourCC(); int size = reader.ReadInt32(); switch (text3) { case "phdr": array = PresetInfo.ReadFromChunk(reader, size); break; case "pbag": array2 = ZoneInfo.ReadFromChunk(reader, size); break; case "pmod": Modulator.DiscardData(reader, size); break; case "pgen": array3 = Generator.ReadFromChunk(reader, size); break; case "inst": array4 = InstrumentInfo.ReadFromChunk(reader, size); break; case "ibag": array5 = ZoneInfo.ReadFromChunk(reader, size); break; case "imod": Modulator.DiscardData(reader, size); break; case "igen": array6 = Generator.ReadFromChunk(reader, size); break; case "shdr": sampleHeaders = SampleHeader.ReadFromChunk(reader, size); break; default: throw new InvalidDataException("The INFO list contains an unknown ID '" + text3 + "'."); } } if (array == null) { throw new InvalidDataException("The PHDR sub-chunk was not found."); } if (array2 == null) { throw new InvalidDataException("The PBAG sub-chunk was not found."); } if (array3 == null) { throw new InvalidDataException("The PGEN sub-chunk was not found."); } if (array4 == null) { throw new InvalidDataException("The INST sub-chunk was not found."); } if (array5 == null) { throw new InvalidDataException("The IBAG sub-chunk was not found."); } if (array6 == null) { throw new InvalidDataException("The IGEN sub-chunk was not found."); } if (sampleHeaders == null) { throw new InvalidDataException("The SHDR sub-chunk was not found."); } Zone[] zones = Zone.Create(array5, array6); instruments = Instrument.Create(array4, zones, sampleHeaders); Zone[] zones2 = Zone.Create(array2, array3); presets = Preset.Create(array, zones2, instruments); } } internal sealed class SoundFontSampleData { private readonly int bitsPerSample; private readonly short[] samples; public int BitsPerSample => bitsPerSample; public short[] Samples => samples; internal SoundFontSampleData(BinaryReader reader) { string text = reader.ReadFourCC(); if (text != "LIST") { throw new InvalidDataException("The LIST chunk was not found."); } long num = reader.BaseStream.Position + reader.ReadInt32(); string text2 = reader.ReadFourCC(); if (text2 != "sdta") { throw new InvalidDataException("The type of the LIST chunk must be 'sdta', but was '" + text2 + "'."); } while (reader.BaseStream.Position < num) { string text3 = reader.ReadFourCC(); int num2 = reader.ReadInt32(); string text4 = text3; string text5 = text4; if (!(text5 == "smpl")) { if (text5 == "sm24") { reader.BaseStream.Position += num2; continue; } throw new InvalidDataException("The INFO list contains an unknown ID '" + text3 + "'."); } bitsPerSample = 16; samples = new short[num2 / 2]; for (int i = 0; i < samples.Length; i++) { samples[i] = reader.ReadInt16(); } } if (samples == null) { throw new InvalidDataException("No valid sample data was found."); } if (!BitConverter.IsLittleEndian) { throw new NotSupportedException("Big endian architectures are not yet supported."); } } } public struct SoundFontVersion { private readonly short major; private readonly short minor; public short Major => major; public short Minor => minor; internal SoundFontVersion(short major, short minor) { this.major = major; this.minor = minor; } public override string ToString() { return $"{major}.{minor}"; } } public sealed class Synthesizer : IAudioRenderer { private static readonly int channelCount = 16; private static readonly int percussionChannel = 9; private readonly SoundFont soundFont; private readonly int sampleRate; private readonly int blockSize; private readonly int maximumPolyphony; private readonly bool enableReverbAndChorus; private readonly int minimumVoiceDuration; private readonly Dictionary<int, Preset> presetLookup; private readonly Preset defaultPreset; private readonly Channel[] channels; private readonly VoiceCollection voices; private readonly float[] blockLeft; private readonly float[] blockRight; private readonly float inverseBlockSize; private int blockRead; private float masterVolume; private Reverb reverb; private float[] reverbInput; private float[] reverbOutputLeft; private float[] reverbOutputRight; private Chorus chorus; private float[] chorusInputLeft; private float[] chorusInputRight; private float[] chorusOutputLeft; private float[] chorusOutputRight; public int BlockSize => blockSize; public int MaximumPolyphony => maximumPolyphony; public int ChannelCount => channelCount; public int PercussionChannel => percussionChannel; public SoundFont SoundFont => soundFont; public int SampleRate => sampleRate; public int ActiveVoiceCount => voices.ActiveVoiceCount; public float MasterVolume { get { return masterVolume; } set { masterVolume = value; } } internal int MinimumVoiceDuration => minimumVoiceDuration; internal Channel[] Channels => channels; public Synthesizer(string soundFontPath, int sampleRate) : this(new SoundFont(soundFontPath), new SynthesizerSettings(sampleRate)) { } public Synthesizer(SoundFont soundFont, int sampleRate) : this(soundFont, new SynthesizerSettings(sampleRate)) { } public Synthesizer(string soundFontPath, SynthesizerSettings settings) : this(new SoundFont(soundFontPath), settings) { } public Synthesizer(SoundFont soundFont, SynthesizerSettings settings) { if (soundFont == null) { throw new ArgumentNullException("soundFont"); } if (settings == null) { throw new ArgumentNullException("settings"); } this.soundFont = soundFont; sampleRate = settings.SampleRate; blockSize = settings.BlockSize; maximumPolyphony = settings.MaximumPolyphony; enableReverbAndChorus = settings.EnableReverbAndChorus; minimumVoiceDuration = sampleRate / 500; presetLookup = new Dictionary<int, Preset>(); int num = int.MaxValue; Preset[] presetArray = soundFont.PresetArray; foreach (Preset preset in presetArray) { int num2 = (preset.BankNumber << 16) | preset.PatchNumber; if (!presetLookup.ContainsKey(num2)) { presetLookup.Add(num2, preset); } if (num2 < num) { defaultPreset = preset; num = num2; } } Debug.Assert(defaultPreset != null); channels = new Channel[channelCount]; for (int j = 0; j < channels.Length; j++) { channels[j] = new Channel(this, j == percussionChannel); } voices = new VoiceCollection(this, maximumPolyphony); blockLeft = new float[blockSize]; blockRight = new float[blockSize]; inverseBlockSize = 1f / (float)blockSize; blockRead = blockSize; masterVolume = 0.5f; if (enableReverbAndChorus) { reverb = new Reverb(sampleRate); reverbInput = new float[blockSize]; reverbOutputLeft = new float[blockSize]; reverbOutputRight = new float[blockSize]; chorus = new Chorus(sampleRate, 0.002, 0.0019, 0.4); chorusInputLeft = new float[blockSize]; chorusInputRight = new float[blockSize]; chorusOutputLeft = new float[blockSize]; chorusOutputRight = new float[blockSize]; } } public void ProcessMidiMessage(int channel, int command, int data1, int data2) { if (0 > channel || channel >= channels.Length) { return; } Channel channel2 = channels[channel]; switch (command) { case 128: NoteOff(channel, data1); break; case 144: NoteOn(channel, data1, data2); break; case 176: switch (data1) { case 0: channel2.SetBank(data2); break; case 1: channel2.SetModulationCoarse(data2); break; case 33: channel2.SetModulationFine(data2); break; case 6: channel2.DataEntryCoarse(data2); break; case 38: channel2.DataEntryFine(data2); break; case 7: channel2.SetVolumeCoarse(data2); break; case 39: channel2.SetVolumeFine(data2); break; case 10: channel2.SetPanCoarse(data2); break; case 42: channel2.SetPanFine(data2); break; case 11: channel2.SetExpressionCoarse(data2); break; case 43: channel2.SetExpressionFine(data2); break; case 64: channel2.SetHoldPedal(data2); break; case 91: channel2.SetReverbSend(data2); break; case 93: channel2.SetChorusSend(data2); break; case 101: channel2.SetRpnCoarse(data2); break; case 100: channel2.SetRpnFine(data2); break; case 120: NoteOffAll(channel, immediate: true); break; case 121: ResetAllControllers(channel); break; case 123: NoteOffAll(channel, immediate: false); break; } break; case 192: channel2.SetPatch(data1); break; case 224: channel2.SetPitchBend(data1, data2); break; } } public void NoteOff(int channel, int key) { if (0 > channel || channel >= channels.Length) { return; } foreach (Voice voice in voices) { if (voice.Channel == channel && voice.Key == key) { voice.End(); } } } public void NoteOn(int channel, int key, int velocity) { if (velocity == 0) { NoteOff(channel, key); } else { if (0 > channel || channel >= channels.Length) { return; } Channel channel2 = channels[channel]; int key2 = (channel2.BankNumber << 16) | channel2.PatchNumber; if (!presetLookup.TryGetValue(key2, out var value)) { int key3 = ((channel2.BankNumber < 128) ? channel2.PatchNumber : 8388608); if (!presetLookup.TryGetValue(key3, out value)) { value = defaultPreset; } } PresetRegion[] regionArray = value.RegionArray; foreach (PresetRegion presetRegion in regionArray) { if (!presetRegion.Contains(key, velocity)) { continue; } InstrumentRegion[] regionArray2 = presetRegion.Instrument.RegionArray; foreach (InstrumentRegion instrumentRegion in regionArray2) { if (instrumentRegion.Contains(key, velocity)) { RegionPair region = new RegionPair(presetRegion, instrumentRegion); voices.RequestNew(instrumentRegion, channel)?.Start(region, channel, key, velocity); } } } } } public void NoteOffAll(bool immediate) { if (immediate) { voices.Clear(); return; } foreach (Voice voice in voices) { voice.End(); } } public void NoteOffAll(int channel, bool immediate) { if (immediate) { foreach (Voice voice in voices) { if (voice.Channel == channel) { voice.Kill(); } } return; } foreach (Voice voice2 in voices) { if (voice2.Channel == channel) { voice2.End(); } } } public void ResetAllControllers() { Channel[] array = channels; foreach (Channel channel in array) { channel.ResetAllControllers(); } } public void ResetAllControllers(int channel) { if (0 <= channel && channel < channels.Length) { channels[channel].ResetAllControllers(); } } public void Reset() { voices.Clear(); Channel[] array = channels; foreach (Channel channel in array) { channel.Reset(); } if (enableReverbAndChorus) { reverb.Mute(); chorus.Mute(); } blockRead = blockSize; } public void Render(Span<float> left, Span<float> right) { if (left.Length != right.Length) { throw new ArgumentException("The output buffers for the left and right must be the same length."); } int num; for (int i = 0; i < left.Length; i += num) { if (blockRead == blockSize) { RenderBlock(); blockRead = 0; } int val = blockSize - blockRead; int val2 = left.Length - i; num = Math.Min(val, val2); new Span<float>(blockLeft, blockRead, num).CopyTo(left.Slice(i, num)); new Span<float>(blockRight, blockRead, num).CopyTo(right.Slice(i, num)); blockRead += num; } } private void RenderBlock() { voices.Process(); Array.Clear(blockLeft, 0, blockLeft.Length); Array.Clear(blockRight, 0, blockRight.Length); foreach (Voice voice in voices) { float previousGain = masterVolume * voice.PreviousMixGainLeft; float currentGain = masterVolume * voice.CurrentMixGainLeft; WriteBlock(previousGain, currentGain, voice.Block, blockLeft); float previousGain2 = masterVolume * voice.PreviousMixGainRight; float currentGain2 = masterVolume * voice.CurrentMixGainRight; WriteBlock(previousGain2, currentGain2, voice.Block, blockRight); } if (!enableReverbAndChorus) { return; } Array.Clear(chorusInputLeft, 0, chorusInputLeft.Length); Array.Clear(chorusInputRight, 0, chorusInputRight.Length); foreach (Voice voice2 in voices) { float previousGain3 = voice2.PreviousChorusSend * voice2.PreviousMixGainLeft; float currentGain3 = voice2.CurrentChorusSend * voice2.CurrentMixGainLeft; WriteBlock(previousGain3, currentGain3, voice2.Block, chorusInputLeft); float previousGain4 = voice2.PreviousChorusSend * voice2.PreviousMixGainRight; float currentGain4 = voice2.CurrentChorusSend * voice2.CurrentMixGainRight; WriteBlock(previousGain4, currentGain4, voice2.Block, chorusInputRight); } chorus.Process(chorusInputLeft, chorusInputRight, chorusOutputLeft, chorusOutputRight); ArrayMath.MultiplyAdd(masterVolume, chorusOutputLeft, blockLeft); ArrayMath.MultiplyAdd(masterVolume, chorusOutputRight, blockRight); Array.Clear(reverbInput, 0, reverbInput.Length); foreach (Voice voice3 in voices) { float previousGain5 = reverb.InputGain * voice3.PreviousReverbSend * (voice3.PreviousMixGainLeft + voice3.PreviousMixGainRight); float currentGain5 = reverb.InputGain * voice3.CurrentReverbSend * (voice3.CurrentMixGainLeft + voice3.C