| | from flask import Flask, request, jsonify |
| | import os |
| | from spleeter.separator import Separator |
| | import autochord |
| | import pretty_midi |
| | import librosa |
| | import matchering as mg |
| | from pedalboard import Pedalboard, HighpassFilter, Compressor, Limiter, Reverb, Gain |
| | from pedalboard.io import AudioFile |
| | import numpy as np |
| | from scipy.signal import butter, lfilter |
| |
|
| | app = Flask(__name__) |
| |
|
| | |
| | def separate_audio(input_path, output_path): |
| | separator = Separator('spleeter:5stems') |
| | os.makedirs(output_path, exist_ok=True) |
| | separator.separate_to_file(input_path, output_path) |
| | return { |
| | "vocals": os.path.join(output_path, 'vocals.wav'), |
| | "accompaniment": os.path.join(output_path, 'other.wav'), |
| | "bass": os.path.join(output_path, 'bass.wav'), |
| | "drums": os.path.join(output_path, 'drums.wav'), |
| | "piano": os.path.join(output_path, 'piano.wav') |
| | } |
| |
|
| | |
| | class MusicToChordsConverter: |
| | def __init__(self, audio_file): |
| | self.audio_file = audio_file |
| | self.chords = None |
| | self.midi_chords = pretty_midi.PrettyMIDI() |
| | self.instrument_chords = pretty_midi.Instrument(program=0) |
| |
|
| | def recognize_chords(self): |
| | self.chords = autochord.recognize(self.audio_file, lab_fn='chords.lab') |
| |
|
| | def chord_to_midi_notes(self, chord_name): |
| | note_mapping = { |
| | 'C:maj': ['C4', 'E4', 'G4'], |
| | 'C:min': ['C4', 'E-4', 'G4'], |
| | 'D:maj': ['D4', 'F#4', 'A4'], |
| | 'D:min': ['D4', 'F4', 'A4'], |
| | 'E:maj': ['E4', 'G#4', 'B4'], |
| | 'E:min': ['E4', 'G4', 'B4'], |
| | 'F:maj': ['F4', 'A4', 'C5'], |
| | 'F:min': ['F4', 'A-4', 'C5'], |
| | 'G:maj': ['G4', 'B4', 'D5'], |
| | 'G:min': ['G4', 'B-4', 'D5'], |
| | 'A:maj': ['A4', 'C#5', 'E5'], |
| | 'A:min': ['A4', 'C5', 'E5'], |
| | 'B:maj': ['B4', 'D#5', 'F#5'], |
| | 'B:min': ['B4', 'D5', 'F#5'] |
| | } |
| | return note_mapping.get(chord_name, []) |
| |
|
| | def generate_midi(self): |
| | for chord in self.chords: |
| | start_time = chord[0] |
| | end_time = chord[1] |
| | chord_name = chord[2] |
| | if chord_name != 'N': |
| | chord_notes = self.chord_to_midi_notes(chord_name) |
| | for note_name in chord_notes: |
| | midi_note = pretty_midi.Note( |
| | velocity=100, |
| | pitch=librosa.note_to_midi(note_name), |
| | start=start_time, |
| | end=end_time |
| | ) |
| | self.instrument_chords.notes.append(midi_note) |
| | self.midi_chords.instruments.append(self.instrument_chords) |
| |
|
| | def save_midi(self, output_file): |
| | self.midi_chords.write(output_file) |
| | return output_file |
| |
|
| | |
| | def master_audio(input_path, reference_path, output_path): |
| | mg.log(warning_handler=print) |
| | mg.process( |
| | target=input_path, |
| | reference=reference_path, |
| | results=[mg.pcm16(output_path)], |
| | preview_target=mg.pcm16("preview_target.flac"), |
| | preview_result=mg.pcm16("preview_result.flac"), |
| | ) |
| |
|
| | |
| | def process_audio(input_path, output_path): |
| | with AudioFile(input_path) as f: |
| | audio = f.read(f.frames) |
| | sample_rate = f.samplerate |
| |
|
| | def stereo_widen(audio, width=1.2): |
| | left_channel = audio[0::2] * width |
| | right_channel = audio[1::2] * width |
| | widened_audio = np.empty_like(audio) |
| | widened_audio[0::2] = left_channel |
| | widened_audio[1::2] = right_channel |
| | return widened_audio |
| |
|
| | def reduce_piano_volume(audio, sample_rate, freq_low=200, freq_high=2000, reduction_db=-18): |
| | nyquist = 0.5 * sample_rate |
| | low = freq_low / nyquist |
| | high = freq_high / nyquist |
| | b, a = butter(1, [low, high], btype='band') |
| | filtered_audio = lfilter(b, a, audio) |
| | gain_reduction = 10 ** (reduction_db / 20) |
| | reduced_audio = audio - (filtered_audio * gain_reduction) |
| | return reduced_audio |
| |
|
| | board = Pedalboard([ |
| | HighpassFilter(cutoff_frequency_hz=100), |
| | Compressor(threshold_db=-20, ratio=4), |
| | Limiter(threshold_db=-0.1), |
| | Reverb(room_size=0.3, wet_level=0.2), |
| | Gain(gain_db=3), |
| | ]) |
| | processed_audio = board(audio, sample_rate) |
| | processed_audio = stereo_widen(processed_audio) |
| | processed_audio = reduce_piano_volume(processed_audio, sample_rate) |
| | with AudioFile(output_path, 'w', sample_rate, processed_audio.shape[0]) as f: |
| | f.write(processed_audio) |
| |
|
| | @app.route('/process_audio', methods=['POST']) |
| | def process_audio_api(): |
| | file = request.files['audio'] |
| | input_path = os.path.join('uploads', file.filename) |
| | os.makedirs('uploads', exist_ok=True) |
| | file.save(input_path) |
| |
|
| | output_base_path = 'output' |
| | base_name = os.path.splitext(os.path.basename(input_path))[0] |
| | output_path = os.path.join(output_base_path, base_name) |
| | os.makedirs(output_path, exist_ok=True) |
| |
|
| | |
| | separated_files = separate_audio(input_path, output_path) |
| |
|
| | |
| | converter = MusicToChordsConverter(separated_files['piano']) |
| | converter.recognize_chords() |
| | midi_output_file = os.path.join(output_path, f'{base_name}_chords.mid') |
| | converter.generate_midi() |
| | converter.save_midi(midi_output_file) |
| |
|
| | |
| | master_audio_path = os.path.join(output_path, f'{base_name}_master.wav') |
| | master_audio(separated_files['piano'], input_path, master_audio_path) |
| |
|
| | |
| | final_output_path = os.path.join(output_path, f'{base_name}_final.wav') |
| | process_audio(master_audio_path, final_output_path) |
| |
|
| | return jsonify({ |
| | 'separated_files': separated_files, |
| | 'midi_output_file': midi_output_file, |
| | 'final_output_path': final_output_path |
| | }) |
| |
|
| | if __name__ == "__main__": |
| | app.run(debug=True) |
| |
|