diff --git a/OpenAI.sln b/OpenAI.sln
index 968679d1..f9cd1a08 100755
--- a/OpenAI.sln
+++ b/OpenAI.sln
@@ -42,6 +42,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenerateDocs", "src\helpers\GenerateDocs\GenerateDocs.csproj", "{ECC219F0-209A-412B-ADEC-6D97AB379E7C}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenAI.Microphone", "src\libs\OpenAI.Microphone\OpenAI.Microphone.csproj", "{C008F8CF-1A0E-4FE5-A5E1-5672D3B3A1E5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenAI.CLI", "src\libs\OpenAI.CLI\OpenAI.CLI.csproj", "{AAD812B2-77D6-42E0-A3FA-432BD6A73791}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -68,6 +72,14 @@ Global
{ECC219F0-209A-412B-ADEC-6D97AB379E7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECC219F0-209A-412B-ADEC-6D97AB379E7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ECC219F0-209A-412B-ADEC-6D97AB379E7C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C008F8CF-1A0E-4FE5-A5E1-5672D3B3A1E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C008F8CF-1A0E-4FE5-A5E1-5672D3B3A1E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C008F8CF-1A0E-4FE5-A5E1-5672D3B3A1E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C008F8CF-1A0E-4FE5-A5E1-5672D3B3A1E5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AAD812B2-77D6-42E0-A3FA-432BD6A73791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AAD812B2-77D6-42E0-A3FA-432BD6A73791}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AAD812B2-77D6-42E0-A3FA-432BD6A73791}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AAD812B2-77D6-42E0-A3FA-432BD6A73791}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -79,6 +91,8 @@ Global
{A3F06E45-DFA8-4236-BFF5-425091762548} = {AAA11B78-2764-4520-A97E-46AA7089A588}
{2D8B78DE-7269-417B-9D0B-8981FA513ACB} = {E793AF18-4371-4EBD-96FC-195EB1798855}
{ECC219F0-209A-412B-ADEC-6D97AB379E7C} = {1A008ECD-2300-4BE4-A302-49DDF8BE0D54}
+ {C008F8CF-1A0E-4FE5-A5E1-5672D3B3A1E5} = {61E7E11E-4558-434C-ACE8-06316A3097B3}
+ {AAD812B2-77D6-42E0-A3FA-432BD6A73791} = {61E7E11E-4558-434C-ACE8-06316A3097B3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CED9A020-DBA5-4BE6-8096-75E528648EC1}
diff --git a/src/libs/OpenAI.CLI/Commands/VoiceCommand.cs b/src/libs/OpenAI.CLI/Commands/VoiceCommand.cs
new file mode 100644
index 00000000..45213dab
--- /dev/null
+++ b/src/libs/OpenAI.CLI/Commands/VoiceCommand.cs
@@ -0,0 +1,28 @@
+using System.CommandLine;
+
+namespace OpenAI.CLI.Commands;
+
+///
+///
+///
+internal sealed class VoiceCommand : Command
+{
+ public VoiceCommand() : base(name: "voice", description: "Generates client sdk using a OpenAPI spec.")
+ {
+ var inputOption = new Argument(
+ name: "input",
+ getDefaultValue: () => string.Empty,
+ description: "Input file path");
+ AddArgument(inputOption);
+
+ this.SetHandler(
+ HandleAsync,
+ inputOption);
+ }
+
+ private static async Task HandleAsync(
+ string input)
+ {
+ Console.WriteLine("Done.");
+ }
+}
\ No newline at end of file
diff --git a/src/libs/OpenAI.CLI/OpenAI.CLI.csproj b/src/libs/OpenAI.CLI/OpenAI.CLI.csproj
new file mode 100644
index 00000000..6fdb4d52
--- /dev/null
+++ b/src/libs/OpenAI.CLI/OpenAI.CLI.csproj
@@ -0,0 +1,25 @@
+
+
+
+ Exe
+ net8.0
+ $(NoWarn)
+
+
+
+ tryAGI.OpenAI.CLI
+ true
+ openai
+ Advanced Voice from command line
+ openai;advanced-voice;ai;realtime;api;microphone
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/libs/OpenAI.CLI/Program.cs b/src/libs/OpenAI.CLI/Program.cs
new file mode 100644
index 00000000..a43cd4db
--- /dev/null
+++ b/src/libs/OpenAI.CLI/Program.cs
@@ -0,0 +1,8 @@
+using System.CommandLine;
+using OpenAI.CLI.Commands;
+
+var rootCommand = new RootCommand(
+ description: "CLI tool to use AutoSDK");
+rootCommand.AddCommand(new VoiceCommand());
+
+return await rootCommand.InvokeAsync(args).ConfigureAwait(false);
\ No newline at end of file
diff --git a/src/libs/OpenAI.CLI/Properties/launchSettings.json b/src/libs/OpenAI.CLI/Properties/launchSettings.json
new file mode 100644
index 00000000..492768f8
--- /dev/null
+++ b/src/libs/OpenAI.CLI/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "FixOpenApiSpec": {
+ "commandName": "Project",
+ "commandLineArgs": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libs/OpenAI.Microphone/Microphone.cs b/src/libs/OpenAI.Microphone/Microphone.cs
new file mode 100644
index 00000000..a8bc5f31
--- /dev/null
+++ b/src/libs/OpenAI.Microphone/Microphone.cs
@@ -0,0 +1,206 @@
+using System.Runtime.InteropServices;
+using PortAudioSharp;
+
+namespace OpenAI;
+
+///
+/// Implements a Microphone using PortAudio
+///
+public sealed class Microphone : IDisposable
+{
+ ///
+ ///
+ ///
+ static Microphone()
+ {
+ PortAudio.Initialize();
+ }
+
+ ///
+ ///
+ ///
+ public static void Terminate()
+ {
+ PortAudio.Terminate();
+ }
+
+ ///
+ ///
+ ///
+ public Action? PushCallback { get; set; }
+
+ ///
+ /// Sample rate
+ ///
+ public int SampleRate { get; set; } = 48_000;
+
+ ///
+ /// Chunk size
+ ///
+ public int ChunkSize { get; set; } = 32_000;
+
+ ///
+ /// Number of channels
+ ///
+ public int Channels { get; set; } = 1;
+
+ ///
+ /// Input device index
+ ///
+ public int DeviceIndex { get; set; } = PortAudio.NoDevice;
+
+ ///
+ /// Sample Format
+ ///
+ public SampleFormat SampleFormat { get; set; } = SampleFormat.Int16;
+
+ ///
+ ///
+ ///
+ public bool IsMuted { get; private set; }
+
+ private PortAudioSharp.Stream? _stream;
+ private CancellationTokenSource? _exitToken;
+
+ ///
+ /// Start begins the listening on the microphone
+ ///
+ ///
+ public bool Start()
+ {
+ if (_stream != null)
+ {
+ return false;
+ }
+
+ // reset exit token
+ _exitToken?.Dispose();
+ _exitToken = new CancellationTokenSource();
+
+ // Get the device info
+ if (DeviceIndex == PortAudio.NoDevice)
+ {
+ DeviceIndex = PortAudio.DefaultInputDevice;
+ if (DeviceIndex == PortAudio.NoDevice)
+ {
+ return false;
+ }
+ }
+
+ DeviceInfo info = PortAudio.GetDeviceInfo(DeviceIndex);
+
+ // Create the stream
+ _stream = new PortAudioSharp.Stream(
+ inParams: new StreamParameters
+ {
+ device = DeviceIndex,
+ channelCount = Channels,
+ sampleFormat = SampleFormat,
+ suggestedLatency = info.defaultLowInputLatency,
+ hostApiSpecificStreamInfo = IntPtr.Zero,
+ },
+ outParams: null,
+ sampleRate: SampleRate,
+ framesPerBuffer: (uint)ChunkSize,
+ streamFlags: StreamFlags.ClipOff,
+ callback: Сallback,
+ userData: IntPtr.Zero
+ );
+
+ // Start the stream
+ _stream.Start();
+ return true;
+ }
+
+ ///
+ ///
+ ///
+ public void Stop()
+ {
+ // Check if we have a stream
+ if (_stream == null)
+ {
+ return;
+ }
+
+ // signal stop
+ _exitToken?.Cancel();
+
+ // Stop the stream
+ _stream.Stop();
+
+ _stream.Dispose();
+ _stream = null;
+ _exitToken?.Dispose();
+ _exitToken = null;
+ }
+
+ private StreamCallbackResult Сallback(
+ nint input,
+ nint output,
+ uint frameCount,
+ ref StreamCallbackTimeInfo timeInfo,
+ StreamCallbackFlags statusFlags,
+ nint userDataPtr)
+ {
+ // Check if the input is null
+ if (input == IntPtr.Zero)
+ {
+ return StreamCallbackResult.Continue;
+ }
+
+ // Check if the exit token is set
+ if (_exitToken is { IsCancellationRequested: true })
+ {
+ return StreamCallbackResult.Abort;
+ }
+
+ // copy and send the data
+ byte[] buf = new byte[frameCount * sizeof(Int16)];
+
+ if (IsMuted)
+ {
+ buf = new byte[buf.Length];
+ }
+ else
+ {
+ Marshal.Copy(input, buf, 0, buf.Length);
+ }
+
+ PushCallback?.Invoke(buf, buf.Length);
+
+ return StreamCallbackResult.Continue;
+ }
+
+ ///
+ ///
+ ///
+ public void Mute()
+ {
+ if (_stream != null)
+ {
+ return;
+ }
+
+ IsMuted = true;
+ }
+
+ ///
+ ///
+ ///
+ public void Unmute()
+ {
+ if (_stream != null)
+ {
+ return;
+ }
+
+ IsMuted = false;
+ }
+
+ ///
+ public void Dispose()
+ {
+ Stop();
+ }
+}
diff --git a/src/libs/OpenAI.Microphone/OpenAI.Microphone.csproj b/src/libs/OpenAI.Microphone/OpenAI.Microphone.csproj
new file mode 100644
index 00000000..ef044d78
--- /dev/null
+++ b/src/libs/OpenAI.Microphone/OpenAI.Microphone.csproj
@@ -0,0 +1,24 @@
+
+
+
+ netstandard2.0;net8.0
+
+
+
+
+ <_Parameter1>false
+
+
+
+
+ tryAGI.OpenAI.Microphone
+ Provides Microphone access
+ openai;advanced-voice;ai;realtime;api;microphone
+ false
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/tests/OpenAI.IntegrationTests/OpenAI.IntegrationTests.csproj b/src/tests/OpenAI.IntegrationTests/OpenAI.IntegrationTests.csproj
index 04109c6f..55163185 100644
--- a/src/tests/OpenAI.IntegrationTests/OpenAI.IntegrationTests.csproj
+++ b/src/tests/OpenAI.IntegrationTests/OpenAI.IntegrationTests.csproj
@@ -19,6 +19,7 @@
+
@@ -30,6 +31,7 @@
+
diff --git a/src/tests/OpenAI.IntegrationTests/Tests.Realtime.cs b/src/tests/OpenAI.IntegrationTests/Tests.Realtime.cs
index 4090ae5f..28861554 100755
--- a/src/tests/OpenAI.IntegrationTests/Tests.Realtime.cs
+++ b/src/tests/OpenAI.IntegrationTests/Tests.Realtime.cs
@@ -2,6 +2,25 @@ namespace OpenAI.IntegrationTests;
public partial class Tests
{
+ [Test]
+ [Explicit]
+ public async Task Microphone()
+ {
+ using var microphone = new Microphone();
+ microphone.PushCallback = (bytes, count) =>
+ {
+ Console.WriteLine($"Pushed {count} bytes.");
+ };
+
+ microphone.Start();
+
+ await Task.Delay(TimeSpan.FromSeconds(15));
+
+ microphone.Stop();
+
+ OpenAI.Microphone.Terminate();
+ }
+
[Test]
[Explicit]
public async Task Realtime()