- Introduced .python-version for Python version management. - Added AGENTS.md for documentation on agent usage and best practices. - Created alembic.ini for database migration configurations. - Implemented main.py as the entry point for the application. - Established pyproject.toml for project dependencies and configurations. - Initialized README.md for project overview. - Generated uv.lock for dependency locking. - Documented milestones and specifications in docs/milestones.md and docs/spec.md. - Created logs/status_line.json for logging status information. - Added initial spike implementations for UI tray hotkeys, audio capture, ASR latency, and encryption validation. - Set up NoteFlow core structure in src/noteflow with necessary modules and services. - Developed test suite in tests directory for application, domain, infrastructure, and integration testing. - Included initial migration scripts in infrastructure/persistence/migrations for database setup. - Established security protocols in infrastructure/security for key management and encryption. - Implemented audio infrastructure for capturing and processing audio data. - Created converters for ASR and ORM in infrastructure/converters. - Added export functionality for different formats in infrastructure/export. - Ensured all new files are included in the repository for future development.
109 lines
3.1 KiB
Python
109 lines
3.1 KiB
Python
"""Timestamped audio ring buffer implementation.
|
|
|
|
Stores recent audio with timestamps for ASR processing and playback sync.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections import deque
|
|
|
|
from .protocols import TimestampedAudio
|
|
|
|
|
|
class TimestampedRingBuffer:
|
|
"""Ring buffer for timestamped audio chunks.
|
|
|
|
Automatically discards old audio when the buffer exceeds max_duration.
|
|
Thread-safe for single-producer, single-consumer use.
|
|
"""
|
|
|
|
def __init__(self, max_duration: float = 30.0) -> None:
|
|
"""Initialize ring buffer.
|
|
|
|
Args:
|
|
max_duration: Maximum audio duration to keep in seconds.
|
|
|
|
Raises:
|
|
ValueError: If max_duration is not positive.
|
|
"""
|
|
if max_duration <= 0:
|
|
raise ValueError("max_duration must be positive")
|
|
|
|
self._max_duration = max_duration
|
|
self._buffer: deque[TimestampedAudio] = deque()
|
|
self._total_duration: float = 0.0
|
|
|
|
def push(self, audio: TimestampedAudio) -> None:
|
|
"""Add audio to the buffer.
|
|
|
|
Old audio is discarded if buffer exceeds max_duration.
|
|
|
|
Args:
|
|
audio: Timestamped audio chunk to add.
|
|
"""
|
|
self._buffer.append(audio)
|
|
self._total_duration += audio.duration
|
|
|
|
# Evict old chunks if over capacity
|
|
while self._total_duration > self._max_duration and self._buffer:
|
|
old = self._buffer.popleft()
|
|
self._total_duration -= old.duration
|
|
|
|
def get_window(self, duration_seconds: float) -> list[TimestampedAudio]:
|
|
"""Get the last N seconds of audio.
|
|
|
|
Args:
|
|
duration_seconds: How many seconds of audio to retrieve.
|
|
|
|
Returns:
|
|
List of TimestampedAudio chunks, ordered oldest to newest.
|
|
"""
|
|
if duration_seconds <= 0:
|
|
return []
|
|
|
|
result: list[TimestampedAudio] = []
|
|
accumulated_duration = 0.0
|
|
|
|
# Iterate from newest to oldest
|
|
for audio in reversed(self._buffer):
|
|
result.append(audio)
|
|
accumulated_duration += audio.duration
|
|
if accumulated_duration >= duration_seconds:
|
|
break
|
|
|
|
# Return in chronological order (oldest first)
|
|
result.reverse()
|
|
return result
|
|
|
|
def get_all(self) -> list[TimestampedAudio]:
|
|
"""Get all buffered audio.
|
|
|
|
Returns:
|
|
List of all TimestampedAudio chunks, ordered oldest to newest.
|
|
"""
|
|
return list(self._buffer)
|
|
|
|
def clear(self) -> None:
|
|
"""Clear all audio from the buffer."""
|
|
self._buffer.clear()
|
|
self._total_duration = 0.0
|
|
|
|
@property
|
|
def duration(self) -> float:
|
|
"""Total duration of buffered audio in seconds."""
|
|
return self._total_duration
|
|
|
|
@property
|
|
def max_duration(self) -> float:
|
|
"""Maximum buffer duration in seconds."""
|
|
return self._max_duration
|
|
|
|
@property
|
|
def chunk_count(self) -> int:
|
|
"""Number of audio chunks in the buffer."""
|
|
return len(self._buffer)
|
|
|
|
def __len__(self) -> int:
|
|
"""Return number of chunks in buffer."""
|
|
return len(self._buffer)
|