Callum.Voice 0.0.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package Callum.Voice --version 0.0.1
                    
NuGet\Install-Package Callum.Voice -Version 0.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Callum.Voice" Version="0.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Callum.Voice" Version="0.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Callum.Voice" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Callum.Voice --version 0.0.1
                    
#r "nuget: Callum.Voice, 0.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Callum.Voice@0.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Callum.Voice&version=0.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Callum.Voice&version=0.0.1
                    
Install as a Cake Tool

Callum.Voice — کتابخانه صوت بلادرنگ (Real-Time Voice Chat)

کتابخانه‌ی .NET برای ارتباط صوتی بلادرنگ چندسکویی از طریق سرور Go (phonil-opus).
تمام منطق ضبط میکروفن، پخش صدا، اتصال WebSocket، میکس صدای چند کاربر، تشخیص صحبت و مدیریت اتاق در این کتابخانه پیاده‌سازی شده است.


فهرست


معماری کتابخانه

┌──────────────────────────────────────────────────────────┐
│                    Callum.Voice                         │
│                                                          │
│  ┌─────────────┐     ┌──────────────┐     ┌──────────┐ │
│  │ VoiceClient  │────▶│ IAudioEngine │────▶│ Platform │ │
│  │              │◀────│  (interface) │◀────│  Audio   │ │
│  │ • WebSocket  │     │              │     │  Engine  │ │
│  │ • Room Mgmt │     │ • Capture    │     │          │ │
│  │ • Peer Track│     │ • Playback   │     │ • WASAPI │ │
│  │ • Speaking  │     │ • Volume     │     │ • AVAudio│ │
│  │ • Reconnect │     │ • Power Mgmt │     │ • AudioR.│ │
│  └─────────────┘     └──────────────┘     └──────────┘ │
│         │                                      ▲         │
│         │ WebSocket (PCM 16-bit mono)          │         │
│         ▼                                      │         │
│  ┌─────────────┐                              │         │
│  │  Go Server   │────── PCM Audio ────────────┘         │
│  │ (phonil-opus)│                                        │
│  └─────────────┘                                        │
└──────────────────────────────────────────────────────────┘

لایه‌ها

لایه مسئولیت
VoiceClient منطق شبکه (WebSocket)، مدیریت اتاق، ردیابی کاربران، تشخیص صحبت، اتصال مجدد خودکار
IAudioEngine رابط (interface) برای ضبط و پخش صدا — هر پلتفرم پیاده‌سازی خاص خود را دارد
AudioEngine (Platform) پیاده‌سازی اختصاصی هر پلتفرم: Android (AudioRecord/AudioTrack)، iOS (AVAudioEngine)، Windows (WASAPI)

ساختار فایل‌ها

Callum.Voice/
├── VoiceClient.cs              # منطق اصلی: WebSocket، اتاق، میکس، تشخیص صحبت
├── VoiceConfig.cs              # تنظیمات اتصال (سرور، API Key، نرخ نمونه‌برداری)
├── VoiceClientState.cs         # وضعیت‌های اتصال (Disconnected → Connected → InRoom)
├── IAudioEngine.cs             # رابط انتزاعی موتور صدا
├── Platforms/
│   ├── Android/
│   │   ├── AudioEngine.cs      # ضبط/پخش با AudioRecord/AudioTrack + Ring Buffer
│   │   └── VoiceForegroundService.cs  # سرویس پس‌زمینه Android
│   ├── iOS/
│   │   └── AudioEngine.cs      # ضبط/پخش با AVAudioEngine + AVAudioPlayerNode
│   ├── MacCatalyst/
│   │   └── (از همان iOS engine استفاده می‌شود)
│   └── Windows/
│       └── AudioEngine.cs      # ضبط/پخش با WASAPI (COM Interop)
└── Callum.Voice.csproj

پروتکل ارتباط

WebSocket Connection

ws(s)://{server}/ws?room=__lobby__&peer={peerId}&api_key={apiKey}

پیام متنی (JSON) — ورود به اتاق

