Files
noteflow/spikes/spike_02_audio_capture/ring_buffer_impl.py
Travis Vasceannie af1285b181 Add initial project structure and files
- 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.
2025-12-17 18:28:59 +00:00

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)