feat: fix sample variety per section and reorganize sample library

- Fix compose.py to select different samples per section instead of one per role
- Add select_many() to SampleSelector for diverse sample selection
- Migrate 862 samples from scattered dirs to libreria/samples/{role}/
- Rename files with consistent convention: {role}_{key}_{bpm}_{character}_{hash}.wav
- Add migrate_library.py script with dry-run and verification
- Backup original index as sample_index_pre_migration.json
- 72 tests passing
This commit is contained in:
renato97
2026-05-03 14:43:11 -03:00
parent d5c2490a05
commit 32dafd94e0
5 changed files with 128944 additions and 6 deletions

View File

@@ -235,13 +235,11 @@ def build_section_tracks(
# Build one track per role
tracks: list[TrackDef] = []
# Track used sample IDs per role for diversity
used_sample_ids: dict[str, list[str]] = {}
for role, role_cfg in roles.items():
sample_role = ROLE_TO_SAMPLE_ROLE.get(role, role)
generator_name = role_cfg.get("notes_template", "")
# Select sample for this role
sample_match = selector.select_one(role=sample_role, key=key, bpm=bpm)
sample_path = sample_match.get("original_path") if sample_match else None
# Collect clips for each section
section_clips: list[ClipDef] = []
@@ -251,6 +249,20 @@ def build_section_tracks(
vel_mult = section.energy
vol_mult = section.energy
# For audio roles, select a different sample per section
sample_path = None
if role in AUDIO_ROLES:
exclude = used_sample_ids.get(role, [])
diverse_results = selector.select_diverse(
role=sample_role, n=1, exclude=exclude, key=key, bpm=bpm
)
if diverse_results:
sample = diverse_results[0]
sample_path = sample.get("original_path")
sample_id = sample.get("file_hash", "")
if sample_id:
used_sample_ids.setdefault(role, []).append(sample_id)
if role in ROLE_RHYTHM_GENERATORS:
gen_name = ROLE_RHYTHM_GENERATORS[role]
note_dict = get_notes(gen_name, section.bars, velocity_mult=vel_mult)
@@ -281,12 +293,12 @@ def build_section_tracks(
gen_fn = ROLE_MELODIC_GENERATORS[role]
note_list = gen_fn(key=key, bars=section.bars, velocity_mult=vel_mult)
midi_notes = melodic_to_midi(note_list)
# Melodic roles use MIDI instruments — no audio_path needed
clip = ClipDef(
position=sec_offset * 4.0,
length=section.bars * 4.0,
name=f"{section.name.capitalize()} {role.capitalize()}",
midi_notes=midi_notes,
audio_path=sample_path,
)
section_clips.append(clip)