{
  "type": "join",
  "room": "room-id",
  "peer": "user-123",
  "sampleRate": 48000
}

پیام باینری (صدا) — فرمت بسته

[1B RoomLen][RoomID][1B PeerLen][PeerID][PCM Audio Data]
  • PCM Format: 16-bit signed integer, mono, 48 kHz
  • هر بسته شامل شناسه اتاق، شناسه فرستنده و داده‌ی صوتی خام است

پلتفرم‌های پشتیبانی‌شده

۱. .NET MAUI

وضعیت: پشتیبانی کامل — آماده تولید

کتابخانه در حال حاضر با <UseMaui>true</UseMaui> بیلد می‌شود و شامل پیاده‌سازی اختصاصی برای Android، iOS، MacCatalyst و Windows است.

Target Frameworks:

<TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst;net10.0-windows10.0.19041.0</TargetFrameworks>
مثال MAUI — صفحه اصلی
using Callum.Voice;

public partial class VoicePage : ContentPage
{
    private VoiceClient? _client;

    public VoicePage()
    {
        InitializeComponent();
    }

    protected async override void OnAppearing()
    {
        base.OnAppearing();

        // 1. ساخت موتور صدا (پیاده‌سازی خودکار بر اساس پلتفرم)
        var audio = new AudioEngine(sampleRate: 48_000);

        // 2. ساخت کلاینت
        var config = new VoiceConfig
        {
            Server = "callem.cloudfort.ir",
            ApiKey = "vc_live_YOUR_API_KEY",
            PeerId = $"user-{Guid.NewGuid():N}",
            AutoReconnect = true,
        };

        _client = new VoiceClient(config, audio);

        // 3. ثبت رویدادها
        _client.Connected += () =>
            MainThread.BeginInvokeOnMainThread(() =>
                lblStatus.Text = "Connected");

        _client.PeerJoined += peerId =>
            MainThread.BeginInvokeOnMainThread(() =>
                lblPeers.Text += $"\n{peerId}");

        _client.PeerSpeaking += peerId =>
            MainThread.BeginInvokeOnMainThread(() =>
                lblSpeaking.Text = $"{peerId} is speaking...");

        _client.Error += ex =>
            MainThread.BeginInvokeOnMainThread(() =>
                lblStatus.Text = $"Error: {ex.Message}");

        // 4. اتصال و ورود به اتاق
        await _client.ConnectAsync();
        await _client.JoinRoomAsync("general");

        // 5. فعال‌سازی میکروفن
        await _client.EnableMicAsync();
    }

    private async void OnToggleMic(object sender, EventArgs e)
    {
        bool isOn = await _client!.ToggleMicAsync();
        btnMic.Text = isOn ? "🎤 Mute" : "🎤 Unmute";
    }

    protected override void OnDisappearing()
    {
        _client?.Dispose();
        base.OnDisappearing();
    }
}

۲. WPF (Windows)

وضعیت: قابل استفاده — نیاز به پیاده‌سازی IAudioEngine مستقل از MAUI

کتابخانه فعلی <UseMaui>true</UseMaui> دارد. برای WPF دو راه وجود دارد:

راه اول: استفاده مستقیم از AudioEngine ویندوز (توصیه می‌شود)

کلاس AudioEngine ویندوز (WASAPI) در Platforms/Windows/AudioEngine.cs کاملاً مستقل از MAUI است و فقط از System.Runtime.InteropServices و WASAPI COM Interop استفاده می‌کند.

مراحل:

  1. کتابخانه را با target net10.0-windows10.0.19041.0 بیلد بگیرید (بدون نیاز به MAUI SDK)
  2. یا کلاس AudioEngine ویندوز را در پروژه WPF خود کپی کنید
مثال WPF
using System.Windows;
using Callum.Voice;

namespace MyWpfApp;

public partial class MainWindow : Window
{
    private VoiceClient? _client;

    public MainWindow()
    {
        InitializeComponent();
    }

