"""
score_engine.py — SongScore data model, templates and in-memory singleton.

Pure Python — zero dependencies on Ableton, MCP, or any audio library.
This module is designed to be importable from anywhere: server.py, ai_loop.py,
test scripts, etc.

SongScore JSON schema:
{
  "meta": { "title", "tempo", "key", "genre", "time_signature", "gap_bars", "version" },
  "structure": [ { "name", "start_bar", "duration_bars" } ],
  "tracks": [
    {
      "id", "name", "type",          # type = "audio" | "midi"
      "instrument",                   # only for MIDI tracks (e.g. "Wavetable")
      "clips": [
        {
          "section",                  # section name → resolves start_bar automatically
          "start_bar",                # OR explicit start position (in bars)
          "duration_bars",
          "sample",                   # audio only  e.g. "kick/auto" or "kick/kick1.wav"
          "pattern",                  # MIDI only   e.g. "dembow_standard"
          "notes",                    # MIDI only   explicit note list (overrides pattern)
          "loop", "warp"              # audio flags
        }
      ],
      "mixer": { "volume","pan","eq_preset","compression_preset","send_reverb","send_delay" }
    }
  ]
}
"""

import json
import os
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional

# Scores directory (created automatically)
SCORES_DIR = Path(__file__).parent / "scores"
SCORES_DIR.mkdir(exist_ok=True)

# Valid MIDI pattern names (used by sanitization)
_VALID_PATTERNS_SET = {
    "dembow_minimal", "dembow_standard", "dembow_double",
    "bass_sub", "bass_pluck", "bass_octaves", "bass_sustained",
    "chords_verse", "chords_chorus", "melody_simple",
}

# In-memory singleton (one active score per MCP server process)
_current_score: Optional["SongScore"] = None


# ==================================================================
# Data classes
# ==================================================================

class MixerDef:
    __slots__ = ("volume", "pan", "eq_preset", "compression_preset",
                 "send_reverb", "send_delay")

    def __init__(self, volume: float = 0.75, pan: float = 0.0,
                 eq_preset: str = None, compression_preset: str = None,
                 send_reverb: float = 0.0, send_delay: float = 0.0):
        self.volume = float(volume)
        self.pan = float(pan)
        self.eq_preset = eq_preset
        self.compression_preset = compression_preset
        self.send_reverb = float(send_reverb)
        self.send_delay = float(send_delay)

    def to_dict(self) -> Dict:
        d: Dict[str, Any] = {"volume": self.volume, "pan": self.pan}
        if self.eq_preset:
            d["eq_preset"] = self.eq_preset
        if self.compression_preset:
            d["compression_preset"] = self.compression_preset
        if self.send_reverb:
            d["send_reverb"] = self.send_reverb
        if self.send_delay:
            d["send_delay"] = self.send_delay
        return d

    @classmethod
    def from_dict(cls, d: Dict) -> "MixerDef":
        return cls(
            volume=d.get("volume", 0.75),
            pan=d.get("pan", 0.0),
            eq_preset=d.get("eq_preset"),
            compression_preset=d.get("compression_preset"),
            send_reverb=d.get("send_reverb", 0.0),
            send_delay=d.get("send_delay", 0.0),
        )


