Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added OpenAI.Microphone library and OpenAI.CLI to use Realtime API through console #124

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions OpenAI.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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}
Expand Down
28 changes: 28 additions & 0 deletions src/libs/OpenAI.CLI/Commands/VoiceCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.CommandLine;

namespace OpenAI.CLI.Commands;

/// <summary>
///
/// </summary>
internal sealed class VoiceCommand : Command
{
public VoiceCommand() : base(name: "voice", description: "Generates client sdk using a OpenAPI spec.")
{
var inputOption = new Argument<string>(
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.");
}
}
25 changes: 25 additions & 0 deletions src/libs/OpenAI.CLI/OpenAI.CLI.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<NoWarn>$(NoWarn)</NoWarn>
</PropertyGroup>

<PropertyGroup Label="NuGet">
<PackageId>tryAGI.OpenAI.CLI</PackageId>
<PackAsTool>true</PackAsTool>
<ToolCommandName>openai</ToolCommandName>
<Description>Advanced Voice from command line</Description>
<PackageTags>openai;advanced-voice;ai;realtime;api;microphone</PackageTags>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\OpenAI\OpenAI.csproj" />
</ItemGroup>

</Project>
8 changes: 8 additions & 0 deletions src/libs/OpenAI.CLI/Program.cs
Original file line number Diff line number Diff line change
@@ -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);
8 changes: 8 additions & 0 deletions src/libs/OpenAI.CLI/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"profiles": {
"FixOpenApiSpec": {
"commandName": "Project",
"commandLineArgs": ""
}
}
}
206 changes: 206 additions & 0 deletions src/libs/OpenAI.Microphone/Microphone.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
using System.Runtime.InteropServices;
using PortAudioSharp;

namespace OpenAI;

/// <summary>
/// Implements a Microphone using PortAudio
/// </summary>
public sealed class Microphone : IDisposable
{
/// <summary>
///
/// </summary>
static Microphone()
{
PortAudio.Initialize();
}

/// <summary>
///
/// </summary>
public static void Terminate()
{
PortAudio.Terminate();
}

/// <summary>
///
/// </summary>
public Action<byte[], int>? PushCallback { get; set; }

/// <summary>
/// Sample rate
/// </summary>
public int SampleRate { get; set; } = 48_000;

/// <summary>
/// Chunk size
/// </summary>
public int ChunkSize { get; set; } = 32_000;

/// <summary>
/// Number of channels
/// </summary>
public int Channels { get; set; } = 1;

/// <summary>
/// Input device index
/// </summary>
public int DeviceIndex { get; set; } = PortAudio.NoDevice;

/// <summary>
/// Sample Format
/// </summary>
public SampleFormat SampleFormat { get; set; } = SampleFormat.Int16;

/// <summary>
///
/// </summary>
public bool IsMuted { get; private set; }

private PortAudioSharp.Stream? _stream;
private CancellationTokenSource? _exitToken;

/// <summary>
/// Start begins the listening on the microphone
/// </summary>
/// <returns></returns>
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;
}

/// <summary>
///
/// </summary>
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;
}

/// <summary>
///
/// </summary>
public void Mute()
{
if (_stream != null)
{
return;
}

IsMuted = true;
}

/// <summary>
///
/// </summary>
public void Unmute()
{
if (_stream != null)
{
return;
}

IsMuted = false;
}

/// <inheritdoc/>
public void Dispose()
{
Stop();
}
}
24 changes: 24 additions & 0 deletions src/libs/OpenAI.Microphone/OpenAI.Microphone.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
</PropertyGroup>

<ItemGroup Label="CLSCompliant">
<AssemblyAttribute Include="System.CLSCompliantAttribute">
<_Parameter1>false</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

<PropertyGroup Label="Nuget">
<PackageId>tryAGI.OpenAI.Microphone</PackageId>
<Description>Provides Microphone access</Description>
<PackageTags>openai;advanced-voice;ai;realtime;api;microphone</PackageTags>
<SignAssembly>false</SignAssembly>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="PortAudioSharp2" Version="1.0.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<PackageReference Include="FluentAssertions" Version="6.12.2" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageReference Include="org.k2fsa.portaudio.runtime.osx-arm64" Version="1.0.4" />
</ItemGroup>

<ItemGroup Label="GlobalUsings">
Expand All @@ -30,6 +31,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\libs\OpenAI.Microphone\OpenAI.Microphone.csproj" />
<ProjectReference Include="..\..\libs\OpenAI\OpenAI.csproj" />
</ItemGroup>

Expand Down
19 changes: 19 additions & 0 deletions src/tests/OpenAI.IntegrationTests/Tests.Realtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down