    private async void BtnConnect_Click(object sender, RoutedEventArgs e)
    {
        // AudioEngine ویندوز (WASAPI) — بدون وابستگی به MAUI
        var audio = new AudioEngine(sampleRate: 48_000);

        var config = new VoiceConfig
        {
            Server = "callem.cloudfort.ir",
            ApiKey = "vc_live_YOUR_API_KEY",
            PeerId = $"wpf-user-{Guid.NewGuid():N}",
        };

        _client = new VoiceClient(config, audio);

        _client.Connected += () =>
            Dispatcher.Invoke(() => txtStatus.Text = "Connected");

        _client.PeerSpeaking += peerId =>
            Dispatcher.Invoke(() => txtSpeaking.Text = $"{peerId} speaking");

        await _client.ConnectAsync();
        await _client.JoinRoomAsync("general");
        await _client.EnableMicAsync();
    }

    private async void BtnToggleMic_Click(object sender, RoutedEventArgs e)
    {
        bool isOn = await _client!.ToggleMicAsync();
        btnMic.Content = isOn ? "Mute" : "Unmute";
    }

    protected override void OnClosed(EventArgs e)
    {
        _client?.Dispose();
        base.OnClosed(e);
    }
}

نکته: برای بیلد بدون MAUI، باید Callum.Voice.csproj را اصلاح کنید:

<TargetFrameworks>net10.0-windows10.0.19041.0</TargetFrameworks>
<UseMaui>false</UseMaui>

و فقط فایل‌های Platforms/Windows/AudioEngine.cs و فایل‌های مشترک را نگه دارید.

راه دوم: پیاده‌سازی IAudioEngine سفارشی برای WPF
using Callum.Voice;
using NAudio.Wave; // NuGet: NAudio

public class WpfAudioEngine : IAudioEngine
{
    private WaveInEvent? _waveIn;
    private WaveOutEvent? _waveOut;
    // ... پیاده‌سازی با NAudio

    public int SampleRate => 48_000;
    public event Action<short[]>? AudioCaptured;
    public event Action<string>? ErrorOccurred;

    public void StartCapture()
    {
        _waveIn = new WaveInEvent { WaveFormat = new WaveFormat(48000, 16, 1) };
        _waveIn.DataAvailable += (s, e) =>
        {
            var samples = new short[e.BytesRecorded / 2];
            Buffer.BlockCopy(e.Buffer, 0, samples, 0, e.BytesRecorded);
            AudioCaptured?.Invoke(samples);
        };
        _waveIn.StartRecording();
    }

    public void StopCapture() => _waveIn?.StopRecording();
    public void StartPlayback() { /* ... */ }
    public void StopPlayback() { /* ... */ }
    public void EnqueuePeerAudio(string peerId, short[] samples) { /* ... */ }
    public void RemovePeer(string peerId) { /* ... */ }
    public void ClearPeers() { /* ... */ }
    public void SetPlaybackVolume(float volume) { /* ... */ }
    public void KeepScreenAwake() { /* no-op on WPF */ }
    public void ReleaseScreenAwake() { /* no-op on WPF */ }
    public Task<bool> RequestMicrophonePermission() => Task.FromResult(true);
    public void Dispose() { StopCapture(); StopPlayback(); }
}

۳. ASP.NET Core MVC

وضعیت: فقط سمت سرور — بدون ضبط/پخش صدا

ASP.NET Core یک فریمورک سمت سرور است و دسترسی مستقیم به میکروفن/اسپیکر ندارد. اما می‌تواند:

  • سرور واسط برای مسیریابی صدا باشد
  • SDK جاوااسکریپت (voice-chat-sdk.js) را به مرورگر ارائه دهد
  • از VoiceClient برای اتصال سرور به اتاق (مثلاً برای ضبط یا نظارت) استفاده کند
مثال ASP.NET Core — ارائه SDK به مرورگر
// Controllers/VoiceController.cs
using Microsoft.AspNetCore.Mvc;

namespace MyWebApp.Controllers;

public class VoiceController : Controller
{
    public IActionResult Chat()
    {
        // صفحه HTML با SDK جاوااسکریپت
        return View();
    }
}

