diff --git a/ComfyUI-basic-pitch/__init__.py b/ComfyUI-basic-pitch/__init__.py new file mode 100644 index 0000000..50fb1bb --- /dev/null +++ b/ComfyUI-basic-pitch/__init__.py @@ -0,0 +1,3 @@ +from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] \ No newline at end of file diff --git a/ComfyUI-basic-pitch/nodes.py b/ComfyUI-basic-pitch/nodes.py new file mode 100644 index 0000000..097579b --- /dev/null +++ b/ComfyUI-basic-pitch/nodes.py @@ -0,0 +1,161 @@ +import os +from typing import Tuple, List, Dict, Any +from basic_pitch.inference import predict +from midiutil import MIDIFile + +class AudioToMidi: + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Dict[str, Any]]: + return { + "required": { + "audio_path": ("STRING", { + "multiline": False, + "placeholder": "Path to audio file" + }), + }, + "optional": { + "onset_threshold": ("FLOAT", { + "default": 0.5, + "min": 0.0, + "max": 1.0, + "step": 0.01 + }), + "frame_threshold": ("FLOAT", { + "default": 0.3, + "min": 0.0, + "max": 1.0, + "step": 0.01 + }), + } + } + + RETURN_TYPES = ("MIDI_DATA",) + FUNCTION = "convert_audio_to_midi" + CATEGORY = "basic-pitch" + + def convert_audio_to_midi(self, + audio_path: str, + onset_threshold: float = 0.5, + frame_threshold: float = 0.3) -> Tuple[List]: + if not os.path.exists(audio_path): + raise ValueError(f"Audio file not found: {audio_path}") + + try: + # Perform pitch estimation with the correct parameters + model_output, midi_data, note_events = predict( + audio_path, + onset_threshold=onset_threshold, + frame_threshold=frame_threshold + ) + + # Process the MIDI data from the PrettyMIDI object + processed_midi_data = [] + + # Iterate through all instruments + for instrument in midi_data.instruments: + # Get all notes from the instrument + for note in instrument.notes: + processed_midi_data.append(( + int(note.pitch), + float(note.start), + float(note.end), + int(note.velocity) + )) + + # Sort by start time + processed_midi_data.sort(key=lambda x: x[1]) + + return (processed_midi_data,) + + except Exception as e: + raise RuntimeError(f"Error during audio-to-MIDI conversion: {str(e)}") + + +class SaveMidi: + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Dict[str, Any]]: + return { + "required": { + "midi_data": ("MIDI_DATA",), + "file_name": ("STRING", { + "default": "output.mid", + "multiline": False, + "placeholder": "Output file name" + }), + "output_path": ("STRING", { + "default": "D:\\OLD_ComfyUI_windows_portable\\ComfyUI\\output\\midi", + "multiline": False, + "placeholder": "Output directory path" + }), + "tempo": ("INT", { + "default": 120, + "min": 20, + "max": 300, + "step": 1 + }), + }, + } + + RETURN_TYPES = () + FUNCTION = "save_midi" + CATEGORY = "basic-pitch" + OUTPUT_NODE = True + + def save_midi(self, midi_data: List, file_name: str, output_path: str, tempo: int = 120) -> Tuple: + if not midi_data: + raise ValueError("No MIDI data provided") + + # Create a MIDI file + midi = MIDIFile(1) # One track + track = 0 + time = 0 + channel = 0 + + # Add track name and tempo + midi.addTrackName(track, time, "Track 1") + midi.addTempo(track, time, tempo) + + # Add notes + for note in midi_data: + if len(note) != 4: + continue # Skip invalid note data + + pitch, start_time, end_time, velocity = note + duration = end_time - start_time + + # Ensure values are within valid MIDI ranges + pitch = max(0, min(127, int(pitch))) + velocity = max(0, min(127, int(velocity))) + + midi.addNote( + track=track, + channel=channel, + pitch=pitch, + time=start_time, + duration=duration, + volume=velocity + ) + + # Ensure output directory exists + os.makedirs(output_path, exist_ok=True) + + # Save the MIDI file + output_file = os.path.join(output_path, file_name) + try: + with open(output_file, "wb") as f: + midi.writeFile(f) + except Exception as e: + raise RuntimeError(f"Error saving MIDI file: {str(e)}") + + return () + + +NODE_CLASS_MAPPINGS = { + "AudioToMidi": AudioToMidi, + "SaveMidi": SaveMidi, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "AudioToMidi": "Audio to MIDI Converter", + "SaveMidi": "Save MIDI File", +} \ No newline at end of file diff --git a/ComfyUI-basic-pitch/requirements.txt b/ComfyUI-basic-pitch/requirements.txt new file mode 100644 index 0000000..a87ee75 --- /dev/null +++ b/ComfyUI-basic-pitch/requirements.txt @@ -0,0 +1,2 @@ +basic-pitch +midiutil \ No newline at end of file