class ClipDef:
    """Represents a single clip inside a track."""

    def __init__(self, start_bar: float = 0.0, duration_bars: float = 4.0,
                 clip_type: str = "audio", sample: str = None,
                 pattern: str = None, notes: List[Dict] = None,
                 loop: bool = True, warp: bool = True, section: str = None,
                 name: str = None):
        self.start_bar = float(start_bar)
        self.duration_bars = float(duration_bars)
        self.clip_type = clip_type      # "audio" | "midi"
        self.sample = sample            # relative ref or "/abs/path.wav"
        self.pattern = pattern          # e.g. "dembow_standard"
        self.notes = notes or []        # explicit MIDI notes
        self.loop = bool(loop)
        self.warp = bool(warp)
        self.section = section          # section name (informational)
        self.name = name

    def to_dict(self) -> Dict:
        d: Dict[str, Any] = {
            "start_bar": self.start_bar,
            "duration_bars": self.duration_bars,
        }
        if self.section:
            d["section"] = self.section
        if self.name:
            d["name"] = self.name
        if self.sample:
            d["sample"] = self.sample
            d["loop"]   = self.loop
            d["warp"]   = self.warp
        if self.pattern:
            d["pattern"] = self.pattern
        if self.notes:
            d["notes"] = self.notes
        return d

    @classmethod
    def from_raw(cls, raw: Dict, structure: List[Dict] = None) -> "ClipDef":
        """Build ClipDef from a raw dict, resolving section → start_bar if needed."""
        start_bar     = raw.get("start_bar")
        duration_bars = raw.get("duration_bars")
        section_name  = raw.get("section")

        if start_bar is None and section_name and structure:
            for sec in structure:
                if sec["name"] == section_name:
                    start_bar     = sec["start_bar"]
                    if duration_bars is None:
                        duration_bars = sec["duration_bars"]
                    break

        if start_bar is None:
            start_bar = 0.0
        if duration_bars is None:
            duration_bars = 4.0

        # Infer clip type from keys
        clip_type = "audio" if raw.get("sample") else "midi"

        return cls(
            start_bar     = start_bar,
            duration_bars = duration_bars,
            clip_type     = clip_type,
            sample        = raw.get("sample"),
            pattern       = raw.get("pattern"),
            notes         = raw.get("notes", []),
            loop          = raw.get("loop", True),
            warp          = raw.get("warp", True),
            section       = section_name,
            name          = raw.get("name"),
        )


class TrackDef:
    """Represents a single track with all its clips."""

    def __init__(self, track_id: str, name: str, track_type: str,
                 instrument: str = None,
                 clips: List[ClipDef] = None,
                 mixer: MixerDef = None):
        self.id         = track_id
        self.name       = name
        self.type       = track_type    # "audio" | "midi"
        self.instrument = instrument    # "Wavetable", "Operator", etc.
        self.clips      = clips or []
        self.mixer      = mixer or MixerDef()

    def to_dict(self) -> Dict:
        d: Dict[str, Any] = {
            "id":    self.id,
            "name":  self.name,
            "type":  self.type,
            "clips": [c.to_dict() for c in self.clips],
            "mixer": self.mixer.to_dict(),
        }
        if self.instrument:
            d["instrument"] = self.instrument
        return d

    @classmethod
    def from_raw(cls, raw: Dict, structure: List[Dict] = None) -> "TrackDef":
        track_type = raw.get("type", "audio")

        # ── Phase 1: Auto-correct track type from ORIGINAL clip data (before coercion) ──
        raw_clips = raw.get("clips", [])
        orig_has_sample = any(c.get("sample") for c in raw_clips)
        orig_has_pattern = any(c.get("pattern") for c in raw_clips)
        orig_has_notes = any(c.get("notes") for c in raw_clips)
        orig_has_midi = orig_has_pattern or orig_has_notes

        if track_type == "midi" and orig_has_sample and not orig_has_midi:
            track_type = "audio"
        elif track_type == "midi" and orig_has_sample and orig_has_midi:
            all_samples_not_patterns = all(
                c.get("sample") and c.get("sample").replace("/auto", "").replace("/", "_")
                not in _VALID_PATTERNS_SET
                for c in raw_clips if c.get("sample")
            )
            sample_count = sum(1 for c in raw_clips if c.get("sample"))
            midi_count = sum(1 for c in raw_clips if c.get("pattern") or c.get("notes"))
            if sample_count > midi_count:
                track_type = "audio"
        elif track_type == "audio" and orig_has_midi and not orig_has_sample:
            track_type = "midi"
        elif track_type == "audio" and orig_has_sample and not orig_has_midi:
            all_samples_are_patterns = all(
                c.get("sample", "").replace("/auto", "").replace("/", "_")
                in _VALID_PATTERNS_SET
                for c in raw_clips if c.get("sample")
            )
            if all_samples_are_patterns:
                track_type = "midi"

        # ── Phase 2: Build clips with corrected track type ──
        clips = [ClipDef.from_raw(c, structure) for c in raw_clips]

        for clip in clips:
            if track_type == "midi":
                clip.clip_type = "midi"
                if not clip.pattern and not clip.notes:
                    if clip.sample:
                        from score_renderer import _sanitize_pattern_name
                        clip.pattern = _sanitize_pattern_name(clip.sample)
                    else:
                        clip.pattern = "dembow_standard"
                    clip.sample = None
                elif clip.sample and (clip.pattern or clip.notes):
                    clip.sample = None
            else:
                clip.clip_type = "audio"
                if clip.pattern and not clip.sample:
                    from score_renderer import _sanitize_sample_ref
                    clip.sample = _sanitize_sample_ref(clip.pattern)
                    clip.pattern = None
                elif clip.pattern and clip.sample:
                    clip.pattern = None

        # Ensure MIDI tracks have an instrument
        instrument = raw.get("instrument")
        if track_type == "midi" and not instrument:
            if any(c.pattern and ("chord" in c.pattern or "melody" in c.pattern) for c in clips):
                instrument = "Wavetable"
            else:
                instrument = "Operator"

        mixer = MixerDef.from_dict(raw.get("mixer", {}))

        return cls(
            track_id    = raw.get("id", raw.get("name", "Track")),
            name        = raw.get("name", "Track"),
            track_type  = track_type,
            instrument  = instrument,
            clips       = clips,
            mixer       = mixer,
        )