<!DOCTYPE html>
<html>
<head>
    <title>Voice Chat</title>
    
    <script src="~/js/voice-chat-sdk.js"></script>
</head>
<body>
    <h1>Voice Chat Room</h1>
    <button onclick="join()">Join</button>
    <button onclick="toggleMic()">Toggle Mic</button>

    <script>
        const client = new VoiceChatSDK({
            server: 'callem.cloudfort.ir',
            apiKey: 'vc_live_YOUR_API_KEY',
            peerId: 'web-user-@ViewBag.UserId',
        });

        client.on('connected', () => client.joinRoom('general'));
        client.on('peer-speaking', (id) => console.log(`${id} speaking`));
        client.connect();

        async function join() {
            await client.joinRoom('general');
            await client.enableMic();
        }
        async function toggleMic() { await client.toggleMic(); }
    </script>
</body>
</html>
مثال ASP.NET Core — استفاده از VoiceClient سمت سرور (نظارت/ضبط)
// Services/VoiceMonitor.cs
using Callum.Voice;

public class VoiceMonitorService : BackgroundService
{
    private VoiceClient? _client;

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        // سرور بدون میکروفن — فقط برای شنود/نظارت
        var audio = new HeadlessAudioEngine(); // پیاده‌سازی بدون صدا
        var config = new VoiceConfig
        {
            Server = "callem.cloudfort.ir",
            ApiKey = "vc_live_YOUR_API_KEY",
            PeerId = "server-monitor-01",
        };

        _client = new VoiceClient(config, audio);
        _client.PeerSpeaking += peerId =>
            Console.WriteLine($"[Monitor] {peerId} is speaking");

        await _client.ConnectAsync(ct);
        await _client.JoinRoomAsync("general");
    }
}

// پیاده‌سازی HeadlessAudioEngine (بدون سخت‌افزار صدا)
public class HeadlessAudioEngine : IAudioEngine
{
    public int SampleRate => 48_000;
    public event Action<short[]>? AudioCaptured;
    public event Action<string>? ErrorOccurred;
    public Task<bool> RequestMicrophonePermission() => Task.FromResult(true);
    public void StartCapture() { }
    public void StopCapture() { }
    public void StartPlayback() { }
    public void StopPlayback() { }
    public void EnqueuePeerAudio(string peerId, short[] samples)
    {
        // صدا را دریافت کنید ولی پخش نکنید (فقط برای نظارت)
    }
    public void RemovePeer(string peerId) { }
    public void ClearPeers() { }
    public void SetPlaybackVolume(float volume) { }
    public void KeepScreenAwake() { }
    public void ReleaseScreenAwake() { }
    public void Dispose() { }
}

۴. Blazor WebAssembly

وضعیت: قابل استفاده — با Web Audio API از طریق JS Interop

Blazor WASM در مرورگر اجرا می‌شود، بنابراین باید از voice-chat-sdk.js (SDK جاوااسکریپت) از طریق IJSRuntime استفاده کند.

مثال Blazor WASM
@page "/voice"
@inject IJSRuntime JS
@implements IAsyncDisposable

<h3>Voice Chat</h3>
<p>Status: @_status</p>
<p>Speaking: @_speaking</p>

<button @onclick="JoinRoom">Join</button>
<button @onclick="ToggleMic">Toggle Mic</button>

@code {
    private string _status = "Disconnected";
    private string _speaking = "";
    private IJSObjectReference? _jsModule;

    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            _jsModule = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./js/voice-interop.js");
        }
    }

    public async Task JoinRoom()
    {
        if (_jsModule is not null)
            await _jsModule.InvokeVoidAsync("joinVoiceRoom", "general");
    }

    public async Task ToggleMic()
    {
        if (_jsModule is not null)
            await _jsModule.InvokeVoidAsync("toggleMic");
    }

    public async ValueTask DisposeAsync()
    {
        if (_jsModule is not null)
            await _jsModule.DisposeAsync();
    }
}
// wwwroot/js/voice-interop.js
import VoiceChatSDK from './voice-chat-sdk.js';

