diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba88c78 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vs +*.suo +bin/ +obj/ diff --git a/DssiDescriptor.cs b/DssiDescriptor.cs new file mode 100644 index 0000000..3edb479 --- /dev/null +++ b/DssiDescriptor.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +namespace LADSPA.NET +{ + public class DssiDescriptor + { + public readonly LadspaLibraryContext Library; + public readonly LadspaDescriptor LadspaDescriptor; + public readonly bool IsSynth; + + internal readonly IntPtr DataHandle; + internal readonly DssiDescriptorConfigureCallback Configure; + internal readonly DssiDescriptorGetProgramCallback GetProgram; + internal readonly DssiDescriptorSelectProgramCallback SelectProgram; + internal readonly DssiDescriptorGetMidiControllerForPortCallback GetMidiControllerForPort; + internal readonly DssiDescriptorRunSynthCallback RunSynth; + internal readonly DssiDescriptorRunSynthCallback RunSynthAdding; + internal readonly DssiDescriptorRunMultiSynthCallback RunMultiSynth; + internal readonly DssiDescriptorRunMultiSynthCallback RunMultiSynthAdding; + + private DssiProgramDescriptor[] _programs; + public DssiProgramDescriptor[] Programs + { + get + { + lock (this) + { + if (_programs == null) + { + if (LadspaDescriptor.Instantiate != null && LadspaDescriptor.Cleanup != null) + { + IntPtr handle = LadspaDescriptor.Instantiate(LadspaDescriptor.DataHandle, 44100); + if (handle != IntPtr.Zero) + { + try { _programs = LoadProgramsFromHandle(handle); } + finally { LadspaDescriptor.Cleanup(handle); } + } + } + if (_programs == null) { _programs = new DssiProgramDescriptor[0]; } + } + return (_programs); + } + } + } + + internal DssiDescriptor(LadspaLibraryContext library, uint index, IntPtr dataPtr) + { + DssiDescriptorStruct data = (DssiDescriptorStruct)Marshal.PtrToStructure(dataPtr, typeof(DssiDescriptorStruct)); + Library = library; + DataHandle = dataPtr; + LadspaDescriptor = new LadspaDescriptor(library, index, data.LadspaDescriptor); + if (data.Configure != IntPtr.Zero) { Configure = (DssiDescriptorConfigureCallback)Marshal.GetDelegateForFunctionPointer(data.Configure, typeof(DssiDescriptorConfigureCallback)); } + if (data.GetProgram != IntPtr.Zero) { GetProgram = (DssiDescriptorGetProgramCallback)Marshal.GetDelegateForFunctionPointer(data.GetProgram, typeof(DssiDescriptorGetProgramCallback)); } + if (data.SelectProgram != IntPtr.Zero) { SelectProgram = (DssiDescriptorSelectProgramCallback)Marshal.GetDelegateForFunctionPointer(data.SelectProgram, typeof(DssiDescriptorSelectProgramCallback)); } + if (data.GetMidiControllerForPort != IntPtr.Zero) { GetMidiControllerForPort = (DssiDescriptorGetMidiControllerForPortCallback)Marshal.GetDelegateForFunctionPointer(data.GetMidiControllerForPort, typeof(DssiDescriptorGetMidiControllerForPortCallback)); } + if (data.RunSynth != IntPtr.Zero) { RunSynth = (DssiDescriptorRunSynthCallback)Marshal.GetDelegateForFunctionPointer(data.RunSynth, typeof(DssiDescriptorRunSynthCallback)); } + if (data.RunSynthAdding != IntPtr.Zero) { RunSynthAdding = (DssiDescriptorRunSynthCallback)Marshal.GetDelegateForFunctionPointer(data.RunSynthAdding, typeof(DssiDescriptorRunSynthCallback)); } + if (data.RunMultiSynth != IntPtr.Zero) { RunMultiSynth = (DssiDescriptorRunMultiSynthCallback)Marshal.GetDelegateForFunctionPointer(data.RunMultiSynth, typeof(DssiDescriptorRunMultiSynthCallback)); } + if (data.RunMultiSynthAdding != IntPtr.Zero) { RunMultiSynthAdding = (DssiDescriptorRunMultiSynthCallback)Marshal.GetDelegateForFunctionPointer(data.RunMultiSynthAdding, typeof(DssiDescriptorRunMultiSynthCallback)); } + IsSynth = data.RunSynth != null; + } + + public DssiInstance CreateInstance(int sampleRate, int bufferSize = 1024) + { + IntPtr handle = LadspaDescriptor.Instantiate != null ? LadspaDescriptor.Instantiate(LadspaDescriptor.DataHandle, (uint)sampleRate) : IntPtr.Zero; + if (handle == IntPtr.Zero) { throw new Exception("Failed to create instance."); } + lock (this) { if (_programs == null) { _programs = LoadProgramsFromHandle(handle); } } + return (new DssiInstance(this, handle, sampleRate, bufferSize)); + } + + private DssiProgramDescriptor[] LoadProgramsFromHandle(IntPtr handle) + { + List programs = new List(); + if (GetProgram != null) + { + for (uint index = 0; true; index++) + { + IntPtr programPtr = GetProgram(handle, index); + if (programPtr == IntPtr.Zero) { break; } + programs.Add((DssiProgramDescriptor)Marshal.PtrToStructure(programPtr, typeof(DssiProgramDescriptor))); + } + } + return (programs.ToArray()); + } + } +} diff --git a/DssiDescriptorStruct.cs b/DssiDescriptorStruct.cs new file mode 100644 index 0000000..7c12e87 --- /dev/null +++ b/DssiDescriptorStruct.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +namespace LADSPA.NET +{ + [StructLayout(LayoutKind.Sequential)] + internal struct DssiDescriptorStruct + { + public uint APIVersion; + public IntPtr LadspaDescriptor; // LadspaDescriptorStruct + public IntPtr Configure; // string (IntPtr handle, string key, string value) + public IntPtr GetProgram; // IntPtr (IntPtr handle, uint index) + public IntPtr SelectProgram; // void (IntPtr handle, uint bank, uint program) + public IntPtr GetMidiControllerForPort; // int (IntPtr handle, uint port) + public IntPtr RunSynth; // void (IntPtr thisPtr, uint sampleCount, IntPtr events, uint eventCount) + public IntPtr RunSynthAdding; // void (IntPtr thisPtr, uint sampleCount, IntPtr events, uint eventCount) + public IntPtr RunMultiSynth; // void (uint thisCount, IntPtr thisPtrs, uint sampleCount, IntPtr events, IntPtr eventCounts) + public IntPtr RunMultiSynthAdding; // void (uint thisCount, IntPtr thisPtrs, uint sampleCount, IntPtr events, IntPtr eventCounts) + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate string DssiDescriptorConfigureCallback(IntPtr thisPtr, string key, string value); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate IntPtr DssiDescriptorGetProgramCallback(IntPtr thisPtr, uint index); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void DssiDescriptorSelectProgramCallback(IntPtr thisPtr, uint bank, uint program); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int DssiDescriptorGetMidiControllerForPortCallback(IntPtr thisPtr, uint port); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void DssiDescriptorRunSynthCallback(IntPtr thisPtr, uint sampleCount, IntPtr events, uint eventCount); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void DssiDescriptorRunMultiSynthCallback(uint thisCount, IntPtr thisPtrs, uint sampleCount, IntPtr events, IntPtr eventCounts); +} diff --git a/DssiInstance.cs b/DssiInstance.cs new file mode 100644 index 0000000..1128bf7 --- /dev/null +++ b/DssiInstance.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +namespace LADSPA.NET +{ + public class DssiInstance : LadspaInstance + { + public readonly DssiDescriptor DssiDescriptor; + + protected readonly List eventList = new List(); + protected IntPtr eventBuffer; + protected int eventBufferSize; + protected int eventBufferCount; + + internal DssiInstance(DssiDescriptor descriptor, IntPtr handle, int sampleRate, int bufferSize) + : base(descriptor.LadspaDescriptor, handle, sampleRate, bufferSize) + { + DssiDescriptor = descriptor; + } + + protected void PrepareEvents() + { + int i, offset, count = eventList.Count; + int size = count * 28; + if (eventBuffer == IntPtr.Zero || eventBufferSize < size) + { + if (eventBuffer != IntPtr.Zero) { Marshal.FreeHGlobal(eventBuffer); eventBuffer = IntPtr.Zero; } + eventBuffer = Marshal.AllocHGlobal(eventBufferSize = size + 140); + } + for (i = 0, offset = 0, eventBufferCount = 0; i < count; i++) + { + DssiMidiEvent e = eventList[i]; + int command = e.MidiCommand & 0xf0; + switch (command) + { + case 0x80: // Note Off + case 0x90: // Note On + case 0xA0: // Aftertouch + int code = (command == 0x80 || (e.MidiData1 == 0 && command == 0x90)) ? 7 : command == 0x90 ? 6 : 8; + Marshal.WriteInt32(eventBuffer, offset, code); offset += 4; // Type code + Marshal.WriteInt32(eventBuffer, offset, e.SampleIndex); offset += 4; // Ticks + Marshal.WriteInt64(eventBuffer, offset, 0); offset += 8; // Realtime timestamp + addresses (not used) + Marshal.WriteByte(eventBuffer, offset, (byte)(e.MidiCommand & 0x0f)); offset++; // Channel + Marshal.WriteByte(eventBuffer, offset, e.MidiData0); offset++; // Pitch + Marshal.WriteByte(eventBuffer, offset, e.MidiData1 == 0 ? (byte)64 : e.MidiData1); offset++; // Velocity + Marshal.WriteByte(eventBuffer, offset, 0); offset++; // Off Velocity (not used) + Marshal.WriteInt64(eventBuffer, offset, 0); offset += 8; // Duration (not used) + eventBufferCount++; + break; + + case 0xB0: // Controller + Marshal.WriteInt32(eventBuffer, offset, 10); offset += 4; // Type code + Marshal.WriteInt32(eventBuffer, offset, e.SampleIndex); offset += 4; // Ticks + Marshal.WriteInt64(eventBuffer, offset, 0); offset += 8; // Realtime timestamp + addresses (not used) + Marshal.WriteInt32(eventBuffer, offset, e.MidiCommand & 0x0f); offset += 4; // Channel + Unused + Marshal.WriteInt32(eventBuffer, offset, e.MidiData0); offset += 4; // Control parameter + Marshal.WriteInt32(eventBuffer, offset, e.MidiData1); offset += 4; // Value + eventBufferCount++; + break; + + case 0xC0: // Change program + SelectProgram((uint)(e.MidiCommand & 0x0f), e.MidiData0); + break; + + case 0xD0: // Channel pressure + Marshal.WriteInt32(eventBuffer, offset, 12); offset += 4; // Type code + Marshal.WriteInt32(eventBuffer, offset, e.SampleIndex); offset += 4; // Ticks + Marshal.WriteInt64(eventBuffer, offset, 0); offset += 8; // Realtime timestamp + addresses (not used) + Marshal.WriteInt32(eventBuffer, offset, e.MidiCommand & 0x0f); offset += 4; // Channel + Unused + Marshal.WriteInt32(eventBuffer, offset, 0); offset += 4; // Control parameter + Marshal.WriteInt32(eventBuffer, offset, e.MidiData0); offset += 4; // Value + eventBufferCount++; + break; + + case 0xE0: // Pitchbend + Marshal.WriteInt32(eventBuffer, offset, 13); offset += 4; // Type code + Marshal.WriteInt32(eventBuffer, offset, e.SampleIndex); offset += 4; // Ticks + Marshal.WriteInt64(eventBuffer, offset, 0); offset += 8; // Realtime timestamp + addresses (not used) + Marshal.WriteInt32(eventBuffer, offset, e.MidiCommand & 0x0f); offset += 4; // Channel + Unused + Marshal.WriteInt32(eventBuffer, offset, 0); offset += 4; // Control parameter + Marshal.WriteInt32(eventBuffer, offset, (short)(((int)e.MidiData0 << 2) + ((int)e.MidiData1 << 9)) >> 2); offset += 4; // Value + eventBufferCount++; + break; + } + } + eventList.Clear(); + } + + public string Configure(string key, string value) + { + if (handle == IntPtr.Zero || DssiDescriptor.Configure == null) { return (null); } + return (DssiDescriptor.Configure(handle, key, value)); + } + + public void SelectProgram(uint bank, uint program) + { + if (handle == IntPtr.Zero || DssiDescriptor.SelectProgram == null) { return; } + DssiDescriptor.SelectProgram(handle, bank, program); + } + + public int GetMidiControllerForPort(uint port) + { + if (handle == IntPtr.Zero || DssiDescriptor.GetMidiControllerForPort == null) { return (0); } + return (DssiDescriptor.GetMidiControllerForPort(handle, port)); + } + + public void AddSynthEvents(params DssiMidiEvent[] events) + { + if (events != null && events.Length > 0) + { + eventList.AddRange(events); + } + } + + public bool Run(float[][] outputs) + { + if (handle != IntPtr.Zero) + { + if (DssiDescriptor.RunSynth != null) + { + PrepareEvents(); + DssiDescriptor.RunSynth(handle, (uint)bufferSize, eventBuffer, (uint)eventBufferCount); + CopyOutputs(outputs); + return (true); + } + else if (LadspaDescriptor.Run != null) + { + LadspaDescriptor.Run(handle, (uint)bufferSize); + CopyOutputs(outputs); + return (true); + } + } + if (eventList.Count > 0) { eventList.Clear(); } + return (false); + } + + public bool RunAdding(float[][] outputs) + { + if (handle != IntPtr.Zero) + { + if (DssiDescriptor.RunSynthAdding != null) + { + PrepareEvents(); + DssiDescriptor.RunSynthAdding(handle, (uint)bufferSize, eventBuffer, (uint)eventBufferCount); + CopyOutputs(outputs); + return (true); + } + else if (LadspaDescriptor.RunAdding != null) + { + LadspaDescriptor.RunAdding(handle, (uint)bufferSize); + CopyOutputs(outputs); + return (true); + } + } + if (eventList.Count > 0) { eventList.Clear(); } + return (false); + } + + public override bool Run(float[][] inputs, float[][] outputs) + { + if (handle != IntPtr.Zero) { CopyInputs(inputs); } + return (Run(outputs)); + } + + public override bool RunAdding(float[][] inputs, float[][] outputs) + { + if (handle != IntPtr.Zero) { CopyInputs(inputs); } + return (RunAdding(outputs)); + } + + public override LadspaInstance Clone(int sampleRate = 0, int bufferSize = 0) + { + if (sampleRate == 0) { sampleRate = this.sampleRate; } + if (bufferSize == 0) { bufferSize = this.bufferSize; } + DssiInstance instance = DssiDescriptor.CreateInstance(sampleRate, bufferSize); + for (int i = 0; i < LadspaDescriptor.Ports.Length; i++) { instance.SetParameter(i, GetParameter(i)); } + return (instance); + } + + public override void Dispose() + { + base.Dispose(); + if (eventBuffer != IntPtr.Zero) + { + Marshal.FreeHGlobal(eventBuffer); + eventBuffer = IntPtr.Zero; + } + } + } +} diff --git a/DssiMidiEvent.cs b/DssiMidiEvent.cs new file mode 100644 index 0000000..b71ff80 --- /dev/null +++ b/DssiMidiEvent.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +namespace LADSPA.NET +{ + public struct DssiMidiEvent + { + public int SampleIndex; + public byte MidiCommand; + public byte MidiData0; + public byte MidiData1; + public byte MidiData2; + } +} diff --git a/DssiProgramDescriptorStruct.cs b/DssiProgramDescriptorStruct.cs new file mode 100644 index 0000000..82ae8b6 --- /dev/null +++ b/DssiProgramDescriptorStruct.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +namespace LADSPA.NET +{ + [StructLayout(LayoutKind.Sequential)] + public struct DssiProgramDescriptor + { + public uint Bank; + public uint Program; + public string Name; + } +} diff --git a/LADSPA.NET.csproj b/LADSPA.NET.csproj new file mode 100644 index 0000000..1a6c736 --- /dev/null +++ b/LADSPA.NET.csproj @@ -0,0 +1,69 @@ + + + + + Debug + AnyCPU + {4009C190-47E2-4132-9E6A-DF87BEF84FCE} + Library + Properties + LADSPA.NET + LADSPA.NET + v4.0 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + AnyCPU + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + AnyCPU + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LADSPA.NET.sln b/LADSPA.NET.sln new file mode 100644 index 0000000..cbbde35 --- /dev/null +++ b/LADSPA.NET.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27428.2043 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LADSPA.NET", "LADSPA.NET.csproj", "{4009C190-47E2-4132-9E6A-DF87BEF84FCE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4009C190-47E2-4132-9E6A-DF87BEF84FCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4009C190-47E2-4132-9E6A-DF87BEF84FCE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4009C190-47E2-4132-9E6A-DF87BEF84FCE}.Debug|x64.ActiveCfg = Debug|Any CPU + {4009C190-47E2-4132-9E6A-DF87BEF84FCE}.Debug|x64.Build.0 = Debug|Any CPU + {4009C190-47E2-4132-9E6A-DF87BEF84FCE}.Debug|x86.ActiveCfg = Debug|Any CPU + {4009C190-47E2-4132-9E6A-DF87BEF84FCE}.Debug|x86.Build.0 = Debug|Any CPU + {4009C190-47E2-4132-9E6A-DF87BEF84FCE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4009C190-47E2-4132-9E6A-DF87BEF84FCE}.Release|Any CPU.Build.0 = Release|Any CPU + {4009C190-47E2-4132-9E6A-DF87BEF84FCE}.Release|x64.ActiveCfg = Release|Any CPU + {4009C190-47E2-4132-9E6A-DF87BEF84FCE}.Release|x64.Build.0 = Release|Any CPU + {4009C190-47E2-4132-9E6A-DF87BEF84FCE}.Release|x86.ActiveCfg = Release|Any CPU + {4009C190-47E2-4132-9E6A-DF87BEF84FCE}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CE0132EC-A75A-4BDA-ABCA-784D93C8B3FF} + EndGlobalSection +EndGlobal diff --git a/LadspaDescriptor.cs b/LadspaDescriptor.cs new file mode 100644 index 0000000..014d9b2 --- /dev/null +++ b/LadspaDescriptor.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +namespace LADSPA.NET +{ + public class LadspaDescriptor + { + public readonly LadspaLibraryContext Library; + public readonly uint Index; + public readonly uint UniqueID; + public readonly LadspaPropertiesEnum Properties; + public readonly string Label; + public readonly string Name; + public readonly string Maker; + public readonly string Copyright; + public readonly LadspaPort[] Ports; + + internal readonly IntPtr DataHandle; + internal readonly LadspaDescriptorInstantiateCallback Instantiate; + internal readonly LadspaDescriptorConnectPortCallback ConnectPort; + internal readonly LadspaDescriptorCallback Activate; + internal readonly LadspaDescriptorRunCallback Run; + internal readonly LadspaDescriptorRunCallback RunAdding; + internal readonly LadspaDescriptorSetRunGainCallback SetRunAddingGain; + internal readonly LadspaDescriptorCallback Deactivate; + internal readonly LadspaDescriptorCallback Cleanup; + + internal LadspaDescriptor(LadspaLibraryContext library, uint index, IntPtr dataPtr) + { + LadspaDescriptorStruct data = (LadspaDescriptorStruct)Marshal.PtrToStructure(dataPtr, typeof(LadspaDescriptorStruct)); + Library = library; + DataHandle = dataPtr; + Index = index; + UniqueID = data.UniqueID; + Properties = data.Properties; + Label = data.Label; + Name = data.Name; + Maker = data.Maker; + Copyright = data.Copyright; + Ports = new LadspaPort[data.PortCount]; + int hintsSize = Marshal.SizeOf(typeof(LadspaPortRangeHintsStruct)); + int[] types = new int[data.PortCount]; + IntPtr[] names = new IntPtr[data.PortCount]; + if (data.PortDescriptors != IntPtr.Zero) { Marshal.Copy(data.PortDescriptors, types, 0, types.Length); } + if (data.PortNames != IntPtr.Zero) { Marshal.Copy(data.PortNames, names, 0, names.Length); } + for (int i = 0; i < data.PortCount; i++) + { + string name = names[i] != IntPtr.Zero ? Marshal.PtrToStringAnsi(names[i]) : null; + LadspaPortRangeHintsStruct hints = data.PortRangeHints == IntPtr.Zero ? new LadspaPortRangeHintsStruct() : + (LadspaPortRangeHintsStruct)Marshal.PtrToStructure(IntPtr.Add(data.PortRangeHints, hintsSize * (int)i), typeof(LadspaPortRangeHintsStruct)); + Ports[i] = new LadspaPort(this, i, (LadspaPortDescriptorEnum)types[i], hints.Hints, name, hints.LowerBound, hints.UpperBound); + } + if (data.Instantiate != IntPtr.Zero) { Instantiate = (LadspaDescriptorInstantiateCallback)Marshal.GetDelegateForFunctionPointer(data.Instantiate, typeof(LadspaDescriptorInstantiateCallback)); } + if (data.ConnectPort != IntPtr.Zero) { ConnectPort = (LadspaDescriptorConnectPortCallback)Marshal.GetDelegateForFunctionPointer(data.ConnectPort, typeof(LadspaDescriptorConnectPortCallback)); } + if (data.Activate != IntPtr.Zero) { Activate = (LadspaDescriptorCallback)Marshal.GetDelegateForFunctionPointer(data.Activate, typeof(LadspaDescriptorCallback)); } + if (data.Run != IntPtr.Zero) { Run = (LadspaDescriptorRunCallback)Marshal.GetDelegateForFunctionPointer(data.Run, typeof(LadspaDescriptorRunCallback)); } + if (data.RunAdding != IntPtr.Zero) { RunAdding = (LadspaDescriptorRunCallback)Marshal.GetDelegateForFunctionPointer(data.RunAdding, typeof(LadspaDescriptorRunCallback)); } + if (data.SetRunAddingGain != IntPtr.Zero) { SetRunAddingGain = (LadspaDescriptorSetRunGainCallback)Marshal.GetDelegateForFunctionPointer(data.SetRunAddingGain, typeof(LadspaDescriptorSetRunGainCallback)); } + if (data.Deactivate != IntPtr.Zero) { Deactivate = (LadspaDescriptorCallback)Marshal.GetDelegateForFunctionPointer(data.Deactivate, typeof(LadspaDescriptorCallback)); } + if (data.Cleanup != IntPtr.Zero) { Cleanup = (LadspaDescriptorCallback)Marshal.GetDelegateForFunctionPointer(data.Cleanup, typeof(LadspaDescriptorCallback)); } + } + + public LadspaInstance CreateInstance(int sampleRate, int bufferSize = 1024) + { + IntPtr handle = Instantiate != null ? Instantiate(DataHandle, (uint)sampleRate) : IntPtr.Zero; + if (handle == IntPtr.Zero) { throw new Exception("Failed to create instance."); } + return (new LadspaInstance(this, handle, sampleRate, bufferSize)); + } + } +} diff --git a/LadspaDescriptorStruct.cs b/LadspaDescriptorStruct.cs new file mode 100644 index 0000000..9f3b68e --- /dev/null +++ b/LadspaDescriptorStruct.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +namespace LADSPA.NET +{ + [StructLayout(LayoutKind.Sequential)] + internal struct LadspaDescriptorStruct + { + public uint UniqueID; + public string Label; + public LadspaPropertiesEnum Properties; + public string Name; + public string Maker; + public string Copyright; + public uint PortCount; + public IntPtr PortDescriptors; // LadspaPortDescriptorEnum[] + public IntPtr PortNames; // string[] + public IntPtr PortRangeHints; // LadspaPortRangeHintsStruct[] + public IntPtr ImplementationData; + public IntPtr Instantiate; // IntPtr (LadspaDescriptorStruct* this, uint sampleRate) + public IntPtr ConnectPort; // void (IntPtr handle, uint port, float* data) + public IntPtr Activate; // void (IntPtr handle) + public IntPtr Run; // void (IntPtr handle, uint sampleCount) + public IntPtr RunAdding; // void (IntPtr handle, uint sampleCount) + public IntPtr SetRunAddingGain; // void (IntPtr handle, float gain) + public IntPtr Deactivate; // void (IntPtr handle) + public IntPtr Cleanup; // void (IntPtr handle) + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate IntPtr LadspaDescriptorInstantiateCallback(IntPtr thisPtr, uint sampleRate); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void LadspaDescriptorConnectPortCallback(IntPtr handle, uint port, IntPtr data); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void LadspaDescriptorCallback(IntPtr handle); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void LadspaDescriptorRunCallback(IntPtr handle, uint sampleCount); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void LadspaDescriptorSetRunGainCallback(IntPtr handle, float gain); +} diff --git a/LadspaInstance.cs b/LadspaInstance.cs new file mode 100644 index 0000000..af8161b --- /dev/null +++ b/LadspaInstance.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +namespace LADSPA.NET +{ + public class LadspaInstance : IDisposable + { + [DllImport("kernel32.dll")] + protected static extern void RtlZeroMemory(IntPtr dst, int length); + + public readonly LadspaDescriptor LadspaDescriptor; + + protected IntPtr handle; + protected IntPtr data; + protected bool active; + protected int sampleRate; + protected readonly int[] dataOffsets; + protected readonly int[] inputOffsets; + protected readonly int[] outputOffsets; + protected readonly float[] paramBuffer; + protected readonly int bufferSize; + protected float[] mixBuffer; + + public readonly int InputCount; + public readonly int OutputCount; + + internal LadspaInstance(LadspaDescriptor descriptor, IntPtr handle, int sampleRate, int bufferSize) + { + if (bufferSize < 512) { bufferSize = 512; } + LadspaDescriptor = descriptor; + this.handle = handle; + this.bufferSize = bufferSize; + this.sampleRate = sampleRate; + int inputCount = 0, outputCount = 0; + for (int i = 0; i < descriptor.Ports.Length; i++) + { + if (descriptor.Ports[i].PortType != LadspaPortType.Audio) { continue; } + LadspaPortDirection dir = descriptor.Ports[i].PortDirection; + if (dir == LadspaPortDirection.Input) { inputCount++; } + else if (dir == LadspaPortDirection.Output) { outputCount++; } + } + InputCount = inputCount; + OutputCount = outputCount; + dataOffsets = new int[descriptor.Ports.Length]; + inputOffsets = new int[inputCount]; + outputOffsets = new int[outputCount]; + paramBuffer = new float[1]; + if (descriptor.ConnectPort != null) + { + int dataSize = 0, blockSize = (int)(bufferSize * 4); + inputCount = 0; outputCount = 0; + for (int i = 0; i < descriptor.Ports.Length; i++) + { + dataOffsets[i] = dataSize; + if (descriptor.Ports[i].PortType != LadspaPortType.Audio) { dataSize += 4; } + else + { + LadspaPortDirection dir = descriptor.Ports[i].PortDirection; + if (dir == LadspaPortDirection.Input) { inputOffsets[inputCount++] = dataSize; } + else if (dir == LadspaPortDirection.Output) { outputOffsets[outputCount++] = dataSize; } + dataSize += blockSize; + } + } + if (dataSize > 0) + { + data = Marshal.AllocHGlobal(dataSize); + RtlZeroMemory(data, dataSize); + ResetParameters(); + for (uint i = 0; i < descriptor.Ports.Length; i++) + { + descriptor.ConnectPort(handle, i, IntPtr.Add(data, dataOffsets[i])); + } + } + } + } + + protected void CopyInputs(float[][] inputs) + { + if (data == IntPtr.Zero) { return; } + for (int i = 0; i < inputOffsets.Length; i++) + { + int copy = inputs != null && inputs.Length > i && inputs[i] != null ? Math.Min(inputs[i].Length, bufferSize) : 0; + if (copy > 0) { Marshal.Copy(inputs[i], 0, IntPtr.Add(data, inputOffsets[i]), copy); } + if (copy < bufferSize) { RtlZeroMemory(IntPtr.Add(data, inputOffsets[i]), (bufferSize - copy) * 4); } + } + } + + protected void CopyOutputs(float[][] outputs) + { + if (data == IntPtr.Zero) { return; } + if (outputs == null) { return; } + int outMax = Math.Min(outputOffsets.Length, outputs.Length); + for (int i = 0; i < outMax; i++) + { + if (outputs[i] == null) { continue; } + int copy = Math.Min(outputs[i].Length, bufferSize); + if (copy > 0) { Marshal.Copy(IntPtr.Add(data, outputOffsets[i]), outputs[i], 0, copy); } + if (copy < outputs.Length) { Array.Clear(outputs, copy, outputs.Length - copy); } + } + if (outputs.Length == 1 && outputOffsets.Length > 1 && outputs[0] != null) + { + if (mixBuffer == null || mixBuffer.Length < bufferSize) { mixBuffer = new float[bufferSize]; } + Marshal.Copy(IntPtr.Add(data, outputOffsets[1]), mixBuffer, 0, bufferSize); + float[] buffer = outputs[0]; + int copy = Math.Min(buffer.Length, bufferSize); + for (int i = 0; i < copy; i++) { buffer[i] = (buffer[i] + mixBuffer[i]) * 0.5f; } + } + else + { + for (int i = outMax; i < outputs.Length; i++) + { + if (outputs[i] == null) { continue; } + if (i == 1 && outputs[0] != null) + { + Array.Copy(outputs[0], outputs[1], Math.Min(outputs[0].Length, outputs[1].Length)); + } + else + { + Array.Clear(outputs[i], 0, outputs[i].Length); + } + } + } + } + + public int GetSampleRate() + { + return (sampleRate); + } + + public int GetBufferSize() + { + return (bufferSize); + } + + public virtual LadspaInstance Clone(int sampleRate = 0, int bufferSize = 0) + { + if (sampleRate == 0) { sampleRate = this.sampleRate; } + if (bufferSize == 0) { bufferSize = this.bufferSize; } + LadspaInstance instance = LadspaDescriptor.CreateInstance(sampleRate, bufferSize); + for (int i = 0; i < LadspaDescriptor.Ports.Length; i++) { instance.SetParameter(i, GetParameter(i)); } + return (instance); + } + + public void Activate() + { + if (handle == IntPtr.Zero) { return; } + if (active) { return; } + if (LadspaDescriptor.Activate != null) { LadspaDescriptor.Activate(handle); } + active = true; + } + + public void Deactivate() + { + if (handle == IntPtr.Zero) { return; } + if (!active) { return; } + if (LadspaDescriptor.Deactivate != null) { LadspaDescriptor.Deactivate(handle); } + active = false; + } + + public void ResetParameters() + { + if (handle == IntPtr.Zero || data == IntPtr.Zero) { return; } + for (int i = 0; i < LadspaDescriptor.Ports.Length; i++) + { + if (LadspaDescriptor.Ports[i].PortType != LadspaPortType.Control) { continue; } + paramBuffer[0] = LadspaDescriptor.Ports[i].DefaultValue; + Marshal.Copy(paramBuffer, 0, IntPtr.Add(data, dataOffsets[i]), 1); + } + } + + public float GetParameter(int index) + { + if (handle == IntPtr.Zero) { return (0); } + if (index < 0 || index >= dataOffsets.Length) { return (0); } + if (LadspaDescriptor.Ports[index].PortType != LadspaPortType.Control) { return (0); } + Marshal.Copy(IntPtr.Add(data, dataOffsets[index]), paramBuffer, 0, 1); + return (paramBuffer[0]); + } + + public void SetParameter(int index, float value) + { + if (handle == IntPtr.Zero) { return; } + if (index < 0 || index >= dataOffsets.Length) { return; } + if (LadspaDescriptor.Ports[index].PortType != LadspaPortType.Control) { return; } + paramBuffer[0] = value; + Marshal.Copy(paramBuffer, 0, IntPtr.Add(data, dataOffsets[index]), 1); + } + + public void SetRunAddingGain(float gain) + { + if (handle == IntPtr.Zero) { return; } + if (LadspaDescriptor.SetRunAddingGain == null) { return; } + LadspaDescriptor.SetRunAddingGain(handle, gain); + } + + public virtual bool Run(float[][] inputs, float[][] outputs) + { + if (handle == IntPtr.Zero) { return (false); } + if (LadspaDescriptor.Run == null) { return (false); } + if (!active) { Activate(); } + CopyInputs(inputs); + LadspaDescriptor.Run(handle, (uint)bufferSize); + CopyOutputs(outputs); + return (true); + } + + public virtual bool RunAdding(float[][] inputs, float[][] outputs) + { + if (handle == IntPtr.Zero) { return (false); } + if (LadspaDescriptor.RunAdding == null) { return (false); } + if (!active) { Activate(); } + CopyInputs(inputs); + LadspaDescriptor.RunAdding(handle, (uint)bufferSize); + CopyOutputs(outputs); + return (true); + } + + public virtual void Dispose() + { + if (handle == IntPtr.Zero) { return; } + Deactivate(); + if (LadspaDescriptor.Cleanup != null) { LadspaDescriptor.Cleanup(handle); } + if (data != IntPtr.Zero) { Marshal.FreeHGlobal(data); } + data = IntPtr.Zero; + handle = IntPtr.Zero; + } + } +} diff --git a/LadspaLibraryContext.cs b/LadspaLibraryContext.cs new file mode 100644 index 0000000..eadeaed --- /dev/null +++ b/LadspaLibraryContext.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + +namespace LADSPA.NET +{ + public class LadspaLibraryContext : IDisposable + { + [DllImport("Kernel32.dll")] + private static extern IntPtr LoadLibrary(string path); + + [DllImport("Kernel32.dll")] + private static extern void FreeLibrary(IntPtr hModule); + + [DllImport("Kernel32.dll")] + private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate IntPtr LadspaDescriptorCallback(uint index); + + private IntPtr library; + + public readonly LadspaDescriptor[] LadspaDescriptors; + public readonly DssiDescriptor[] DssiDescriptors; + + public LadspaLibraryContext(string fileName) + { + library = LoadLibrary(fileName); + if (library == IntPtr.Zero) { throw new Exception("Failed to load library - file not found or not a valid library."); } + try + { + IntPtr func = GetProcAddress(library, "ladspa_descriptor"); + IntPtr dssiFunc = GetProcAddress(library, "dssi_descriptor"); + if (func == IntPtr.Zero && dssiFunc == IntPtr.Zero) + { + throw new Exception("Not a LADSPA plugin: ladspa_descriptor not found."); + } + List descriptors = new List(); + List dssiDescriptors = new List(); + if (func != IntPtr.Zero) + { + LadspaDescriptorCallback callback = (LadspaDescriptorCallback)Marshal.GetDelegateForFunctionPointer(func, typeof(LadspaDescriptorCallback)); + for (uint index = 0; true; index++) + { + IntPtr data = callback(index); + if (data == IntPtr.Zero) { break; } + descriptors.Add(new LadspaDescriptor(this, index, data)); + } + } + if (dssiFunc != IntPtr.Zero) + { + LadspaDescriptorCallback callback = (LadspaDescriptorCallback)Marshal.GetDelegateForFunctionPointer(dssiFunc, typeof(LadspaDescriptorCallback)); + for (uint index = 0; true; index++) + { + IntPtr data = callback(index); + if (data == IntPtr.Zero) { break; } + dssiDescriptors.Add(new DssiDescriptor(this, index, data)); + } + } + LadspaDescriptors = descriptors.ToArray(); + DssiDescriptors = dssiDescriptors.ToArray(); + } + catch + { + FreeLibrary(library); + throw; + } + } + + public void Dispose() + { + if (library == IntPtr.Zero) { return; } + FreeLibrary(library); + library = IntPtr.Zero; + } + } +} diff --git a/LadspaPort.cs b/LadspaPort.cs new file mode 100644 index 0000000..70e7f7d --- /dev/null +++ b/LadspaPort.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LADSPA.NET +{ + public class LadspaPort + { + public readonly LadspaDescriptor Descriptor; + public readonly int Index; + public readonly LadspaPortDescriptorEnum Type; + public readonly LadspaPortRangeHintsEnum Hints; + public readonly string Name; + + public readonly LadspaPortDirection PortDirection; + public readonly LadspaPortType PortType; + public readonly float DefaultValue; + public readonly float MinimumValue; + public readonly float MaximumValue; + public readonly bool IsLogarithmic; + public readonly bool IsInteger; + public readonly bool IsSampleRateMultiple; + public readonly bool IsToggled; + + internal LadspaPort(LadspaDescriptor descriptor, int index, + LadspaPortDescriptorEnum type, LadspaPortRangeHintsEnum hints, string name, + float minBound, float maxBound) + { + Descriptor = descriptor; + Index = index; + Type = type; + Name = name; + Hints = hints; + PortType = type.HasFlag(LadspaPortDescriptorEnum.LADSPA_PORT_CONTROL) ? LadspaPortType.Control : + type.HasFlag(LadspaPortDescriptorEnum.LADSPA_PORT_AUDIO) ? LadspaPortType.Audio : LadspaPortType.Unknown; + PortDirection = type.HasFlag(LadspaPortDescriptorEnum.LADSPA_PORT_INPUT) ? LadspaPortDirection.Input : + type.HasFlag(LadspaPortDescriptorEnum.LADSPA_PORT_OUTPUT) ? LadspaPortDirection.Output : LadspaPortDirection.Unknown; + IsLogarithmic = hints.HasFlag(LadspaPortRangeHintsEnum.LADSPA_HINT_LOGARITHMIC); + IsInteger = hints.HasFlag(LadspaPortRangeHintsEnum.LADSPA_HINT_INTEGER); + IsSampleRateMultiple = hints.HasFlag(LadspaPortRangeHintsEnum.LADSPA_HINT_SAMPLE_RATE); + IsToggled = hints.HasFlag(LadspaPortRangeHintsEnum.LADSPA_HINT_TOGGLED); + if (hints.HasFlag(LadspaPortRangeHintsEnum.LADSPA_HINT_BOUNDED_BELOW)) { MinimumValue = minBound; } else { MinimumValue = 0; } + if (hints.HasFlag(LadspaPortRangeHintsEnum.LADSPA_HINT_BOUNDED_ABOVE)) { MaximumValue = maxBound; } else { MaximumValue = 1; } + switch (hints & LadspaPortRangeHintsEnum.LADSPA_HINT_DEFAULT_MASK) + { + case LadspaPortRangeHintsEnum.LADSPA_HINT_DEFAULT_MINIMUM: DefaultValue = MinimumValue; break; + case LadspaPortRangeHintsEnum.LADSPA_HINT_DEFAULT_LOW: + DefaultValue = IsLogarithmic ? (float)Math.Exp((Math.Log(MinimumValue) * 3 + Math.Log(MaximumValue)) * 0.25) : + (MinimumValue * 3 + MaximumValue) * 0.25f; + break; + case LadspaPortRangeHintsEnum.LADSPA_HINT_DEFAULT_MIDDLE: + DefaultValue = IsLogarithmic ? (float)Math.Exp((Math.Log(MinimumValue) + Math.Log(MaximumValue)) * 0.5) : + (MinimumValue + MaximumValue) * 0.5f; + break; + case LadspaPortRangeHintsEnum.LADSPA_HINT_DEFAULT_HIGH: + DefaultValue = IsLogarithmic ? (float)Math.Exp((Math.Log(MinimumValue) + Math.Log(MaximumValue) * 3) * 0.25) : + (MinimumValue + MaximumValue * 3) * 0.25f; + break; + case LadspaPortRangeHintsEnum.LADSPA_HINT_DEFAULT_MAXIMUM: DefaultValue = MaximumValue; break; + case LadspaPortRangeHintsEnum.LADSPA_HINT_DEFAULT_0: DefaultValue = 0; break; + case LadspaPortRangeHintsEnum.LADSPA_HINT_DEFAULT_1: DefaultValue = 1; break; + case LadspaPortRangeHintsEnum.LADSPA_HINT_DEFAULT_100: DefaultValue = 100; break; + case LadspaPortRangeHintsEnum.LADSPA_HINT_DEFAULT_440: DefaultValue = 440; break; + default: break; + } + } + } + + public enum LadspaPortDirection + { + Unknown, Input, Output + } + + public enum LadspaPortType + { + Unknown, Audio, Control + } +} diff --git a/LadspaPortDescriptorEnum.cs b/LadspaPortDescriptorEnum.cs new file mode 100644 index 0000000..3558d06 --- /dev/null +++ b/LadspaPortDescriptorEnum.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +namespace LADSPA.NET +{ + [Flags] + public enum LadspaPortDescriptorEnum : int + { + LADSPA_PORT_INPUT = 0x1, + LADSPA_PORT_OUTPUT = 0x2, + LADSPA_PORT_CONTROL = 0x4, + LADSPA_PORT_AUDIO = 0x8 + } +} diff --git a/LadspaPortRangeHintsEnum.cs b/LadspaPortRangeHintsEnum.cs new file mode 100644 index 0000000..02dcf30 --- /dev/null +++ b/LadspaPortRangeHintsEnum.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +namespace LADSPA.NET +{ + [Flags] + public enum LadspaPortRangeHintsEnum : int + { + LADSPA_HINT_BOUNDED_BELOW = 0x1, + LADSPA_HINT_BOUNDED_ABOVE = 0x2, + LADSPA_HINT_TOGGLED = 0x4, + LADSPA_HINT_SAMPLE_RATE = 0x8, + LADSPA_HINT_LOGARITHMIC = 0x10, + LADSPA_HINT_INTEGER = 0x20, + LADSPA_HINT_DEFAULT_MASK = 0x3C0, + LADSPA_HINT_DEFAULT_NONE = 0x0, + LADSPA_HINT_DEFAULT_MINIMUM = 0x40, + LADSPA_HINT_DEFAULT_LOW = 0x80, + LADSPA_HINT_DEFAULT_MIDDLE = 0xC0, + LADSPA_HINT_DEFAULT_HIGH = 0x100, + LADSPA_HINT_DEFAULT_MAXIMUM = 0x140, + LADSPA_HINT_DEFAULT_0 = 0x200, + LADSPA_HINT_DEFAULT_1 = 0x240, + LADSPA_HINT_DEFAULT_100 = 0x280, + LADSPA_HINT_DEFAULT_440 = 0x2C0 + } +} diff --git a/LadspaPortRangeHintsStruct.cs b/LadspaPortRangeHintsStruct.cs new file mode 100644 index 0000000..2a0cc0c --- /dev/null +++ b/LadspaPortRangeHintsStruct.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +namespace LADSPA.NET +{ + [StructLayout(LayoutKind.Sequential)] + internal struct LadspaPortRangeHintsStruct + { + public LadspaPortRangeHintsEnum Hints; + public float LowerBound; + public float UpperBound; + } +} diff --git a/LadspaPropertiesEnum.cs b/LadspaPropertiesEnum.cs new file mode 100644 index 0000000..ab1bd90 --- /dev/null +++ b/LadspaPropertiesEnum.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +namespace LADSPA.NET +{ + [Flags] + public enum LadspaPropertiesEnum : int + { + LADSPA_PROPERTY_REALTIME = 0x1, + LADSPA_PROPERTY_INPLACE_BROKEN = 0x2, + LADSPA_PROPERTY_HARD_RT_CAPABLE = 0x4 + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3d1c659 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("LADSPA.NET")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("XTremeGaida")] +[assembly: AssemblyProduct("LADSPA.NET")] +[assembly: AssemblyCopyright("Copyright © XTremeGaida 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("487d3fbc-3722-4b8c-8380-5d98d336104b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/dist/LADSPA.NET.dll b/dist/LADSPA.NET.dll new file mode 100644 index 0000000..f30e8d5 Binary files /dev/null and b/dist/LADSPA.NET.dll differ