Following this instruction :
1. Strictly adhere to PEP-8 standards, ensuring code is clean, well-structured, and follows Python's style guide for readability.
2. Use appropriate modules like `typing` for type hints, ensuring code clarity, maintainability, and leveraging Python's standard libraries effectively.
3. Focus on time, space, robustness, scalability; write optimized, efficient code that can handle large datasets and scale seamlessly.
4. Implement comprehensive error handling, catching exceptions gracefully, providing meaningful error messages, and ensuring code robustness under unexpected conditions.
5. Ensure the developer agent writes advanced, maintainable Python code, balancing performance and readability while adhering to best practices.


TASK :list(audio files extensions ) , sampling_rate: user select , mono-channel select user choice , (write end to end code :note)
augmentation names list only names
.mp3,.wav,.flac,.aac,.ogg,.m4a,.wma,.alac,.aiff,.opus

each functionality with nature compose(functionality (so that user 1 0r more augment)

Time Stretching
Pitch Shifting
Noise Injection
Reverb
Equalization
Gain Adjustment
Speed Change
Frequency Masking
Volume Shifting
Dynamic Range Compression
Low Pass Filter
High Pass Filter
Time Warping
Background Noise Addition
Phase Shifting
Band-pass Filtering
Random Cropping
Spectral Augmentation
Pitch Bending
Resampling




import os
import sys
import logging
from typing import List, Optional

import numpy as np
import librosa
import soundfile as sf

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Supported audio file extensions
AUDIO_EXTENSIONS = {
    '.mp3', '.wav', '.flac', '.aac', '.ogg', '.m4a',
    '.wma', '.alac', '.aiff', '.opus'
}

# Define type alias for augmentation functions
AudioData = np.ndarray
AugmentationFunction = Optional[callable]


def list_audio_files(directory: str) -> List[str]:
    """
    Recursively list audio files in a directory with supported extensions.

    Args:
        directory (str): The root directory to search for audio files.

    Returns:
        List[str]: A list of file paths to audio files.
    """
    audio_files = []
    for root, _, files in os.walk(directory):
        for file in files:
            if any(file.lower().endswith(ext) for ext in AUDIO_EXTENSIONS):
                audio_files.append(os.path.join(root, file))
    logger.info(f"Found {len(audio_files)} audio files.")
    return audio_files


def load_audio(file_path: str, sr: Optional[int], mono: bool) -> AudioData:
    """
    Load an audio file.

    Args:
        file_path (str): Path to the audio file.
        sr (Optional[int]): Sampling rate. If None, uses the file's original sampling rate.
        mono (bool): Whether to convert the audio to mono.

    Returns:
        AudioData: The loaded audio data.
    """
    try:
        audio_data, _ = librosa.load(file_path, sr=sr, mono=mono)
        logger.debug(f"Loaded {file_path} with shape {audio_data.shape}.")
        return audio_data
    except Exception as e:
        logger.error(f"Error loading {file_path}: {e}")
        raise


def save_audio(file_path: str, audio_data: AudioData, sr: int) -> None:
    """
    Save audio data to a file.

    Args:
        file_path (str): Path where the audio will be saved.
        audio_data (AudioData): The audio data to save.
        sr (int): Sampling rate.
    """
    try:
        sf.write(file_path, audio_data, sr)
        logger.debug(f"Saved augmented audio to {file_path}.")
    except Exception as e:
        logger.error(f"Error saving {file_path}: {e}")
        raise


def apply_augmentations(
    audio_data: AudioData,
    sr: int,
    augmentations: List[str]
) -> AudioData:
    """
    Apply a series of augmentations to the audio data.

    Args:
        audio_data (AudioData): The original audio data.
        sr (int): Sampling rate of the audio data.
        augmentations (List[str]): List of augmentation names to apply.

    Returns:
        AudioData: The augmented audio data.
    """
    for aug_name in augmentations:
        augmentation = AUGMENTATION_FUNCTIONS.get(aug_name)
        if augmentation:
            try:
                audio_data = augmentation(audio_data, sr)
                logger.debug(f"Applied {aug_name} augmentation.")
            except Exception as e:
                logger.error(f"Error applying {aug_name}: {e}")
                raise
        else:
            logger.warning(f"Augmentation {aug_name} is not implemented.")
    return audio_data


# Augmentation functions
def time_stretch(audio_data: AudioData, _: int) -> AudioData:
    rate = np.random.uniform(0.8, 1.2)
    return librosa.effects.time_stretch(audio_data, rate)


def pitch_shift(audio_data: AudioData, sr: int) -> AudioData:
    n_steps = np.random.uniform(-2, 2)
    return librosa.effects.pitch_shift(audio_data, sr, n_steps)


def noise_injection(audio_data: AudioData, _: int) -> AudioData:
    noise_factor = 0.005 * np.random.uniform(0.9, 1.1)
    noise = np.random.randn(len(audio_data))
    return audio_data + noise_factor * noise


def reverb(audio_data: AudioData, _: int) -> AudioData:
    reverbed = np.convolve(audio_data, np.ones((5000,)) / 5000, mode='same')
    return reverbed


# Map augmentation names to functions
AUGMENTATION_FUNCTIONS = {
    'Time Stretching': time_stretch,
    'Pitch Shifting': pitch_shift,
    'Noise Injection': noise_injection,
    'Reverb': reverb,
    # Implement other augmentations accordingly
}