Public Access
Add meeting assistant speech and summary automation
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
using NAudio.Wave;
|
||||
|
||||
namespace MeetingAssistant.Recording;
|
||||
|
||||
internal sealed class RollingAudioBuffer
|
||||
{
|
||||
private readonly TimeSpan retention;
|
||||
private readonly object gate = new();
|
||||
private readonly Queue<TimedAudioChunk> chunks = new();
|
||||
private TimeSpan position;
|
||||
|
||||
public RollingAudioBuffer(TimeSpan retention)
|
||||
{
|
||||
this.retention = retention > TimeSpan.Zero ? retention : TimeSpan.FromMinutes(10);
|
||||
}
|
||||
|
||||
public void Append(AudioChunk chunk)
|
||||
{
|
||||
if (chunk.Pcm.Length == 0 || chunk.SampleRate <= 0 || chunk.Channels <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (gate)
|
||||
{
|
||||
var start = position;
|
||||
var end = start + chunk.Duration;
|
||||
chunks.Enqueue(new TimedAudioChunk(start, end, chunk));
|
||||
position = end;
|
||||
Prune(end - retention);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] TryExtractWav(TimeSpan start, TimeSpan end)
|
||||
{
|
||||
if (end <= start)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
List<TimedAudioChunk> matchingChunks;
|
||||
lock (gate)
|
||||
{
|
||||
matchingChunks = chunks
|
||||
.Where(chunk => chunk.End > start && chunk.Start < end)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (matchingChunks.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var format = new WaveFormat(
|
||||
matchingChunks[0].Chunk.SampleRate,
|
||||
16,
|
||||
matchingChunks[0].Chunk.Channels);
|
||||
using var output = new MemoryStream();
|
||||
using (var writer = new WaveFileWriter(output, format))
|
||||
{
|
||||
foreach (var timedChunk in matchingChunks)
|
||||
{
|
||||
if (timedChunk.Chunk.SampleRate != format.SampleRate || timedChunk.Chunk.Channels != format.Channels)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var blockAlign = format.BlockAlign;
|
||||
var copyStart = Max(start, timedChunk.Start);
|
||||
var copyEnd = Min(end, timedChunk.End);
|
||||
var firstFrame = (int)Math.Floor((copyStart - timedChunk.Start).TotalSeconds * format.SampleRate);
|
||||
var afterLastFrame = (int)Math.Ceiling((copyEnd - timedChunk.Start).TotalSeconds * format.SampleRate);
|
||||
var byteOffset = Clamp(firstFrame * blockAlign, 0, timedChunk.Chunk.Pcm.Length);
|
||||
var byteCount = Clamp(afterLastFrame * blockAlign, byteOffset, timedChunk.Chunk.Pcm.Length) - byteOffset;
|
||||
if (byteCount > 0)
|
||||
{
|
||||
writer.Write(timedChunk.Chunk.Pcm, byteOffset, byteCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output.ToArray();
|
||||
}
|
||||
|
||||
private void Prune(TimeSpan cutoff)
|
||||
{
|
||||
while (chunks.Count > 0 && chunks.Peek().End < cutoff)
|
||||
{
|
||||
chunks.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
private static TimeSpan Max(TimeSpan left, TimeSpan right)
|
||||
{
|
||||
return left >= right ? left : right;
|
||||
}
|
||||
|
||||
private static TimeSpan Min(TimeSpan left, TimeSpan right)
|
||||
{
|
||||
return left <= right ? left : right;
|
||||
}
|
||||
|
||||
private static int Clamp(int value, int min, int max)
|
||||
{
|
||||
return Math.Min(Math.Max(value, min), max);
|
||||
}
|
||||
|
||||
private sealed record TimedAudioChunk(TimeSpan Start, TimeSpan End, AudioChunk Chunk);
|
||||
}
|
||||
Reference in New Issue
Block a user