Spaces:
Sleeping
Sleeping
| import torchaudio | |
| import soundfile as sf | |
| import numpy as np | |
| import torch | |
| import logging | |
| def _safe_sr_frames(path: str): | |
| try: | |
| info = sf.info(path) | |
| return info.samplerate, info.frames | |
| except sf.LibsndfileError: | |
| # soundfile can't read some formats (e.g. mp3); fall back to torchaudio, | |
| # which uses the same ffmpeg/soundfile backends without needing torchcodec. | |
| info = torchaudio.info(path) | |
| return info.sample_rate, info.num_frames | |
| def load_audio_chunk(path, target_n_samples, target_sr, start=None, verbose=False): | |
| """ | |
| Load a chunk of audio from a file using torchaudio. | |
| Args: | |
| path: Path to audio file | |
| target_n_samples: Number of samples to load (at target_sr) | |
| target_sr: Target sample rate | |
| start: Starting frame (at original sr). If None, random start. | |
| verbose: Print debug info | |
| Returns: | |
| audio: Tensor of shape [n_samples, n_channels] | |
| """ | |
| # Get audio metadata using soundfile | |
| sr, frames = _safe_sr_frames(path) | |
| print(f"length of audio in seconds: {frames/sr}") if verbose else None | |
| print(f"Original sample rate: {sr}") if verbose else None | |
| # Adjust for MP3 padding | |
| if path.split(".")[-1].lower() == "mp3": | |
| frames = frames - 8192 | |
| # Calculate how many frames to load at original sr | |
| new_target_n_samples = int(target_n_samples * sr / target_sr) | |
| print(f"New target n samples: {new_target_n_samples}") if verbose else None | |
| # Random start if not specified | |
| if start is None: | |
| max_start = max(1, frames - new_target_n_samples) | |
| start = np.random.randint(0, max_start) | |
| # Load audio chunk with torchaudio | |
| # torchaudio.load returns (waveform, sample_rate) where waveform is [channels, samples] | |
| audio, loaded_sr = torchaudio.load( | |
| path, | |
| frame_offset=start, | |
| num_frames=new_target_n_samples, | |
| normalize=True, | |
| ) | |
| # Resample if needed | |
| if loaded_sr != target_sr: | |
| audio = torchaudio.functional.resample(audio, loaded_sr, target_sr) | |
| print(f"Resampled to {target_sr}, shape of audio: {audio.shape}") if verbose else None | |
| # pad if needed | |
| if audio.shape[1] < target_n_samples: | |
| audio = torch.nn.functional.pad(audio, (0, target_n_samples - audio.shape[1])) | |
| # Convert from [channels, samples] to [samples, channels] | |
| audio = audio.T | |
| return audio | |
| def load_full_audio(path, target_sr, verbose=False): | |
| """ | |
| Load full audio file using torchaudio. | |
| Args: | |
| path: Path to audio file | |
| target_sr: Target sample rate | |
| verbose: Print debug info | |
| Returns: | |
| audio: Tensor of shape [n_channels, n_samples] | |
| """ | |
| # Load with torchaudio - returns [channels, samples] | |
| audio, sr = torchaudio.load(path, normalize=True) | |
| # If stereo, average to mono | |
| if audio.shape[0] == 2: | |
| audio = audio.mean(dim=0, keepdim=True) | |
| # Resample if needed | |
| if sr != target_sr: | |
| audio = torchaudio.functional.resample(audio, sr, target_sr) | |
| return audio | |
| def load_full_and_split(path, target_sr, target_n_samples, hop=None, verbose=False): | |
| """ | |
| Load full audio and split into overlapping chunks. | |
| Args: | |
| path: Path to audio file | |
| target_sr: Target sample rate | |
| target_n_samples: Samples per chunk | |
| hop: Hop size between chunks (default: target_n_samples) | |
| verbose: Print debug info | |
| Returns: | |
| audio: Tensor of shape [n_chunks, 1, target_n_samples] | |
| """ | |
| hop = target_n_samples if hop is None else hop | |
| audio = load_full_audio(path, target_sr, verbose=verbose) | |
| audio = audio.squeeze() | |
| # If audio is shorter than target, repeat | |
| if audio.shape[0] < target_n_samples: | |
| n_repeats = int(np.ceil(target_n_samples / audio.shape[0])) | |
| audio = audio.repeat(n_repeats) | |
| audio = audio.unfold(0, int(target_n_samples), int(hop)).unsqueeze(1) | |
| return audio | |