AICoverGen / src /modules /presets.py
R-Kentaren's picture
Upload src/modules/presets.py with huggingface_hub
73763f8 verified
Raw
History Blame Contribute Delete
8.23 kB
"""
Preset Manager
==============
Allows users to save and load combinations of voice model + processing
settings so they don't have to re-configure everything for each run.
A preset stores:
- name, description, voice_model
- pitch, pitch_all, index_rate, filter_radius, rms_mix_rate, protect
- f0_method, crepe_hop_length, steps
- main_gain, backup_gain, inst_gain
- reverb_rm_size, reverb_wet, reverb_dry, reverb_damping
- output_format, extra_denoise
Presets are persisted as JSON in <BASE_DIR>/data/presets.json.
"""
import json
import os
import time
from typing import Any, Dict, List, Optional
PRESET_FIELDS = [
"voice_model",
"pitch",
"pitch_all",
"index_rate",
"filter_radius",
"rms_mix_rate",
"protect",
"f0_method",
"crepe_hop_length",
"steps",
"main_gain",
"backup_gain",
"inst_gain",
"reverb_rm_size",
"reverb_wet",
"reverb_dry",
"reverb_damping",
"output_format",
"extra_denoise",
]
# Built-in starter presets that ship with the app
BUILTIN_PRESETS: List[Dict[str, Any]] = [
{
"name": "Default",
"description": "Balanced settings suitable for most songs.",
"voice_model": "",
"pitch": 0,
"pitch_all": 0,
"index_rate": 0.5,
"filter_radius": 3,
"rms_mix_rate": 0.25,
"protect": 0.33,
"f0_method": "rmvpe+",
"crepe_hop_length": 128,
"steps": 1,
"main_gain": 0,
"backup_gain": 0,
"inst_gain": 0,
"reverb_rm_size": 0.15,
"reverb_wet": 0.2,
"reverb_dry": 0.8,
"reverb_damping": 0.7,
"output_format": "mp3",
"extra_denoise": True,
"builtin": True,
},
{
"name": "Male -> Female",
"description": "Pitch up by 12 semitones, smooth rmvpe detection.",
"voice_model": "",
"pitch": 1,
"pitch_all": 0,
"index_rate": 0.6,
"filter_radius": 3,
"rms_mix_rate": 0.25,
"protect": 0.33,
"f0_method": "rmvpe",
"crepe_hop_length": 128,
"steps": 1,
"main_gain": 0,
"backup_gain": 0,
"inst_gain": 0,
"reverb_rm_size": 0.15,
"reverb_wet": 0.2,
"reverb_dry": 0.8,
"reverb_damping": 0.7,
"output_format": "mp3",
"extra_denoise": True,
"builtin": True,
},
{
"name": "Female -> Male",
"description": "Pitch down by 12 semitones, extra protection.",
"voice_model": "",
"pitch": -1,
"pitch_all": 0,
"index_rate": 0.6,
"filter_radius": 3,
"rms_mix_rate": 0.3,
"protect": 0.25,
"f0_method": "rmvpe",
"crepe_hop_length": 128,
"steps": 1,
"main_gain": 0,
"backup_gain": 0,
"inst_gain": 0,
"reverb_rm_size": 0.18,
"reverb_wet": 0.18,
"reverb_dry": 0.82,
"reverb_damping": 0.7,
"output_format": "mp3",
"extra_denoise": True,
"builtin": True,
},
{
"name": "Studio Quality",
"description": "WAV output, mangio-crepe, max index rate.",
"voice_model": "",
"pitch": 0,
"pitch_all": 0,
"index_rate": 0.85,
"filter_radius": 3,
"rms_mix_rate": 0.25,
"protect": 0.33,
"f0_method": "mangio-crepe",
"crepe_hop_length": 128,
"steps": 2,
"main_gain": 0,
"backup_gain": 0,
"inst_gain": 0,
"reverb_rm_size": 0.12,
"reverb_wet": 0.15,
"reverb_dry": 0.85,
"reverb_damping": 0.7,
"output_format": "wav",
"extra_denoise": True,
"builtin": True,
},
{
"name": "Karaoke Mode",
"description": "Reduced reverb, clean dry vocals for karaoke.",
"voice_model": "",
"pitch": 0,
"pitch_all": 0,
"index_rate": 0.5,
"filter_radius": 3,
"rms_mix_rate": 0.25,
"protect": 0.33,
"f0_method": "rmvpe+",
"crepe_hop_length": 128,
"steps": 1,
"main_gain": 2,
"backup_gain": -3,
"inst_gain": -2,
"reverb_rm_size": 0.08,
"reverb_wet": 0.08,
"reverb_dry": 0.92,
"reverb_damping": 0.6,
"output_format": "mp3",
"extra_denoise": True,
"builtin": True,
},
{
"name": "Concert Hall",
"description": "Heavy reverb for a live-concert feel.",
"voice_model": "",
"pitch": 0,
"pitch_all": 0,
"index_rate": 0.5,
"filter_radius": 3,
"rms_mix_rate": 0.25,
"protect": 0.33,
"f0_method": "rmvpe+",
"crepe_hop_length": 128,
"steps": 1,
"main_gain": 0,
"backup_gain": 0,
"inst_gain": 0,
"reverb_rm_size": 0.55,
"reverb_wet": 0.4,
"reverb_dry": 0.6,
"reverb_damping": 0.5,
"output_format": "mp3",
"extra_denoise": True,
"builtin": True,
},
]
class PresetManager:
"""Manages persistence and lookup of preset configurations."""
def __init__(self, presets_path: str):
self.path = presets_path
self._ensure_file()
def _ensure_file(self) -> None:
if not os.path.exists(self.path):
os.makedirs(os.path.dirname(self.path), exist_ok=True)
self._write(BUILTIN_PRESETS)
def _read(self) -> List[Dict[str, Any]]:
try:
with open(self.path, "r", encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, OSError):
return list(BUILTIN_PRESETS)
def _write(self, presets: List[Dict[str, Any]]) -> None:
os.makedirs(os.path.dirname(self.path), exist_ok=True)
with open(self.path, "w", encoding="utf-8") as f:
json.dump(presets, f, indent=2, ensure_ascii=False)
def list_names(self) -> List[str]:
return [p["name"] for p in self._read()]
def get(self, name: str) -> Optional[Dict[str, Any]]:
for p in self._read():
if p["name"] == name:
return p
return None
def save(self, name: str, description: str, **values) -> str:
name = (name or "").strip()
if not name:
return "Error: preset name is required."
presets = self._read()
# Build the preset from provided values (only keep known fields)
preset: Dict[str, Any] = {
"name": name,
"description": description or "",
"builtin": False,
"created_at": int(time.time()),
}
for field in PRESET_FIELDS:
preset[field] = values.get(field, None)
# Replace if exists (but never overwrite builtin)
new_list = []
replaced = False
for p in presets:
if p["name"] == name:
if p.get("builtin"):
return f"Error: cannot overwrite built-in preset '{name}'."
new_list.append(preset)
replaced = True
else:
new_list.append(p)
if not replaced:
new_list.append(preset)
self._write(new_list)
return f"Saved preset '{name}'."
def delete(self, name: str) -> str:
presets = self._read()
new_list = []
deleted = False
for p in presets:
if p["name"] == name:
if p.get("builtin"):
return f"Error: cannot delete built-in preset '{name}'."
deleted = True
continue
new_list.append(p)
if not deleted:
return f"Error: preset '{name}' not found."
self._write(new_list)
return f"Deleted preset '{name}'."
def reset_builtin(self) -> str:
"""Restore the built-in presets (keeps user presets)."""
presets = self._read()
names = {p["name"] for p in presets if not p.get("builtin")}
new_list = [p for p in presets if not p.get("builtin")] + list(BUILTIN_PRESETS)
self._write(new_list)
return f"Restored {len(BUILTIN_PRESETS)} built-in presets ({len(names)} user presets kept)."