From 14c7db0ae511254cdc41522a3641b0c62365d483 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Mon, 27 May 2024 18:36:12 -0400 Subject: [PATCH] Initial commit --- .gitmodules | 3 + .../src/Output/Debug/MBS.Audio.MIDI.dll | Bin 0 -> 20992 bytes .../src/Output/Debug/MBS.Audio.MIDI.pdb | Bin 0 -> 8752 bytes .../src/audio-dotnet/.vscode/settings.json | 3 + .../DeviceOptionalFunctionality.cs | 29 + .../audio-dotnet/MBS.Audio.MIDI/DeviceType.cs | 40 ++ .../Internal/Linux/Alsa/Constants.cs | 54 ++ .../Internal/Linux/Alsa/Methods.cs | 115 ++++ .../Internal/Windows/Constants.cs | 85 +++ .../Internal/Windows/Delegates.cs | 28 + .../Internal/Windows/Methods.cs | 88 +++ .../Internal/Windows/Structures.cs | 102 +++ .../audio-dotnet/MBS.Audio.MIDI/Listener.cs | 60 ++ .../MBS.Audio.MIDI/MBS.Audio.MIDI.csproj | 65 ++ .../audio-dotnet/MBS.Audio.MIDI/Message.cs | 65 ++ .../MBS.Audio.MIDI/MessageReceivedEvent.cs | 24 + .../MBS.Audio.MIDI/MessageType.cs | 38 ++ .../audio-dotnet/MBS.Audio.MIDI/MidiDevice.cs | 436 ++++++++++++ .../MBS.Audio.MIDI/MidiDeviceFunctionality.cs | 32 + .../MBS.Audio.MIDI/Properties/AssemblyInfo.cs | 36 + .../audio-dotnet/MBS.Audio.MIDI/SoundCard.cs | 257 +++++++ ....Audio.MIDI.csproj.AssemblyReference.cache | Bin 0 -> 671 bytes ....Audio.MIDI.csproj.CoreCompileInputs.cache | 1 + ...MBS.Audio.MIDI.csproj.FileListAbsolute.txt | 6 + .../obj/Debug/MBS.Audio.MIDI.dll | Bin 0 -> 20992 bytes .../obj/Debug/MBS.Audio.MIDI.pdb | Bin 0 -> 8752 bytes .../src/audio-dotnet/MBS.Audio/AudioDevice.cs | 18 + .../src/audio-dotnet/MBS.Audio/AudioEngine.cs | 68 ++ .../src/audio-dotnet/MBS.Audio/AudioPlayer.cs | 167 +++++ .../MBS.Audio/AudioPlayerState.cs | 14 + .../MBS.Audio/AudioPlayerStateChangedEvent.cs | 20 + .../AudioPlayerStateChangedReason.cs | 10 + .../MBS.Audio/AudioSampleFormat.cs | 20 + .../src/audio-dotnet/MBS.Audio/AudioStream.cs | 129 ++++ .../MBS.Audio/AudioStreamFlags.cs | 18 + .../audio-dotnet/MBS.Audio/AudioTimestamp.cs | 187 +++++ .../src/audio-dotnet/MBS.Audio/BarBeatTick.cs | 143 ++++ .../audio-dotnet/MBS.Audio/CustomTransport.cs | 58 ++ .../src/audio-dotnet/MBS.Audio/ITransport.cs | 95 +++ .../MBS.Audio/Internal/PortAudio/Constants.cs | 58 ++ .../MBS.Audio/Internal/PortAudio/Delegates.cs | 17 + .../Internal/PortAudio/Linux/Methods.cs | 78 +++ .../MBS.Audio/Internal/PortAudio/Methods.cs | 641 ++++++++++++++++++ .../Internal/PortAudio/Structures.cs | 96 +++ .../Internal/PortAudio/Windows/Methods.cs | 78 +++ .../MBS.Audio/Jack/Internal/Constants.cs | 116 ++++ .../MBS.Audio/Jack/Internal/Delegates.cs | 26 + .../MBS.Audio/Jack/Internal/Methods.cs | 253 +++++++ .../MBS.Audio/Jack/Internal/Structures.cs | 138 ++++ .../MBS.Audio/Jack/JackAudioEngine.cs | 46 ++ .../audio-dotnet/MBS.Audio/Jack/JackClient.cs | 277 ++++++++ .../MBS.Audio/Jack/JackException.cs | 28 + .../MBS.Audio/Jack/JackInputPort.cs | 40 ++ .../MBS.Audio/Jack/JackOpenOptions.cs | 48 ++ .../MBS.Audio/Jack/JackOutputPort.cs | 38 ++ .../audio-dotnet/MBS.Audio/Jack/JackPort.cs | 91 +++ .../MBS.Audio/Jack/JackPortFlags.cs | 73 ++ .../MBS.Audio/Jack/JackPortRegisteredEvent.cs | 13 + .../MBS.Audio/Jack/JackPortTypes.cs | 29 + .../MBS.Audio/Jack/JackProcessEvent.cs | 16 + .../MBS.Audio/Jack/JackTransport.cs | 146 ++++ .../MBS.Audio/Jack/JackTransportState.cs | 51 ++ .../Jack/Networking/Internal/Constants.cs | 11 + .../Jack/Networking/Internal/Methods.cs | 11 + .../Jack/Networking/Internal/Structures.cs | 35 + .../Jack/Networking/JackNetworkSlave.cs | 20 + .../MBS.Audio/Jack/ServerException.cs | 24 + .../Jack/VersionMismatchException.cs | 24 + .../audio-dotnet/MBS.Audio/MBS.Audio.csproj | 118 ++++ .../MBS.Audio/Metronome/Metronome.cs | 179 +++++ .../MBS.Audio/PortAudio/PortAudioDevice.cs | 52 ++ .../MBS.Audio/PortAudio/PortAudioEngine.cs | 99 +++ .../MBS.Audio/PortAudio/PortAudioStream.cs | 145 ++++ .../PortAudio/PortAudioStreamFlags.cs | 14 + .../MBS.Audio/Properties/AssemblyInfo.cs | 36 + ...amework,Version=v4.0.AssemblyAttributes.cs | 4 + .../MBS.Audio.csproj.AssemblyReference.cache | Bin 0 -> 671 bytes .../obj/Debug/MBS.Audio.csproj.CopyComplete | 0 .../MBS.Audio.csproj.CoreCompileInputs.cache | 1 + .../MBS.Audio.csproj.FileListAbsolute.txt | 10 + .../MBS.Audio/obj/Debug/MBS.Audio.dll | Bin 0 -> 55808 bytes .../MBS.Audio/obj/Debug/MBS.Audio.pdb | Bin 0 -> 22844 bytes .../src/audio-dotnet/audio-dotnet.sln | 31 + editor-dotnet | 1 + 84 files changed, 5726 insertions(+) create mode 100644 .gitmodules create mode 100644 audio-dotnet/src/Output/Debug/MBS.Audio.MIDI.dll create mode 100644 audio-dotnet/src/Output/Debug/MBS.Audio.MIDI.pdb create mode 100644 audio-dotnet/src/audio-dotnet/.vscode/settings.json create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/DeviceOptionalFunctionality.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/DeviceType.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Linux/Alsa/Constants.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Linux/Alsa/Methods.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Constants.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Delegates.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Methods.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Internal/Windows/Structures.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Listener.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MBS.Audio.MIDI.csproj create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Message.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MessageReceivedEvent.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MessageType.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MidiDevice.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/MidiDeviceFunctionality.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/Properties/AssemblyInfo.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/SoundCard.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.csproj.AssemblyReference.cache create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.csproj.CoreCompileInputs.cache create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.csproj.FileListAbsolute.txt create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.dll create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio.MIDI/obj/Debug/MBS.Audio.MIDI.pdb create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/AudioDevice.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/AudioEngine.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayer.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayerState.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayerStateChangedEvent.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/AudioPlayerStateChangedReason.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/AudioSampleFormat.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/AudioStream.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/AudioStreamFlags.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/AudioTimestamp.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/BarBeatTick.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/CustomTransport.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/ITransport.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Constants.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Delegates.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Linux/Methods.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Methods.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Structures.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Internal/PortAudio/Windows/Methods.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Constants.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Delegates.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Methods.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Internal/Structures.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackAudioEngine.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackClient.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackException.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackInputPort.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackOpenOptions.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackOutputPort.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPort.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPortFlags.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPortRegisteredEvent.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackPortTypes.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackProcessEvent.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackTransport.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/JackTransportState.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/Internal/Constants.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/Internal/Methods.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/Internal/Structures.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/Networking/JackNetworkSlave.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/ServerException.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Jack/VersionMismatchException.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/MBS.Audio.csproj create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Metronome/Metronome.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioDevice.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioEngine.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioStream.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/PortAudio/PortAudioStreamFlags.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/Properties/AssemblyInfo.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/.NETFramework,Version=v4.0.AssemblyAttributes.cs create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.AssemblyReference.cache create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.CopyComplete create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.CoreCompileInputs.cache create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.csproj.FileListAbsolute.txt create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.dll create mode 100644 audio-dotnet/src/audio-dotnet/MBS.Audio/obj/Debug/MBS.Audio.pdb create mode 100644 audio-dotnet/src/audio-dotnet/audio-dotnet.sln create mode 160000 editor-dotnet 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 0000000000000000000000000000000000000000..1c33e2c14277fee938a932be434709f0d9927c39 GIT binary patch literal 20992 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 tS5EAcb}7?=mK3C2gRq*T<6KB>@S#|wK-bsrpY< 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..35a6321f4b9ea3d3e4ab84affc348207a6a30521 GIT binary patch literal 671 zcmeZu3JP{+WMpPwU|>|zFD)+8&&f>E&&|)v*EiBL&`m7J)XyzW&MyLr>ZRo5Ffsxa zHh7u632eH}!N|zS0h4580!knQkU|h(tcTDHU;#z}C=W!jz+^apjO|r1VUi{fIvKrT zM(QYpr4|)u=I7Z0T>^qS3eKfDC8b5Fwt1%#`Pr#?wn^sZ ziKfY>W`>r=CaDG%mQV{A7!|O4Ex59{BsCYC$JiiJ)bo}n#6W6#N)@lCob!uPvH6S} d>S>TXINn)dp#-GKjx|3dQxSeN)-$Cn;s7ZZl5hY3 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1c33e2c14277fee938a932be434709f0d9927c39 GIT binary patch literal 20992 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 tS5EAcb}7?=mK3C2gRq*T<6KB>@S#|wK-bsrpY< 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..166da10d9913ff695a7eb02debd1022d7c7d7254 GIT binary patch literal 671 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..86cf534d4da40f872a4c968b24f9f62383890db4 GIT binary patch literal 22844 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 literal 0 HcmV?d00001 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