add loudnorm; default model to hf hub
This commit is contained in:
+3
-3
@@ -2,6 +2,7 @@
|
|||||||
"""
|
"""
|
||||||
This script generates MIDI files from text prompts using the MIDI-LLM model with vLLM backend.
|
This script generates MIDI files from text prompts using the MIDI-LLM model with vLLM backend.
|
||||||
vLLM provides faster inference compared to standard HuggingFace model.generate() mixin.
|
vLLM provides faster inference compared to standard HuggingFace model.generate() mixin.
|
||||||
|
Caveat is that initialization + compilation takes more time, so best used for inference with many prompts.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@@ -235,8 +236,8 @@ Examples:
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--model",
|
"--model",
|
||||||
type=str,
|
type=str,
|
||||||
required=True,
|
default="slseanwu/MIDI-LLM_Llama-3.2-1B",
|
||||||
help="Path to MIDI-LLM model checkpoint"
|
help="Path to MIDI-LLM model checkpoint, can be HuggingFace model ID or local path (default: slseanwu/MIDI-LLM_Llama-3.2-1B)"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Input arguments (not required if using --interactive only)
|
# Input arguments (not required if using --interactive only)
|
||||||
@@ -494,4 +495,3 @@ Examples:
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
+32
-4
@@ -23,12 +23,20 @@ except ImportError:
|
|||||||
|
|
||||||
# Optional dependencies for audio synthesis
|
# Optional dependencies for audio synthesis
|
||||||
SYNTHESIS_AVAILABLE = False
|
SYNTHESIS_AVAILABLE = False
|
||||||
|
LOUDNESS_NORM_AVAILABLE = False
|
||||||
try:
|
try:
|
||||||
import midi2audio
|
import midi2audio
|
||||||
import librosa
|
import librosa
|
||||||
import librosa.effects
|
import librosa.effects
|
||||||
import soundfile as sf
|
import soundfile as sf
|
||||||
SYNTHESIS_AVAILABLE = True
|
SYNTHESIS_AVAILABLE = True
|
||||||
|
|
||||||
|
# Optional loudness normalization
|
||||||
|
try:
|
||||||
|
import pyloudnorm as pyln
|
||||||
|
LOUDNESS_NORM_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -89,16 +97,18 @@ def synthesize_midi_to_audio(
|
|||||||
midi_path: str,
|
midi_path: str,
|
||||||
soundfont_path: str,
|
soundfont_path: str,
|
||||||
save_mp3: bool = True,
|
save_mp3: bool = True,
|
||||||
samplerate: Optional[int] = None
|
samplerate: Optional[int] = None,
|
||||||
|
target_loudness: float = -18.0
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Synthesize MIDI file to audio (WAV/MP3) using FluidSynth.
|
Synthesize MIDI file to audio (WAV/MP3) using FluidSynth with loudness normalization.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
midi_path: Path to MIDI file
|
midi_path: Path to MIDI file
|
||||||
soundfont_path: Path to SoundFont (.sf2) file
|
soundfont_path: Path to SoundFont (.sf2) file
|
||||||
save_mp3: If True, convert to MP3 and delete WAV
|
save_mp3: If True, convert to MP3 and delete WAV
|
||||||
samplerate: Optional sample rate for audio
|
samplerate: Optional sample rate for audio
|
||||||
|
target_loudness: Target loudness in LUFS (default: -14.0, Spotify standard)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if successful, False otherwise
|
True if successful, False otherwise
|
||||||
@@ -106,7 +116,7 @@ def synthesize_midi_to_audio(
|
|||||||
if not SYNTHESIS_AVAILABLE:
|
if not SYNTHESIS_AVAILABLE:
|
||||||
print("Warning: Audio synthesis libraries not available. Skipping synthesis.")
|
print("Warning: Audio synthesis libraries not available. Skipping synthesis.")
|
||||||
print("Install with: conda install conda-forge::fluidsynth conda-forge::ffmpeg")
|
print("Install with: conda install conda-forge::fluidsynth conda-forge::ffmpeg")
|
||||||
print(" pip install midi2audio librosa soundfile")
|
print(" pip install midi2audio librosa soundfile pyloudnorm")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -120,9 +130,27 @@ def synthesize_midi_to_audio(
|
|||||||
# Synthesize MIDI to WAV
|
# Synthesize MIDI to WAV
|
||||||
fs.midi_to_audio(midi_path, wav_path)
|
fs.midi_to_audio(midi_path, wav_path)
|
||||||
|
|
||||||
# Trim silence from audio
|
# Load and trim silence from audio
|
||||||
wav, sr = librosa.load(wav_path)
|
wav, sr = librosa.load(wav_path)
|
||||||
wav, _ = librosa.effects.trim(wav, top_db=30)
|
wav, _ = librosa.effects.trim(wav, top_db=30)
|
||||||
|
|
||||||
|
# Apply loudness normalization
|
||||||
|
if LOUDNESS_NORM_AVAILABLE:
|
||||||
|
try:
|
||||||
|
# Measure the loudness
|
||||||
|
meter = pyln.Meter(sr)
|
||||||
|
loudness = meter.integrated_loudness(wav)
|
||||||
|
|
||||||
|
# Normalize to target loudness
|
||||||
|
wav = pyln.normalize.loudness(wav, loudness, target_loudness)
|
||||||
|
|
||||||
|
# Prevent clipping
|
||||||
|
if wav.max() > 1.0 or wav.min() < -1.0:
|
||||||
|
wav = wav / max(abs(wav.max()), abs(wav.min()))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Loudness normalization failed: {e}")
|
||||||
|
|
||||||
|
# Write normalized audio
|
||||||
sf.write(wav_path, wav, sr)
|
sf.write(wav_path, wav, sr)
|
||||||
|
|
||||||
if save_mp3:
|
if save_mp3:
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ jsonschema-specifications==2025.9.1
|
|||||||
lark==1.2.2
|
lark==1.2.2
|
||||||
lazy_loader==0.4
|
lazy_loader==0.4
|
||||||
librosa==0.11.0
|
librosa==0.11.0
|
||||||
|
pyloudnorm==0.1.1
|
||||||
llguidance==0.7.30
|
llguidance==0.7.30
|
||||||
llvmlite==0.44.0
|
llvmlite==0.44.0
|
||||||
lm-format-enforcer==0.11.3
|
lm-format-enforcer==0.11.3
|
||||||
@@ -150,3 +151,4 @@ websockets==15.0.1
|
|||||||
xformers==0.0.32.post1
|
xformers==0.0.32.post1
|
||||||
xgrammar==0.1.25
|
xgrammar==0.1.25
|
||||||
yarl==1.22.0
|
yarl==1.22.0
|
||||||
|
future==1.0.0
|
||||||
Reference in New Issue
Block a user