class SectionDef:
    """A named temporal section of the song."""

    def __init__(self, name: str, start_bar: float, duration_bars: float):
        self.name          = name
        self.start_bar     = float(start_bar)
        self.duration_bars = float(duration_bars)

    def to_dict(self) -> Dict:
        return {
            "name":          self.name,
            "start_bar":     self.start_bar,
            "duration_bars": self.duration_bars,
        }


# ==================================================================
# SongScore — main model
# ==================================================================

class SongScore:
    """Complete musical score — pure data, no Ableton dependencies.

    Build using the builder API (set_structure, add_track, add_clip, etc.)
    or load from a dict/JSON/template.
    """

    SCHEMA_VERSION = "1.0"

    def __init__(self, title: str = "Untitled", tempo: float = 95.0,
                 key: str = "Am", genre: str = "reggaeton",
                 time_signature: str = "4/4", gap_bars: float = 2.0):
        self.meta: Dict[str, Any] = {
            "title":          title,
            "tempo":          float(tempo),
            "key":            key,
            "genre":          genre,
            "time_signature": time_signature,
            "gap_bars":       float(gap_bars),
            "version":        self.SCHEMA_VERSION,
            "created_at":     datetime.now().isoformat(),
        }
        self.structure: List[SectionDef] = []
        self.tracks:    List[TrackDef]   = []

    # ------------------------------------------------------------------
    # Builder API
    # ------------------------------------------------------------------

    def set_structure(self, sections: List[Dict]) -> "SongScore":
        """Set the temporal structure. Calculates start_bar using meta['gap_bars']."""
        gap          = float(self.meta.get("gap_bars", 2.0))
        current_bar  = 0.0
        self.structure = []

        for sec in sections:
            name     = sec.get("name", "Section")
            duration = float(sec.get("duration_bars", 8))
            # Explicit start_bar overrides auto-calculation
            start    = float(sec.get("start_bar", current_bar))
            self.structure.append(SectionDef(name, start, duration))
            current_bar = start + duration + gap

        return self

    def add_track(self, track: TrackDef) -> "SongScore":
        """Add or replace a track by ID."""
        for i, t in enumerate(self.tracks):
            if t.id == track.id:
                self.tracks[i] = track
                return self
        self.tracks.append(track)
        return self

    def add_clip_to_track(self, track_id: str, clip_raw: Dict) -> "SongScore":
        """Add a clip to an existing track. clip_raw may use 'section' keyword."""
        track = self.get_track(track_id)
        if track is None:
            raise KeyError("Track '%s' not found. Create it first." % track_id)
        clip = ClipDef.from_raw(clip_raw, self.get_structure_dict())
        track.clips.append(clip)
        return self

    def set_mixer(self, track_id: str, **kwargs) -> "SongScore":
        """Update mixer settings for a track."""
        track = self.get_track(track_id)
        if track is None:
            raise KeyError("Track '%s' not found." % track_id)
        for k, v in kwargs.items():
            if hasattr(track.mixer, k):
                setattr(track.mixer, k, v)
        return self

    # ------------------------------------------------------------------
    # Query helpers
    # ------------------------------------------------------------------

    def get_track(self, track_id: str) -> Optional[TrackDef]:
        for t in self.tracks:
            if t.id == track_id:
                return t
        return None

    def get_section(self, name: str) -> Optional[SectionDef]:
        for s in self.structure:
            if s.name == name:
                return s
        return None

    def get_structure_dict(self) -> List[Dict]:
        return [s.to_dict() for s in self.structure]

    def total_bars(self) -> float:
        if not self.structure:
            return 0.0
        last = self.structure[-1]
        return last.start_bar + last.duration_bars

    # ------------------------------------------------------------------
    # Validation
    # ------------------------------------------------------------------

    def validate(self) -> List[str]:
        """Return a list of warning strings. Empty list = valid."""
        warnings: List[str] = []

        if not self.structure:
            warnings.append("No structure defined — call set_structure() first.")
        if not self.tracks:
            warnings.append("No tracks defined.")

        seen_names = set()
        for s in self.structure:
            if s.name in seen_names:
                warnings.append(
                    "Duplicate section name '%s' — clips may map to wrong scene." % s.name
                )
            seen_names.add(s.name)

        section_names = {s.name for s in self.structure}
        for track in self.tracks:
            if not track.clips:
                warnings.append("Track '%s' has no clips." % track.id)
                continue
            for clip in track.clips:
                if clip.section and clip.section not in section_names:
                    warnings.append(
                        "Track '%s': clip section '%s' not in structure."
                        % (track.id, clip.section)
                    )
                if track.type == "audio" and not clip.sample:
                    warnings.append(
                        "Track '%s': audio clip has no sample defined." % track.id
                    )
                if track.type == "midi" and not clip.pattern and not clip.notes:
                    warnings.append(
                        "Track '%s': MIDI clip has no pattern or notes." % track.id
                    )

        return warnings

    # ------------------------------------------------------------------
    # Serialization
    # ------------------------------------------------------------------

    def to_dict(self) -> Dict:
        return {
            "meta":      self.meta,
            "structure": [s.to_dict() for s in self.structure],
            "tracks":    [t.to_dict() for t in self.tracks],
        }

    def to_json(self, indent: int = 2) -> str:
        return json.dumps(self.to_dict(), indent=indent, ensure_ascii=False)

    @classmethod
    def from_dict(cls, d: Dict) -> "SongScore":
        meta   = d.get("meta", {})
        score  = cls(
            title          = meta.get("title", "Untitled"),
            tempo          = meta.get("tempo", 95),
            key            = meta.get("key", "Am"),
            genre          = meta.get("genre", "reggaeton"),
            time_signature = meta.get("time_signature", "4/4"),
            gap_bars       = meta.get("gap_bars", 2.0),
        )
        # Preserve all meta fields
        score.meta.update(meta)

        # Structure — ignore start_bar from JSON, calculate automatically
        gap = float(score.meta.get("gap_bars", 2.0))
        current_bar = 0.0
        seen_names = set()
        
        for sec in d.get("structure", []):
            name = sec["name"]
            duration = sec.get("duration_bars", 8)
            
            # Auto-deduplicate section names
            base_name = name
            counter = 2
            while name in seen_names:
                name = "%s %d" % (base_name, counter)
                counter += 1
            seen_names.add(name)
            
            score.structure.append(SectionDef(name, current_bar, duration))
            current_bar += duration + gap

        # Tracks (clips resolved against structure)
        struct_dict = score.get_structure_dict()
        for raw in d.get("tracks", []):
            score.tracks.append(TrackDef.from_raw(raw, struct_dict))

        return score

    @classmethod
    def from_json(cls, json_str: str) -> "SongScore":
        return cls.from_dict(json.loads(json_str))

    def save(self, path: Path) -> Path:
        path = Path(path)
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(self.to_json(), encoding="utf-8")
        return path

    @classmethod
    def load(cls, path: Path) -> "SongScore":
        return cls.from_json(Path(path).read_text(encoding="utf-8"))

    # ------------------------------------------------------------------
    # Templates
    # ------------------------------------------------------------------

    @classmethod
    def from_template(cls, template_name: str, **meta_overrides) -> "SongScore":
        """Create a complete SongScore from a named template.
        
        meta_overrides: tempo, key, gap_bars, title, etc.
        Available templates: reggaeton_basic, reggaeton_13scenes, minimal_loop
        """
        templates = _get_templates()
        if template_name not in templates:
            raise ValueError(
                "Template '%s' not found. Available: %s"
                % (template_name, sorted(templates.keys()))
            )

        tmpl = templates[template_name]
        meta = {**tmpl["meta"], **meta_overrides}

        score = cls(
            title          = meta.get("title", template_name.replace("_", " ").title()),
            tempo          = meta.get("tempo", 95),
            key            = meta.get("key", "Am"),
            genre          = meta.get("genre", "reggaeton"),
            time_signature = meta.get("time_signature", "4/4"),
            gap_bars       = meta.get("gap_bars", 2.0),
        )

        score.set_structure(tmpl["structure"])
        struct_dict = score.get_structure_dict()

        for raw in tmpl["tracks"]:
            score.tracks.append(TrackDef.from_raw(raw, struct_dict))

        return score

    def list_templates(self) -> List[str]:
        return sorted(_get_templates().keys())


