|
import numpy as np
|
|
import librosa
|
|
import textgrids
|
|
import os
|
|
import python_speech_features
|
|
from tqdm import tqdm
|
|
|
|
|
|
def readLabels(path, sample_rate):
|
|
|
|
labeled_list = []
|
|
grid = textgrids.TextGrid(path)
|
|
|
|
for interval in grid['silences']:
|
|
if interval.text == "-" or interval.text == " ":
|
|
label = 0
|
|
else:
|
|
label = 1
|
|
|
|
dur = interval.dur
|
|
dur_samples = int(np.round(dur * sample_rate))
|
|
|
|
for i in range(dur_samples):
|
|
labeled_list.append(label)
|
|
|
|
return np.array(labeled_list)
|
|
|
|
def load_files(audio_path, audio_extension):
|
|
"""
|
|
Recursively loads audio files from a specified directory.
|
|
|
|
Args:
|
|
audio_path (str): The root directory to search for audio files.
|
|
audio_extension (str, optional): The audio file extension to filter
|
|
for (default is ".wav").
|
|
|
|
Returns:
|
|
list: A sorted list of full paths to the found audio files.
|
|
Raises:
|
|
FileNotFoundError: If the specified audio_path does not exist.
|
|
"""
|
|
|
|
if not os.path.exists(audio_path):
|
|
raise FileNotFoundError(f"Audio path '{audio_path}' not found.")
|
|
|
|
audio_files = []
|
|
|
|
for root, _, files in os.walk(audio_path):
|
|
for file in files:
|
|
if file.endswith(audio_extension):
|
|
audio_files.append(os.path.join(root, file))
|
|
|
|
return sorted(audio_files)
|
|
|
|
def max_signal_length(audio_files):
|
|
"""
|
|
Determines the maximum signal length among a list of audio files.
|
|
|
|
Args:
|
|
audio_files (list): A list of paths to audio files.
|
|
|
|
Returns:
|
|
int: The maximum signal length found among the audio files.
|
|
|
|
Raises:
|
|
ValueError: If the input list is empty.
|
|
IOError: If any audio file cannot be loaded.
|
|
"""
|
|
|
|
if not audio_files:
|
|
raise ValueError("Audio file list cannot be empty")
|
|
|
|
max_length = 0
|
|
for audio_file in audio_files:
|
|
try:
|
|
signal, _ = librosa.load(audio_file)
|
|
max_length = max(max_length, len(signal))
|
|
except Exception as e:
|
|
raise IOError(f"Error loading audio file '{audio_file}': {e}")
|
|
|
|
return max_length
|
|
|
|
def object_padding(object, length):
|
|
"""
|
|
Pad the object to the given length
|
|
|
|
Args:
|
|
object (np.array): time series object
|
|
max_length (int): Desired length to pad/truncate signals to.
|
|
Returns:
|
|
object (np.array): processed time series object
|
|
"""
|
|
if len(object) < length:
|
|
padding_length = length - len(object)
|
|
|
|
object = np.pad(object, (0, padding_length), mode="constant")
|
|
else:
|
|
object = object[:length]
|
|
return np.array(object)
|
|
|
|
def fbank_features_extraction(audio_files, max_length, preemphasis_coef=0.97, window_length=0.025, window_step=0.01, window_function=np.hamming, num_nfft=551, num_features=40):
|
|
"""
|
|
Extracts log Mel-filterbank (fbank) features from a list of audio files.
|
|
|
|
Args:
|
|
audio_files (list): List of paths to audio files.
|
|
max_length (int): Desired length to pad/truncate signals to.
|
|
preemphasis_coef (float): Pre-emphasis filter coefficient (default: 0.97).
|
|
window_length (float): Length of the analysis window in seconds (default: 0.025).
|
|
window_step (float): Step between successive windows in seconds (default: 0.01).
|
|
window_function (callable): Window function to apply (default: np.hamming).
|
|
nfft (int): Number of FFT points (default: 551).
|
|
num_features (int): Number of Mel filters (default: 40).
|
|
|
|
Returns:
|
|
np.ndarray: 2D array of shape (num_files, num_frames, num_features + 1)
|
|
where num_features + 1 represents the log energy feature.
|
|
"""
|
|
|
|
fbank_features = list()
|
|
for i in tqdm(range(len(audio_files))):
|
|
|
|
signal, sample_rate = librosa.load(audio_files[i])
|
|
|
|
signal = object_padding(signal, max_length)
|
|
|
|
|
|
|
|
features_fbank, feature_energy = python_speech_features.base.fbank(signal=signal,
|
|
samplerate=sample_rate,
|
|
winlen=window_length,
|
|
winstep=window_step,
|
|
nfilt=num_features,
|
|
nfft=num_nfft,
|
|
lowfreq=0,
|
|
highfreq=None,
|
|
preemph=preemphasis_coef,
|
|
winfunc=window_function)
|
|
|
|
features_logfbank = np.log(features_fbank)
|
|
feature_logenergy = np.log(feature_energy)
|
|
|
|
features = np.hstack((feature_logenergy.reshape(feature_logenergy.shape[0], 1), features_logfbank))
|
|
|
|
fbank_features.append(features)
|
|
|
|
return np.array(fbank_features)
|
|
|
|
def supervised_features_extraction(audio_files, annotation_files, max_length, preemphasis_coef=0.97, window_length=0.025, window_step=0.01, window_function=np.hamming, num_nfft=551, num_features=40):
|
|
"""
|
|
Extracts log Mel-filterbank (fbank) features from a list of audio files.
|
|
|
|
Args:
|
|
audio_files (list): List of paths to audio files.
|
|
annotation_files (list): List of paths to annotation files
|
|
max_length (int): Desired length to pad/truncate signals to.
|
|
preemphasis_coef (float): Pre-emphasis filter coefficient (default: 0.97).
|
|
window_length (float): Length of the analysis window in seconds (default: 0.025).
|
|
window_step (float): Step between successive windows in seconds (default: 0.01).
|
|
window_function (callable): Window function to apply (default: np.hamming).
|
|
nfft (int): Number of FFT points (default: 551).
|
|
num_features (int): Number of Mel filters (default: 40).
|
|
|
|
Returns:
|
|
np.ndarray: 2D array of shape (num_files, num_frames, num_features + 1)
|
|
where num_features + 1 represents the log energy feature.
|
|
"""
|
|
|
|
fbank_features = list()
|
|
labels = list()
|
|
for i in tqdm(range(len(audio_files))):
|
|
|
|
signal, sample_rate = librosa.load(audio_files[i])
|
|
signal = object_padding(signal, max_length)
|
|
truth_labels = readLabels(path=annotation_files[i], sample_rate=sample_rate)
|
|
truth_labels = object_padding(truth_labels, max_length)
|
|
|
|
|
|
|
|
features_fbank, feature_energy = python_speech_features.base.fbank(signal=signal,
|
|
samplerate=sample_rate,
|
|
winlen=window_length,
|
|
winstep=window_step,
|
|
nfilt=num_features,
|
|
nfft=num_nfft,
|
|
lowfreq=0,
|
|
highfreq=None,
|
|
preemph=preemphasis_coef,
|
|
winfunc=window_function)
|
|
|
|
features_logfbank = np.log(features_fbank)
|
|
feature_logenergy = np.log(feature_energy)
|
|
|
|
features = np.hstack((feature_logenergy.reshape(feature_logenergy.shape[0], 1), features_logfbank))
|
|
|
|
|
|
temp_label = python_speech_features.sigproc.framesig(sig=truth_labels,
|
|
frame_len=window_length * sample_rate,
|
|
frame_step=window_step * sample_rate,
|
|
winfunc=np.ones)
|
|
label = np.zeros(temp_label.shape[0])
|
|
label = np.array([1 if np.sum(temp_label[j], axis=0) > temp_label.shape[0] / 2 else 0 for j in range(temp_label.shape[0])])
|
|
|
|
fbank_features.append(features)
|
|
labels.append(label)
|
|
|
|
return np.array(fbank_features), np.array(labels)
|
|
|