Add main entry point + systemd services + integration tests
New files:
main.py - PedalApp: boots all subsystems in order,
wires MIDI/footswitch callbacks, graceful
teardown reverses boot order
src/system/config.py - YAML config loader with deep-merge
(separated to avoid hardware deps)
src/system/services.py - systemd unit generator for pedal.service
+ multi-fx-pedal.target
scripts/install_service.sh - copies project, creates venv, installs
+ enables service units
tests/test_integration.py - 41 tests: boot, routing, display sync,
teardown, systemd content, CLI, edge cases
Modified:
tests/conftest.py - add project root to sys.path
This commit is contained in:
@@ -17,6 +17,7 @@ import subprocess
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from collections.abc import Callable
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -143,6 +144,10 @@ class AudioSystem:
|
||||
|
||||
def __init__(self, config: Optional[AudioConfig] = None) -> None:
|
||||
self.config = config or AudioConfig()
|
||||
self._tempo_bpm: float = 120.0
|
||||
self._tempo_source: str = "default" # "default", "midi_clock", "manual"
|
||||
self._midi_clock_enabled: bool = False
|
||||
self._tempo_callback: Optional[Callable[[float], None]] = None
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# I2S overlay management
|
||||
@@ -580,6 +585,70 @@ WantedBy=multi-user.target
|
||||
return False
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# MIDI clock sync (tempo integration for time-based FX)
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
@property
|
||||
def tempo_bpm(self) -> float:
|
||||
"""Current tempo in BPM for time-based effects (delay, reverb)."""
|
||||
return self._tempo_bpm
|
||||
|
||||
@tempo_bpm.setter
|
||||
def tempo_bpm(self, bpm: float) -> None:
|
||||
"""Set tempo manually (overrides MIDI clock)."""
|
||||
self._tempo_source = "manual"
|
||||
self._tempo_bpm = max(20.0, min(300.0, bpm))
|
||||
if self._tempo_callback:
|
||||
self._tempo_callback(self._tempo_bpm)
|
||||
logger.info("Tempo set manually: %.1f BPM", self._tempo_bpm)
|
||||
|
||||
@property
|
||||
def tempo_source(self) -> str:
|
||||
return self._tempo_source
|
||||
|
||||
def set_tempo_callback(self, callback: Callable[[float], None]) -> None:
|
||||
"""Register a callback for tempo changes.
|
||||
|
||||
Args:
|
||||
callback: Called with new BPM value when tempo changes.
|
||||
"""
|
||||
self._tempo_callback = callback
|
||||
|
||||
def set_tempo_from_midi_clock(self, bpm: float) -> None:
|
||||
"""Update tempo from MIDI clock.
|
||||
|
||||
Called by MIDIHandler's clock callback. Only updates if MIDI
|
||||
clock sync is enabled.
|
||||
|
||||
Args:
|
||||
bpm: Detected BPM from MIDI clock (20-300).
|
||||
"""
|
||||
if not self._midi_clock_enabled:
|
||||
return
|
||||
bpm = max(20.0, min(300.0, bpm))
|
||||
if abs(self._tempo_bpm - bpm) > 0.5 or self._tempo_source != "midi_clock":
|
||||
self._tempo_bpm = bpm
|
||||
self._tempo_source = "midi_clock"
|
||||
if self._tempo_callback:
|
||||
self._tempo_callback(bpm)
|
||||
logger.debug("Tempo synced from MIDI clock: %.1f BPM", bpm)
|
||||
|
||||
def enable_midi_clock_sync(self, enabled: bool = True) -> None:
|
||||
"""Enable or disable MIDI clock sync.
|
||||
|
||||
When enabled, tempo follows MIDI clock. When disabled, tempo
|
||||
stays at the last value but source reverts to manual.
|
||||
|
||||
Args:
|
||||
enabled: Whether to follow MIDI clock.
|
||||
"""
|
||||
self._midi_clock_enabled = enabled
|
||||
if not enabled and self._tempo_source == "midi_clock":
|
||||
self._tempo_source = "manual"
|
||||
logger.info("MIDI clock sync %s", "enabled" if enabled else "disabled")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Internal helpers
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user