# ==================================================================
# Singleton helpers (used by server.py)
# ==================================================================

def get_current_score() -> Optional[SongScore]:
    return _current_score


def set_current_score(score: Optional[SongScore]) -> None:
    global _current_score
    _current_score = score


def require_score() -> SongScore:
    if _current_score is None:
        raise RuntimeError("No active score. Call new_score() or load_score() first.")
    return _current_score


# ==================================================================
# Templates
# ==================================================================

def _get_templates() -> Dict[str, Dict]:
    """Return all built-in templates."""
    # Clips that reference 'section' get start_bar resolved automatically
    return {

        # ──────────────────────────────────────────────────────────────
        "reggaeton_basic": {
            "meta": {"tempo": 95, "key": "Am", "genre": "reggaeton", "gap_bars": 2.0},
            "structure": [
                {"name": "Intro",    "duration_bars": 4},
                {"name": "Verse",    "duration_bars": 8},
                {"name": "Chorus",   "duration_bars": 8},
                {"name": "Verse 2",  "duration_bars": 8},
                {"name": "Chorus 2", "duration_bars": 8},
                {"name": "Bridge",   "duration_bars": 4},
                {"name": "Outro",    "duration_bars": 4},
            ],
            "tracks": [
                {
                    "id": "drum_loop", "name": "Drum Loop", "type": "audio",
                    "clips": [
                        {"section": "Verse",    "sample": "drumloops/auto", "loop": True},
                        {"section": "Chorus",   "sample": "drumloops/auto", "loop": True},
                        {"section": "Verse 2",  "sample": "drumloops/auto", "loop": True},
                        {"section": "Chorus 2", "sample": "drumloops/auto", "loop": True},
                    ],
                    "mixer": {"volume": 0.95},
                },
                {
                    "id": "kick", "name": "Kick", "type": "audio",
                    "clips": [
                        {"section": "Verse",    "sample": "kick/auto"},
                        {"section": "Chorus",   "sample": "kick/auto"},
                        {"section": "Verse 2",  "sample": "kick/auto"},
                        {"section": "Chorus 2", "sample": "kick/auto"},
                    ],
                    "mixer": {"volume": 0.85, "eq_preset": "kick",
                              "compression_preset": "kick_punch"},
                },
                {
                    "id": "snare", "name": "Snare", "type": "audio",
                    "clips": [
                        {"section": "Verse",    "sample": "snare/auto"},
                        {"section": "Chorus",   "sample": "snare/auto"},
                        {"section": "Verse 2",  "sample": "snare/auto"},
                        {"section": "Chorus 2", "sample": "snare/auto"},
                    ],
                    "mixer": {"volume": 0.82, "eq_preset": "snare"},
                },
                {
                    "id": "perc", "name": "Perc", "type": "audio",
                    "clips": [
                        {"section": "Verse",    "sample": "perc loop/auto", "loop": True},
                        {"section": "Chorus",   "sample": "perc loop/auto", "loop": True},
                        {"section": "Verse 2",  "sample": "perc loop/auto", "loop": True},
                        {"section": "Chorus 2", "sample": "perc loop/auto", "loop": True},
                    ],
                    "mixer": {"volume": 0.65},
                },
                {
                    "id": "dembow", "name": "Dembow", "type": "midi",
                    "instrument": "Wavetable",
                    "clips": [
                        {"section": "Intro",    "pattern": "dembow_minimal"},
                        {"section": "Verse",    "pattern": "dembow_standard"},
                        {"section": "Chorus",   "pattern": "dembow_double"},
                        {"section": "Verse 2",  "pattern": "dembow_standard"},
                        {"section": "Chorus 2", "pattern": "dembow_double"},
                    ],
                    "mixer": {"volume": 0.80},
                },
                {
                    "id": "bass", "name": "Sub Bass", "type": "midi",
                    "instrument": "Operator",
                    "clips": [
                        {"section": "Verse",    "pattern": "bass_pluck"},
                        {"section": "Chorus",   "pattern": "bass_octaves"},
                        {"section": "Verse 2",  "pattern": "bass_pluck"},
                        {"section": "Chorus 2", "pattern": "bass_octaves"},
                    ],
                    "mixer": {"volume": 0.70},
                },
                {
                    "id": "chords", "name": "Chords", "type": "midi",
                    "instrument": "Wavetable",
                    "clips": [
                        {"section": "Verse",    "pattern": "chords_verse"},
                        {"section": "Chorus",   "pattern": "chords_chorus"},
                        {"section": "Verse 2",  "pattern": "chords_verse"},
                        {"section": "Chorus 2", "pattern": "chords_chorus"},
                    ],
                    "mixer": {"volume": 0.68},
                },
            ],
        },

        # ──────────────────────────────────────────────────────────────
        "reggaeton_13scenes": {
            "meta": {"tempo": 95, "key": "Am", "genre": "reggaeton", "gap_bars": 2.0},
            "structure": [
                {"name": "Intro Suave",    "duration_bars": 4},
                {"name": "Build Up",       "duration_bars": 4},
                {"name": "Intro Full",     "duration_bars": 4},
                {"name": "Verse A",        "duration_bars": 8},
                {"name": "Pre-Chorus",     "duration_bars": 4},
                {"name": "Chorus A",       "duration_bars": 8},
                {"name": "Verse B",        "duration_bars": 8},
                {"name": "Pre-Chorus 2",   "duration_bars": 4},
                {"name": "Chorus B",       "duration_bars": 8},
                {"name": "Bridge",         "duration_bars": 4},
                {"name": "Breakdown",      "duration_bars": 4},
                {"name": "Final Chorus",   "duration_bars": 8},
                {"name": "Outro",          "duration_bars": 4},
            ],
            "tracks": [
                {
                    "id": "kick", "name": "Kick", "type": "audio",
                    "clips": [
                        {"section": "Intro Full",   "sample": "kick/auto"},
                        {"section": "Verse A",      "sample": "kick/auto"},
                        {"section": "Pre-Chorus",   "sample": "kick/auto"},
                        {"section": "Chorus A",     "sample": "kick/auto"},
                        {"section": "Verse B",      "sample": "kick/auto"},
                        {"section": "Pre-Chorus 2", "sample": "kick/auto"},
                        {"section": "Chorus B",     "sample": "kick/auto"},
                        {"section": "Final Chorus", "sample": "kick/auto"},
                    ],
                    "mixer": {"volume": 0.85, "eq_preset": "kick",
                              "compression_preset": "kick_punch"},
                },
                {
                    "id": "snare", "name": "Snare", "type": "audio",
                    "clips": [
                        {"section": "Verse A",      "sample": "snare/auto"},
                        {"section": "Chorus A",     "sample": "snare/auto"},
                        {"section": "Verse B",      "sample": "snare/auto"},
                        {"section": "Chorus B",     "sample": "snare/auto"},
                        {"section": "Final Chorus", "sample": "snare/auto"},
                    ],
                    "mixer": {"volume": 0.82, "eq_preset": "snare"},
                },
                {
                    "id": "drum_loop", "name": "Drum Loop", "type": "audio",
                    "clips": [
                        {"section": "Verse A",      "sample": "drumloops/auto", "loop": True},
                        {"section": "Chorus A",     "sample": "drumloops/auto", "loop": True},
                        {"section": "Verse B",      "sample": "drumloops/auto", "loop": True},
                        {"section": "Chorus B",     "sample": "drumloops/auto", "loop": True},
                        {"section": "Final Chorus", "sample": "drumloops/auto", "loop": True},
                    ],
                    "mixer": {"volume": 0.90},
                },
                {
                    "id": "dembow", "name": "Dembow", "type": "midi",
                    "instrument": "Wavetable",
                    "clips": [
                        {"section": "Build Up",     "pattern": "dembow_minimal"},
                        {"section": "Intro Full",   "pattern": "dembow_minimal"},
                        {"section": "Verse A",      "pattern": "dembow_standard"},
                        {"section": "Pre-Chorus",   "pattern": "dembow_standard"},
                        {"section": "Chorus A",     "pattern": "dembow_double"},
                        {"section": "Verse B",      "pattern": "dembow_standard"},
                        {"section": "Pre-Chorus 2", "pattern": "dembow_standard"},
                        {"section": "Chorus B",     "pattern": "dembow_double"},
                        {"section": "Final Chorus", "pattern": "dembow_double"},
                    ],
                    "mixer": {"volume": 0.80},
                },
                {
                    "id": "bass", "name": "Sub Bass", "type": "midi",
                    "instrument": "Operator",
                    "clips": [
                        {"section": "Verse A",      "pattern": "bass_pluck"},
                        {"section": "Chorus A",     "pattern": "bass_octaves"},
                        {"section": "Verse B",      "pattern": "bass_pluck"},
                        {"section": "Chorus B",     "pattern": "bass_octaves"},
                        {"section": "Final Chorus", "pattern": "bass_octaves"},
                    ],
                    "mixer": {"volume": 0.70},
                },
            ],
        },

        # ──────────────────────────────────────────────────────────────
        "minimal_loop": {
            "meta": {"tempo": 100, "key": "C", "genre": "reggaeton", "gap_bars": 0.0},
            "structure": [
                {"name": "Loop", "duration_bars": 8},
            ],
            "tracks": [
                {
                    "id": "drum", "name": "Drums", "type": "audio",
                    "clips": [{"section": "Loop", "sample": "drumloops/auto", "loop": True}],
                    "mixer": {"volume": 0.95},
                },
                {
                    "id": "bass", "name": "Bass", "type": "midi",
                    "instrument": "Operator",
                    "clips": [{"section": "Loop", "pattern": "bass_sub"}],
                    "mixer": {"volume": 0.75},
                },
                {
                    "id": "dembow", "name": "Dembow", "type": "midi",
                    "instrument": "Wavetable",
                    "clips": [{"section": "Loop", "pattern": "dembow_standard"}],
                    "mixer": {"volume": 0.80},
                },
            ],
        },

    }
