| 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) |
|
|