const client = new VoiceChatSDK({
    server: 'callem.cloudfort.ir',
    apiKey: 'vc_live_YOUR_API_KEY',
    peerId: 'blazor-user-' + Date.now(),
});

client.on('connected', () => {
    DotNet.invokeMethodAsync('MyBlazorApp', 'OnVoiceStatus', 'Connected');
});

client.on('peer-speaking', (id) => {
    DotNet.invokeMethodAsync('MyBlazorApp', 'OnPeerSpeaking', id);
});

client.connect();

export function joinVoiceRoom(roomId) {
    client.joinRoom(roomId).then(() => client.enableMic());
}

export function toggleMic() {
    client.toggleMic();
}

۵. Avalonia UI

وضعیت: قابل استفاده — مشابه WPF

Avalonia یک فریمورک cross-platform UI است. برای استفاده از Callum.Voice:

  • Windows: از AudioEngine ویندوز (WASAPI) استفاده کنید
  • Linux/macOS: باید IAudioEngine سفارشی با PulseAudio/CoreAudio پیاده‌سازی کنید
مثال Avalonia
// MainWindowViewModel.cs
using Callum.Voice;

public class MainWindowViewModel : ViewModelBase
{
    private VoiceClient? _client;
    private string _status = "Disconnected";

    public string Status
    {
        get => _status;
        set => this.RaiseAndSetIfChanged(ref _status, value);
    }

    public async Task ConnectAsync()
    {
        var audio = new AudioEngine(sampleRate: 48_000); // Windows WASAPI
        var config = new VoiceConfig
        {
            Server = "callem.cloudfort.ir",
            ApiKey = "vc_live_YOUR_API_KEY",
            PeerId = $"avalonia-{Guid.NewGuid():N}",
        };

        _client = new VoiceClient(config, audio);
        _client.Connected += () => Status = "Connected";
        _client.PeerSpeaking += id => Status = $"{id} speaking";

        await _client.ConnectAsync();
        await _client.JoinRoomAsync("general");
        await _client.EnableMicAsync();
    }
}

۶. Console App (.NET)

وضعیت: قابل استفاده — Headless (بدون پخش/ضبط)

برای ابزارهای خط فرمان، بات‌ها، یا سرویس‌های پس‌زمینه:

مثال Console — ربات نظارت
using Callum.Voice;

var audio = new HeadlessAudioEngine();
var config = new VoiceConfig
{
    Server = "callem.cloudfort.ir",
    ApiKey = "vc_live_YOUR_API_KEY",
    PeerId = $"bot-{Guid.NewGuid():N}",
};

using var client = new VoiceClient(config, audio);

client.Connected += () => Console.WriteLine("[+] Connected");
client.PeerJoined += id => Console.WriteLine($"[+] Peer joined: {id}");
client.PeerSpeaking += id => Console.WriteLine($"[!] {id} is speaking");
client.PeerLeft += id => Console.WriteLine($"[-] Peer left: {id}");
client.Disconnected += () => Console.WriteLine("[-] Disconnected");

await client.ConnectAsync();
await client.JoinRoomAsync("general");

Console.WriteLine("Monitoring room... Press Enter to exit.");
Console.ReadLine();

API Reference

VoiceClient

Method Return Type توضیحات
ConnectAsync(ct) Task اتصال به سرور
Disconnect() void قطع اتصال
JoinRoomAsync(roomId) Task ورود به اتاق
LeaveRoomAsync() Task خروج از اتاق
EnableMicAsync() Task فعال‌سازی میکروفن (با درخواست مجوز)
DisableMic() void غیرفعال‌سازی میکروفن
ToggleMicAsync() Task<bool> تغییر وضعیت میکروفن
EnableSpeaker() void فعال‌سازی بلندگو (Android)
DisableSpeaker() void غیرفعال‌سازی بلندگو
ToggleSpeaker() bool تغییر وضعیت بلندگو
IsPeerSpeaking(peerId) bool آیا کاربر در حال صحبت است؟
IsPeerSpeaking(peerId, out level) bool سطح RMS را هم برمی‌گرداند
Dispose() void آزادسازی منابع

