# MIT License
#
# Copyright (c) 2023 - Captain FLAM
# Copyright (c) 2021 - Roman Solovyev (ZFTurbo)
#
# https://github.com/Captain-FLAM/KaraFan
# https://github.com/ZFTurbo/Audio-separation-models-checker
import os, glob, datetime, librosa, soundfile as sf, numpy as np
"""
SDR - Source to distortion ratio
SIR - Source to inferences ratio
SNR - Source to noise ration
SAR - Source to artifacts ratio
"""
# compute SDR for one song
def calculate(reference, estimate):
references = np.expand_dims(reference, axis=0)
estimates = np.expand_dims(estimate, axis=0)
if estimates.shape != references.shape:
print('Warning: Different audio lengths : {} != {} !'.format(estimates.shape, references.shape))
return [None]
delta = 1e-7 # avoid numerical errors
num = np.sum(np.square(references), axis=(1, 2))
den = np.sum(np.square(references - estimates), axis=(1, 2))
num += delta
den += delta
return 10 * np.log10(num / den)
def SDR(song_output_path, output_format, Gdrive, elapsed_time):
# The "song_output_path" contains the NAME of the song to compare within the "Gdrive > KaraFan_user > Multi-Song" folder
song_name = os.path.basename(song_output_path)
MultiSong = os.path.join(Gdrive, "KaraFan_user", "Multi_Song", "Stems")
instrum = os.path.join(MultiSong, song_name[-3:] + '_instrum.flac') # Get only the number of the song
vocals = os.path.join(MultiSong, song_name[-3:] + '_vocals.flac')
if not os.path.exists(MultiSong): print ("No Multi-Song folder found !"); return
if not os.path.exists(instrum) or not os.path.exists(vocals): print (f'No References Stems for "{song_name}" found !'); return
if output_format=='PCM_16': ext = 'wav'
elif output_format=='FLOAT': ext = 'wav'
elif output_format == 'WAV': ext = 'wav'
elif output_format== "FLAC": ext = 'flac'
elif output_format== 'MP3': ext = 'mp3'
Extracted_files = glob.glob(os.path.join(song_output_path, '*.' + ext))
if len(Extracted_files) == 0: print('Check output folder. Cant find any files !'); return
# The stems we want to process
Scores = {"instrum": [], "vocals": []}
References = {"instrum": None, "vocals": None}
Results = ""
References["instrum"], _ = sf.read(os.path.join(MultiSong, song_name[-3:] + '_instrum.flac'), dtype='float32') # Get only the number of the song
References["vocals"], _ = sf.read(os.path.join(MultiSong, song_name[-3:] + '_vocals.flac'), dtype='float32')
Extracted_files = sorted(Extracted_files)
for extract in Extracted_files:
file_name = os.path.basename(extract)
file_name = file_name.replace(os.path.splitext(file_name)[1], "") # Remove extension
if "Bleedings" in file_name: continue # Skip Bleedings
if "Vocal" in file_name: type = "vocals"
elif "Music" in file_name: type = "instrum"
else: type = "others"
if type == "others": continue # Skip Others
estimate, _ = sf.read(extract, dtype='float32')
song_score = calculate(References[type], estimate)[0]
pad = 40 - len(file_name)
if song_score is None:
print("• " + file_name + (" " * pad) + 'SDR : --- [ Skipped ]')
else:
print("• " + file_name + (" " * pad) + 'SDR : {:9.6f}'.format(song_score))
Results += file_name + (" " * pad) + 'SDR : {:9.6f}'.format(song_score) + "\n"
Scores[type].append(song_score)
print("\n")
# TODO : Use for batch SDR with multiple songs
# for type in Scores:
# if len(Scores[type]) > 0:
# print('Average SDR {} : {:9.6f}'.format(type, np.array(Scores[type]).mean()))
# else:
# print('Average SDR {} : ---'.format(type))
# Write results on disk
if Results != "":
with open(os.path.join(song_output_path, "SDR_Results.txt"), 'a', encoding='utf-8') as file:
file.write(f"\n► {datetime.datetime.now().strftime('%Y-%m-%d ~ %H:%M:%S')} - {song_name}\n\n")
file.write(Results)
file.write("\n" + elapsed_time + "\n")
# def Spectrograms(audio_file1, audio_file2):
# audio1, _ = sf.read(audio_file1, sr=None, mono=False, dtype='float32')
# audio2, _ = sf.read(audio_file2, sr=None, mono=False, dtype='float32')
# # Convertir les signaux audio en spectrogrammes
# spec1 = librosa.stft(audio1.T, n_fft=4096, hop_length=1024)
# spec2 = librosa.stft(audio2.T, n_fft=4096, hop_length=1024)
# # Calculer la distance euclidienne entre les spectrogrammes
# distance = np.linalg.norm(spec2 - spec1)
# # Normaliser la distance pour obtenir une mesure de similarité (plus proche de zéro est meilleur)
# similarity = 1 / (1 + distance)
# return f"{similarity * 100:.6f} %"