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