diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..7d64673
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "editor-dotnet"]
+ path = editor-dotnet
+ url = git@gitea.azcona-becker.net:universaleditor/editor-dotnet
diff --git a/audio-dotnet/src/Output/Debug/MBS.Audio.MIDI.dll b/audio-dotnet/src/Output/Debug/MBS.Audio.MIDI.dll
new file mode 100644
index 0000000..1c33e2c
Binary files /dev/null and b/audio-dotnet/src/Output/Debug/MBS.Audio.MIDI.dll differ
diff --git a/audio-dotnet/src/Output/Debug/MBS.Audio.MIDI.pdb b/audio-dotnet/src/Output/Debug/MBS.Audio.MIDI.pdb
new file mode 100644
index 0000000..f87b5fb
Binary files /dev/null and b/audio-dotnet/src/Output/Debug/MBS.Audio.MIDI.pdb differ
diff --git a/audio-dotnet/src/audio-dotnet/.vscode/settings.json b/audio-dotnet/src/audio-dotnet/.vscode/settings.json
new file mode 100644
index 0000000..4ce0372
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "dotnet.preferCSharpExtension": true
+}
\ No newline at end of file
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/DeviceOptionalFunctionality.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/DeviceOptionalFunctionality.cs
new file mode 100644
index 0000000..2aefbda
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/DeviceOptionalFunctionality.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio.MIDI
+{
+ [Flags()]
+ public enum DeviceOptionalFunctionality : uint
+ {
+ None = 0x0,
+ ///
+ /// Supports volume control.
+ ///
+ Volume = 0x1,
+ ///
+ /// Supports separate left and right volume control.
+ ///
+ StereoVolume = 0x2,
+ ///
+ /// Supports patch caching.
+ ///
+ PatchCaching = 0x4,
+ ///
+ /// Provides direct support for the midiStreamOut function.
+ ///
+ Streaming = 0x8
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/DeviceType.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/DeviceType.cs
new file mode 100644
index 0000000..da48117
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/DeviceType.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio.MIDI
+{
+ public enum DeviceType : ushort
+ {
+ None = 0,
+ ///
+ /// MIDI hardware port.
+ ///
+ HardwarePort = 1,
+ ///
+ /// Synthesizer.
+ ///
+ Synthesizer = 2,
+ ///
+ /// Square wave synthesizer.
+ ///
+ SquareWaveSynthesizer = 3,
+ ///
+ /// FM synthesizer.
+ ///
+ FMSynthesizer = 4,
+ ///
+ /// Microsoft MIDI mapper.
+ ///
+ MicrosoftMIDIMapper = 5,
+ ///
+ /// Hardware wavetable synthesizer.
+ ///
+ HardwareWavetable = 6,
+ ///
+ /// Software synthesizer.
+ ///
+ Software = 7
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Linux/Alsa/Constants.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Linux/Alsa/Constants.cs
new file mode 100644
index 0000000..977b8be
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Linux/Alsa/Constants.cs
@@ -0,0 +1,54 @@
+//
+// Constants.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2013 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+
+namespace MBS.Audio.MIDI.Internal.Linux.Alsa
+{
+ internal static class Constants
+ {
+ public enum snd_rawmidi_stream
+ {
+ Output = 0,
+ Input = 1
+ }
+
+ [Flags()]
+ public enum SoundOpenFlags
+ {
+ ///
+ /// No flags specified
+ ///
+ None = 0x0000,
+ ///
+ /// Non-blocking mode
+ ///
+ NonBlocking = 0x0001,
+ ///
+ /// Async notification
+ ///
+ Async = 0x0002,
+ ///
+ /// Read-only mode
+ ///
+ ReadOnly = 0x0004
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Linux/Alsa/Methods.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Linux/Alsa/Methods.cs
new file mode 100644
index 0000000..540a9cd
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Linux/Alsa/Methods.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Text;
+using System.Runtime.InteropServices;
+
+namespace MBS.Audio.MIDI.Internal.Linux.Alsa
+{
+ internal static class Methods
+ {
+ private const string LIBRARY_FILENAME = "libasound.so.2";
+
+ ///
+ /// Retrieves the ID of the sound card directly after the sound card with ID cardNum.
+ ///
+ /// The ID of the sound card at which to begin enumeration. This can be -1 to retrieve the first sound card.
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int snd_card_next(ref int cardNum);
+
+ ///
+ /// Opens the card located at the specified device path and returns the handle in .
+ ///
+ /// Returns a handle to the sound card associated with the device at the specified path.
+ /// The path to the device to open.
+ ///
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int snd_ctl_open(ref IntPtr cardHandle, string devicePath, Constants.SoundOpenFlags flags);
+
+ ///
+ /// Closes the sound card referenced by the specified handle.
+ ///
+ /// The handle of the sound card to close.
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern void snd_ctl_close(IntPtr cardHandle);
+
+ ///
+ /// Retrieves a friendly description for the specified error code.
+ ///
+ /// The error code for which to return a description.
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern string snd_strerror(int error);
+
+ ///
+ /// Get the number of the next MIDI device on the specified card.
+ ///
+ /// Handle to the sound card on which to fetch the next MIDI device.
+ /// Receives the ID of the next MIDI device on the specified card.
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int snd_ctl_rawmidi_next_device(IntPtr cardHandle, ref int devNum);
+
+ ///
+ /// Opens the MIDI device at the specified device path.
+ ///
+ /// Unknown.
+ /// Receives a handle to the MIDI device at the specified device path.
+ /// The path of the MIDI device to open; i.e. "hw:cardnum,devnum,subdevnum"
+ ///
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int snd_rawmidi_open(ref IntPtr inputHandle, ref IntPtr outputHandle, string devicePath, int flags);
+
+ ///
+ /// Writes the specified buffer to the MIDI device with the specified handle.
+ ///
+ /// The handle to the MIDI device on which to write.
+ /// The data to write to the MIDI device.
+ /// The length of the buffer.
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int snd_rawmidi_write(IntPtr handle, byte[] buffer, int bufferLength);
+ ///
+ /// Read the specified buffer from the MIDI device with the specified handle.
+ ///
+ /// The handle to the MIDI device on which to read.
+ /// The data to write to the MIDI device.
+ /// The length of the buffer.
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int snd_rawmidi_read(IntPtr handle, byte[] buffer, int bufferLength);
+
+ ///
+ /// Closes the MIDI device referenced by the specified handle.
+ ///
+ /// The handle of the MIDI device to close.
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int snd_rawmidi_close(IntPtr handle);
+
+ #region RawMidi Info
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int snd_rawmidi_info_malloc(ref IntPtr hInfo);
+
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern void snd_rawmidi_info_free(IntPtr hInfo);
+
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int snd_rawmidi_info(IntPtr handle, IntPtr hInfo);
+
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int snd_ctl_rawmidi_info(IntPtr handle, IntPtr hInfo);
+
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern string snd_rawmidi_info_get_name(ref IntPtr hInfo);
+
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern void snd_rawmidi_info_set_device(IntPtr hInfo, uint deviceID);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern void snd_rawmidi_info_set_subdevice(IntPtr hInfo, int subDeviceID);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern void snd_rawmidi_info_set_stream(IntPtr hInfo, Constants.snd_rawmidi_stream stream);
+ #endregion
+
+ public static void snd_error_code_to_exception(int error)
+ {
+ if (error < 0)
+ {
+ throw new Exception(snd_strerror(error));
+ }
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Constants.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Constants.cs
new file mode 100644
index 0000000..f1f925f
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Constants.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio.MIDI.Internal.Windows
+{
+ internal static class Constants
+ {
+ // from:
+ // https://github.com/downpoured/downpoured_midi_audio/blob/master/benmidi/trilled-midisketch/CSharpMidiToolkitV4_demo/Multimedia.Midi/Device%20Classes/DeviceException.cs
+
+ public enum MidiError
+ {
+ None = 0,
+ ///
+ /// Unspecified error.
+ ///
+ Unspecified = 1,
+ ///
+ /// The specified device identifier is out of range.
+ ///
+ BadDeviceID = 2,
+ ///
+ /// Driver failed to enable.
+ ///
+ NotEnabled = 3,
+ ///
+ /// The specified resource is already allocated.
+ ///
+ AlreadyAllocated = 4,
+ ///
+ /// The device handle is invalid.
+ ///
+ InvalidHandle = 5,
+ ///
+ /// No device driver is present.
+ ///
+ NoDriver = 6,
+ ///
+ /// The system is unable to allocate or lock memory.
+ ///
+ MemoryError = 7,
+ Unsupported = 8,
+ BadErrorNumber = 9,
+ InvalidFlag = 10,
+ ///
+ /// The specified pointer or structure is invalid.
+ ///
+ InvalidParameter = 11,
+ HandleBusy = 12,
+ ///
+ /// No MIDI port was found. This error occurs only when the mapper is opened.
+ ///
+ NoDevice = 68
+ }
+ ///
+ /// Callback flag for opening the device.
+ ///
+ public enum MidiOpenFlags
+ {
+ ///
+ /// There is no callback mechanism. This value is the default setting.
+ ///
+ None = 0x00000000,
+ ///
+ /// The dwCallback parameter is a window handle.
+ ///
+ Window = 0x00010000,
+ ///
+ /// The dwCallback parameter is a thread identifier.
+ ///
+ Thread = 0x00020000,
+ ///
+ /// The dwCallback parameter is a callback function address.
+ ///
+ Function = 0x00030000,
+ ///
+ /// The dwCallback parameter is an event handle. This callback mechanism is for output
+ /// only.
+ ///
+ Event = 0x00050000
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Delegates.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Delegates.cs
new file mode 100644
index 0000000..8711d14
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Delegates.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio.MIDI.Internal.Windows
+{
+ internal static class Delegates
+ {
+ ///
+ /// The MidiCallback function is the callback function for handling incoming and outgoing MIDI
+ /// messages. MidiCallback is a placeholder for the application-supplied function name. The
+ /// address of the function can be specified in the callback-address parameter of the
+ /// midiOutOpen/midiInOpen functions.
+ ///
+ /// Handle to the MIDI device associated with the callback function.
+ /// MIDI input/output message.
+ /// Instance data supplied by using the midiInOpen/midiOutOpen function.
+ /// Message parameter.
+ /// Message parameter.
+ ///
+ /// Applications should not call any multimedia functions from inside the callback function, as
+ /// doing so can cause a deadlock. Other system functions can safely be called from the
+ /// callback.
+ ///
+ public delegate void MidiCallback(IntPtr hmo, uint wMsg, IntPtr dwInstance, uint dwMidiMessage, uint dwTimestamp);
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Methods.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Methods.cs
new file mode 100644
index 0000000..521d810
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Methods.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace MBS.Audio.MIDI.Internal.Windows
+{
+ internal static class Methods
+ {
+ public const string LIBRARY_FILENAME = "winmm.dll";
+
+ #region Input
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern uint midiInGetNumDevs();
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.MidiError midiInOpen(out IntPtr lphMidiIn, uint uDeviceID, Delegates.MidiCallback dwCallback, IntPtr dwCallbackInstance, Constants.MidiOpenFlags dwFlags);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.MidiError midiInGetDevCaps(uint uDeviceID, out Structures.MIDIINCAPS lpMidiInCaps, uint cbMidiInCaps);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.MidiError midiInReset(IntPtr hmo);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.MidiError midiInStart(IntPtr hMidiIn);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.MidiError midiInStop(IntPtr hMidiIn);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.MidiError midiInClose(IntPtr hmo);
+ #endregion
+
+ #region Output
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern uint midiOutGetNumDevs();
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.MidiError midiOutOpen(out IntPtr lphmo, uint uDeviceID, Delegates.MidiCallback dwCallback, IntPtr dwCallbackInstance, Constants.MidiOpenFlags dwFlags);
+ ///
+ /// Queries a specified MIDI output device to determine its capabilities.
+ ///
+ ///
+ /// Identifier of the MIDI output device. The device identifier specified by this parameter
+ /// varies from zero to one less than the number of devices present. The MIDI_MAPPER constant
+ /// is also a valid device identifier. This parameter can also be a properly cast device
+ /// handle.
+ ///
+ ///
+ /// Pointer to a structure. This structure is filled with
+ /// information about the capabilities of the device.
+ ///
+ ///
+ /// Size, in bytes, of the structure. Only cbMidiOutCaps bytes (or
+ /// less) of information is copied to the location pointed to by lpMidiOutCaps. If
+ /// cbMidiOutCaps is zero, nothing is copied, and the function returns MMSYSERR_NOERROR.
+ ///
+ ///
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.MidiError midiOutGetDevCaps(uint uDeviceID, out Structures.MIDIOUTCAPS lpMidiOutCaps, uint cbMidiOutCaps);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.MidiError midiOutReset(IntPtr hmo);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.MidiError midiOutClose(IntPtr hmo);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.MidiError midiOutShortMsg(uint mvarID, int dwMsg);
+ #endregion
+
+ [System.Diagnostics.DebuggerNonUserCode()]
+ internal static void midiErrorToException(Constants.MidiError error)
+ {
+ switch (error)
+ {
+ case Constants.MidiError.InvalidHandle:
+ {
+ throw new ArgumentException("Invalid handle");
+ }
+ case Constants.MidiError.InvalidParameter:
+ {
+ throw new ArgumentException();
+ }
+ case Constants.MidiError.BadDeviceID:
+ {
+ throw new ArgumentException("Bad device ID");
+ }
+ case Constants.MidiError.Unsupported:
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Structures.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Structures.cs
new file mode 100644
index 0000000..c691ed5
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Structures.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace MBS.Audio.MIDI.Internal.Windows
+{
+ internal static class Structures
+ {
+ [StructLayout(LayoutKind.Sequential)]
+ public struct MIDIOUTCAPS
+ {
+ ///
+ /// Manufacturer identifier of the device driver for the MIDI output device.
+ ///
+ public ushort wMid;
+ ///
+ /// Product identifier of the MIDI output device.
+ ///
+ public ushort wPid;
+
+ #region MMVERSION
+ ///
+ /// Major version number of the device driver for the MIDI output device.
+ ///
+ public byte vDriverVersionMinor;
+ ///
+ /// Minor version number of the device driver for the MIDI output device.
+ ///
+ public byte vDriverVersionMajor;
+
+ // extra two bytes in Windows 7 MMVERSION ???
+ public byte vDriverVersionBuild;
+ public byte vDriverVersionRevision;
+ #endregion
+
+ ///
+ /// Product name in a null-terminated string.
+ ///
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
+ public string szPname;
+ ///
+ /// Type of the MIDI output device.
+ ///
+ public DeviceType wTechnology;
+ ///
+ /// Number of voices supported by an internal synthesizer device. If the device is a port,
+ /// this member is not meaningful and is set to 0.
+ ///
+ public ushort wVoices;
+ ///
+ /// Maximum number of simultaneous notes that can be played by an internal synthesizer
+ /// device. If the device is a port, this member is not meaningful and is set to 0.
+ ///
+ public ushort wNotes;
+ ///
+ /// Channels that an internal synthesizer device responds to, where the least significant
+ /// bit refers to channel 0 and the most significant bit to channel 15. Port devices that
+ /// transmit on all channels set this member to 0xFFFF.
+ ///
+ public ushort wChannelMask;
+ ///
+ /// Optional functionality supported by the device.
+ ///
+ public DeviceOptionalFunctionality dwSupport;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct MIDIINCAPS
+ {
+ public ushort wMid;
+ public ushort wPid;
+
+ #region MMVERSION
+ ///
+ /// Major version number of the device driver for the MIDI input device.
+ ///
+ public byte vDriverVersionMinor;
+ ///
+ /// Minor version number of the device driver for the MIDI input device.
+ ///
+ public byte vDriverVersionMajor;
+
+ // extra two bytes in Windows 7 MMVERSION ???
+ public byte vDriverVersionBuild;
+ public byte vDriverVersionRevision;
+ #endregion
+
+ ///
+ /// Product name in a null-terminated string.
+ ///
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
+ public string szPname;
+
+ ///
+ /// Optional functionality supported by the device.
+ ///
+ public DeviceOptionalFunctionality dwSupport;
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Listener.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Listener.cs
new file mode 100644
index 0000000..1aedff9
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Listener.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio.MIDI
+{
+ public class Listener
+ {
+ public MidiDevice InputDevice { get; private set; } = null;
+
+ public SoundCard SoundCard { get; set; } = null;
+
+ private static MessageReceivedEventHandler eventHandler = null;
+
+ public void Start()
+ {
+ if (SoundCard == null)
+ {
+ SoundCard = SoundCard.GetDefaultSoundCard();
+ }
+ if (SoundCard != null)
+ {
+ SoundCard.Open();
+ if (InputDevice == null)
+ {
+ InputDevice = SoundCard.GetDefaultMidiInputDevice();
+ }
+ // output = sc.GetMidiOutputDevices()[1];
+ }
+
+ if (InputDevice != null)
+ {
+ InputDevice.Open();
+ InputDevice.MessageReceived += Listener_MessageReceived;
+ InputDevice.Start();
+ }
+ }
+ public void Stop()
+ {
+ if (InputDevice != null)
+ {
+ InputDevice.Stop();
+ InputDevice.Close();
+ }
+ SoundCard.Close();
+ }
+
+ public event MessageReceivedEventHandler MessageReceived;
+ protected virtual void OnMessageReceived(MessageReceivedEventArgs e)
+ {
+ MessageReceived?.Invoke(this, e);
+ }
+
+ private void Listener_MessageReceived(object sender, MBS.Audio.MIDI.MessageReceivedEventArgs e)
+ {
+ OnMessageReceived(e);
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MBS.Audio.MIDI.csproj b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MBS.Audio.MIDI.csproj
new file mode 100644
index 0000000..b892e83
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MBS.Audio.MIDI.csproj
@@ -0,0 +1,65 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {DDC1CE36-60E0-4B09-A288-CB14ACE252DD}
+ Library
+ Properties
+ MBS.Audio.MIDI
+ MBS.Audio.MIDI
+ v3.5
+ 512
+ 10.0.0
+ 2.0
+
+
+ True
+ full
+ False
+ ..\..\Output\Debug
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ True
+ ..\..\Output\Release
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Message.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Message.cs
new file mode 100644
index 0000000..1bce419
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Message.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio.MIDI
+{
+ [System.Diagnostics.DebuggerNonUserCode()]
+ public class Message
+ {
+ private MessageType mvarMessageType = MessageType.ControlChange;
+ public MessageType MessageType { get { return mvarMessageType; } set { mvarMessageType = value; } }
+
+ private byte mvarChannel = 0;
+ public byte Channel
+ {
+ get { return mvarChannel; }
+ set
+ {
+ if (value < 1 || value > 16)
+ {
+ throw new ArgumentOutOfRangeException("Channel", "value for channel must be between 1 and 16, inclusive");
+ }
+ mvarChannel = value;
+ }
+ }
+
+ private byte mvarParameter1 = 0;
+ public byte Parameter1
+ {
+ get { return mvarParameter1; }
+ set
+ {
+ if (value < 0 || value > 127)
+ {
+ throw new ArgumentOutOfRangeException("Parameter1", "value for parameter 1 must be between 0 and 127, inclusive");
+ }
+ mvarParameter1 = value;
+ }
+ }
+
+ private byte mvarParameter2 = 0;
+ public byte Parameter2
+ {
+ get { return mvarParameter2; }
+ set
+ {
+ if (value < 0 || value > 127)
+ {
+ throw new ArgumentOutOfRangeException("Parameter2", "value for parameter 2 must be between 0 and 127, inclusive");
+ }
+ mvarParameter2 = value;
+ }
+ }
+
+ public Message(MessageType messageType, byte channel, byte parameter1, byte parameter2)
+ {
+ mvarMessageType = messageType;
+ Channel = channel;
+ Parameter1 = parameter1;
+ Parameter2 = parameter2;
+ }
+
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MessageReceivedEvent.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MessageReceivedEvent.cs
new file mode 100644
index 0000000..ee773a6
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MessageReceivedEvent.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio.MIDI
+{
+ public delegate void MessageReceivedEventHandler(object sender, MessageReceivedEventArgs e);
+ public class MessageReceivedEventArgs : EventArgs
+ {
+
+ private Message mvarMessage = null;
+ public Message Message { get { return mvarMessage; } }
+
+ private IntPtr mvarInstanceData = IntPtr.Zero;
+ public IntPtr InstanceData { get { return mvarInstanceData; } }
+
+ public MessageReceivedEventArgs(Message message, IntPtr instanceData)
+ {
+ mvarMessage = message;
+ mvarInstanceData = instanceData;
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MessageType.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MessageType.cs
new file mode 100644
index 0000000..82832e1
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MessageType.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio.MIDI
+{
+ public enum MessageType : byte
+ {
+ NoteOff = 0x80,
+ NoteOn = 0x90,
+ Aftertouch = 0xA0,
+ ControlChange = 0xB0,
+ PatchChange = 0xC0,
+ ChannelPressure = 0xD0,
+ PitchBend = 0xE0,
+
+ ///
+ /// start of system exclusive message
+ ///
+ SysexStart = 0xF0,
+ SysexTimecode = 0xF1,
+ SysexSongPosition = 0xF2,
+ SysexSongSelect = 0xF3,
+ SysexF4 = 0xF4,
+ SysexF5 = 0xF5,
+ SysexTuneRequest = 0xF6,
+ SysexEnd = 0xF7,
+ SysexRealtimeClock = 0xF8,
+ SysexF9 = 0xF9,
+ SysexRealtimeStart = 0xFA,
+ SysexRealtimeContinue = 0xFB,
+ SysexRealtimeStop = 0xFC,
+ SysexFD = 0xFD,
+ SysexActiveSensing = 0xFE,
+ SysexReset = 0xFF
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MidiDevice.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MidiDevice.cs
new file mode 100644
index 0000000..fc09d5a
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MidiDevice.cs
@@ -0,0 +1,436 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio.MIDI
+{
+#if !DEBUG
+ [System.Diagnostics.DebuggerNonUserCode()]
+#endif
+ public class MidiDevice
+ {
+ protected uint mvarID = 0;
+ public uint ID { get { return mvarID; } }
+
+ protected IntPtr mvarInputHandle = IntPtr.Zero;
+ protected IntPtr mvarOutputHandle = IntPtr.Zero;
+ public IntPtr Handle { get { return mvarInputHandle; } }
+
+ public SoundCard Parent { get; private set; }
+
+ internal MidiDevice(uint id, SoundCard parent)
+ {
+ mvarID = id;
+ Parent = parent;
+ try
+ {
+ Refresh();
+ }
+ catch
+ {
+ }
+ }
+
+ public void Open(MidiDeviceFunctionality functionality = MidiDeviceFunctionality.Any)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ mvarInputHandle = IntPtr.Zero;
+ mvarOutputHandle = IntPtr.Zero;
+ IntPtr dummy = IntPtr.Zero;
+ string str = "hw:" + Parent.ID.ToString() + "," + mvarID.ToString() + ",0";
+ int error = Internal.Linux.Alsa.Methods.snd_rawmidi_open(ref mvarInputHandle, ref mvarOutputHandle, str, 0);
+ Internal.Linux.Alsa.Methods.snd_error_code_to_exception(error);
+
+ Refresh();
+ return;
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ {
+ mvarInputHandle = IntPtr.Zero;
+
+ mvarCallback = new Internal.Windows.Delegates.MidiCallback(MidiCallback);
+ Internal.Windows.Constants.MidiError error = Internal.Windows.Methods.midiInOpen(out mvarInputHandle, mvarID, mvarCallback, IntPtr.Zero, Internal.Windows.Constants.MidiOpenFlags.Function);
+ Internal.Windows.Methods.midiErrorToException(error);
+
+ byHandle[mvarInputHandle] = this;
+
+ error = Internal.Windows.Methods.midiOutOpen(out mvarOutputHandle, mvarID, new Internal.Windows.Delegates.MidiCallback(MidiCallback), IntPtr.Zero, Internal.Windows.Constants.MidiOpenFlags.Function);
+ Internal.Windows.Methods.midiErrorToException(error);
+ return;
+ }
+ case PlatformID.Xbox:
+ {
+ break;
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+
+ public bool AutoFlush { get; set; } = true;
+
+ ///
+ /// Updates the MIDI device information.
+ ///
+ public void Refresh()
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ if (mvarInputHandle == IntPtr.Zero) return;
+
+ IntPtr hInfo = IntPtr.Zero;
+ Internal.Linux.Alsa.Methods.snd_rawmidi_info_malloc(ref hInfo);
+ Internal.Linux.Alsa.Methods.snd_rawmidi_info_set_device(hInfo, mvarID);
+ Internal.Linux.Alsa.Methods.snd_rawmidi_info_set_stream(hInfo, Internal.Linux.Alsa.Constants.snd_rawmidi_stream.Output);
+ Internal.Linux.Alsa.Methods.snd_rawmidi_info_set_subdevice(hInfo, -1);
+
+ Internal.Linux.Alsa.Methods.snd_rawmidi_info(mvarInputHandle, hInfo);
+ // mvarName = Internal.Linux.Alsa.Methods.snd_rawmidi_info_get_name(ref hInfo);
+ Internal.Linux.Alsa.Methods.snd_rawmidi_info_free(hInfo);
+ return;
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ {
+ // output
+ Internal.Windows.Structures.MIDIOUTCAPS caps = new Internal.Windows.Structures.MIDIOUTCAPS();
+ Internal.Windows.Methods.midiOutGetDevCaps(mvarID, out caps, (uint)System.Runtime.InteropServices.Marshal.SizeOf(caps));
+
+ mvarManufacturerID = caps.wMid;
+ mvarProductID = caps.wPid;
+ mvarName = caps.szPname;
+ mvarDeviceType = caps.wTechnology;
+ mvarMaximumVoices = caps.wVoices;
+ mvarMaximumNotes = caps.wNotes;
+ mvarDriverVersion = new Version(caps.vDriverVersionMajor, caps.vDriverVersionMinor);
+ mvarSupportedChannels = caps.wChannelMask;
+ mvarSupportedFunctionality = caps.dwSupport;
+
+ // input
+ Internal.Windows.Structures.MIDIINCAPS capsIn = new Internal.Windows.Structures.MIDIINCAPS();
+ Internal.Windows.Methods.midiInGetDevCaps(mvarID, out capsIn, (uint)System.Runtime.InteropServices.Marshal.SizeOf(capsIn));
+
+ mvarManufacturerID = capsIn.wMid;
+ mvarProductID = capsIn.wPid;
+ mvarName = capsIn.szPname;
+ mvarDriverVersion = new Version(capsIn.vDriverVersionMajor, capsIn.vDriverVersionMinor);
+ mvarSupportedFunctionality = capsIn.dwSupport;
+ return;
+ }
+ case PlatformID.Xbox:
+ {
+ break;
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ ///
+ /// Turns off all notes on all MIDI channels for this MIDI output device.
+ ///
+ public void Reset()
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ break;
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ {
+ if (mvarInputHandle != IntPtr.Zero)
+ {
+ Internal.Windows.Constants.MidiError error = Internal.Windows.Methods.midiInReset(mvarInputHandle);
+ Internal.Windows.Methods.midiErrorToException(error);
+ }
+ if (mvarOutputHandle != IntPtr.Zero)
+ {
+ Internal.Windows.Constants.MidiError error = Internal.Windows.Methods.midiInReset(mvarOutputHandle);
+ Internal.Windows.Methods.midiErrorToException(error);
+ }
+ return;
+ }
+ case PlatformID.Xbox:
+ {
+ break;
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public int Flush()
+ {
+ // get the bytes currently in the stream
+ byte[] buffer = stream.ToArray();
+
+ // clear out the memory stream
+ stream = new System.IO.MemoryStream();
+
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ int error = Internal.Linux.Alsa.Methods.snd_rawmidi_write(mvarOutputHandle, buffer, buffer.Length);
+ Internal.Linux.Alsa.Methods.snd_error_code_to_exception(error);
+ return error;
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ {
+ System.IO.BinaryReader br = new System.IO.BinaryReader(new System.IO.MemoryStream(buffer));
+ while (br.BaseStream.Position < br.BaseStream.Length)
+ {
+ byte msgtype = br.ReadByte();
+ byte param1 = 0;
+ byte param2 = 0;
+ if (br.BaseStream.Position < br.BaseStream.Length)
+ {
+ param1 = br.ReadByte();
+ if (br.BaseStream.Position < br.BaseStream.Length)
+ {
+ param2 = br.ReadByte();
+ }
+ }
+
+ int dwMsg = BitConverter.ToInt32(new byte[] { msgtype, param1, param2, 0 }, 0);
+
+ Internal.Windows.Constants.MidiError error = Internal.Windows.Methods.midiOutShortMsg((uint)mvarOutputHandle.ToInt32(), dwMsg);
+ Internal.Windows.Methods.midiErrorToException(error);
+ }
+ br.Close();
+ buffer = new byte[0];
+ return 0;
+ }
+ case PlatformID.Xbox:
+ {
+ break;
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ ///
+ /// Closes this MIDI device.
+ ///
+ public void Close()
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ if (mvarInputHandle != IntPtr.Zero)
+ {
+ int error = Internal.Linux.Alsa.Methods.snd_rawmidi_close(mvarInputHandle);
+ Internal.Linux.Alsa.Methods.snd_error_code_to_exception(error);
+ mvarInputHandle = IntPtr.Zero;
+ }
+ if (mvarOutputHandle != IntPtr.Zero)
+ {
+ int error = Internal.Linux.Alsa.Methods.snd_rawmidi_close(mvarOutputHandle);
+ Internal.Linux.Alsa.Methods.snd_error_code_to_exception(error);
+ mvarOutputHandle = IntPtr.Zero;
+ }
+ return;
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ {
+ try
+ {
+ if (mvarInputHandle != IntPtr.Zero)
+ {
+ Internal.Windows.Methods.midiInClose(mvarInputHandle);
+ }
+ if (mvarOutputHandle != IntPtr.Zero)
+ {
+ Internal.Windows.Methods.midiOutClose(mvarOutputHandle);
+ }
+ }
+ catch
+ {
+ }
+ if (byHandle.ContainsKey(mvarInputHandle))
+ {
+ byHandle.Remove(mvarInputHandle);
+ }
+ if (byHandle.ContainsKey(mvarOutputHandle))
+ {
+ byHandle.Remove(mvarOutputHandle);
+ }
+
+ mvarInputHandle = IntPtr.Zero;
+ mvarOutputHandle = IntPtr.Zero;
+ return;
+ }
+ case PlatformID.Xbox:
+ {
+ break;
+ }
+ }
+ throw new InvalidOperationException();
+ }
+
+
+ private System.IO.MemoryStream stream = new System.IO.MemoryStream();
+ public int Write(byte[] buffer)
+ {
+ return Write(buffer, 0, buffer.Length);
+ }
+ public int Write(byte[] buffer, int start, int length)
+ {
+ stream.Write(buffer, start, length);
+ if (AutoFlush) return Flush();
+ return length;
+ }
+
+ protected string mvarName = String.Empty;
+ public string Name { get { return mvarName; } }
+
+ public event MessageReceivedEventHandler MessageReceived;
+ protected virtual void OnMessageReceived(MessageReceivedEventArgs e)
+ {
+ MessageReceived?.Invoke(this, e);
+ }
+
+ private static Dictionary byHandle = new Dictionary();
+
+ private static void MidiCallback(IntPtr hmo, uint wMsg, IntPtr dwInstance, uint dwMidiMessage, uint dwTimestamp)
+ {
+ MidiDevice device = null;
+ if (byHandle.ContainsKey(hmo)) device = byHandle[hmo];
+ if (device == null) return;
+
+ byte[] msgdata = BitConverter.GetBytes(dwMidiMessage);
+ MessageType type = (MessageType)(msgdata[0] & 0xF0);
+ byte channel = (byte)((msgdata[0] & 0x0F) + 1);
+
+ byte parameter1 = (byte)msgdata[1];
+ byte parameter2 = (byte)msgdata[2];
+
+ device.OnMessageReceived(new MessageReceivedEventArgs(new Message(type, channel, parameter1, parameter2), dwInstance));
+ }
+
+ public void Send(params Message[] messages)
+ {
+ foreach (Message message in messages)
+ {
+ byte status = (byte)((byte)message.MessageType | (message.Channel - 1));
+
+ System.IO.MemoryStream ms = new System.IO.MemoryStream();
+ System.IO.BinaryWriter bw = new System.IO.BinaryWriter(ms);
+ byte[] payload = new byte[] { status, message.Parameter1, message.Parameter2 }; // 93, 90 };
+ bw.Write(payload);
+ bw.Close();
+
+ Write(ms.ToArray());
+ }
+ }
+
+ private Internal.Windows.Delegates.MidiCallback mvarCallback = null;
+
+ private System.Threading.Thread tLinuxThread = null;
+ private void tLinuxThread_ThreadStart()
+ {
+ while (true)
+ {
+ byte[] buffer = new byte[16];
+ Internal.Linux.Alsa.Methods.snd_rawmidi_read(mvarInputHandle, buffer, buffer.Length);
+
+ byte channelId = (byte)(((byte)(buffer[0] << 4)) >> 4);
+ byte messageTypeId = (byte)((byte)(buffer[0] >> 4) << 4);
+ OnMessageReceived(new MessageReceivedEventArgs(new Message((MessageType)messageTypeId, (byte)(channelId + 1), buffer[1], buffer[2]), IntPtr.Zero));
+
+ System.Threading.Thread.Sleep(10);
+ }
+ }
+
+
+ ///
+ /// Starts listening for MIDI input.
+ ///
+ public void Start()
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ tLinuxThread = new System.Threading.Thread(tLinuxThread_ThreadStart);
+ tLinuxThread.Start();
+ break;
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ {
+ Internal.Windows.Constants.MidiError error = Internal.Windows.Methods.midiInStart(mvarInputHandle);
+ Internal.Windows.Methods.midiErrorToException(error);
+ return;
+ }
+ }
+ }
+
+ ///
+ /// Stops listening for MIDI input.
+ ///
+ public void Stop()
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ tLinuxThread.Abort();
+ tLinuxThread = null;
+ break;
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ {
+ Internal.Windows.Constants.MidiError error = Internal.Windows.Methods.midiInStop(mvarInputHandle);
+ Internal.Windows.Methods.midiErrorToException(error);
+ return;
+ }
+ }
+ }
+
+ protected uint mvarManufacturerID = 0;
+ public uint ManufacturerID { get { return mvarManufacturerID; } }
+ protected uint mvarProductID = 0;
+ public uint ProductID { get { return mvarProductID; } }
+ protected ushort mvarMaximumVoices = 0;
+ public ushort MaximumVoices { get { return mvarMaximumVoices; } }
+ protected ushort mvarMaximumNotes = 0;
+ public ushort MaximumNotes { get { return mvarMaximumNotes; } }
+ protected ushort mvarSupportedChannels = 0;
+ public ushort SupportedChannels { get { return mvarSupportedChannels; } }
+ protected Version mvarDriverVersion = new Version(1, 0);
+ public Version DriverVersion { get { return mvarDriverVersion; } }
+ protected DeviceType mvarDeviceType = DeviceType.None;
+ public DeviceType DeviceType { get { return mvarDeviceType; } }
+ protected DeviceOptionalFunctionality mvarSupportedFunctionality = DeviceOptionalFunctionality.None;
+ public DeviceOptionalFunctionality SupportedFunctionality { get { return mvarSupportedFunctionality; } }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MidiDeviceFunctionality.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MidiDeviceFunctionality.cs
new file mode 100644
index 0000000..4ae019b
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MidiDeviceFunctionality.cs
@@ -0,0 +1,32 @@
+//
+// MidiDeviceFunctionality.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2021 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+namespace MBS.Audio.MIDI
+{
+ [Flags()]
+ public enum MidiDeviceFunctionality
+ {
+ None = 0,
+ Input = 1,
+ Output = 2,
+ Any = Input | Output
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Properties/AssemblyInfo.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..96de4e7
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/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("MonoMidi")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("City of Orlando")]
+[assembly: AssemblyProduct("MonoMidi")]
+[assembly: AssemblyCopyright("Copyright © City of Orlando 2013")]
+[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("c04f487c-f580-48e6-b81a-5b550bbd213b")]
+
+// 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/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/SoundCard.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/SoundCard.cs
new file mode 100644
index 0000000..9c1a78d
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/SoundCard.cs
@@ -0,0 +1,257 @@
+using System;
+using System.Collections.Generic;
+
+namespace MBS.Audio.MIDI
+{
+ public class SoundCard
+ {
+ public static SoundCard GetDefaultSoundCard()
+ {
+ return GetSoundCardByID(0);
+ }
+ public static SoundCard GetSoundCardByID(int id)
+ {
+ SoundCard[] cards = GetAllSoundCards();
+ if (id >= 0 && id < cards.Length)
+ {
+ return cards[id];
+ }
+ return null;
+ }
+ public static SoundCard[] GetAllSoundCards()
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ int cardNum = -1;
+ List cards = new List();
+ while (true)
+ {
+ int error = Internal.Linux.Alsa.Methods.snd_card_next(ref cardNum);
+ Internal.Linux.Alsa.Methods.snd_error_code_to_exception(error);
+ if (cardNum == -1)
+ {
+ break;
+ }
+ cards.Add(SoundCard.FromID(cardNum));
+ }
+ return cards.ToArray();
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ {
+ List cards = new List();
+ SoundCard card = new SoundCard(0);
+ cards.Add(card);
+ return cards.ToArray();
+ }
+ case PlatformID.Xbox:
+ {
+ break;
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+
+ private int mvarID = 0;
+ public int ID { get { return mvarID; } }
+
+ private SoundCard(int id)
+ {
+ mvarID = id;
+ }
+
+ public static SoundCard FromID(int id)
+ {
+ return new SoundCard(id);
+ }
+
+ private IntPtr mvarHandle = IntPtr.Zero;
+ public IntPtr Handle { get { return mvarHandle; } }
+
+ public void Open()
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ mvarHandle = IntPtr.Zero;
+ int error = Internal.Linux.Alsa.Methods.snd_ctl_open(ref mvarHandle, "hw:" + mvarID.ToString(), Internal.Linux.Alsa.Constants.SoundOpenFlags.None);
+ Internal.Linux.Alsa.Methods.snd_error_code_to_exception(error);
+ return;
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ {
+ return;
+ }
+ case PlatformID.Xbox:
+ {
+ break;
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public void Close()
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ Internal.Linux.Alsa.Methods.snd_ctl_close(mvarHandle);
+ mvarHandle = IntPtr.Zero;
+ return;
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ {
+ return;
+ }
+ case PlatformID.Xbox:
+ {
+ break;
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+
+ public MidiDevice GetDefaultMidiInputDevice()
+ {
+ MidiDevice[] devices = GetMidiInputDevices();
+ if (devices.Length == 0) return null;
+ return devices[0];
+ }
+ public MidiDevice[] GetMidiInputDevices()
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ if (mvarHandle == IntPtr.Zero) throw new InvalidOperationException("Sound card is not open");
+
+ // Start with the first MIDI device on this card
+ int devNum = -1;
+ List devices = new List();
+ while (true)
+ {
+ // Get the number of the next MIDI device on this card
+ int error = Internal.Linux.Alsa.Methods.snd_ctl_rawmidi_next_device(mvarHandle, ref devNum);
+ Internal.Linux.Alsa.Methods.snd_error_code_to_exception(error);
+
+ // No more MIDI devices on this card? ALSA sets "devNum" to -1 if so.
+ // NOTE: It's possible that this sound card may have no MIDI devices on it
+ // at all, for example if it's only a digital audio card
+ if (devNum < 0) break;
+
+ MidiDevice device = new MidiDevice((uint)devNum, this);
+ if (device == null) continue;
+
+ devices.Add(device);
+ }
+ return devices.ToArray();
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ {
+ uint count = Internal.Windows.Methods.midiInGetNumDevs();
+ List list = new List();
+ for (uint i = 0; i < count; i++)
+ {
+ MidiDevice device = new MidiDevice(i, this);
+ if (device == null) continue;
+ list.Add(device);
+ }
+ return list.ToArray();
+ }
+ case PlatformID.Xbox:
+ {
+ break;
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public MidiDevice GetDefaultMidiOutputDevice()
+ {
+ MidiDevice[] devices = GetMidiOutputDevices();
+ if (devices.Length == 0) return null;
+ return devices[0];
+ }
+ public MidiDevice[] GetMidiOutputDevices()
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ // Start with the first MIDI device on this card
+ int devNum = -1;
+ List devices = new List();
+ while (true)
+ {
+ // Get the number of the next MIDI device on this card
+ int error = Internal.Linux.Alsa.Methods.snd_ctl_rawmidi_next_device(mvarHandle, ref devNum);
+ Internal.Linux.Alsa.Methods.snd_error_code_to_exception(error);
+
+ // No more MIDI devices on this card? ALSA sets "devNum" to -1 if so.
+ // NOTE: It's possible that this sound card may have no MIDI devices on it
+ // at all, for example if it's only a digital audio card
+ if (devNum < 0) break;
+
+ MidiDevice device = new MidiDevice((uint)devNum, this);
+ if (device == null) continue;
+
+ devices.Add(device);
+ }
+ return devices.ToArray();
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ {
+ uint count = Internal.Windows.Methods.midiOutGetNumDevs();
+ List list = new List();
+ for (uint i = 0; i < count; i++)
+ {
+ MidiDevice device = new MidiDevice(i, this);
+ if (device == null) continue;
+ list.Add(device);
+ }
+ return list.ToArray();
+ }
+ case PlatformID.Xbox:
+ {
+ break;
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+
+ public MidiDevice[] GetMidiDevices(MidiDeviceFunctionality functionality)
+ {
+ List list = new List();
+ if ((functionality & MidiDeviceFunctionality.Input) == MidiDeviceFunctionality.Input)
+ {
+ list.AddRange(GetMidiInputDevices());
+ }
+ if ((functionality & MidiDeviceFunctionality.Input) == MidiDeviceFunctionality.Input)
+ {
+ list.AddRange(GetMidiOutputDevices());
+ }
+ return list.ToArray();
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.csproj.AssemblyReference.cache b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.csproj.AssemblyReference.cache
new file mode 100644
index 0000000..35a6321
Binary files /dev/null and b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.csproj.AssemblyReference.cache differ
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.csproj.CoreCompileInputs.cache b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.csproj.CoreCompileInputs.cache
new file mode 100644
index 0000000..079b81d
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.csproj.CoreCompileInputs.cache
@@ -0,0 +1 @@
+d68884522d7a041218a83ca43006c6fff4b0b7b2
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.csproj.FileListAbsolute.txt b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.csproj.FileListAbsolute.txt
new file mode 100644
index 0000000..2478570
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.csproj.FileListAbsolute.txt
@@ -0,0 +1,6 @@
+/home/beckermj/Documents/Projects/alcetech/audio-dotnet/audio-dotnet/src/Output/Debug/MBS.Audio.MIDI.dll
+/home/beckermj/Documents/Projects/alcetech/audio-dotnet/audio-dotnet/src/Output/Debug/MBS.Audio.MIDI.pdb
+/home/beckermj/Documents/Projects/alcetech/audio-dotnet/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.csproj.AssemblyReference.cache
+/home/beckermj/Documents/Projects/alcetech/audio-dotnet/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.csproj.CoreCompileInputs.cache
+/home/beckermj/Documents/Projects/alcetech/audio-dotnet/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.dll
+/home/beckermj/Documents/Projects/alcetech/audio-dotnet/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.pdb
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.dll b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.dll
new file mode 100644
index 0000000..1c33e2c
Binary files /dev/null and b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.dll differ
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.pdb b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.pdb
new file mode 100644
index 0000000..f87b5fb
Binary files /dev/null and b/audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.pdb differ
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioDevice.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioDevice.cs
new file mode 100644
index 0000000..ee023bc
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioDevice.cs
@@ -0,0 +1,18 @@
+using System;
+namespace MBS.Audio
+{
+ public abstract class AudioDevice
+ {
+ public abstract int MaximumInputChannels { get; }
+ public abstract int MaximumOutputChannels { get; }
+
+ public abstract double DefaultLowInputLatency { get; }
+ public abstract double DefaultLowOutputLatency { get; }
+ public abstract double DefaultHighInputLatency { get; }
+ public abstract double DefaultHighOutputLatency { get; }
+
+ public abstract double DefaultSampleRate { get; }
+
+ public static AudioDevice None { get; } = null;
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioEngine.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioEngine.cs
new file mode 100644
index 0000000..87f6a43
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioEngine.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio
+{
+ public abstract class AudioEngine : IDisposable
+ {
+ public abstract AudioDevice DefaultInputDevice { get; }
+ public abstract AudioDevice DefaultOutputDevice { get; }
+
+ public void Initialize()
+ {
+ InitializeInternal();
+ }
+ protected abstract void InitializeInternal();
+
+ public void Terminate()
+ {
+ TerminateInternal();
+ }
+ protected abstract void TerminateInternal();
+
+ public abstract Guid ID { get; }
+ public abstract string Title { get; }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private bool _disposed = false;
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ // Dispose managed state (managed objects).
+ }
+
+ // free unmanaged resources (unmanaged objects) and override a finalizer below.
+ // set large fields to null.
+ Terminate();
+
+ _disposed = true;
+ }
+
+ public AudioEngine()
+ {
+ Initialize();
+ }
+
+ ~AudioEngine() => Dispose(false);
+
+ protected abstract AudioStream CreateAudioStreamInternal(AudioDevice inputDevice, short inputChannelCount, AudioSampleFormat inputSampleFormat, double inputSuggestedLatency, AudioDevice outputDevice, short outputChannelCount, AudioSampleFormat outputSampleFormat, double outputSuggestedLatency, double sampleRate, int framesPerBuffer, AudioStreamFlags flags);
+ public AudioStream CreateAudioStream(AudioDevice inputDevice, short inputChannelCount, AudioSampleFormat inputSampleFormat, double inputSuggestedLatency, AudioDevice outputDevice, short outputChannelCount, AudioSampleFormat outputSampleFormat, double outputSuggestedLatency, double sampleRate, int framesPerBuffer, AudioStreamFlags flags)
+ {
+ return CreateAudioStreamInternal(inputDevice, inputChannelCount, inputSampleFormat, inputSuggestedLatency, outputDevice, outputChannelCount, outputSampleFormat, outputSuggestedLatency, sampleRate, framesPerBuffer, flags);
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayer.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayer.cs
new file mode 100644
index 0000000..2bb4a2c
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayer.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using UniversalEditor.ObjectModels.Multimedia.Audio.Waveform;
+
+namespace MBS.Audio
+{
+ public class AudioPlayer : ITransport
+ {
+ public bool IsPlaying { get { return mvarState != AudioPlayerState.Stopped; } }
+
+ private System.Threading.Thread PlayThread = null;
+
+ public AudioDevice InputDevice { get; set; } = AudioDevice.None;
+ public AudioDevice OutputDevice { get; set; } = AudioDevice.None;
+
+ public event EventHandler StateChanged;
+
+ protected virtual void OnStateChanged(AudioPlayerStateChangedEventArgs e)
+ {
+ StateChanged?.Invoke(this, e);
+ }
+
+ private AudioPlayerState mvarState = AudioPlayerState.Stopped;
+ public AudioPlayerState State { get { return mvarState; } private set { if (mvarState != value) { mvarState = value; OnStateChanged(new AudioPlayerStateChangedEventArgs(value, AudioPlayerStateChangedReason.UserAction)); } } }
+
+ private AudioTimestamp mvarTimestamp = AudioTimestamp.Empty;
+ public AudioTimestamp Timestamp { get { return mvarTimestamp; } set { mvarTimestamp = value; i = mvarTimestamp.TotalSamples; } }
+
+ public void Play()
+ {
+ Play(false);
+ }
+ public void Play(bool async)
+ {
+ if (Document == null) throw new NullReferenceException();
+
+ if (PlayThread != null)
+ {
+ PlayThread.Abort();
+ PlayThread = null;
+ }
+
+ PlayThread = new System.Threading.Thread(PlayThread_ThreadStart);
+ PlayThread.Start();
+
+ if (!async)
+ {
+ while (IsPlaying)
+ {
+ System.Threading.Thread.Sleep(500);
+ }
+ }
+ }
+ public void Play(WaveformAudioObjectModel wave)
+ {
+ Play(wave, false);
+ }
+ public void Play(WaveformAudioObjectModel wave, bool async)
+ {
+ Document = wave;
+ Play(async);
+ }
+
+ public double Volume { get; set; } = 0.5;
+
+ public AudioEngine AudioEngine { get; }
+
+ public AudioPlayer(AudioEngine ae)
+ {
+ AudioEngine = ae;
+ }
+
+ private long i = 0;
+ private void PlayThread_ThreadStart()
+ {
+ int bufferSize = Document.Header.ChannelCount;
+
+ AudioSampleFormat asf = AudioSampleFormat.Int16;
+ switch (Document.Header.BitsPerSample)
+ {
+ case 8: asf = AudioSampleFormat.Int8; break;
+ case 16: asf = AudioSampleFormat.Int16; break;
+ case 24: asf = AudioSampleFormat.Int24; break;
+ case 32: asf = AudioSampleFormat.Int32; break;
+ }
+
+ AudioStream audio = AudioEngine.CreateAudioStream(AudioEngine.DefaultInputDevice, Document.Header.ChannelCount, asf, 0, AudioEngine.DefaultOutputDevice, Document.Header.ChannelCount, asf, 0, Document.Header.SampleRate * Document.Header.ChannelCount, 0, AudioStreamFlags.None);
+
+ mvarState = AudioPlayerState.Playing;
+ OnStateChanged(new AudioPlayerStateChangedEventArgs(AudioPlayerState.Playing, AudioPlayerStateChangedReason.UserAction));
+
+ long start = 0;
+
+ mvarTimestamp = AudioTimestamp.FromSamples((int)0, Document.Header.SampleRate * Document.Header.ChannelCount);
+
+ while (State != AudioPlayerState.Stopped)
+ {
+ if (State != AudioPlayerState.Paused)
+ {
+ for (i = mvarTimestamp.TotalSamples; i < Document.RawSamples.Length;)
+ {
+ short[] buffer = Document.RawSamples[(int)(i * bufferSize), bufferSize];
+
+ /*
+ short sampleL = mvarDocument.RawSamples[mvarTimestamp.TotalSamples];
+ short sampleR = sampleL;
+ if (i + 1 < mvarDocument.RawSamples.Length)
+ {
+ sampleR = mvarDocument.RawSamples[i + 1];
+ }
+
+ audio.Write(new short[] { sampleL, sampleR });
+
+ mvarTimestamp.TotalSamples += 2;
+ */
+
+ for (int j = 0; j < buffer.Length; j++)
+ {
+ buffer[j] = (byte)(Volume * buffer[j]);
+ }
+
+ audio.Write(buffer);
+ mvarTimestamp.TotalSamples += buffer.Length;
+
+ i = mvarTimestamp.TotalSamples;
+ // start = i;
+ if (State != AudioPlayerState.Playing) break;
+ }
+ }
+ System.Threading.Thread.Sleep(500);
+ if (mvarTimestamp.TotalSamples >= Document.RawSamples.Length)
+ {
+ mvarState = AudioPlayerState.Stopped;
+ OnStateChanged(new AudioPlayerStateChangedEventArgs(mvarState, AudioPlayerStateChangedReason.SongEnded));
+ }
+ }
+ audio.Flush();
+
+ }
+ public void Stop()
+ {
+ if (!IsPlaying) return;
+
+ mvarState = AudioPlayerState.Stopped;
+ OnStateChanged(new AudioPlayerStateChangedEventArgs(AudioPlayerState.Stopped, AudioPlayerStateChangedReason.UserAction));
+
+ mvarTimestamp = AudioTimestamp.FromSamples((int)0, Document.Header.SampleRate * 2);
+ }
+
+ public void Pause()
+ {
+ if (State == AudioPlayerState.Playing)
+ {
+ State = AudioPlayerState.Paused;
+ }
+ else if (State == AudioPlayerState.Paused)
+ {
+ State = AudioPlayerState.Playing;
+ }
+ }
+
+ public WaveformAudioObjectModel Document { get; set; } = null;
+ public int ChannelCount { get; set; } = 2;
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayerState.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayerState.cs
new file mode 100644
index 0000000..0a67cce
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayerState.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio
+{
+ public enum AudioPlayerState
+ {
+ Stopped = 0,
+ Playing = 2,
+ Paused = 4
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayerStateChangedEvent.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayerStateChangedEvent.cs
new file mode 100644
index 0000000..50cdb04
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayerStateChangedEvent.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio
+{
+ public delegate void AudioPlayerStateChangedEventHandler(object sender, AudioPlayerStateChangedEventArgs e);
+ public class AudioPlayerStateChangedEventArgs : EventArgs
+ {
+ public AudioPlayerState State { get; private set; } = AudioPlayerState.Stopped;
+ public AudioPlayerStateChangedReason Reason { get; private set; } = AudioPlayerStateChangedReason.Unknown;
+
+ public AudioPlayerStateChangedEventArgs(AudioPlayerState state, AudioPlayerStateChangedReason reason)
+ {
+ State = state;
+ Reason = reason;
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayerStateChangedReason.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayerStateChangedReason.cs
new file mode 100644
index 0000000..b511681
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayerStateChangedReason.cs
@@ -0,0 +1,10 @@
+using System;
+namespace MBS.Audio
+{
+ public enum AudioPlayerStateChangedReason
+ {
+ Unknown,
+ UserAction,
+ SongEnded
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioSampleFormat.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioSampleFormat.cs
new file mode 100644
index 0000000..d8afeda
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioSampleFormat.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio
+{
+ public enum AudioSampleFormat : uint
+ {
+ None = 0,
+ Float32 = 1u,
+ Int32 = 2u,
+ Int24 = 4u,
+ Int16 = 8u,
+ Int8 = 16u,
+ UInt8 = 32u,
+ CustomFormat = 65536u,
+ NonInterleaved = 2147483648u
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioStream.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioStream.cs
new file mode 100644
index 0000000..3dba6f7
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioStream.cs
@@ -0,0 +1,129 @@
+using System;
+namespace MBS.Audio
+{
+ public abstract class AudioStream : System.IO.Stream
+ {
+ public AudioDevice InputDevice { get; }
+ public int InputChannelCount { get; }
+ public AudioSampleFormat InputSampleFormat { get; }
+ public double InputSuggestedLatency { get; }
+
+ public AudioDevice OutputDevice { get; }
+ public int OutputChannelCount { get; }
+ public AudioSampleFormat OutputSampleFormat { get; }
+ public double OutputSuggestedLatency { get; }
+
+ public double SampleRate { get; }
+ public int FramesPerBuffer { get; }
+ public AudioStreamFlags Flags { get; }
+
+ public AudioStream(AudioDevice inputDevice, AudioDevice outputDevice, AudioSampleFormat sampleFormat)
+ : this(inputDevice, outputDevice, sampleFormat, sampleFormat)
+ {
+ }
+ public AudioStream(AudioDevice inputDevice, AudioDevice outputDevice, AudioSampleFormat sampleFormat, double sampleRate)
+ : this(inputDevice, outputDevice, sampleFormat, sampleFormat, sampleRate)
+ {
+ }
+ public AudioStream(AudioDevice inputDevice, AudioDevice outputDevice, AudioSampleFormat inputSampleFormat, AudioSampleFormat outputSampleFormat)
+ : this(inputDevice, inputDevice.MaximumInputChannels, inputSampleFormat, outputDevice, outputDevice.MaximumOutputChannels, outputSampleFormat, outputDevice.DefaultSampleRate, 0)
+ {
+ }
+ public AudioStream(AudioDevice inputDevice, AudioDevice outputDevice, AudioSampleFormat inputSampleFormat, AudioSampleFormat outputSampleFormat, double sampleRate)
+ : this(inputDevice, inputDevice.MaximumInputChannels, inputSampleFormat, outputDevice, outputDevice.MaximumOutputChannels, outputSampleFormat, sampleRate, 0)
+ {
+ }
+ public AudioStream(AudioDevice inputDevice, int inputChannelCount, AudioSampleFormat inputSampleFormat, AudioDevice outputDevice, int outputChannelCount, AudioSampleFormat outputSampleFormat, double sampleRate)
+ : this(inputDevice, inputChannelCount, inputSampleFormat, (inputDevice == null) ? 0 : inputDevice.DefaultLowInputLatency, outputDevice, outputChannelCount, outputSampleFormat, (outputDevice == null) ? 0 : outputDevice.DefaultLowOutputLatency, sampleRate, 0, AudioStreamFlags.None)
+ {
+ }
+ public AudioStream(AudioDevice inputDevice, int inputChannelCount, AudioSampleFormat inputSampleFormat, AudioDevice outputDevice, int outputChannelCount, AudioSampleFormat outputSampleFormat, double sampleRate, int framesPerBuffer)
+ : this(inputDevice, inputChannelCount, inputSampleFormat, (inputDevice == null) ? 0 : inputDevice.DefaultLowInputLatency, outputDevice, outputChannelCount, outputSampleFormat, (outputDevice == null) ? 0 : outputDevice.DefaultLowOutputLatency, sampleRate, framesPerBuffer, AudioStreamFlags.None)
+ {
+ }
+ public AudioStream(AudioDevice inputDevice, int inputChannelCount, AudioSampleFormat inputSampleFormat, AudioDevice outputDevice, int outputChannelCount, AudioSampleFormat outputSampleFormat, double sampleRate, int framesPerBuffer, AudioStreamFlags flags)
+ : this(inputDevice, inputChannelCount, inputSampleFormat, (inputDevice == null) ? 0 : inputDevice.DefaultLowInputLatency, outputDevice, outputChannelCount, outputSampleFormat, (outputDevice == null) ? 0 : outputDevice.DefaultLowOutputLatency, sampleRate, framesPerBuffer, flags)
+ {
+ }
+ public AudioStream(AudioDevice inputDevice, int inputChannelCount, AudioSampleFormat inputSampleFormat, double inputSuggestedLatency, AudioDevice outputDevice, int outputChannelCount, AudioSampleFormat outputSampleFormat, double outputSuggestedLatency, double sampleRate, int framesPerBuffer)
+ : this(inputDevice, inputChannelCount, inputSampleFormat, inputSuggestedLatency, outputDevice, outputChannelCount, outputSampleFormat, outputSuggestedLatency, sampleRate, framesPerBuffer, AudioStreamFlags.None)
+ {
+ }
+ public AudioStream(AudioDevice inputDevice, int inputChannelCount, AudioSampleFormat inputSampleFormat, double inputSuggestedLatency, AudioDevice outputDevice, int outputChannelCount, AudioSampleFormat outputSampleFormat, double outputSuggestedLatency, double sampleRate, int framesPerBuffer, AudioStreamFlags flags)
+ {
+ InputDevice = inputDevice;
+ InputChannelCount = inputChannelCount;
+ InputSampleFormat = inputSampleFormat;
+ InputSuggestedLatency = inputSuggestedLatency;
+ OutputDevice = outputDevice;
+ OutputChannelCount = outputChannelCount;
+ OutputSampleFormat = outputSampleFormat;
+ OutputSuggestedLatency = outputSuggestedLatency;
+ SampleRate = sampleRate;
+ FramesPerBuffer = framesPerBuffer;
+ Flags = flags;
+
+ Initialize();
+ }
+
+ public override bool CanRead
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override bool CanSeek
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override bool CanWrite
+ {
+ get { return true; }
+ }
+
+ protected virtual void InitializeInternal()
+ {
+ }
+ private void Initialize()
+ {
+ InitializeInternal();
+ }
+
+
+ public override long Length
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override long Position
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ set
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ throw new NotImplementedException();
+ }
+
+ public abstract void Read(short[] buffer);
+ public abstract void Write(short[] buffer);
+
+ public override long Seek(long offset, System.IO.SeekOrigin origin)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException();
+ }
+
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioStreamFlags.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioStreamFlags.cs
new file mode 100644
index 0000000..b2c7514
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioStreamFlags.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio
+{
+ [Flags()]
+ public enum AudioStreamFlags : uint
+ {
+ None,
+ ClipOff,
+ DitherOff,
+ NeverDropInput,
+ PrimeOutputBuffersUsingStreamCallback,
+ PlatformSpecificFlags
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioTimestamp.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioTimestamp.cs
new file mode 100644
index 0000000..6454ae1
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/AudioTimestamp.cs
@@ -0,0 +1,187 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio
+{
+ public struct AudioTimestamp : IComparable
+ {
+ public static AudioTimestamp FromSamples(long totalSamples, int samplesPerSecond)
+ {
+ AudioTimestamp timestamp = new AudioTimestamp();
+ timestamp.TotalSamples = totalSamples;
+ timestamp.mvarSamplesPerSecond = samplesPerSecond;
+ timestamp._IsNotEmpty = true;
+ return timestamp;
+ }
+ public static AudioTimestamp FromSamples(long totalSamples, int samplesPerSecond, int bars, int beats, int ticks, float beatsPerBar, double ticksPerBeat)
+ {
+ AudioTimestamp timestamp = new AudioTimestamp();
+ timestamp.TotalSamples = totalSamples;
+ timestamp.mvarSamplesPerSecond = samplesPerSecond;
+ timestamp._IsNotEmpty = true;
+ timestamp.Bars = bars;
+ timestamp.Beats = beats;
+ timestamp.Ticks = ticks;
+ timestamp.BeatsPerBar = beatsPerBar;
+ timestamp.TicksPerBeat = ticksPerBeat;
+ return timestamp;
+ }
+
+ public static AudioTimestamp FromHMS(int hours, int minutes, int seconds, int milliseconds, int samplesPerSecond)
+ {
+ return FromHMS(0, hours, minutes, seconds, milliseconds, samplesPerSecond);
+ }
+ public static AudioTimestamp FromHMS(int days, int hours, int minutes, int seconds, int milliseconds, int samplesPerSecond)
+ {
+ AudioTimestamp timestamp = new AudioTimestamp();
+ timestamp.TotalSamples = (int)(((double)milliseconds / 100) + (seconds) + (minutes * 60) + (hours * 3600) + (days * 24 * 3600)) * samplesPerSecond;
+ timestamp.mvarSamplesPerSecond = samplesPerSecond;
+ timestamp._IsNotEmpty = true;
+ return timestamp;
+ }
+
+ private bool _IsNotEmpty;
+ public bool IsEmpty { get { return !_IsNotEmpty; } }
+
+ public int Bars { get; private set; }
+ public int Beats { get; private set; }
+ public int Ticks { get; private set; }
+ public float BeatsPerBar { get; private set; }
+ public double TicksPerBeat { get; private set; }
+
+ public string ToBBTString(string separator = ".")
+ {
+ return String.Format("{0}{3}{1}{3}{2}", Bars.ToString().PadLeft(3, '0'), Beats.ToString().PadLeft(2, '0'), Ticks.ToString().PadLeft(4, '0'), separator);
+ }
+
+ public BarBeatTick ToBBTTimeSpan()
+ {
+ BarBeatTick bbt = BarBeatTick.FromBBT(Bars, Beats, Ticks, BeatsPerBar, TicksPerBeat);
+ return bbt;
+ }
+
+ public static readonly AudioTimestamp Empty = new AudioTimestamp();
+
+ private int mvarSamplesPerSecond;
+
+ private long mvarTotalSamples;
+ public long TotalSamples { get { return mvarTotalSamples; } set { mvarTotalSamples = value; } }
+
+ public int Days
+ {
+ get { return (int)(TotalDays % 365); }
+ }
+ public int Hours
+ {
+ get { return (int)(TotalHours % 24); }
+ }
+ public int Minutes
+ {
+ get { return (int)(TotalMinutes % 60); }
+ }
+ public int Seconds
+ {
+ get { return (int)(TotalSeconds % 60); }
+ }
+ public int Milliseconds
+ {
+ get { return (int)(TotalMilliseconds % 1000); }
+ }
+ public long Samples
+ {
+ get
+ {
+ if (mvarSamplesPerSecond == 0) return 0;
+ return mvarTotalSamples % mvarSamplesPerSecond;
+ }
+ }
+
+ public double TotalDays
+ {
+ get
+ {
+ return ((double)TotalHours / 24);
+ }
+ }
+ public double TotalHours
+ {
+ get
+ {
+ return ((double)TotalMinutes / 60);
+ }
+ }
+ public double TotalMinutes
+ {
+ get
+ {
+ return ((double)TotalSeconds / 60);
+ }
+ }
+ public double TotalSeconds
+ {
+ get
+ {
+ if (mvarSamplesPerSecond == 0) return 0;
+ return ((double)mvarTotalSamples / mvarSamplesPerSecond);
+ }
+ }
+ public double TotalMilliseconds
+ {
+ get
+ {
+ if (mvarSamplesPerSecond == 0) return 0;
+ return (((double)mvarTotalSamples / mvarSamplesPerSecond * 1000));
+ }
+ }
+
+ public override string ToString()
+ {
+ // return Hours.ToString().PadLeft(2, '0') + ":" + Minutes.ToString().PadLeft(2, '0') + ":" + Seconds.ToString().PadLeft(2, '0') + "." + Milliseconds.ToString() + "/" + Samples.ToString();
+ return Hours.ToString().PadLeft(2, '0') + ":" + Minutes.ToString().PadLeft(2, '0') + ":" + Seconds.ToString().PadLeft(2, '0') + "." + Milliseconds.ToString().PadLeft(3, '0');
+ }
+
+ public TimeSpan ToTimeSpan()
+ {
+ double tsecs = 0;
+ double tms = 0;
+ if (mvarTotalSamples != 0)
+ {
+ tsecs = ((double)mvarTotalSamples / mvarSamplesPerSecond);
+ tms = (((double)mvarTotalSamples / mvarSamplesPerSecond) * 1000) % 1000;
+ }
+
+ int days = (int)(((((double)tsecs / 60) / 60) / 24) % 365);
+ int hours = (int)((((double)tsecs / 60) / 60) % 24);
+ int mins = (int)(((double)tsecs / 60) % 24);
+ int secs = (int)((double)tsecs % 60);
+ int ms = (int)(tms);
+
+ TimeSpan ts = new TimeSpan(days, hours, mins, secs, ms);
+ return ts;
+ }
+
+ public int CompareTo(AudioTimestamp other)
+ {
+ return this.ToTimeSpan().CompareTo(other.ToTimeSpan());
+ }
+
+ public static bool operator <(AudioTimestamp left, AudioTimestamp right)
+ {
+ return left.CompareTo(right) < 0;
+ }
+ public static bool operator >(AudioTimestamp left, AudioTimestamp right)
+ {
+ return left.CompareTo(right) > 0;
+ }
+ public static bool operator <=(AudioTimestamp left, AudioTimestamp right)
+ {
+ return left.CompareTo(right) <= 0;
+ }
+ public static bool operator >=(AudioTimestamp left, AudioTimestamp right)
+ {
+ return left.CompareTo(right) >= 0;
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/BarBeatTick.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/BarBeatTick.cs
new file mode 100644
index 0000000..99d680d
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/BarBeatTick.cs
@@ -0,0 +1,143 @@
+using System;
+namespace MBS.Audio
+{
+ public struct BarBeatTick : IComparable
+ {
+ public int Bars { get; private set; }
+ public int Beats { get; private set; }
+ public int Ticks { get; private set; }
+
+ public float? BeatsPerBar { get; private set; }
+ public double? TicksPerBeat { get; private set; }
+
+ public static BarBeatTick FromBBT(int bars, int beats, int ticks, float? beatsPerBar = null, double? ticksPerBeat = null)
+ {
+ BarBeatTick bbt = new BarBeatTick();
+ bbt.Bars = bars;
+ bbt.Beats = beats;
+ bbt.Ticks = ticks;
+ bbt.BeatsPerBar = beatsPerBar;
+ bbt.TicksPerBeat = ticksPerBeat;
+ bbt._isNotEmpty = true;
+ return bbt;
+ }
+
+ private bool _isNotEmpty;
+ public bool IsEmpty { get { return !_isNotEmpty; } }
+
+ public static readonly BarBeatTick Empty = new BarBeatTick();
+
+ public override bool Equals(object obj)
+ {
+ if (obj is BarBeatTick bbt)
+ {
+ return (Bars == bbt.Bars && Beats == bbt.Beats && Ticks == bbt.Ticks && BeatsPerBar == bbt.BeatsPerBar && TicksPerBeat == bbt.TicksPerBeat && IsEmpty == bbt.IsEmpty);
+ }
+ return false;
+ }
+
+ public int CompareTo(BarBeatTick other)
+ {
+ if (Bars == other.Bars)
+ {
+ if (Beats == other.Beats)
+ {
+ if (Ticks == other.Ticks)
+ {
+ // completely equal
+ return 0;
+ }
+ else
+ {
+ return Ticks.CompareTo(other.Ticks);
+ }
+ }
+ else
+ {
+ return Beats.CompareTo(other.Beats);
+ }
+ }
+ else
+ {
+ return Bars.CompareTo(other.Bars);
+ }
+ }
+
+ public static bool operator ==(BarBeatTick left, BarBeatTick right)
+ {
+ return left.Equals(right);
+ }
+ public static bool operator !=(BarBeatTick left, BarBeatTick right)
+ {
+ return !left.Equals(right);
+ }
+
+ public static bool operator >=(BarBeatTick left, BarBeatTick right)
+ {
+ return (left.CompareTo(right) >= 0);
+ }
+ public static bool operator >(BarBeatTick left, BarBeatTick right)
+ {
+ return (left.CompareTo(right) > 0);
+ }
+ public static bool operator <=(BarBeatTick left, BarBeatTick right)
+ {
+ return (left.CompareTo(right) < 0);
+ }
+ public static bool operator <(BarBeatTick left, BarBeatTick right)
+ {
+ return (left.CompareTo(right) <= 0);
+ }
+
+ public static BarBeatTick operator +(BarBeatTick left, BarBeatTick right)
+ {
+ return left.Add(right);
+ }
+
+ public BarBeatTick Add(int ticks)
+ {
+ BarBeatTick thiss = new BarBeatTick();
+ thiss.Bars = Bars;
+ thiss.Beats = Beats;
+ thiss.Ticks = Ticks + ticks;
+ thiss.TicksPerBeat = TicksPerBeat;
+ thiss.BeatsPerBar = BeatsPerBar;
+ thiss._isNotEmpty = true;
+ return thiss;
+ }
+ public BarBeatTick Add(BarBeatTick other)
+ {
+ int bars = Bars + other.Bars;
+ int beats = Beats + other.Beats;
+ int ticks = Ticks + other.Ticks;
+
+ float? beatsPerBar = BeatsPerBar;
+ if (beatsPerBar == null) beatsPerBar = other.BeatsPerBar;
+ double? ticksPerBeat = TicksPerBeat;
+ if (ticksPerBeat == null) ticksPerBeat = other.TicksPerBeat;
+ if (ticksPerBeat == null) ticksPerBeat = 2000;
+
+ while (ticks > ticksPerBeat)
+ {
+ beats++;
+ ticks -= (int)ticksPerBeat.GetValueOrDefault();
+ }
+ while (beats > beatsPerBar)
+ {
+ bars++;
+ beats -= (int)beatsPerBar.GetValueOrDefault();
+ }
+
+ return BarBeatTick.FromBBT(bars, beats, ticks, beatsPerBar, ticksPerBeat);
+ }
+
+ public override string ToString()
+ {
+ return ToString(" | ");
+ }
+ public string ToString(string separator)
+ {
+ return String.Format("{0}{1}{2}{1}{3}", Bars.ToString().PadLeft(3, '0'), separator, Beats.ToString().PadLeft(2, '0'), Ticks.ToString().PadLeft(4, '0'));
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/CustomTransport.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/CustomTransport.cs
new file mode 100644
index 0000000..3813052
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/CustomTransport.cs
@@ -0,0 +1,58 @@
+//
+// CustomTransport.cs
+//
+// Author:
+// beckermj <>
+//
+// Copyright (c) 2021 ${CopyrightHolder}
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+namespace MBS.Audio
+{
+ public class CustomTransport : ITransport
+ {
+ public CustomTransport(EventHandler play_handler, EventHandler stop_handler, EventHandler pause_handler)
+ {
+ _play_handler = play_handler;
+ _stop_handler = stop_handler;
+ _pause_handler = pause_handler;
+ }
+
+ public AudioTimestamp Timestamp { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
+
+ public bool IsPlaying => throw new NotImplementedException();
+
+ public AudioPlayerState State => throw new NotImplementedException();
+
+ public event EventHandler StateChanged;
+
+ private EventHandler _play_handler = null, _stop_handler = null, _pause_handler = null;
+
+ public void Pause()
+ {
+ _pause_handler?.Invoke(this, EventArgs.Empty);
+ }
+
+ public void Play()
+ {
+ _play_handler?.Invoke(this, EventArgs.Empty);
+ }
+
+ public void Stop()
+ {
+ _stop_handler?.Invoke(this, EventArgs.Empty);
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/ITransport.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/ITransport.cs
new file mode 100644
index 0000000..23b0282
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/ITransport.cs
@@ -0,0 +1,95 @@
+//
+// ITransport.cs - interface for defining the minimum functionality required for an audio transport
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2020-2021 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+using System;
+
+namespace MBS.Audio
+{
+ ///
+ /// Defines the minimum functionality required for an audio transport that
+ /// can play, pause, stop, and seek within the audio.
+ ///
+ public interface ITransport
+ {
+ ///
+ /// Gets or sets the position of the
+ /// transport.
+ ///
+ /// The timestamp to get or set.
+ AudioTimestamp Timestamp { get; set; }
+
+ ///
+ /// Gets a value indicating whether this is
+ /// currently playing (rolling).
+ ///
+ /// true if the transport is playing (rolling);
+ /// otherwise, false.
+ bool IsPlaying { get; }
+
+ ///
+ /// Gets a value indicating whether the transport is currently
+ /// stopped, playing, or paused.
+ ///
+ /// The state of the transport.
+ AudioPlayerState State { get; }
+
+ ///
+ /// Stops playback of the audio stream associated with this
+ /// . This is equivalent to calling
+ /// and setting to the
+ /// beginning of the stream.
+ ///
+ ///
+ /// There is no concept of "pausing" with respect to "stopping" in the
+ /// JACK transport. Therefore, JACK's definition of "stopping" is
+ /// equivalent to "pausing" here, and "stopping" here refers to
+ /// "pausing" followed by resetting the to the
+ /// beginning of the audio stream (if possible).
+ ///
+ void Stop();
+ ///
+ /// Starts playback of the audio stream associated with this
+ /// . If the audio stream is currently
+ /// paused, this MAY result in simply resuming the audio stream where
+ /// it left off.
+ ///
+ void Play();
+ ///
+ /// Pauses playback of the audio stream associated with this
+ /// . This essentially stops the playback
+ /// but does not reset the to the beginning
+ /// of the stream.
+ ///
+ ///
+ /// There is no concept of "pausing" with respect to "stopping" in the
+ /// JACK transport. Therefore, JACK's definition of "stopping" is
+ /// equivalent to "pausing" here, and "stopping" here refers to
+ /// "pausing" followed by resetting the to the
+ /// beginning of the audio stream (if possible).
+ ///
+ void Pause();
+
+ ///
+ /// Occurs when the state of the changes.
+ ///
+ event EventHandler StateChanged;
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Constants.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Constants.cs
new file mode 100644
index 0000000..679751c
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Constants.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio.Internal.PortAudio
+{
+ public static class Constants
+ {
+ public enum PaStreamCallbackFlags : uint
+ {
+ paInputUnderflow = 1u,
+ paInputOverflow,
+ paOutputUnderflow = 4u,
+ paOutputOverflow = 8u,
+ paPrimingOutput = 16u
+ }
+ public enum PaStreamCallbackResult : uint
+ {
+ paContinue,
+ paComplete,
+ paAbort
+ }
+ public enum PaError
+ {
+ paNoError,
+ paNotInitialized = -10000,
+ paUnanticipatedHostError,
+ paInvalidChannelCount,
+ paInvalidSampleRate,
+ paInvalidDevice,
+ paInvalidFlag,
+ paSampleFormatNotSupported,
+ paBadIODeviceCombination,
+ paInsufficientMemory,
+ paBufferTooBig,
+ paBufferTooSmall,
+ paNullCallback,
+ paBadStreamPtr,
+ paTimedOut,
+ paInternalError,
+ paDeviceUnavailable,
+ paIncompatibleHostApiSpecificStreamInfo,
+ paStreamIsStopped,
+ paStreamIsNotStopped,
+ paInputOverflowed,
+ paOutputUnderflowed,
+ paHostApiNotFound,
+ paInvalidHostApi,
+ paCanNotReadFromACallbackStream,
+ paCanNotWriteToACallbackStream,
+ paCanNotReadFromAnOutputOnlyStream,
+ paCanNotWriteToAnInputOnlyStream,
+ paIncompatibleStreamHostApi,
+ paBadBufferPtr
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Delegates.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Delegates.cs
new file mode 100644
index 0000000..72b7bd4
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Delegates.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.InteropServices;
+
+namespace MBS.Audio.Internal.PortAudio
+{
+ public static class Delegates
+ {
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate Constants.PaStreamCallbackResult PaStreamCallbackDelegate(IntPtr input, IntPtr output, uint frameCount, ref Structures.PaStreamCallbackTimeInfo timeInfo, Constants.PaStreamCallbackFlags statusFlags, IntPtr userData);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void PaStreamFinishedCallbackDelegate(IntPtr userData);
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Linux/Methods.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Linux/Methods.cs
new file mode 100644
index 0000000..81878de
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Linux/Methods.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.InteropServices;
+
+namespace MBS.Audio.Internal.PortAudio.Linux
+{
+ internal static class Methods
+ {
+ private const string LIBRARY_FILENAME = "libportaudio.so.2";
+
+ #region Initialization/Termination
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_Initialize();
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_Terminate();
+ #endregion
+ #region Device Enumeration
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int Pa_GetDeviceCount();
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int Pa_GetDefaultInputDevice();
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int Pa_GetDefaultOutputDevice();
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern IntPtr Pa_GetDeviceInfo(int device);
+ #endregion
+ #region Stream Initialization
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_OpenStream(out IntPtr stream, ref Structures.PaStreamParameters inputParameters, ref Structures.PaStreamParameters outputParameters, double sampleRate, uint framesPerBuffer, Audio.PortAudio.PortAudioStreamFlags streamFlags, Delegates.PaStreamCallbackDelegate streamCallback, IntPtr userData);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_OpenDefaultStream(out IntPtr stream, int numInputChannels, int numOutputChannels, uint sampleFormat, double sampleRate, uint framesPerBuffer, Delegates.PaStreamCallbackDelegate streamCallback, IntPtr userData);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_CloseStream(IntPtr stream);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_SetStreamFinishedCallback(ref IntPtr stream, [MarshalAs(UnmanagedType.FunctionPtr)] Delegates.PaStreamFinishedCallbackDelegate streamFinishedCallback);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_StartStream(IntPtr stream);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_StopStream(IntPtr stream);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_AbortStream(IntPtr stream);
+ #endregion
+ #region Stream Reading
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_ReadStream(IntPtr stream, [Out] float[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_ReadStream(IntPtr stream, [Out] byte[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_ReadStream(IntPtr stream, [Out] sbyte[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_ReadStream(IntPtr stream, [Out] ushort[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_ReadStream(IntPtr stream, [Out] short[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_ReadStream(IntPtr stream, [Out] uint[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_ReadStream(IntPtr stream, [Out] int[] buffer, uint frames);
+ #endregion
+ #region Stream Writing
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_WriteStream(IntPtr stream, [In] float[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_WriteStream(IntPtr stream, [In] byte[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_WriteStream(IntPtr stream, [In] sbyte[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_WriteStream(IntPtr stream, [In] ushort[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_WriteStream(IntPtr stream, [In] short[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_WriteStream(IntPtr stream, [In] uint[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_WriteStream(IntPtr stream, [In] int[] buffer, uint frames);
+ #endregion
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Methods.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Methods.cs
new file mode 100644
index 0000000..a834146
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Methods.cs
@@ -0,0 +1,641 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.InteropServices;
+
+namespace MBS.Audio.Internal.PortAudio
+{
+ public static class Methods
+ {
+ #region Initialization/Termination
+ public static Constants.PaError Pa_Initialize()
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_Initialize();
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_Initialize();
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_Terminate()
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_Terminate();
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_Terminate();
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ #endregion
+ #region Device Enumeration
+ public static int Pa_GetDeviceCount()
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_GetDeviceCount();
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_GetDeviceCount();
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static int Pa_GetDefaultInputDevice()
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_GetDefaultInputDevice();
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_GetDefaultInputDevice();
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static int Pa_GetDefaultOutputDevice()
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_GetDefaultOutputDevice();
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_GetDefaultOutputDevice();
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Structures.PaDeviceInfo Pa_GetDeviceInfo(int index)
+ {
+ IntPtr ptr = IntPtr.Zero;
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ ptr = Internal.PortAudio.Linux.Methods.Pa_GetDeviceInfo(index);
+ break;
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ ptr = Internal.PortAudio.Windows.Methods.Pa_GetDeviceInfo(index);
+ break;
+ }
+ }
+
+ Structures.PaDeviceInfo devinfo = new Structures.PaDeviceInfo();
+ if (ptr != IntPtr.Zero)
+ {
+ devinfo = (Structures.PaDeviceInfo)Marshal.PtrToStructure(ptr, typeof(Structures.PaDeviceInfo));
+ }
+ return devinfo;
+ }
+ #endregion
+ #region Stream Initialization
+ public static Constants.PaError Pa_OpenStream(out IntPtr stream, ref Structures.PaStreamParameters inputParameters, ref Structures.PaStreamParameters outputParameters, double sampleRate, uint framesPerBuffer, Audio.PortAudio.PortAudioStreamFlags streamFlags, Delegates.PaStreamCallbackDelegate streamCallback, IntPtr userData)
+ {
+ stream = IntPtr.Zero;
+
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_OpenStream(out stream, ref inputParameters, ref outputParameters, sampleRate, framesPerBuffer, streamFlags, streamCallback, userData);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_OpenStream(out stream, ref inputParameters, ref outputParameters, sampleRate, framesPerBuffer, streamFlags, streamCallback, userData);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_OpenDefaultStream(out IntPtr stream, int numInputChannels, int numOutputChannels, uint sampleFormat, double sampleRate, uint framesPerBuffer, Delegates.PaStreamCallbackDelegate streamCallback, IntPtr userData)
+ {
+ stream = IntPtr.Zero;
+
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_OpenDefaultStream(out stream, numInputChannels, numOutputChannels, sampleFormat, sampleRate, framesPerBuffer, streamCallback, userData);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_OpenDefaultStream(out stream, numInputChannels, numOutputChannels, sampleFormat, sampleRate, framesPerBuffer, streamCallback, userData);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_CloseStream(IntPtr stream)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_CloseStream(stream);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_CloseStream(stream);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_SetStreamFinishedCallback(ref IntPtr stream, [MarshalAs(UnmanagedType.FunctionPtr)] Delegates.PaStreamFinishedCallbackDelegate streamFinishedCallback)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_SetStreamFinishedCallback(ref stream, streamFinishedCallback);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_SetStreamFinishedCallback(ref stream, streamFinishedCallback);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_StartStream(IntPtr stream)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_StartStream(stream);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_StartStream(stream);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_StopStream(IntPtr stream)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_StopStream(stream);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_StopStream(stream);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_AbortStream(IntPtr stream)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_AbortStream(stream);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_AbortStream(stream);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ #endregion
+ #region Stream Reading
+ public static Constants.PaError Pa_ReadStream(IntPtr stream, [Out] float[] buffer, uint frames)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_ReadStream(stream, buffer, frames);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_ReadStream(stream, buffer, frames);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_ReadStream(IntPtr stream, [Out] byte[] buffer, uint frames)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_ReadStream(stream, buffer, frames);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_ReadStream(stream, buffer, frames);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_ReadStream(IntPtr stream, [Out] sbyte[] buffer, uint frames)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_ReadStream(stream, buffer, frames);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_ReadStream(stream, buffer, frames);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_ReadStream(IntPtr stream, [Out] ushort[] buffer, uint frames)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_ReadStream(stream, buffer, frames);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_ReadStream(stream, buffer, frames);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_ReadStream(IntPtr stream, [Out] short[] buffer, uint frames)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_ReadStream(stream, buffer, frames);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_ReadStream(stream, buffer, frames);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_ReadStream(IntPtr stream, [Out] uint[] buffer, uint frames)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_ReadStream(stream, buffer, frames);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_ReadStream(stream, buffer, frames);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_ReadStream(IntPtr stream, [Out] int[] buffer, uint frames)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_ReadStream(stream, buffer, frames);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_ReadStream(stream, buffer, frames);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ #endregion
+ #region Stream Writing
+ public static Constants.PaError Pa_WriteStream(IntPtr stream, [In] float[] buffer, uint frames)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_WriteStream(stream, buffer, frames);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_WriteStream(stream, buffer, frames);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_WriteStream(IntPtr stream, [In] byte[] buffer, uint frames)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_WriteStream(stream, buffer, frames);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_WriteStream(stream, buffer, frames);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_WriteStream(IntPtr stream, [In] sbyte[] buffer, uint frames)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_WriteStream(stream, buffer, frames);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_WriteStream(stream, buffer, frames);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_WriteStream(IntPtr stream, [In] ushort[] buffer, uint frames)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_WriteStream(stream, buffer, frames);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_WriteStream(stream, buffer, frames);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_WriteStream(IntPtr stream, [In] short[] buffer, uint frames)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_WriteStream(stream, buffer, frames);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_WriteStream(stream, buffer, frames);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_WriteStream(IntPtr stream, [In] uint[] buffer, uint frames)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_WriteStream(stream, buffer, frames);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_WriteStream(stream, buffer, frames);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ public static Constants.PaError Pa_WriteStream(IntPtr stream, [In] int[] buffer, uint frames)
+ {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ case PlatformID.Unix:
+ {
+ return Internal.PortAudio.Linux.Methods.Pa_WriteStream(stream, buffer, frames);
+ }
+ case PlatformID.Win32NT:
+ case PlatformID.Win32S:
+ case PlatformID.Win32Windows:
+ case PlatformID.WinCE:
+ case PlatformID.Xbox:
+ {
+ return Internal.PortAudio.Windows.Methods.Pa_WriteStream(stream, buffer, frames);
+ }
+ }
+ throw new PlatformNotSupportedException();
+ }
+ #endregion
+
+ public static void Pa_ResultToException(Constants.PaError result)
+ {
+ switch (result)
+ {
+ case Internal.PortAudio.Constants.PaError.paBadBufferPtr:
+ throw new ArgumentException("Bad buffer pointer.");
+ case Internal.PortAudio.Constants.PaError.paBadIODeviceCombination:
+ throw new ArgumentException("Bad input/output device combination.");
+ case Internal.PortAudio.Constants.PaError.paBadStreamPtr:
+ throw new ArgumentException("Bad stream pointer.");
+ case Internal.PortAudio.Constants.PaError.paBufferTooBig:
+ throw new ArgumentException("Buffer too big.");
+ case Internal.PortAudio.Constants.PaError.paBufferTooSmall:
+ throw new ArgumentException("Buffer too small.");
+ case Internal.PortAudio.Constants.PaError.paCanNotReadFromACallbackStream:
+ throw new System.IO.IOException("Cannot read from a callback stream.");
+ case Internal.PortAudio.Constants.PaError.paCanNotReadFromAnOutputOnlyStream:
+ throw new System.IO.IOException("Cannot read from an output-only stream.");
+ case Internal.PortAudio.Constants.PaError.paCanNotWriteToACallbackStream:
+ throw new System.IO.IOException("Cannot write to a callback stream.");
+ case Internal.PortAudio.Constants.PaError.paCanNotWriteToAnInputOnlyStream:
+ throw new System.IO.IOException("Cannot write to an input-only stream.");
+ case Internal.PortAudio.Constants.PaError.paDeviceUnavailable:
+ throw new System.IO.IOException("The device is unavailable.");
+ case Internal.PortAudio.Constants.PaError.paHostApiNotFound:
+ throw new InvalidOperationException("Host API not found.");
+ case Internal.PortAudio.Constants.PaError.paIncompatibleHostApiSpecificStreamInfo:
+ throw new InvalidOperationException("Incompatible host API-specific stream information.");
+ case Internal.PortAudio.Constants.PaError.paIncompatibleStreamHostApi:
+ throw new InvalidOperationException("Incompatible stream host API.");
+ case Internal.PortAudio.Constants.PaError.paInputOverflowed:
+ throw new OverflowException("Input overflowed.");
+ case Internal.PortAudio.Constants.PaError.paInsufficientMemory:
+ throw new OutOfMemoryException("Insufficient memory.");
+ case Internal.PortAudio.Constants.PaError.paInternalError:
+ throw new Exception("Internal error.");
+ case Internal.PortAudio.Constants.PaError.paInvalidChannelCount:
+ throw new ArgumentException("Invalid channel count.");
+ case Internal.PortAudio.Constants.PaError.paInvalidDevice:
+ throw new ArgumentException("Invalid device.");
+ case Internal.PortAudio.Constants.PaError.paInvalidFlag:
+ throw new ArgumentException("Invalid flag.");
+ case Internal.PortAudio.Constants.PaError.paInvalidHostApi:
+ throw new ArgumentException("Invalid host API.");
+ case Internal.PortAudio.Constants.PaError.paInvalidSampleRate:
+ throw new ArgumentException("Invalid sample rate.");
+ case Internal.PortAudio.Constants.PaError.paNotInitialized:
+ throw new InvalidOperationException("PortAudio has not been initialized.");
+ case Internal.PortAudio.Constants.PaError.paNullCallback:
+ throw new InvalidOperationException("Null callback.");
+ case Internal.PortAudio.Constants.PaError.paOutputUnderflowed:
+ // throw new OverflowException("Output underflowed.");
+ break;
+ case Internal.PortAudio.Constants.PaError.paSampleFormatNotSupported:
+ throw new NotSupportedException("Sample format not supported.");
+ case Internal.PortAudio.Constants.PaError.paStreamIsNotStopped:
+ throw new InvalidOperationException("Stream is not stopped.");
+ case Internal.PortAudio.Constants.PaError.paStreamIsStopped:
+ throw new InvalidOperationException("Stream is stopped.");
+ case Internal.PortAudio.Constants.PaError.paTimedOut:
+ throw new TimeoutException();
+ case Internal.PortAudio.Constants.PaError.paUnanticipatedHostError:
+ throw new Exception("Unanticipated host error.");
+ }
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Structures.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Structures.cs
new file mode 100644
index 0000000..4e18801
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Structures.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.InteropServices;
+
+namespace MBS.Audio.Internal.PortAudio
+{
+ public static class Structures
+ {
+ public struct PaDeviceInfo
+ {
+ public int structVersion;
+ [MarshalAs(UnmanagedType.LPStr)]
+ public string name;
+ public int hostApi;
+ public int maxInputChannels;
+ public int maxOutputChannels;
+ public double defaultLowInputLatency;
+ public double defaultLowOutputLatency;
+ public double defaultHighInputLatency;
+ public double defaultHighOutputLatency;
+ public double defaultSampleRate;
+ public override string ToString()
+ {
+ return string.Concat(new object[]
+ {
+ "[",
+ base.GetType().Name,
+ "]\nname: ",
+ this.name,
+ "\nhostApi: ",
+ this.hostApi,
+ "\nmaxInputChannels: ",
+ this.maxInputChannels,
+ "\nmaxOutputChannels: ",
+ this.maxOutputChannels,
+ "\ndefaultLowInputLatency: ",
+ this.defaultLowInputLatency,
+ "\ndefaultLowOutputLatency: ",
+ this.defaultLowOutputLatency,
+ "\ndefaultHighInputLatency: ",
+ this.defaultHighInputLatency,
+ "\ndefaultHighOutputLatency: ",
+ this.defaultHighOutputLatency,
+ "\ndefaultSampleRate: ",
+ this.defaultSampleRate
+ });
+ }
+ }
+ public struct PaStreamCallbackTimeInfo
+ {
+ public double inputBufferAdcTime;
+ public double currentTime;
+ public double outputBufferDacTime;
+ public override string ToString()
+ {
+ return string.Concat(new object[]
+ {
+ "[",
+ base.GetType().Name,
+ "]\ncurrentTime: ",
+ this.currentTime,
+ "\ninputBufferAdcTime: ",
+ this.inputBufferAdcTime,
+ "\noutputBufferDacTime: ",
+ this.outputBufferDacTime
+ });
+ }
+ }
+ public struct PaStreamParameters
+ {
+ public int device;
+ public int channelCount;
+ public AudioSampleFormat sampleFormat;
+ public double suggestedLatency;
+ public IntPtr hostApiSpecificStreamInfo;
+ public override string ToString()
+ {
+ return string.Concat(new object[]
+ {
+ "[",
+ base.GetType().Name,
+ "]\ndevice: ",
+ this.device,
+ "\nchannelCount: ",
+ this.channelCount,
+ "\nsampleFormat: ",
+ this.sampleFormat,
+ "\nsuggestedLatency: ",
+ this.suggestedLatency
+ });
+ }
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Windows/Methods.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Windows/Methods.cs
new file mode 100644
index 0000000..f5149a3
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Windows/Methods.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.InteropServices;
+
+namespace MBS.Audio.Internal.PortAudio.Windows
+{
+ internal static class Methods
+ {
+ private const string LIBRARY_FILENAME = "PortAudio.dll";
+
+ #region Initialization/Termination
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_Initialize();
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_Terminate();
+ #endregion
+ #region Device Enumeration
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int Pa_GetDeviceCount();
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int Pa_GetDefaultInputDevice();
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int Pa_GetDefaultOutputDevice();
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern IntPtr Pa_GetDeviceInfo(int device);
+ #endregion
+ #region Stream Initialization
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_OpenStream(out IntPtr stream, ref Structures.PaStreamParameters inputParameters, ref Structures.PaStreamParameters outputParameters, double sampleRate, uint framesPerBuffer, Audio.PortAudio.PortAudioStreamFlags streamFlags, Delegates.PaStreamCallbackDelegate streamCallback, IntPtr userData);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_OpenDefaultStream(out IntPtr stream, int numInputChannels, int numOutputChannels, uint sampleFormat, double sampleRate, uint framesPerBuffer, Delegates.PaStreamCallbackDelegate streamCallback, IntPtr userData);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_CloseStream(IntPtr stream);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_SetStreamFinishedCallback(ref IntPtr stream, [MarshalAs(UnmanagedType.FunctionPtr)] Delegates.PaStreamFinishedCallbackDelegate streamFinishedCallback);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_StartStream(IntPtr stream);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_StopStream(IntPtr stream);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_AbortStream(IntPtr stream);
+ #endregion
+ #region Stream Reading
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_ReadStream(IntPtr stream, [Out] float[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_ReadStream(IntPtr stream, [Out] byte[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_ReadStream(IntPtr stream, [Out] sbyte[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_ReadStream(IntPtr stream, [Out] ushort[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_ReadStream(IntPtr stream, [Out] short[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_ReadStream(IntPtr stream, [Out] uint[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_ReadStream(IntPtr stream, [Out] int[] buffer, uint frames);
+ #endregion
+ #region Stream Writing
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_WriteStream(IntPtr stream, [In] float[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_WriteStream(IntPtr stream, [In] byte[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_WriteStream(IntPtr stream, [In] sbyte[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_WriteStream(IntPtr stream, [In] ushort[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_WriteStream(IntPtr stream, [In] short[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_WriteStream(IntPtr stream, [In] uint[] buffer, uint frames);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern Constants.PaError Pa_WriteStream(IntPtr stream, [In] int[] buffer, uint frames);
+ #endregion
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Constants.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Constants.cs
new file mode 100644
index 0000000..0a0416d
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Constants.cs
@@ -0,0 +1,116 @@
+//
+// Constants.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2020 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+namespace MBS.Audio.Jack.Internal
+{
+ internal static class Constants
+ {
+ public enum JackStatus
+ {
+ Success = 0x00,
+ ///
+ /// Overall operation failed.
+ ///
+ Failure = 0x01,
+ ///
+ /// The operation contained an invalid or unsupported option.
+ ///
+ InvalidOption = 0x02,
+ ///
+ /// The desired client name was not unique. With the @ref
+ /// JackUseExactName option this situation is fatal. Otherwise,
+ /// the name was modified by appending a dash and a two-digit
+ /// number in the range "-01" to "-99". The
+ /// jack_get_client_name() function will return the exact string
+ /// that was used. If the specified @a client_name plus these
+ /// extra characters would be too long, the open fails instead.
+ ///
+ NameNotUnique = 0x04,
+ ///
+ /// The JACK server was started as a result of this operation.
+ /// Otherwise, it was running already. In either case the caller
+ /// is now connected to jackd, so there is no race condition.
+ /// When the server shuts down, the client will find out.
+ ///
+ ServerStarted = 0x08,
+ ///
+ /// Unable to connect to the JACK server.
+ ///
+ ServerFailed = 0x10,
+ ///
+ /// Communication error with the JACK server.
+ ///
+ ServerError = 0x20,
+ ///
+ /// Requested client does not exist.
+ ///
+ NoSuchClient = 0x40,
+ ///
+ /// Unable to load internal client
+ ///
+ LoadFailure = 0x80,
+ ///
+ /// Unable to initialize client
+ ///
+ InitFailure = 0x100,
+ ///
+ /// Unable to access shared memory
+ ///
+ ShmFailure = 0x200,
+ ///
+ /// Client's protocol version does not match
+ ///
+ VersionError = 0x400,
+ ///
+ /// Backend error
+ ///
+ BackendError = 0x800,
+ ///
+ /// Client zombified failure
+ ///
+ ClientZombie = 0x1000
+ }
+
+ public enum JackPositionBits
+ {
+ ///
+ /// Bar, Beat, Tick
+ ///
+ PositionBBT = 0x10,
+ ///
+ /// External timecode
+ ///
+ PositionTimecode = 0x20,
+ ///
+ /// Frame offset of BBT information
+ ///
+ BBTFrameOffset = 0x40,
+ ///
+ /// Audio frames per video frame.
+ ///
+ AudioVideoRatio = 0x80,
+ ///
+ /// Frame offset of first video frame.
+ ///
+ VideoFrameOffset = 0x100
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Delegates.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Delegates.cs
new file mode 100644
index 0000000..8048062
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Delegates.cs
@@ -0,0 +1,26 @@
+using System;
+namespace MBS.Audio.Jack.Internal
+{
+ public class Delegates
+ {
+ ///
+ /// Prototype for the client supplied function that is called
+ /// whenever a port is registered or unregistered.
+ ///
+ /// the ID of the port
+ ///
+ /// non-zero if the port is being registered, zero if the port is
+ /// being unregistered
+ ///
+ /// pointer to a client supplied data
+ public delegate void JackPortRegistrationCallback(uint /*jack_port_id_t*/ port, int register, IntPtr arg);
+ ///
+ /// Prototype for the client supplied function that is called
+ /// by the engine anytime there is work to be done.
+ ///
+ /// number of frames to process
+ /// pointer to a client supplied structure
+ /// zero on success; non-zero on error
+ public delegate int JackProcessCallback(uint /*jack_nframes_t*/ nframes, IntPtr arg);
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Methods.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Methods.cs
new file mode 100644
index 0000000..68773cf
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Methods.cs
@@ -0,0 +1,253 @@
+//
+// Methods.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2020 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+using System.Runtime.InteropServices;
+
+namespace MBS.Audio.Jack.Internal
+{
+ internal static class Methods
+ {
+ public const string LIBRARY_FILENAME = "jack";
+
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern IntPtr /*jack_client_t*/ jack_client_open(string client_name, JackOpenOptions options, ref Constants.JackStatus status);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern IntPtr jack_get_client_name(IntPtr /*jack_client_t*/ client);
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int jack_client_name_size();
+
+ ///
+ /// Start the JACK transport rolling. Any client can make this
+ /// request at any time. It takes effect no sooner than the next
+ /// process cycle, perhaps later if there are slow-sync clients.
+ /// This function is realtime-safe.
+ ///
+ ///
+ /// the JACK client structure
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int jack_transport_start(IntPtr /*jack_client_t*/ client);
+
+ ///
+ /// Stop the JACK transport. Any client can make this request at any
+ /// time. It takes effect no sooner than the next process cycle,
+ /// perhaps later if there are slow-sync clients. This function is
+ /// realtime-safe.
+ ///
+ /// the JACK client structure
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern void jack_transport_stop(IntPtr /*jack_client_t*/ client);
+
+ ///
+ /// Establish a connection between two ports. When a connection exists, data written to the source port will
+ /// be available to be read at the destination port.
+ ///
+ ///
+ /// The port types must be identical.
+ /// The of the must include .
+ /// The of the must include .
+ ///
+ /// 0 on success, EEXIST if the connection is already made, otherwise a non-zero error code.
+ /// Client.
+ /// Source port.
+ /// Destination port.
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int jack_connect(IntPtr /*jack_client_t*/ client, string source_port, string destination_port);
+
+ ///
+ /// return JACK's current system time in microseconds, using the JACK clock source.
+ ///
+ /// The value returned is guaranteed to be monotonic, but not linear.
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern ulong jack_get_time();
+
+ ///
+ /// Tell the JACK server to call
+ /// whenever a port is
+ /// registered or unregistered, passing as a
+ /// parameter.
+ ///
+ /// All "notification events" are received in a separated non RT thread,
+ /// the code in the supplied function does not need to be
+ /// suitable for real-time execution.
+ ///
+ /// NOTE: this function cannot be called while the client is activated
+ /// (after jack_activate has been called.)
+ ///
+ /// 0 on success, otherwise a non-zero error code
+ /// Client.
+ /// Registration callback.
+ /// Argument.
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int jack_set_port_registration_callback(IntPtr /*jack_client_t*/ client, Delegates.JackPortRegistrationCallback registration_callback, IntPtr arg);
+ ///
+ /// Tell the Jack server to call
+ /// whenever there is work be done, passing
+ /// as the second argument.
+ ///
+ /// The code in the supplied function must be suitable for real-time
+ /// execution.That means that it cannot call functions that might
+ /// block for a long time. This includes malloc, free, printf,
+ /// pthread_mutex_lock, sleep, wait, poll, select, pthread_join,
+ /// pthread_cond_wait, etc, etc. See
+ /// http://jackit.sourceforge.net/docs/design/design.html#SECTION00411000000000000000
+ /// for more information.
+ ///
+ /// NOTE: this function cannot be called while the client is activated
+ /// (after jack_activate has been called.)
+ ///
+ /// The set process callback.
+ /// Client.
+ /// Process callback.
+ /// Argument.
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int jack_set_process_callback(IntPtr /*jack_client_t*/ client, Delegates.JackProcessCallback process_callback, IntPtr arg);
+
+ ///
+ /// Tell the Jack server that the program is ready to start
+ /// processing audio.
+ ///
+ /// 0 on success, otherwise a non-zero error code
+ /// Handle.
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int jack_activate(IntPtr handle);
+
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern IntPtr /*jack_port_t*/ jack_port_register(IntPtr /*jack_client_t*/ client, string port_name, string port_type, JackPortFlags flags, uint buffer_size);
+
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern IntPtr /*jack_port_t*/ jack_port_by_id(IntPtr /*jack_client_t*/ client, uint /*jack_port_id_t*/ port_id);
+
+ ///
+ /// This returns a pointer to the memory area associated with the
+ /// specified port. For an output port, it will be a memory area
+ /// that can be written to; for an input port, it will be an area
+ /// containing the data from the port's connection(s), or
+ /// zero-filled. if there are multiple inbound connections, the data
+ /// will be mixed appropriately.
+ ///
+ ///
+ /// Caching output ports is DEPRECATED in Jack 2.0, due to some new optimization(like "pipelining").
+ /// Port buffers have to be retrieved in each callback for proper functioning.
+ ///
+ /// A pointer to the memory area associated with the specified port.
+ /// Port whose buffer is to be returned.
+ /// The number of frames to return.
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern IntPtr /*void */ jack_port_get_buffer(IntPtr /*jack_port_t*/ port, uint /*jack_nframes_t*/ nframes);
+
+ [DllImport(LIBRARY_FILENAME, EntryPoint = "jack_port_get_buffer")]
+ public static extern float[] jack_port_get_buffer_f(IntPtr /*jack_port_t*/ port, uint /*jack_nframes_t*/ nframes);
+
+ public static void jack_status_to_exception(Constants.JackStatus status)
+ {
+ switch (status)
+ {
+ case Constants.JackStatus.BackendError: throw new InvalidOperationException("Backend error");
+ case Constants.JackStatus.ClientZombie: throw new InvalidOperationException("Client zombified");
+ case Constants.JackStatus.Failure: throw new Exception("General failure");
+ case Constants.JackStatus.InitFailure: throw new Exception("Initialization failure");
+ case Constants.JackStatus.InvalidOption: throw new ArgumentOutOfRangeException("Invalid operation", (Exception)null);
+ case Constants.JackStatus.LoadFailure: throw new Exception("Load failure");
+ case Constants.JackStatus.NameNotUnique: throw new ArgumentException("must be unique", "JackClient.Name");
+ case Constants.JackStatus.NoSuchClient: throw new ArgumentException("no such client", "Client");
+ case Constants.JackStatus.ServerError: throw new ServerException();
+ case Constants.JackStatus.ServerFailed: throw new ServerException("unable to connect");
+ case Constants.JackStatus.ServerStarted: break;
+ case Constants.JackStatus.ShmFailure: throw new InsufficientMemoryException("unable to access shared memory");
+ case Constants.JackStatus.Success: break;
+ case Constants.JackStatus.VersionError: throw new VersionMismatchException();
+ }
+ }
+
+ ///
+ /// Remove the port from the client, disconnecting any existing connections.
+ ///
+ /// Client.
+ /// Port.
+ /// 0 if successful; nonzero otherwise
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int jack_port_unregister(IntPtr /*jack_client_t*/ client, IntPtr /*jack_port_t*/ port);
+
+ ///
+ /// The free function to be used on memory returned by jack_port_get_connections,
+ /// jack_port_get_all_connections, jack_get_ports and jack_get_internal_client_name functions.
+ /// This is MANDATORY on Windows when otherwise all nasty runtime version related crashes can occur.
+ /// Developers are strongly encouraged to use this function instead of the standard "free" function in new code.
+ ///
+ /// Value.
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern void jack_free(IntPtr value);
+
+ ///
+ /// Do not call this function; it is not implemented.
+ ///
+ /// Client on which to perform the operation.
+ [DllImport(LIBRARY_FILENAME), Obsolete("This function has never been implemented")]
+ public static extern void jack_off(IntPtr /*jack_client_t*/ client);
+
+ ///
+ /// Query the current transport state and position.
+ ///
+ /// This function is realtime-safe, and can be called from any
+ /// thread. If called from the process thread,
+ /// corresponds to the first frame of the
+ /// current cycle and the state returned is valid for the entire
+ /// cycle.
+ ///
+ /// the JACK client structure
+ ///
+ /// pointer to structure for returning current transport position;
+ /// ->valid will show which fields contain
+ /// valid data. If is NULL, do not return
+ /// position information.
+ ///
+ /// Current transport state.
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern JackTransportState jack_transport_query(IntPtr /*const jack_client_t*/ client, ref Structures.jack_position_t pos);
+
+ ///
+ /// Return an estimate of the current transport frame, including any
+ /// time elapsed since the last transport positional update.
+ ///
+ /// the JACK client structure
+ /// an estimate of the current transport frame
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern uint jack_get_current_transport_frame(IntPtr /*jack_client_t*/ client);
+
+ ///
+ /// Request a new transport position.
+ ///
+ /// May be called at any time by any client. The new position takes
+ /// effect in two process cycles.If there are slow-sync clients and
+ /// the transport is already rolling, it will enter the
+ /// ::JackTransportStarting state and begin invoking their @a
+ /// sync_callbacks until ready.This function is realtime-safe.
+ ///
+ ///
+ /// 0 if valid request, EINVAL if position structure rejected
+ /// client the JACK client structure
+ /// requested new transport position
+ ///
+ ///
+ [DllImport(LIBRARY_FILENAME)]
+ public static extern int jack_transport_reposition(IntPtr /*jack_client_t*/ client, Structures.jack_position_t position);
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Structures.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Structures.cs
new file mode 100644
index 0000000..c09cb1c
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Structures.cs
@@ -0,0 +1,138 @@
+//
+// Structures.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2020 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+namespace MBS.Audio.Jack.Internal
+{
+ internal class Structures
+ {
+ public struct jack_position_t
+ {
+ /* these four cannot be set from clients: the server sets them */
+ ///
+ /// unique ID
+ ///
+ public ulong /*jack_unique_t*/ unique_1;
+ ///
+ /// monotonic, free-rolling
+ ///
+ public ulong /*jack_time_t*/ usecs;
+ ///
+ /// current frame rate (per second)
+ ///
+ public uint /*jack_nframes_t*/ frame_rate;
+ ///
+ /// frame number, always present
+ ///
+ public uint /*jack_nframes_t*/ frame;
+
+ ///
+ /// which other fields are valid
+ ///
+ public Constants.JackPositionBits valid;
+
+ /* JackPositionBBT fields: */
+ ///
+ /// Current bar.
+ ///
+ public int bar;
+ ///
+ /// Current beat-within-bar.
+ ///
+ public int beat;
+ ///
+ /// Current tick-within-beat.
+ ///
+ public int tick;
+ public double bar_start_tick;
+
+ ///
+ /// Time signatue numerator.
+ ///
+ public float beats_per_bar;
+ ///
+ /// Timee signature denominator.
+ ///
+ public float beat_type;
+ public double ticks_per_beat;
+ public double beats_per_minute;
+
+ /* JackPositionTimecode fields: (EXPERIMENTAL: could change) */
+ ///
+ /// Current time in seconds.
+ ///
+ public double frame_time;
+ ///
+ /// Next sequential frame_time (unless repositioned).
+ ///
+ public double next_time;
+
+ /* JackBBTFrameOffset fields: */
+ ///
+ /// frame offset for the BBT fields (the given bar, beat, and tick
+ /// values actually refer to a time frame_offset frames
+ /// before the start of the cycle), should be assumed to be 0 if
+ /// JackBBTFrameOffset is not set.If JackBBTFrameOffset is set and
+ /// this value is zero, the BBT time refers to the first frame of
+ /// this cycle. If the value is positive, the BBT time refers to
+ /// a frame that many frames before the start of the cycle.
+ ///
+ public uint /*jack_nframes_t*/ bbt_offset;
+
+ /* JACK video positional data (experimental) */
+ ///
+ /// number of audio frames per video frame. Should be assumed
+ /// zero if JackAudioVideoRatio is not set. If
+ /// JackAudioVideoRatio is set and the value is zero, no video
+ /// data exists within the JACK graph
+ ///
+ public float audio_frames_per_video_frame;
+
+ ///
+ /// audio frame at which the first video frame in this cycle
+ /// occurs. Should be assumed to be 0 if JackVideoFrameOffset
+ /// is not set. If JackVideoFrameOffset is set, but the value is
+ /// zero, there is no video frame within this cycle.
+ ///
+ public uint /*jack_nframes_t*/ video_offset;
+
+ ///
+ /// For binary compatibility, new fields should be allocated from
+ /// this padding area with new valid bits controlling access, so
+ /// the existing structure size and offsets are preserved.
+ ///
+ public int padding1 /*[7]*/;
+ public int padding2 /*[7]*/;
+
+
+ public int padding3 /*[7]*/;
+ public int padding4 /*[7]*/;
+ public int padding5 /*[7]*/;
+ public int padding6 /*[7]*/;
+ public int padding7 /*[7]*/;
+
+ ///
+ /// When ( == ) the
+ /// contents are consistent.
+ ///
+ public ulong /*jack_unique_t*/ unique_2;
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackAudioEngine.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackAudioEngine.cs
new file mode 100644
index 0000000..71ee093
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackAudioEngine.cs
@@ -0,0 +1,46 @@
+//
+// JackAudioEngine.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2020 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+namespace MBS.Audio.Jack
+{
+ public class JackAudioEngine : AudioEngine
+ {
+ public override Guid ID => new Guid("{658df958-7d57-482d-ac14-caca38e8d249}");
+ public override string Title => "JACK";
+
+ public override AudioDevice DefaultInputDevice => throw new NotImplementedException();
+
+ public override AudioDevice DefaultOutputDevice => throw new NotImplementedException();
+
+ protected override AudioStream CreateAudioStreamInternal(AudioDevice inputDevice, short inputChannelCount, AudioSampleFormat inputSampleFormat, double inputSuggestedLatency, AudioDevice outputDevice, short outputChannelCount, AudioSampleFormat outputSampleFormat, double outputSuggestedLatency, double sampleRate, int framesPerBuffer, AudioStreamFlags flags)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override void InitializeInternal()
+ {
+ }
+
+ protected override void TerminateInternal()
+ {
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackClient.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackClient.cs
new file mode 100644
index 0000000..bb1d0c1
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackClient.cs
@@ -0,0 +1,277 @@
+//
+// JackClient.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2020 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+namespace MBS.Audio.Jack
+{
+ public class JackClient
+ {
+ public JackTransport Transport { get; private set; } = null;
+
+ public JackClient()
+ {
+ _registration_callback_d = new Internal.Delegates.JackPortRegistrationCallback(_registration_callback);
+ _process_callback_d = new Internal.Delegates.JackProcessCallback(_process_callback);
+ Transport = new JackTransport(this);
+ }
+ public JackClient(string name) : this()
+ {
+ if (name.Length > MaximumNameLength)
+ {
+
+ }
+ Name = name;
+ Transport = new JackTransport(this);
+ }
+
+ private int? _MaximumNameLength = null;
+ public int MaximumNameLength
+ {
+ get
+ {
+ if (_MaximumNameLength == null)
+ {
+ _MaximumNameLength = Internal.Methods.jack_client_name_size();
+ }
+ return _MaximumNameLength.GetValueOrDefault(0);
+ }
+ }
+
+ public IntPtr Handle { get; private set; } = IntPtr.Zero;
+ public string Name { get; private set; } = null;
+
+ ///
+ /// Gets the actual name of the client as assigned by the JACK server.
+ ///
+ /// The name of the client.
+ public string ClientName
+ {
+ get
+ {
+ if (Handle == IntPtr.Zero)
+ return null;
+
+ IntPtr hName = Internal.Methods.jack_get_client_name(Handle);
+ string value = System.Runtime.InteropServices.Marshal.PtrToStringAuto(hName);
+
+ Internal.Methods.jack_free(hName);
+ return value;
+ }
+ }
+
+ ///
+ /// Open an external client session with a JACK server. Clients may
+ /// choose which of several servers to connect, and control
+ /// whether and how to start the server automatically, if it was not
+ /// already running. There is also an option for JACK to generate a
+ /// unique client name, when necessary.
+ ///
+ /// for opening an external client.
+ public void Open(JackOpenOptions options = JackOpenOptions.None)
+ {
+ string clientName = Name;
+ if (clientName == null)
+ {
+ throw new ArgumentNullException(nameof(Name));
+ }
+
+ Internal.Constants.JackStatus status = Internal.Constants.JackStatus.Success;
+ IntPtr handle = Internal.Methods.jack_client_open(clientName, options, ref status);
+
+ // set up the event handlers
+ // Internal.Methods.jack_set_process_callback(handle, _process_callback_d, IntPtr.Zero);
+ Internal.Methods.jack_set_port_registration_callback(handle, _registration_callback_d, IntPtr.Zero);
+
+ Internal.Methods.jack_status_to_exception(status);
+ Handle = handle;
+ }
+
+ private Internal.Delegates.JackPortRegistrationCallback _registration_callback_d;
+ private void _registration_callback(uint /*jack_port_id_t*/ port, int register, IntPtr arg)
+ {
+ IntPtr hPort = Internal.Methods.jack_port_by_id(Handle, port);
+ if (hPort != IntPtr.Zero)
+ {
+ JackPortRegisteredEventArgs e = new JackPortRegisteredEventArgs(hPort);
+ OnPortRegistered(e);
+ }
+ }
+
+ private Internal.Delegates.JackProcessCallback _process_callback_d;
+ private int _process_callback(uint nframes, IntPtr arg)
+ {
+ JackProcessEventArgs ee = new JackProcessEventArgs(nframes, arg);
+ OnProcess(ee);
+ return ee.ReturnValue;
+ }
+
+ ///
+ /// Tell the Jack server that the program is ready to start
+ /// processing audio.
+ ///
+ public void Activate()
+ {
+ Internal.Methods.jack_activate(Handle);
+ }
+
+ public event EventHandler Process;
+ protected virtual void OnProcess(JackProcessEventArgs e)
+ {
+ Process?.Invoke(this, e);
+ }
+
+ public event EventHandler PortRegistered;
+ protected virtual void OnPortRegistered(JackPortRegisteredEventArgs e)
+ {
+ PortRegistered?.Invoke(this, e);
+ }
+
+ public void Connect(string sourcePortName, string destinationPortName)
+ {
+ if (Handle == IntPtr.Zero)
+ {
+ throw new InvalidOperationException("please Open() the JackClient first!");
+ }
+ Internal.Methods.jack_connect(Handle, sourcePortName, destinationPortName);
+ }
+
+ ///
+ /// Create a new port for the client. This is an object used for moving
+ /// data of any type in or out of the client. Ports may be connected
+ /// in various ways.
+ ///
+ /// Each port has a short name. The port's full name contains the name
+ /// of the client concatenated with a colon (:) followed by its short
+ /// name. The jack_port_name_size() is the maximum length of this full
+ /// name. Exceeding that will cause the port registration to fail and
+ /// return NULL.
+ ///
+ /// The @a port_name must be unique among all ports owned by this client.
+ /// If the name is not unique, the registration will fail.
+ ///
+ /// All ports have a type, which may be any non-NULL and non-zero
+ /// length string, passed as an argument. Some port types are built
+ /// into the JACK API, currently only JACK_DEFAULT_AUDIO_TYPE.
+ ///
+ ///
+ /// Non-empty short name for the new port, not including the leading
+ /// "client_name:". Must be unique within the client.
+ ///
+ ///
+ /// Port type name. If longer than
+ /// , only that many
+ /// characters are significant.
+ ///
+ /// Flags.
+ ///
+ /// Must be non-zero if this is not a built-in port_type.
+ /// Otherwise, it is ignored
+ ///
+ ///
+ /// A or ,
+ /// depending on the value of , on success;
+ /// otherwise, .
+ ///
+ private JackPort RegisterPort(string portName, string portType, JackPortFlags flags, long bufferSize)
+ {
+ IntPtr handle = Internal.Methods.jack_port_register(Handle, portName, portType, flags, (uint)bufferSize);
+ if (handle == IntPtr.Zero)
+ {
+ throw new InvalidOperationException("jack client not valid");
+ }
+
+ if ((flags & JackPortFlags.IsInput) == JackPortFlags.IsInput)
+ {
+ JackInputPort port = new JackInputPort(this, handle, portName, portType, bufferSize, (flags & JackPortFlags.CanMonitor) == JackPortFlags.CanMonitor, (flags & JackPortFlags.IsPhysical) == JackPortFlags.IsPhysical, (flags & JackPortFlags.IsTerminal) == JackPortFlags.IsTerminal);
+ return port;
+ }
+ else if ((flags & JackPortFlags.IsOutput) == JackPortFlags.IsOutput)
+ {
+ JackOutputPort port = new JackOutputPort(this, handle, portName, portType, bufferSize, (flags & JackPortFlags.CanMonitor) == JackPortFlags.CanMonitor, (flags & JackPortFlags.IsPhysical) == JackPortFlags.IsPhysical, (flags & JackPortFlags.IsTerminal) == JackPortFlags.IsTerminal);
+ return port;
+ }
+ throw new InvalidOperationException("port must be either input or output");
+ }
+
+ ///
+ /// Create a new input port for the client. This is an object used for
+ /// moving data of any type into the client. Ports may be connected
+ /// in various ways.
+ ///
+ /// Each port has a short name. The port's full name contains the name
+ /// of the client concatenated with a colon (:) followed by its short
+ /// name. The jack_port_name_size() is the maximum length of this full
+ /// name. Exceeding that will cause the port registration to fail and
+ /// return NULL.
+ ///
+ /// The must be unique among all ports
+ /// owned by this client. If the name is not unique, the registration
+ /// will fail.
+ ///
+ /// All ports have a type, which may be any non-NULL and non-zero
+ /// length string, passed as an argument. Some port types are built
+ /// into the JACK API, currently only
+ /// and
+ /// .
+ ///
+ ///
+ /// Non-empty short name for the new port, not including the leading
+ /// "client_name:". Must be unique within the client.
+ ///
+ ///
+ /// Port type name. If longer than
+ /// , only that many
+ /// characters are significant.
+ ///
+ /// Flags.
+ ///
+ /// Must be non-zero if this is not a built-in port_type.
+ /// Otherwise, it is ignored
+ ///
+ ///
+ /// A or ,
+ /// depending on the value of , on success;
+ /// otherwise, .
+ ///
+ public JackInputPort RegisterInput(string portName, string portType = null, long bufferSize = 0, bool isMonitor = false, bool isPhysical = false, bool isTerminal = false)
+ {
+ JackPortFlags flags = JackPortFlags.IsInput;
+ if (isMonitor) flags |= JackPortFlags.CanMonitor;
+ if (isPhysical) flags |= JackPortFlags.IsPhysical;
+ if (isTerminal) flags |= JackPortFlags.IsTerminal;
+ if (portType == null) portType = JackPort.DefaultPortType;
+ return (JackInputPort)RegisterPort(portName, portType, flags, bufferSize);
+ }
+ public JackOutputPort RegisterOutput(string portName, string portType = null, long bufferSize = 0, bool isMonitor = false, bool isPhysical = false, bool isTerminal = false)
+ {
+ JackPortFlags flags = JackPortFlags.IsOutput;
+ if (isMonitor) flags |= JackPortFlags.CanMonitor;
+ if (isPhysical) flags |= JackPortFlags.IsPhysical;
+ if (isTerminal) flags |= JackPortFlags.IsTerminal;
+ if (portType == null) portType = JackPort.DefaultPortType;
+ return (JackOutputPort)RegisterPort(portName, portType, flags, bufferSize);
+ }
+
+ public void UnregisterPort(JackPort port)
+ {
+ Internal.Methods.jack_port_unregister(Handle, port.Handle);
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackException.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackException.cs
new file mode 100644
index 0000000..c2f287d
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackException.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace MBS.Audio.Jack
+{
+ ///
+ /// The base class for all JACK s that are not
+ /// provided by the un
+ ///
+ public class JackException : Exception
+ {
+ public JackException()
+ {
+ }
+
+ protected JackException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+
+ public JackException(string message) : base(message)
+ {
+ }
+
+ public JackException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackInputPort.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackInputPort.cs
new file mode 100644
index 0000000..8d4e765
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackInputPort.cs
@@ -0,0 +1,40 @@
+//
+// JackInputPort.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2020 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+namespace MBS.Audio.Jack
+{
+ public class JackInputPort : JackPort
+ {
+ public JackInputPort(JackClient client, IntPtr handle, string portName, string portType, long bufferSize, bool isMonitor, bool isPhysical, bool isTerminal) : base(client, handle, portName, portType, bufferSize, true, false, isMonitor, isPhysical, isTerminal)
+ {
+ }
+
+ public float[] Read(long frameCount)
+ {
+ uint fc = (uint)frameCount;
+ IntPtr hBuffer = Internal.Methods.jack_port_get_buffer(Handle, fc);
+
+ float[] buffer = new float[fc];
+ System.Runtime.InteropServices.Marshal.Copy(hBuffer, buffer, 0, (int)fc);
+ return buffer;
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackOpenOptions.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackOpenOptions.cs
new file mode 100644
index 0000000..928616c
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackOpenOptions.cs
@@ -0,0 +1,48 @@
+//
+// JackOpenOptions.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2020 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+namespace MBS.Audio.Jack
+{
+ public enum JackOpenOptions
+ {
+ None = 0x00,
+ ///
+ /// Do not automatically start the JACK server when it is not
+ /// already running. This option is always selected if
+ /// $JACK_NO_START_SERVER is defined in the calling process
+ /// environment.
+ ///
+ NoStartServer = 0x01,
+ ///
+ /// Use the exact client name requested. Otherwise, JACK
+ /// automatically generates a unique one, if needed.
+ ///
+ UseExactName = 0x02,
+ ///
+ /// Open with optional server_name parameter.
+ ///
+ ServerName = 0x04,
+ ///
+ /// Pass a SessionID Token this allows the sessionmanager to identify the client again.
+ ///
+ SessionID = 0x20
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackOutputPort.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackOutputPort.cs
new file mode 100644
index 0000000..54f25ff
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackOutputPort.cs
@@ -0,0 +1,38 @@
+//
+// JackOutputPort.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2020 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+namespace MBS.Audio.Jack
+{
+ public class JackOutputPort : JackPort
+ {
+ public JackOutputPort(JackClient client, IntPtr handle, string portName, string portType, long bufferSize, bool isMonitor, bool isPhysical, bool isTerminal) : base(client, handle, portName, portType, bufferSize, false, true, isMonitor, isPhysical, isTerminal)
+ {
+ }
+
+ public void Write(float[] buffer, long frameCount)
+ {
+ uint fc = (uint)frameCount;
+ IntPtr hBuffer = Internal.Methods.jack_port_get_buffer(Handle, fc);
+
+ System.Runtime.InteropServices.Marshal.Copy(buffer, 0, hBuffer, (int)fc);
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPort.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPort.cs
new file mode 100644
index 0000000..2ca1f76
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPort.cs
@@ -0,0 +1,91 @@
+//
+// JackPort.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2020 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+namespace MBS.Audio.Jack
+{
+ public abstract class JackPort
+ {
+ public JackClient Client { get; private set; } = null;
+ public IntPtr Handle { get; private set; } = IntPtr.Zero;
+
+ public string Name { get; private set; } = null;
+ public string Type { get; private set; } = null;
+ public long BufferSize { get; private set; } = 0;
+
+ public static string DefaultPortType = JackPortTypes.DefaultAudioType;
+
+ ///
+ /// Indicates that the port can receive data.
+ ///
+ /// true if the port can receive data; otherwise, false.
+ public bool IsInput { get; private set; } = false;
+ ///
+ /// Indicates that data can be read from the port.
+ ///
+ /// true if data can be read from the port; otherwise, false.
+ public bool IsOutput { get; private set; } = false;
+ ///
+ /// Indicates that a call on this port to
+ /// makes sense.
+ ///
+ /// Precisely what this means is dependent on the client. A typical
+ /// result of it being called with TRUE as the second argument is
+ /// that data that would be available from an output port (with
+ /// set) is sent to a physical output connector
+ /// as well, so that it can be heard/seen/whatever.
+ ///
+ /// Clients that do not control physical interfaces
+ /// should never create ports with this bit set.
+ ///
+ public bool CanMonitor { get; private set; } = false;
+ ///
+ /// Indicates that the port corresponds to some kind of physical I/O connector.
+ ///
+ /// true if is physical; otherwise, false.
+ public bool IsPhysical { get; private set; } = false;
+ ///
+ /// For an input port, indicates that data received by the port will
+ /// not be passed on or made available at any other port.
+ ///
+ /// For an output port, indicates that data available at the port
+ /// does not originate from any other port.
+ ///
+ /// Audio synthesizers, I/O hardware interface clients, HDR
+ /// systems are examples of clients that would set this flag for
+ /// their ports.
+ ///
+ public bool IsTerminal { get; private set; } = false;
+
+ internal JackPort(JackClient client, IntPtr handle, string portName, string portType, long bufferSize, bool isInput, bool isOutput, bool canMonitor, bool isPhysical, bool isTerminal)
+ {
+ Client = client;
+ Handle = handle;
+ Name = portName;
+ Type = portType;
+ BufferSize = bufferSize;
+ IsInput = isInput;
+ IsOutput = isOutput;
+ CanMonitor = canMonitor;
+ IsPhysical = isPhysical;
+ IsTerminal = isTerminal;
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPortFlags.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPortFlags.cs
new file mode 100644
index 0000000..16091a3
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPortFlags.cs
@@ -0,0 +1,73 @@
+//
+// JackPortFlags.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2020 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+namespace MBS.Audio.Jack
+{
+ ///
+ /// A port has a set of flags that are formed by AND-ing together the
+ /// desired values from this enum. The flags and
+ /// are mutually exclusive and it is an error to
+ /// use them both.
+ ///
+ [Flags()]
+ public enum JackPortFlags
+ {
+ ///
+ /// The port can receive data.
+ ///
+ IsInput = 0x1,
+ ///
+ /// Data can be read from the port.
+ ///
+ IsOutput = 0x2,
+ ///
+ /// The port corresponds to some kind of physical I/O connector.
+ ///
+ IsPhysical = 0x4,
+
+ ///
+ /// Indicates that a call on this port to
+ /// makes sense.
+ ///
+ /// Precisely what this means is dependent on the client. A typical
+ /// result of it being called with TRUE as the second argument is
+ /// that data that would be available from an output port (with
+ /// set) is sent to a physical output connector
+ /// as well, so that it can be heard/seen/whatever.
+ ///
+ /// Clients that do not control physical interfaces
+ /// should never create ports with this bit set.
+ ///
+ CanMonitor = 0x8,
+ ///
+ /// For an input port, indicates that data received by the port will
+ /// not be passed on or made available at any other port.
+ ///
+ /// For an output port, indicates that data available at the port
+ /// does not originate from any other port.
+ ///
+ /// Audio synthesizers, I/O hardware interface clients, HDR
+ /// systems are examples of clients that would set this flag for
+ /// their ports.
+ ///
+ IsTerminal = 0x10,
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPortRegisteredEvent.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPortRegisteredEvent.cs
new file mode 100644
index 0000000..0c18ed6
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPortRegisteredEvent.cs
@@ -0,0 +1,13 @@
+using System;
+namespace MBS.Audio.Jack
+{
+ public class JackPortRegisteredEventArgs : EventArgs
+ {
+ public IntPtr Handle { get; private set; } = IntPtr.Zero;
+
+ public JackPortRegisteredEventArgs(IntPtr handle)
+ {
+ Handle = handle;
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPortTypes.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPortTypes.cs
new file mode 100644
index 0000000..fafc991
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPortTypes.cs
@@ -0,0 +1,29 @@
+//
+// JackPortTypes.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2020 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+namespace MBS.Audio.Jack
+{
+ public static class JackPortTypes
+ {
+ public const string DefaultAudioType = "32 bit float mono audio";
+ public const string DefaultMidiType = "8 bit raw midi";
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackProcessEvent.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackProcessEvent.cs
new file mode 100644
index 0000000..759468c
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackProcessEvent.cs
@@ -0,0 +1,16 @@
+using System;
+namespace MBS.Audio.Jack
+{
+ public class JackProcessEventArgs : EventArgs
+ {
+ public long FrameCount { get; private set; } = 0;
+ public IntPtr UserData { get; private set; } = IntPtr.Zero;
+ public int ReturnValue { get; set; } = 0;
+
+ public JackProcessEventArgs(long nframes, IntPtr arg)
+ {
+ FrameCount = nframes;
+ UserData = arg;
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackTransport.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackTransport.cs
new file mode 100644
index 0000000..b15298c
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackTransport.cs
@@ -0,0 +1,146 @@
+//
+// JackTransport.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2020 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+namespace MBS.Audio.Jack
+{
+ public class JackTransport : ITransport
+ {
+ public JackClient Client { get; private set; }
+
+ internal JackTransport(JackClient client)
+ {
+ Client = client;
+ }
+
+ public JackTransportState TransportState
+ {
+ get
+ {
+ Internal.Structures.jack_position_t pos = new Internal.Structures.jack_position_t();
+ JackTransportState state = Internal.Methods.jack_transport_query(Client.Handle, ref pos);
+
+ return state;
+ }
+ }
+
+ public AudioTimestamp Timestamp
+ {
+ get
+ {
+ Internal.Structures.jack_position_t pos = new Internal.Structures.jack_position_t();
+ JackTransportState state = Internal.Methods.jack_transport_query(Client.Handle, ref pos);
+
+ return AudioTimestamp.FromSamples((long)pos.frame, (int)pos.frame_rate, pos.bar, pos.beat, pos.tick, pos.beats_per_bar, pos.ticks_per_beat);
+ }
+ set
+ {
+ Internal.Structures.jack_position_t pos = new Internal.Structures.jack_position_t();
+ Internal.Methods.jack_transport_query(Client.Handle, ref pos);
+
+ pos.bar = value.Bars;
+ pos.beat = value.Beats;
+ pos.tick = value.Ticks;
+
+ pos.frame_time = (uint)(value.TotalSamples);
+ pos.valid = Internal.Constants.JackPositionBits.PositionTimecode;
+ Internal.Methods.jack_transport_reposition(Client.Handle, pos);
+ }
+ }
+
+ public bool IsPlaying => State != AudioPlayerState.Stopped;
+
+ public AudioPlayerState State
+ {
+ get
+ {
+ switch (TransportState)
+ {
+ case JackTransportState.Looping:
+ case JackTransportState.Rolling:
+ {
+ return AudioPlayerState.Playing;
+ }
+ case JackTransportState.NetworkStarting:
+ case JackTransportState.Starting:
+ {
+ return AudioPlayerState.Stopped;
+ }
+ case JackTransportState.Stopped:
+ {
+ if (_paused)
+ return AudioPlayerState.Paused;
+ return AudioPlayerState.Stopped;
+ }
+ }
+ return AudioPlayerState.Stopped;
+ }
+ }
+
+ private bool _paused = false;
+
+ public event EventHandler StateChanged;
+
+ ///
+ /// Start the JACK transport rolling. Any client can make this
+ /// request at any time. It takes effect no sooner than the next
+ /// process cycle, perhaps later if there are slow-sync clients.
+ /// This function is realtime-safe.
+ ///
+ public void Play()
+ {
+ _paused = false;
+ Internal.Methods.jack_transport_start(Client.Handle);
+ }
+ ///
+ /// Stop the JACK transport. Any client can make this request at any
+ /// time. It takes effect no sooner than the next process cycle,
+ /// perhaps later if there are slow-sync clients. This function is
+ /// realtime-safe.
+ ///
+ public void Pause()
+ {
+ _paused = !_paused;
+ Internal.Methods.jack_transport_stop(Client.Handle);
+ }
+ ///
+ /// Stop the JACK transport and reset the position to the beginning.
+ /// Any client can make this request at any time. It takes effect no
+ /// sooner than the next process cycle, perhaps later if there are
+ /// slow-sync clients. This function is realtime-safe.
+ ///
+ public void Stop()
+ {
+ Pause();
+ Seek(0);
+
+ _paused = false;
+ }
+
+ public void Seek(long totalSamples)
+ {
+ // TODO: implement this
+ Internal.Structures.jack_position_t pos = new Internal.Structures.jack_position_t();
+ pos.valid = Internal.Constants.JackPositionBits.PositionTimecode;
+ pos.frame_time = totalSamples;
+ Internal.Methods.jack_transport_reposition(Client.Handle, pos);
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackTransportState.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackTransportState.cs
new file mode 100644
index 0000000..250f1d4
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackTransportState.cs
@@ -0,0 +1,51 @@
+//
+// JackTransportState.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2020 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+namespace MBS.Audio.Jack
+{
+ ///
+ /// Transport states.
+ ///
+ public enum JackTransportState
+ {
+ /* the order matters for binary compatibility */
+ ///
+ /// Transport is halted.
+ ///
+ Stopped = 0,
+ ///
+ /// Transport is playing.
+ ///
+ Rolling = 1,
+ ///
+ /// Ignored.
+ ///
+ Looping = 2,
+ ///
+ /// Waiting for sync ready.
+ ///
+ Starting = 3,
+ ///
+ /// Waiting for sync ready on the network.
+ ///
+ NetworkStarting = 4,
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/Internal/Constants.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/Internal/Constants.cs
new file mode 100644
index 0000000..fadcdb9
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/Internal/Constants.cs
@@ -0,0 +1,11 @@
+using System;
+namespace MBS.Audio.Jack.Networking.Internal
+{
+ internal static class Constants
+ {
+ public static readonly System.Net.IPAddress DEFAULT_MULTICAST_IP = System.Net.IPAddress.Parse("225.3.19.154");
+ public const int DEFAULT_PORT = 19000;
+ public const int DEFAULT_MTU = 1500;
+ public const int MASTER_NAME_SIZE = 256;
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/Internal/Methods.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/Internal/Methods.cs
new file mode 100644
index 0000000..d4e4567
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/Internal/Methods.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace MBS.Audio.Jack.Networking.Internal
+{
+ internal static class Methods
+ {
+ [DllImport(Jack.Internal.Methods.LIBRARY_FILENAME)]
+ public static extern IntPtr /*jack_net_slave_t*/ jack_net_slave_open(string ip, int port, string name, ref Internal.Structures.jack_slave_t request, ref Internal.Structures.jack_master_t result);
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/Internal/Structures.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/Internal/Structures.cs
new file mode 100644
index 0000000..f657641
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/Internal/Structures.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace MBS.Audio.Jack.Networking.Internal
+{
+ internal static class Structures
+ {
+ public struct jack_slave_t
+ {
+ public int audio_input; // from master or to slave (-1 to take master audio physical inputs)
+ public int audio_output; // to master or from slave (-1 to take master audio physical outputs)
+ public int midi_input; // from master or to slave (-1 to take master MIDI physical inputs)
+ public int midi_output; // to master or from slave (-1 to take master MIDI physical outputs)
+ public int mtu; // network Maximum Transmission Unit
+ public int time_out; // in second, -1 means infinite
+ public int encoder; // encoder type (one of JackNetEncoder)
+ public int kbps; // KB per second for CELT or OPUS codec
+ public int latency; // network latency in number of buffers
+ }
+
+ public struct jack_master_t
+ {
+ public int audio_input; // master audio physical outputs (-1 to take slave wanted audio inputs)
+ public int audio_output; // master audio physical inputs (-1 to take slave wanted audio outputs)
+ public int midi_input; // master MIDI physical outputs (-1 to take slave wanted MIDI inputs)
+ public int midi_output; // master MIDI physical inputs (-1 to take slave wanted MIDI outputs)
+ public uint /*jack_nframes_t*/ buffer_size; // master buffer size
+ public uint /*jack_nframes_t*/ sample_rate; // master sample rate
+ [MarshalAs(UnmanagedType.LPWStr, SizeConst = Constants.MASTER_NAME_SIZE)]
+ public string master_name; // master machine name
+ int time_out; // in second, -1 means infinite
+ int partial_cycle; // if 'true', partial buffers will be used
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/JackNetworkSlave.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/JackNetworkSlave.cs
new file mode 100644
index 0000000..f1c1304
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/JackNetworkSlave.cs
@@ -0,0 +1,20 @@
+using System;
+namespace MBS.Audio.Jack.Networking
+{
+ public class JackNetworkSlave
+ {
+ public IntPtr Handle { get; private set; } = IntPtr.Zero;
+
+ public void Open(System.Net.IPAddress ipAddress, int port, string name)
+ {
+ Internal.Structures.jack_slave_t request = new Internal.Structures.jack_slave_t();
+ Internal.Structures.jack_master_t result = new Internal.Structures.jack_master_t();
+
+ IntPtr handle = Internal.Methods.jack_net_slave_open(ipAddress.ToString(), port, name, ref request, ref result);
+ if (handle != IntPtr.Zero)
+ {
+ Handle = handle;
+ }
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/ServerException.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/ServerException.cs
new file mode 100644
index 0000000..027b599
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/ServerException.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace MBS.Audio.Jack
+{
+ public class ServerException : JackException
+ {
+ public ServerException()
+ {
+ }
+
+ protected ServerException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+
+ public ServerException(string message) : base(message)
+ {
+ }
+
+ public ServerException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/VersionMismatchException.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/VersionMismatchException.cs
new file mode 100644
index 0000000..c143aa6
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/VersionMismatchException.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace MBS.Audio.Jack
+{
+ public class VersionMismatchException : JackException
+ {
+ public VersionMismatchException() : base("client protocol version mismatch")
+ {
+ }
+
+ protected VersionMismatchException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+
+ public VersionMismatchException(string message) : base(message)
+ {
+ }
+
+ public VersionMismatchException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/MBS.Audio.csproj b/audio-dotnet/src/audio-dotnet/MBS.Audio/MBS.Audio.csproj
new file mode 100644
index 0000000..1ba1d55
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/MBS.Audio.csproj
@@ -0,0 +1,118 @@
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {E0897B7B-617A-4709-A4C6-FC0F6B441B2A}
+ Library
+ Properties
+ MBS.Audio
+ MBS.Audio
+ v4.0
+ 512
+
+
+
+ true
+ full
+ false
+ ..\..\Output\Debug
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ ..\..\Output\Release
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {30467E5C-05BC-4856-AADC-13906EF4CADD}
+ UniversalEditor.Essential
+
+
+ {BE4D0BA3-0888-42A5-9C09-FC308A4509D2}
+ UniversalEditor.Plugins.Multimedia
+
+
+ {2D4737E6-6D95-408A-90DB-8DFF38147E85}
+ UniversalEditor.Core
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Metronome/Metronome.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Metronome/Metronome.cs
new file mode 100644
index 0000000..9c7cda8
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/Metronome/Metronome.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using MBS.Audio.PortAudio;
+using UniversalEditor;
+using UniversalEditor.Accessors;
+using UniversalEditor.DataFormats.Multimedia.Audio.Waveform.MicrosoftWave;
+using UniversalEditor.ObjectModels.Multimedia.Audio.Waveform;
+
+namespace MBS.Audio.Metronome
+{
+ public class Metronome
+ {
+ public AudioEngine AudioEngine { get; } = null;
+
+ public string AudioSamplePath { get; set; } = String.Empty;
+
+ private Dictionary waves = new Dictionary();
+
+ private double mvarTempo = 120.0;
+ public double Tempo { get { return mvarTempo; } set { mvarTempo = value; } }
+
+ private System.Threading.Thread _thread = null;
+
+ public bool IsPlaying
+ {
+ get { return (_thread != null && _thread.IsAlive); }
+ }
+
+ public void Start()
+ {
+ if (_thread != null)
+ {
+ _thread.Abort();
+ _thread = null;
+ }
+ _thread = new System.Threading.Thread(_thread_ThreadStart);
+ _thread.Start();
+ }
+ public void Stop()
+ {
+ if (_thread == null) return;
+ _thread.Abort();
+ _thread = null;
+ }
+
+
+ private void _thread_ThreadStart()
+ {
+ WaveformAudioObjectModel click = waves["Click"];
+
+ PortAudioStream stream = new PortAudioStream(AudioEngine.DefaultInputDevice as PortAudioDevice, 2, AudioSampleFormat.Int16, AudioEngine.DefaultOutputDevice as PortAudioDevice, click.Header.ChannelCount, AudioSampleFormat.Int16, click.Header.SampleRate * click.Header.ChannelCount, 0, AudioStreamFlags.ClipOff);
+
+ WaveformAudioObjectModel one = waves["One"];
+ WaveformAudioObjectModel two = waves["Two"];
+ WaveformAudioObjectModel three = waves["Three"];
+ WaveformAudioObjectModel four = waves["Four"];
+
+ WaveformAudioObjectModel[] countoffs = new WaveformAudioObjectModel[]
+ {
+ one,
+ null,
+ two,
+ null,
+ one,
+ two,
+ three,
+ four
+ };
+
+ // 1/120 minutes per beat =
+ double bpm = (1000 - (mvarTempo * ((double)500 / (double)120)));
+ int ms = (int)bpm;
+
+ int icountoff = 0;
+
+ while (true)
+ {
+ // short[] rawSamples = (click.RawSamples.Clone() as short[]);
+ short[] rawSamples = click.RawSamples.RawData;
+
+ if (icountoff < countoffs.Length)
+ {
+ WaveformAudioObjectModel countoff = countoffs[icountoff];
+ if (countoff != null)
+ {
+ rawSamples = countoff.RawSamples.RawData;
+ /*
+ // mix the countoff into the click
+ if (countoff.RawSamples.Length > click.RawSamples.Length)
+ {
+ rawSamples = (countoff.RawSamples.Clone() as short[]);
+ for (int i = 0; i < click.RawSamples.Length; i++)
+ {
+ rawSamples[i] = (short)((click.RawSamples[i] + rawSamples[i]) - ((click.RawSamples[i] + rawSamples[i]) / short.MaxValue));
+ }
+ }
+ else
+ {
+ for (int i = 0; i < rawSamples.Length; i++)
+ {
+ rawSamples[i] = (short)((countoff.RawSamples[i] + rawSamples[i]) - ((countoff.RawSamples[i] + rawSamples[i]) / short.MaxValue));
+ }
+ }
+ */
+ }
+ }
+
+ stream.Write(rawSamples);
+ OnTick(EventArgs.Empty);
+
+ System.Threading.Thread.Sleep(ms - 30);
+
+ if (icountoff <= countoffs.Length) icountoff++;
+ }
+ }
+
+ public event EventHandler Tick;
+ protected virtual void OnTick(EventArgs e)
+ {
+ if (Tick != null) Tick(this, e);
+ }
+
+ public Metronome(string path, double tempo = 120.0)
+ {
+ string[] FileNames = new string[]
+ {
+ path + System.IO.Path.DirectorySeparatorChar.ToString() + "Click.wav",
+ path + System.IO.Path.DirectorySeparatorChar.ToString() + "One.wav",
+ path + System.IO.Path.DirectorySeparatorChar.ToString() + "Two.wav",
+ path + System.IO.Path.DirectorySeparatorChar.ToString() + "Three.wav",
+ path + System.IO.Path.DirectorySeparatorChar.ToString() + "Four.wav"
+ };
+
+ foreach (string filename in FileNames)
+ {
+ string filetitle = System.IO.Path.GetFileNameWithoutExtension(filename);
+ WaveformAudioObjectModel wave = new WaveformAudioObjectModel();
+ MicrosoftWaveDataFormat wav = new MicrosoftWaveDataFormat();
+ Document.Load(wave, wav, new FileAccessor(filename, false, false), true);
+
+ waves.Add(filetitle, wave);
+ }
+
+ WaveformAudioObjectModel one = waves["One"];
+ WaveformAudioObjectModel two = waves["Two"];
+ WaveformAudioObjectModel three = waves["Three"];
+ WaveformAudioObjectModel four = waves["Four"];
+ WaveformAudioObjectModel click = waves["Click"];
+ WaveformAudioObjectModel[] countoffs = new WaveformAudioObjectModel[] { one, two, three, four };
+
+ foreach (WaveformAudioObjectModel countoff in countoffs)
+ {
+ short[] rawSamples = null;
+ if (countoff.RawSamples.Length > click.RawSamples.Length)
+ {
+ rawSamples = countoff.RawSamples.RawData;
+ for (int i = 0; i < click.RawSamples.Length; i++)
+ {
+ rawSamples[i] = (short)((click.RawSamples[i] + rawSamples[i]) - ((click.RawSamples[i] + rawSamples[i]) / (short.MaxValue + 1)));
+ }
+ }
+ else
+ {
+ rawSamples = (click.RawSamples.Clone() as short[]);
+ for (int i = 0; i < rawSamples.Length; i++)
+ {
+ rawSamples[i] = (short)((countoff.RawSamples[i] + rawSamples[i]) - ((countoff.RawSamples[i] + rawSamples[i]) / (short.MaxValue + 1)));
+ }
+ countoff.RawSamples = new WaveformAudioSamples(rawSamples);
+ }
+ }
+
+ AudioSamplePath = path;
+ mvarTempo = tempo;
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioDevice.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioDevice.cs
new file mode 100644
index 0000000..fb3bca8
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioDevice.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio.PortAudio
+{
+ public class PortAudioDevice : AudioDevice
+ {
+ internal PortAudioDevice(int handle)
+ {
+ Handle = handle;
+
+ Internal.PortAudio.Structures.PaDeviceInfo devinfo = Internal.PortAudio.Methods.Pa_GetDeviceInfo(handle);
+ _maximumInputChannels = devinfo.maxInputChannels;
+ _maximumOutputChannels = devinfo.maxOutputChannels;
+ _defaultSampleRate = devinfo.defaultSampleRate;
+ _defaultLowInputLatency = devinfo.defaultLowInputLatency;
+ _defaultHighInputLatency = devinfo.defaultHighInputLatency;
+ _defaultLowOutputLatency = devinfo.defaultLowOutputLatency;
+ _defaultHighOutputLatency = devinfo.defaultHighOutputLatency;
+ HostAPI = devinfo.hostApi;
+ Name = devinfo.name;
+ }
+
+ public string Name { get; }
+ public int HostAPI { get; } = 0;
+
+ private int _maximumInputChannels;
+ public override int MaximumInputChannels => _maximumInputChannels;
+ private int _maximumOutputChannels;
+ public override int MaximumOutputChannels => _maximumOutputChannels;
+
+ private double _defaultHighInputLatency;
+ public override double DefaultHighInputLatency => _defaultHighInputLatency;
+ private double _defaultHighOutputLatency;
+ public override double DefaultHighOutputLatency => _defaultHighOutputLatency;
+ private double _defaultLowInputLatency;
+ public override double DefaultLowInputLatency => _defaultLowInputLatency;
+ private double _defaultLowOutputLatency;
+ public override double DefaultLowOutputLatency => _defaultLowOutputLatency;
+
+ private double _defaultSampleRate;
+ public override double DefaultSampleRate => _defaultSampleRate;
+ public int Handle { get; } = 0;
+
+ public override string ToString()
+ {
+ return Name;
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioEngine.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioEngine.cs
new file mode 100644
index 0000000..ef959cd
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioEngine.cs
@@ -0,0 +1,99 @@
+//
+// PortAudioEngine.cs
+//
+// Author:
+// Michael Becker
+//
+// Copyright (c) 2020 Mike Becker's Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+using System;
+using System.Collections.Generic;
+
+namespace MBS.Audio.PortAudio
+{
+ public class PortAudioEngine : AudioEngine
+ {
+ private PortAudioDevice mvarDefaultInput = null;
+ public override AudioDevice DefaultInputDevice
+ {
+ get
+ {
+ int defaultOutputDeviceHandle = Internal.PortAudio.Methods.Pa_GetDefaultInputDevice();
+ mvarDefaultOutput = new PortAudioDevice(defaultOutputDeviceHandle);
+ return mvarDefaultOutput;
+ }
+ }
+ private PortAudioDevice mvarDefaultOutput = null;
+ public override AudioDevice DefaultOutputDevice
+ {
+ get
+ {
+ int defaultOutputDeviceHandle = Internal.PortAudio.Methods.Pa_GetDefaultOutputDevice();
+ mvarDefaultOutput = new PortAudioDevice(defaultOutputDeviceHandle);
+ return mvarDefaultOutput;
+ }
+ }
+
+ public override Guid ID => new Guid("{361b1dd6-b3d7-4358-bdee-b8741f48c7fe}");
+ public override string Title => "PortAudio";
+
+ private static PortAudioDevice[] mvarDevices = null;
+ public static PortAudioDevice[] GetDevices()
+ {
+ if (mvarDevices == null || mvarDevices.Length == 0)
+ {
+ List devices = new List();
+ int count = Internal.PortAudio.Methods.Pa_GetDeviceCount();
+ for (int i = 0; i < count; i++)
+ {
+ PortAudioDevice device = new PortAudioDevice(i);
+ devices.Add(device);
+ }
+ mvarDevices = devices.ToArray();
+ }
+ return mvarDevices;
+ }
+
+ public PortAudioDevice OpenAudioDevice(int handle)
+ {
+ return new PortAudioDevice(handle);
+ }
+
+ protected override void InitializeInternal()
+ {
+ Internal.PortAudio.Constants.PaError result = Internal.PortAudio.Methods.Pa_Initialize();
+ Internal.PortAudio.Methods.Pa_ResultToException(result);
+
+ int defaultInputDeviceHandle = Internal.PortAudio.Methods.Pa_GetDefaultInputDevice();
+ Internal.PortAudio.Methods.Pa_ResultToException(result);
+
+ int defaultOutputDeviceHandle = Internal.PortAudio.Methods.Pa_GetDefaultOutputDevice();
+ Internal.PortAudio.Methods.Pa_ResultToException(result);
+ mvarDefaultInput = new PortAudioDevice(defaultInputDeviceHandle);
+ mvarDefaultOutput = new PortAudioDevice(defaultOutputDeviceHandle);
+ }
+
+ protected override AudioStream CreateAudioStreamInternal(AudioDevice inputDevice, short inputChannelCount, AudioSampleFormat inputSampleFormat, double inputSuggestedLatency, AudioDevice outputDevice, short outputChannelCount, AudioSampleFormat outputSampleFormat, double outputSuggestedLatency, double sampleRate, int framesPerBuffer, AudioStreamFlags flags)
+ {
+ return new PortAudioStream(inputDevice, inputChannelCount, inputSampleFormat, inputSuggestedLatency, outputDevice, outputChannelCount, outputSampleFormat, outputSuggestedLatency, sampleRate, framesPerBuffer, flags);
+ }
+
+ protected override void TerminateInternal()
+ {
+ Internal.PortAudio.Constants.PaError result = Internal.PortAudio.Methods.Pa_Terminate();
+ Internal.PortAudio.Methods.Pa_ResultToException(result);
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioStream.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioStream.cs
new file mode 100644
index 0000000..d7f2464
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioStream.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MBS.Audio.PortAudio
+{
+ public class PortAudioStream : AudioStream
+ {
+ public PortAudioStream(AudioDevice inputDevice, AudioDevice outputDevice, AudioSampleFormat sampleFormat)
+ : this(inputDevice, outputDevice, sampleFormat, sampleFormat)
+ {
+ }
+ public PortAudioStream(AudioDevice inputDevice, AudioDevice outputDevice, AudioSampleFormat sampleFormat, double sampleRate)
+ : this(inputDevice, outputDevice, sampleFormat, sampleFormat, sampleRate)
+ {
+ }
+ public PortAudioStream(AudioDevice inputDevice, AudioDevice outputDevice, AudioSampleFormat inputSampleFormat, AudioSampleFormat outputSampleFormat)
+ : this(inputDevice, inputDevice.MaximumInputChannels, inputSampleFormat, outputDevice, outputDevice.MaximumOutputChannels, outputSampleFormat, outputDevice.DefaultSampleRate, 0)
+ {
+ }
+ public PortAudioStream(AudioDevice inputDevice, AudioDevice outputDevice, AudioSampleFormat inputSampleFormat, AudioSampleFormat outputSampleFormat, double sampleRate)
+ : this(inputDevice, inputDevice.MaximumInputChannels, inputSampleFormat, outputDevice, outputDevice.MaximumOutputChannels, outputSampleFormat, sampleRate, 0)
+ {
+ }
+ public PortAudioStream(AudioDevice inputDevice, int inputChannelCount, AudioSampleFormat inputSampleFormat, AudioDevice outputDevice, int outputChannelCount, AudioSampleFormat outputSampleFormat, double sampleRate)
+ : this(inputDevice, inputChannelCount, inputSampleFormat, (inputDevice == null) ? 0 : inputDevice.DefaultLowInputLatency, outputDevice, outputChannelCount, outputSampleFormat, (outputDevice == null) ? 0 : outputDevice.DefaultLowOutputLatency, sampleRate, 0, AudioStreamFlags.None)
+ {
+ }
+ public PortAudioStream(AudioDevice inputDevice, int inputChannelCount, AudioSampleFormat inputSampleFormat, AudioDevice outputDevice, int outputChannelCount, AudioSampleFormat outputSampleFormat, double sampleRate, int framesPerBuffer)
+ : this(inputDevice, inputChannelCount, inputSampleFormat, (inputDevice == null) ? 0 : inputDevice.DefaultLowInputLatency, outputDevice, outputChannelCount, outputSampleFormat, (outputDevice == null) ? 0 : outputDevice.DefaultLowOutputLatency, sampleRate, framesPerBuffer, AudioStreamFlags.None)
+ {
+ }
+ public PortAudioStream(AudioDevice inputDevice, int inputChannelCount, AudioSampleFormat inputSampleFormat, AudioDevice outputDevice, int outputChannelCount, AudioSampleFormat outputSampleFormat, double sampleRate, int framesPerBuffer, AudioStreamFlags flags)
+ : this(inputDevice, inputChannelCount, inputSampleFormat, (inputDevice == null) ? 0 : inputDevice.DefaultLowInputLatency, outputDevice, outputChannelCount, outputSampleFormat, (outputDevice == null) ? 0 : outputDevice.DefaultLowOutputLatency, sampleRate, framesPerBuffer, flags)
+ {
+ }
+ public PortAudioStream(AudioDevice inputDevice, int inputChannelCount, AudioSampleFormat inputSampleFormat, double inputSuggestedLatency, AudioDevice outputDevice, int outputChannelCount, AudioSampleFormat outputSampleFormat, double outputSuggestedLatency, double sampleRate, int framesPerBuffer)
+ : this(inputDevice, inputChannelCount, inputSampleFormat, inputSuggestedLatency, outputDevice, outputChannelCount, outputSampleFormat, outputSuggestedLatency, sampleRate, framesPerBuffer, AudioStreamFlags.None)
+ {
+ }
+ public PortAudioStream(AudioDevice inputDevice, int inputChannelCount, AudioSampleFormat inputSampleFormat, double inputSuggestedLatency, AudioDevice outputDevice, int outputChannelCount, AudioSampleFormat outputSampleFormat, double outputSuggestedLatency, double sampleRate, int framesPerBuffer, AudioStreamFlags flags) :
+ base(inputDevice, inputChannelCount, inputSampleFormat, inputSuggestedLatency, outputDevice, outputChannelCount, outputSampleFormat, outputSuggestedLatency, sampleRate, framesPerBuffer, flags)
+ {
+ }
+
+ private IntPtr _handle = IntPtr.Zero;
+
+ protected override void InitializeInternal()
+ {
+ base.InitializeInternal();
+
+ Internal.PortAudio.Structures.PaStreamParameters inputParameters = new Internal.PortAudio.Structures.PaStreamParameters();
+ Internal.PortAudio.Structures.PaStreamParameters outputParameters = new Internal.PortAudio.Structures.PaStreamParameters();
+
+ if (InputDevice != null)
+ {
+ inputParameters.channelCount = InputChannelCount;
+ inputParameters.device = (InputDevice as PortAudioDevice).Handle;
+ inputParameters.sampleFormat = InputSampleFormat;
+ inputParameters.suggestedLatency = InputSuggestedLatency;
+ }
+ else if (OutputDevice != null)
+ {
+ inputParameters.channelCount = OutputChannelCount;
+ inputParameters.device = (OutputDevice as PortAudioDevice).Handle;
+ inputParameters.sampleFormat = OutputSampleFormat;
+ inputParameters.suggestedLatency = OutputSuggestedLatency;
+ }
+
+ if (OutputDevice != null)
+ {
+ outputParameters.channelCount = OutputChannelCount;
+ outputParameters.device = (OutputDevice as PortAudioDevice).Handle;
+ outputParameters.sampleFormat = OutputSampleFormat;
+ outputParameters.suggestedLatency = OutputSuggestedLatency;
+ }
+ else
+ {
+ outputParameters.channelCount = InputChannelCount;
+ outputParameters.device = (InputDevice as PortAudioDevice).Handle;
+ outputParameters.sampleFormat = InputSampleFormat;
+ outputParameters.suggestedLatency = InputSuggestedLatency;
+ }
+ mvarOutputChannelCount = outputParameters.channelCount;
+
+ Internal.PortAudio.Delegates.PaStreamCallbackDelegate streamCallback = null; // new Internal.PortAudio.Delegates.PaStreamCallbackDelegate(_streamCallback);
+ IntPtr userData = IntPtr.Zero;
+
+ Internal.PortAudio.Constants.PaError result1 = Internal.PortAudio.Methods.Pa_OpenStream(out _handle, ref inputParameters, ref outputParameters, SampleRate, (uint)FramesPerBuffer, AudioStreamFlagsToPortAudioStreamFlags(Flags), streamCallback, userData);
+ if (result1 == Internal.PortAudio.Constants.PaError.paNoError)
+ {
+ Internal.PortAudio.Constants.PaError result2 = Internal.PortAudio.Methods.Pa_StartStream(_handle);
+ Internal.PortAudio.Methods.Pa_ResultToException(result2);
+ }
+ else
+ {
+ // result1 = Internal.PortAudio.Methods.Pa_OpenDefaultStream(out mvarHandle, inputChannelCount, outputChannelCount, (uint)outputSampleFormat, sampleRate, framesPerBuffer, streamCallback, userData);
+ Internal.PortAudio.Methods.Pa_ResultToException(result1);
+ }
+ }
+
+ private static PortAudioStreamFlags AudioStreamFlagsToPortAudioStreamFlags(AudioStreamFlags flags)
+ {
+ PortAudioStreamFlags flags2 = PortAudioStreamFlags.None;
+ if ((flags & AudioStreamFlags.ClipOff) == AudioStreamFlags.ClipOff) flags2 |= PortAudioStreamFlags.ClipOff;
+ if ((flags & AudioStreamFlags.DitherOff) == AudioStreamFlags.DitherOff) flags2 |= PortAudioStreamFlags.DitherOff;
+ if ((flags & AudioStreamFlags.NeverDropInput) == AudioStreamFlags.NeverDropInput) flags2 |= PortAudioStreamFlags.NeverDropInput;
+ // if ((flags & AudioStreamFlags.PlatformSpecificFlags) == AudioStreamFlags.PlatformSpecificFlags) flags2 |= PortAudioStreamFlags.PlatformSpecificFlags;
+ if ((flags & AudioStreamFlags.PrimeOutputBuffersUsingStreamCallback) == AudioStreamFlags.PrimeOutputBuffersUsingStreamCallback) flags2 |= PortAudioStreamFlags.PrimeOutputBuffersUsingStreamCallback;
+ return flags2;
+ }
+
+ private int mvarOutputChannelCount = 0;
+
+ private Internal.PortAudio.Constants.PaStreamCallbackResult _streamCallback(IntPtr input, IntPtr output, uint frameCount, ref Internal.PortAudio.Structures.PaStreamCallbackTimeInfo timeInfo, Internal.PortAudio.Constants.PaStreamCallbackFlags statusFlags, IntPtr userData)
+ {
+ return Internal.PortAudio.Constants.PaStreamCallbackResult.paComplete;
+ }
+
+ public override void Flush()
+ {
+ Internal.PortAudio.Methods.Pa_StopStream(_handle);
+ Internal.PortAudio.Methods.Pa_StartStream(_handle);
+ }
+
+ public override void Read(short[] buffer)
+ {
+ Internal.PortAudio.Constants.PaError result = Internal.PortAudio.Methods.Pa_ReadStream(_handle, buffer, (uint)buffer.Length);
+ Internal.PortAudio.Methods.Pa_ResultToException(result);
+ }
+
+ public override void Write(short[] buffer)
+ {
+ Internal.PortAudio.Constants.PaError result = Internal.PortAudio.Methods.Pa_WriteStream(_handle, buffer, (uint)(buffer.Length));
+ Internal.PortAudio.Methods.Pa_ResultToException(result);
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ Internal.PortAudio.Constants.PaError result = Internal.PortAudio.Methods.Pa_WriteStream(_handle, buffer, (uint)count);
+ Internal.PortAudio.Methods.Pa_ResultToException(result);
+ }
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioStreamFlags.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioStreamFlags.cs
new file mode 100644
index 0000000..b6d5bf9
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioStreamFlags.cs
@@ -0,0 +1,14 @@
+using System;
+namespace MBS.Audio.PortAudio
+{
+ [Flags()]
+ public enum PortAudioStreamFlags : uint
+ {
+ None,
+ ClipOff,
+ DitherOff,
+ NeverDropInput = 4u,
+ PrimeOutputBuffersUsingStreamCallback = 8u,
+ PlatformSpecificFlags = 4294901760u
+ }
+}
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/Properties/AssemblyInfo.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..cb90aac
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/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("Intelligent Sound Engine")]
+[assembly: AssemblyDescription("Interface with PortAudio for .NET")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Mike Becker's Software")]
+[assembly: AssemblyProduct("Intelligent Sound Engine")]
+[assembly: AssemblyCopyright("Copyright ©2012 Mike Becker's Software")]
+[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("2f5f69df-3795-4d81-9717-950d69f6ab21")]
+
+// 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/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/.NETFramework,Version=v4.0.AssemblyAttributes.cs b/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/.NETFramework,Version=v4.0.AssemblyAttributes.cs
new file mode 100644
index 0000000..5d01041
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/.NETFramework,Version=v4.0.AssemblyAttributes.cs
@@ -0,0 +1,4 @@
+//
+using System;
+using System.Reflection;
+[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.0", FrameworkDisplayName = ".NET Framework 4")]
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.AssemblyReference.cache b/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.AssemblyReference.cache
new file mode 100644
index 0000000..166da10
Binary files /dev/null and b/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.AssemblyReference.cache differ
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.CopyComplete b/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.CopyComplete
new file mode 100644
index 0000000..e69de29
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.CoreCompileInputs.cache b/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.CoreCompileInputs.cache
new file mode 100644
index 0000000..3517aef
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.CoreCompileInputs.cache
@@ -0,0 +1 @@
+b8e3b9299bf3d3b899326bf341d4c6b745e38237
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.FileListAbsolute.txt b/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.FileListAbsolute.txt
new file mode 100644
index 0000000..2974459
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.FileListAbsolute.txt
@@ -0,0 +1,10 @@
+/home/beckermj/Documents/Projects/MBS_OLD/MBS.Audio/Output/Debug/MBS.Audio.dll
+/home/beckermj/Documents/Projects/MBS_OLD/MBS.Audio/Output/Debug/MBS.Audio.pdb
+/home/beckermj/Documents/Projects/MBS_OLD/MBS.Audio/Libraries/MBS.Audio/obj/Debug/MBS.Audio.csproj.CoreCompileInputs.cache
+/home/beckermj/Documents/Projects/MBS_OLD/MBS.Audio/Libraries/MBS.Audio/obj/Debug/MBS.Audio.csproj.CopyComplete
+/home/beckermj/Documents/Projects/MBS_OLD/MBS.Audio/Libraries/MBS.Audio/obj/Debug/MBS.Audio.dll
+/home/beckermj/Documents/Projects/MBS_OLD/MBS.Audio/Libraries/MBS.Audio/obj/Debug/MBS.Audio.pdb
+/home/beckermj/Documents/Projects/alcetech/audio-dotnet/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.AssemblyReference.cache
+/home/beckermj/Documents/Projects/alcetech/audio-dotnet/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.CoreCompileInputs.cache
+/home/beckermj/Documents/Projects/alcetech/audio-dotnet/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.dll
+/home/beckermj/Documents/Projects/alcetech/audio-dotnet/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.pdb
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.dll b/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.dll
new file mode 100644
index 0000000..4b4aedd
Binary files /dev/null and b/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.dll differ
diff --git a/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.pdb b/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.pdb
new file mode 100644
index 0000000..86cf534
Binary files /dev/null and b/audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.pdb differ
diff --git a/audio-dotnet/src/audio-dotnet/audio-dotnet.sln b/audio-dotnet/src/audio-dotnet/audio-dotnet.sln
new file mode 100644
index 0000000..b41e3c1
--- /dev/null
+++ b/audio-dotnet/src/audio-dotnet/audio-dotnet.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.002.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBS.Audio", "MBS.Audio\MBS.Audio.csproj", "{A64EAB72-EB03-4529-BD16-5ED4D243DC72}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBS.Audio.MIDI", "MBS.Audio.MIDI\MBS.Audio.MIDI.csproj", "{37B4859E-DE4C-4700-B313-99D04177EE04}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A64EAB72-EB03-4529-BD16-5ED4D243DC72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A64EAB72-EB03-4529-BD16-5ED4D243DC72}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A64EAB72-EB03-4529-BD16-5ED4D243DC72}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A64EAB72-EB03-4529-BD16-5ED4D243DC72}.Release|Any CPU.Build.0 = Release|Any CPU
+ {37B4859E-DE4C-4700-B313-99D04177EE04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {37B4859E-DE4C-4700-B313-99D04177EE04}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {37B4859E-DE4C-4700-B313-99D04177EE04}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {37B4859E-DE4C-4700-B313-99D04177EE04}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {64A50985-7081-414C-9364-5294B4E1A14A}
+ EndGlobalSection
+EndGlobal
diff --git a/editor-dotnet b/editor-dotnet
new file mode 160000
index 0000000..62f6a45
--- /dev/null
+++ b/editor-dotnet
@@ -0,0 +1 @@
+Subproject commit 62f6a45689041e2fd45451c671fd911ce244fce3