Spaces:
Running
on
Zero
Running
on
Zero
# Copyright (c) 2023 Amphion. | |
# | |
# This source code is licensed under the MIT license found in the | |
# LICENSE file in the root directory of this source tree. | |
import numpy as np | |
import scipy.signal as sig | |
import copy | |
import librosa | |
def bandpower(ps, mode="time"): | |
""" | |
estimate bandpower, see https://de.mathworks.com/help/signal/ref/bandpower.html | |
""" | |
if mode == "time": | |
x = ps | |
l2norm = np.linalg.norm(x) ** 2.0 / len(x) | |
return l2norm | |
elif mode == "psd": | |
return sum(ps) | |
def getIndizesAroundPeak(arr, peakIndex, searchWidth=1000): | |
peakBins = [] | |
magMax = arr[peakIndex] | |
curVal = magMax | |
for i in range(searchWidth): | |
newBin = peakIndex + i | |
if newBin >= len(arr): | |
break | |
newVal = arr[newBin] | |
if newVal > curVal: | |
break | |
else: | |
peakBins.append(int(newBin)) | |
curVal = newVal | |
curVal = magMax | |
for i in range(searchWidth): | |
newBin = peakIndex - i | |
if newBin < 0: | |
break | |
newVal = arr[newBin] | |
if newVal > curVal: | |
break | |
else: | |
peakBins.append(int(newBin)) | |
curVal = newVal | |
return np.array(list(set(peakBins))) | |
def freqToBin(fAxis, Freq): | |
return np.argmin(abs(fAxis - Freq)) | |
def getPeakInArea(psd, faxis, estimation, searchWidthHz=10): | |
""" | |
returns bin and frequency of the maximum in an area | |
""" | |
binLow = freqToBin(faxis, estimation - searchWidthHz) | |
binHi = freqToBin(faxis, estimation + searchWidthHz) | |
peakbin = binLow + np.argmax(psd[binLow : binHi + 1]) | |
return peakbin, faxis[peakbin] | |
def getHarmonics(fund, sr, nHarmonics=6, aliased=False): | |
harmonicMultipliers = np.arange(2, nHarmonics + 2) | |
harmonicFs = fund * harmonicMultipliers | |
if not aliased: | |
harmonicFs[harmonicFs > sr / 2] = -1 | |
harmonicFs = np.delete(harmonicFs, harmonicFs == -1) | |
else: | |
nyqZone = np.floor(harmonicFs / (sr / 2)) | |
oddEvenNyq = nyqZone % 2 | |
harmonicFs = np.mod(harmonicFs, sr / 2) | |
harmonicFs[oddEvenNyq == 1] = (sr / 2) - harmonicFs[oddEvenNyq == 1] | |
return harmonicFs | |
def extract_snr(audio, sr=None): | |
"""Extract Signal-to-Noise Ratio for a given audio.""" | |
if sr != None: | |
audio, _ = librosa.load(audio, sr=sr) | |
else: | |
audio, sr = librosa.load(audio, sr=sr) | |
faxis, ps = sig.periodogram( | |
audio, fs=sr, window=("kaiser", 38) | |
) # get periodogram, parametrized like in matlab | |
fundBin = np.argmax( | |
ps | |
) # estimate fundamental at maximum amplitude, get the bin number | |
fundIndizes = getIndizesAroundPeak( | |
ps, fundBin | |
) # get bin numbers around fundamental peak | |
fundFrequency = faxis[fundBin] # frequency of fundamental | |
nHarmonics = 18 | |
harmonicFs = getHarmonics( | |
fundFrequency, sr, nHarmonics=nHarmonics, aliased=True | |
) # get harmonic frequencies | |
harmonicBorders = np.zeros([2, nHarmonics], dtype=np.int16).T | |
fullHarmonicBins = np.array([], dtype=np.int16) | |
fullHarmonicBinList = [] | |
harmPeakFreqs = [] | |
harmPeaks = [] | |
for i, harmonic in enumerate(harmonicFs): | |
searcharea = 0.1 * fundFrequency | |
estimation = harmonic | |
binNum, freq = getPeakInArea(ps, faxis, estimation, searcharea) | |
harmPeakFreqs.append(freq) | |
harmPeaks.append(ps[binNum]) | |
allBins = getIndizesAroundPeak(ps, binNum, searchWidth=1000) | |
fullHarmonicBins = np.append(fullHarmonicBins, allBins) | |
fullHarmonicBinList.append(allBins) | |
harmonicBorders[i, :] = [allBins[0], allBins[-1]] | |
fundIndizes.sort() | |
pFund = bandpower(ps[fundIndizes[0] : fundIndizes[-1]]) # get power of fundamental | |
noisePrepared = copy.copy(ps) | |
noisePrepared[fundIndizes] = 0 | |
noisePrepared[fullHarmonicBins] = 0 | |
noiseMean = np.median(noisePrepared[noisePrepared != 0]) | |
noisePrepared[fundIndizes] = noiseMean | |
noisePrepared[fullHarmonicBins] = noiseMean | |
noisePower = bandpower(noisePrepared) | |
r = 10 * np.log10(pFund / noisePower) | |
return r, 10 * np.log10(noisePower) | |