maestroai / tools /build_electronic_reference.py
gabrielpamplonapg
Enable MERT for electronic gear: synth reference bank + identify tool
d821d6b
Raw
History Blame Contribute Delete
3.13 kB
"""build_electronic_reference.py — Seed the MERT reference DB with electronic
gear (drum machines + synths) so embedding-based ID works on electronic audio.
Backs up the existing (acoustic) DB first, then adds several variants per gear
class (different tempos/seeds for drums, different pitches for synths) for
nearest-neighbor robustness.
Usage: python3 tools/build_electronic_reference.py
"""
import logging
import os
import shutil
import sys
import tempfile
import numpy as np
import soundfile as sf
logging.basicConfig(level=logging.WARNING)
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from backend.mert_embeddings import MERTExtractor, get_reference_db, InstrumentReferenceDB
from tools.synth_gear import SR, KITS, SYNTHS, synth_riff
DRUM_SEEDS = [1, 2, 3] # pattern/tempo variants per machine
# Dense root coverage (~G1..D4, every few semitones) so a query at any pitch has
# a near reference. Synths are rendered as arpeggio riffs, not sustained drones.
SYNTH_ROOTS = [49, 65, 82, 98, 123, 147, 185, 233, 294]
def _embed_array(extractor, y):
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tf:
path = tf.name
try:
sf.write(path, y, SR)
return extractor.extract(path)
finally:
os.unlink(path)
def main():
db = get_reference_db()
# Back up the acoustic-only DB once.
backup = InstrumentReferenceDB.DB_PATH.with_suffix(".backup_acoustic.json")
if not backup.exists():
shutil.copy(InstrumentReferenceDB.DB_PATH, backup)
print(f"Backed up acoustic DB -> {backup.name}")
# Don't double-seed: skip if electronic entries already present.
existing_models = {e["model"] for e in db.entries}
already = existing_models & (set(KITS) | set(SYNTHS))
if already:
print(f"Electronic entries already present ({len(already)} models). "
f"Delete them or restore the backup to rebuild. Aborting.")
return
start = len(db.entries)
extractor = MERTExtractor()
print(f"Seeding electronic gear (starting from {start} acoustic entries)…\n")
# Drum machines
for model, render in KITS.items():
for seed in DRUM_SEEDS:
y = render(seed=seed)
emb = _embed_array(extractor, y)
db.add(emb, model=model, family="drum_machine",
source_file=f"synth:{model}:seed{seed}")
print(f" ✓ {model} ({len(DRUM_SEEDS)} variants)")
# Synths — rendered as arpeggio riffs across dense root coverage
for model, (family, fn) in SYNTHS.items():
for root in SYNTH_ROOTS:
y = synth_riff(fn, root=root)
emb = _embed_array(extractor, y)
db.add(emb, model=model, family=family,
source_file=f"synth_riff:{model}:{root}Hz")
print(f" ✓ {model} ({len(SYNTH_ROOTS)} roots, family={family})")
added = len(db.entries) - start
print(f"\nDone. Added {added} electronic reference embeddings "
f"(DB now {len(db.entries)} total).")
if __name__ == "__main__":
main()