Properties

Property Type توضیحات
State VoiceClientState وضعیت فعلی اتصال
IsConnected bool آیا متصل است؟
IsInRoom bool آیا در اتاق است؟
IsMicEnabled bool آیا میکروفن فعال است؟
CurrentRoom string? شناسه اتاق فعلی
Peers IReadOnlyCollection<string> لیست کاربران حاضر

VoiceConfig

Property Type Default توضیحات
Server string آدرس سرور (مثال: callem.cloudfort.ir)
ApiKey string کلید API (vc_live_...)
PeerId string شناسه یکتای کاربر
UseTls bool? null استفاده از WSS (null = خودکار)
SampleRate int 48000 نرخ نمونه‌برداری (Hz)
AutoReconnect bool true اتصال مجدد خودکار
MaxReconnectAttempts int 5 حداکثر تلاش reconnect
ReconnectDelay TimeSpan 2s فاصله بین تلاش‌ها
EchoCancellation bool true حذف اکو
NoiseSuppression bool true حذف نویز
AutoGainControl bool true کنترل خودکار gain

Events

Event Type توضیحات
Connected Action اتصال به سرور برقرار شد
Disconnected Action اتصال قطع شد
Reconnecting Action<int> تلاش برای اتصال مجدد (با شماره تلاش)
AuthFailed Action<string> احراز هویت ناموفق
RoomJoined Action<string> ورود به اتاق
RoomLeft Action<string> خروج از اتاق
PeerJoined Action<string> ورود کاربر جدید
PeerLeft Action<string> خروج کاربر
PeerSpeaking Action<string> کاربر شروع به صحبت کرد
PeerStopped Action<string> کاربر صحبت را متوقف کرد
MicEnabled Action میکروفن فعال شد
MicDisabled Action میکروفن غیرفعال شد
Error Action<Exception> خطا رخ داد
StateChanged Action<VoiceClientState> تغییر وضعیت اتصال

نکات مهم

خلاصه پشتیبانی پلتفرم‌ها

پلتفرم ضبط/پخش VoiceClient نیاز به IAudioEngine سفارشی
MAUI Android ✅ آماده ✅ آماده ❌ ندارد
MAUI iOS ✅ آماده ✅ آماده ❌ ندارد
MAUI Windows ✅ آماده ✅ آماده ❌ ندارد
WPF ❌ نیاز به اصلاح csproj ✅ آماده ⚠️ یا WASAPI کپی یا NAudio
Avalonia ⚠️ فقط Windows ✅ آماده ⚠️ برای Linux/macOS
ASP.NET Core ❌ سمت سرور ✅ آماده ✅ HeadlessAudioEngine
Blazor WASM ✅ مرورگر ✅ از طریق JS Interop ✅ SDK جاوااسکریپت
Console ❌ بدون سخت‌افزار ✅ آماده ✅ HeadlessAudioEngine

مجوزهای لازم

پلتفرم مجوز
Android RECORD_AUDIO (runtime)
iOS NSMicrophoneUsageDescription (Info.plist)
Windows میکروفن (از تنظیمات OS)

فرمت صوتی

  • PCM 16-bit signed integer
  • Mono (تک کاناله)
  • 48,000 Hz sample rate (پیش‌فرض)
  • Bitrate: ~768 kbps (48000 × 16 × 1)

سرور

سرور Go (phonil-opus) در آدرس callem.cloudfort.ir در دسترس است.
برای راه‌اندازی سرور اختصاصی، README-SDK.md سرور را مطالعه کنید.

Product Compatible and additional computed target framework versions.
.NET net10.0-android36.0 is compatible.  net10.0-ios26.0 is compatible.  net10.0-maccatalyst26.0 is compatible.  net10.0-windows10.0.19041 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0-android36.0

  • net10.0-ios26.0

    • No dependencies.
  • net10.0-maccatalyst26.0

    • No dependencies.
  • net10.0-windows10.0.19041

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.2 88 8/26/2026
0.0.1 87 8/25/2026