import gradio as gr import librosa import numpy as np from scipy import signal import mido from mido import MidiFile, MidiTrack, Message import tempfile import os def mp3_to_midi(audio_file, threshold=0.1, hop_length=512): """ Convert MP3 audio to MIDI file. Args: audio_file: Path to MP3 file threshold: Confidence threshold for note detection (0-1) hop_length: Number of samples between successive frames Returns: Path to generated MIDI file """ try: # Load audio file y, sr = librosa.load(audio_file, sr=None) # Compute constant-Q transform for pitch detection fmin = librosa.note_to_hz('C1') n_bins = 84 bins_per_octave = 12 cqt = librosa.cqt(y, sr=sr, hop_length=hop_length, fmin=fmin, n_bins=n_bins, bins_per_octave=bins_per_octave) cqt_magnitude = np.abs(cqt) # Get the note with maximum magnitude at each time step notes = np.argmax(cqt_magnitude, axis=0) # Compute frequencies for each CQT bin frequencies = librosa.cqt_frequencies(n_bins=n_bins, fmin=fmin, bins_per_octave=bins_per_octave) # Get magnitude values note_magnitudes = cqt_magnitude[notes, np.arange(len(notes))] # Normalize magnitudes max_magnitude = np.max(note_magnitudes) normalized_magnitudes = note_magnitudes / max_magnitude if max_magnitude > 0 else note_magnitudes # Apply threshold active_notes = normalized_magnitudes > threshold # Create MIDI file mid = MidiFile() track = MidiTrack() mid.tracks.append(track) # Set tempo (microseconds per beat) tempo = mido.bpm2tempo(120) track.append(Message('program_change', program=0, time=0)) # Calculate time per frame in ticks time_per_frame = int((60 * 1000000 / tempo) / (sr / hop_length) * 1000) current_note = None note_start = 0 for i in range(len(notes)): freq = frequencies[notes[i]] # Convert frequency to MIDI note number midi_note = librosa.hz_to_midi(freq) midi_note = int(round(midi_note)) # Clamp to valid MIDI range midi_note = max(0, min(127, midi_note)) if active_notes[i]: if current_note is None or current_note != midi_note: # Note changed or new note started if current_note is not None: # End previous note duration = max(1, (i - note_start) * time_per_frame) track.append(Message('note_off', note=current_note, velocity=80, time=duration)) # Start new note track.append(Message('note_on', note=midi_note, velocity=100, time=0)) current_note = midi_note note_start = i else: if current_note is not None: # End current note duration = max(1, (i - note_start) * time_per_frame) track.append(Message('note_off', note=current_note, velocity=80, time=duration)) current_note = None # End any remaining note if current_note is not None: duration = max(1, (len(notes) - note_start) * time_per_frame) track.append(Message('note_off', note=current_note, velocity=80, time=duration)) # Save MIDI file output_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mid') output_path = output_file.name output_file.close() mid.save(output_path) return output_path, "✓ Conversion successful!" except Exception as e: return None, f"✗ Error: {str(e)}" def create_interface(): """Create and launch the Gradio interface""" with gr.Blocks(title="MP3 to MIDI Converter") as demo: gr.Markdown( """ # 🎵 MP3 to MIDI Converter Convert your MP3 audio files to MIDI format using pitch detection. **How it works:** 1. Upload an MP3 file 2. Adjust the sensitivity threshold (lower = more notes detected) 3. Click Convert 4. Download the generated MIDI file """ ) with gr.Row(): with gr.Column(): gr.Markdown("### Input") audio_input = gr.Audio( label="Upload MP3 File", type="filepath" ) threshold = gr.Slider( minimum=0.01, maximum=0.5, value=0.1, step=0.01, label="Detection Threshold", info="Lower values detect quieter notes (more sensitive)" ) convert_btn = gr.Button("🎹 Convert to MIDI", variant="primary", scale=2) with gr.Column(): gr.Markdown("### Output") status_text = gr.Textbox( label="Status", interactive=False, lines=1 ) midi_output = gr.File( label="Download MIDI File", interactive=False ) gr.Markdown( """ ### Tips for Best Results - **Clear audio**: Use high-quality recordings for better pitch detection - **Single instrument**: Works best with monophonic (single-note) audio - **Adjust sensitivity**: If you're getting too many notes, increase threshold - **Clean recordings**: Minimize background noise for accurate conversion """ ) # Handle conversion def convert(audio_file, threshold_val): if audio_file is None: return None, "✗ Please upload an MP3 file" midi_path, status = mp3_to_midi(audio_file, threshold=threshold_val) if midi_path and os.path.exists(midi_path): return midi_path, status else: return None, status convert_btn.click( fn=convert, inputs=[audio_input, threshold], outputs=[midi_output, status_text] ) return demo if __name__ == "__main__": demo = create_interface() demo.launch(share=False)