# Delta for melody-engine

## ADDED Requirements

| # | Requirement | RFC |
|---|------------|-----|
| R1 | Motif generation with 3 reggaeton styles | MUST |
| R2 | Deterministic output from seed | MUST |
| R3 | Call-and-response phrase structure | MUST |
| R4 | Chord-aware note selection | MUST |
| R5 | Motif variation via transpose/rhythmic shift | SHOULD |
| R6 | build_lead_track() delegation | MUST |

### Requirement: Motif Generation (R1)

`build_motif(key_root, key_minor, style, bars, seed)` MUST generate a 2–4 bar repeating motif using scale-aware note selection. Three styles:

- **hook**: Arch contour (ascend then descend), chord tones on beats 0, 2, 4..., 4–8 notes
- **stabs**: Short 16th-duration hits on dembow grid positions [1.0, 2.5, 3.0, 3.5] per bar
- **smooth**: Stepwise scalar motion at eighth-note density, ≤2 semitones between consecutive notes

MUST accept `bars` parameter (2–8) defaulting to 4. MUST return `list[MidiNote]`.

#### Scenario: hook style generates arch contour with chord tones

- GIVEN key Am, style "hook", bars=4, seed=42
- WHEN `build_motif("A", True, "hook", 4, 42)` is called
- THEN returns 4–12 MidiNote objects
- AND notes on quarter-beat positions (0, 2, 4, …) are within the i-VI-III-VII chord tones ≥70% of the time

#### Scenario: stabs style generates dembow-positioned hits

- GIVEN key Am, style "stabs", bars=2, seed=1
- WHEN `build_motif("A", True, "stabs", 2, 1)` is called
- THEN all note start times are within {1.0, 2.5, 3.0, 3.5} per bar
- AND each note duration ≤ 0.25 beats (16th note)

#### Scenario: smooth style generates stepwise motion

- GIVEN key Am, style "smooth", bars=4, seed=7
- WHEN `build_motif("A", True, "smooth", 4, 7)` is called
- THEN pitch difference between consecutive notes ≤ 2 semitones

#### Scenario: invalid style raises ValueError

- GIVEN an unrecognized style string
- WHEN `build_motif("A", True, "invalid", 4, 42)` is called
- THEN raises ValueError with message containing valid styles

### Requirement: Deterministic Output (R2)

`build_motif()` and `apply_variation()` MUST produce identical output for identical input parameters (key, style, bars, seed). MUST NOT rely on global RNG state.

#### Scenario: same seed produces identical output

- GIVEN fixed parameters
- WHEN `build_motif("A", True, "hook", 4, 42)` is called twice
- THEN both calls return identical lists of MidiNote objects

#### Scenario: different seeds produce different output

- GIVEN same key and style but different seeds
- WHEN `build_motif("A", True, "hook", 4, 42)` and `build_motif("A", True, "hook", 4, 99)` are called
- THEN the returned note lists differ

### Requirement: Call-and-Response Structure (R3)

`build_call_response(motif, bars, key_root, key_minor, seed)` MUST generate two halves: **call** (motif + variation, ending on V or VII degree) and **response** (motif, resolving to tonic i). Total length MUST equal `bars` parameter. SHALL repeat motif to fill section length.

#### Scenario: call ends on tension, response resolves

- GIVEN an Am hook motif, bars=8, seed=42
- WHEN `build_call_response(motif, 8, "A", True, 42)` is called
- THEN the last note of the first 4 bars has pitch in {E, G} (V or VII of Am)
- AND the last note of the final bar (bar 8) has pitch in {A} (tonic)

#### Scenario: fills section with motif repetition

- GIVEN a 2-bar motif and bars=8
- WHEN `build_call_response(motif, 8, "A", True, 42)` is called
- THEN returns notes spanning 8 bars total
- AND motif content repeats at least 2 times within the 8 bars

### Requirement: Chord-Aware Notes (R4)

Note selection on strong beats (quarter note positions 0, 4, 8, 12 per bar in 16th-note grid) MUST favor chord tones from `CHORD_PROGRESSION`. Weak beats (all other positions) MAY use any scale degree.

#### Scenario: strong beats favor chord tones

- GIVEN key Am (CHORD_PROGRESSION = i-VI-III-VII), style "hook", bars=8
- WHEN a motif is generated
- THEN ≥70% of notes starting on quarter-beat boundaries belong to active chord tones

### Requirement: Motif Variation (R5)

`apply_variation(motif, shift_beats, transpose_semitones)` SHOULD produce a recognizable variant of the input motif. `shift_beats` offsets all start times within the loop. `transpose_semitones` shifts pitches within the scale. MUST return `list[MidiNote]`.

#### Scenario: rhythmic shift preserves note count and structure

- GIVEN a 4-bar hook motif
- WHEN `apply_variation(motif, shift_beats=0.25)` is called
- THEN note count equals original
- AND all note durations equal original
- AND inter-onset intervals are preserved

#### Scenario: transpose within scale preserves motif contour

- GIVEN a 4-bar hook motif in Am
- WHEN `apply_variation(motif, transpose_semitones=3)` is called
- THEN all pitches are offset by ±3 semitones (within pentatonic scale)

### Requirement: build_lead_track() Delegation (R6)

`build_lead_track()` in `compose.py` MUST delegate to `melody_engine.build_call_response()` instead of generating random pentatonic notes directly. MUST keep identical function signature. MUST pass existing tests after adjusting expected note counts.

#### Scenario: build_lead_track uses call-response structure

- GIVEN seed=42, key Am, sections containing "chorus" and "final"
- WHEN `build_lead_track(sections, offsets, "A", True, 42)` is called
- THEN returned TrackDef clips contain notes organized as call-response phrases
- AND at least one clip has notes ending on tonic pitch
