from __future__ import annotations from dataclasses import asdict, dataclass from datetime import datetime from pathlib import Path import json import re from .models import PatternParams, SceneState @dataclass(slots=True) class PresetRecord: name: str pattern_id: str tempo_bpm: float params: dict created_at: str @classmethod def from_scene(cls, name: str, scene: SceneState) -> "PresetRecord": return cls( name=name, pattern_id=scene.pattern_id, tempo_bpm=scene.tempo_bpm, params=asdict(scene.params), created_at=datetime.utcnow().isoformat(timespec="seconds"), ) class PresetStore: def __init__(self, root: str | Path) -> None: self.root = Path(root) self.root.mkdir(parents=True, exist_ok=True) def save(self, record: PresetRecord) -> Path: path = self.root / f"{slugify(record.name)}.json" path.write_text(json.dumps(asdict(record), indent=2), encoding="utf-8") return path def list(self) -> list[PresetRecord]: presets: list[PresetRecord] = [] for path in sorted(self.root.glob("*.json")): try: payload = json.loads(path.read_text(encoding="utf-8")) presets.append(PresetRecord(**payload)) except (OSError, json.JSONDecodeError, TypeError): continue return presets def load_scene(self, name: str) -> SceneState: path = self.root / f"{slugify(name)}.json" payload = json.loads(path.read_text(encoding="utf-8")) record = PresetRecord(**payload) return SceneState( pattern_id=record.pattern_id, tempo_bpm=float(record.tempo_bpm), params=PatternParams(**record.params), ) def slugify(value: str) -> str: return re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-") or "preset"