from __future__ import annotations

import logging
import numpy as np
import soundfile as sf
from pathlib import Path
from typing import Any, Dict

from reaper_mcp.core.mcp_core import mcp
from reaper_mcp.core.util import _generate_timestamped_filename, _validate_audio_params

logger = logging.getLogger(__name__)


def _generate_sine_wave(
    frequency: float,
    duration: float,
    amplitude: float = 0.5,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Internal implementation: Generate a pure sine wave and save it as a WAV file.
    
    Args:
        frequency: Frequency in Hz (e.g., 440.0 for A4)
        duration: Duration in seconds
        amplitude: Amplitude (0.0 to 1.0, default 0.5)
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with file path and generation details
    """
    try:
        # Validate parameters
        error = _validate_audio_params(frequency, duration, amplitude)
        if error:
            return {"error": error}
        
        # Generate time array
        t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
        
        # Generate sine wave
        waveform = amplitude * np.sin(2 * np.pi * frequency * t)
        
        # Save to file
        output_path = _generate_timestamped_filename("generated_audio", "sine", "wav")
        sf.write(str(output_path), waveform, sample_rate)
        
        logger.info(f"Generated sine wave: {output_path}")
        return {
            "file_path": str(output_path),
            "frequency": frequency,
            "duration": duration,
            "amplitude": amplitude,
            "sample_rate": sample_rate,
        }
    except Exception as e:
        logger.error(f"Failed to generate sine wave: {e}", exc_info=True)
        return {"error": f"Failed to generate sine wave: {e}"}


@mcp.tool()
def generate_sine_wave(
    frequency: float,
    duration: float,
    amplitude: float = 0.5,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Generate a pure sine wave and save it as a WAV file.
    
    Args:
        frequency: Frequency in Hz (e.g., 440.0 for A4)
        duration: Duration in seconds
        amplitude: Amplitude (0.0 to 1.0, default 0.5)
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with file path and generation details
    """
    return _generate_sine_wave(frequency, duration, amplitude, sample_rate)


def _generate_square_wave(
    frequency: float,
    duration: float,
    amplitude: float = 0.5,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Internal implementation: Generate a square wave and save it as a WAV file.
    
    Args:
        frequency: Frequency in Hz
        duration: Duration in seconds
        amplitude: Amplitude (0.0 to 1.0, default 0.5)
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with file path and generation details
    """
    try:
        # Validate parameters
        error = _validate_audio_params(frequency, duration, amplitude)
        if error:
            return {"error": error}
        
        # Generate time array
        t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
        
        # Generate square wave (using sign of sine wave)
        waveform = amplitude * np.sign(np.sin(2 * np.pi * frequency * t))
        
        # Save to file
        output_path = _generate_timestamped_filename("generated_audio", "square", "wav")
        sf.write(str(output_path), waveform, sample_rate)
        
        logger.info(f"Generated square wave: {output_path}")
        return {
            "file_path": str(output_path),
            "frequency": frequency,
            "duration": duration,
            "amplitude": amplitude,
            "sample_rate": sample_rate,
        }
    except Exception as e:
        logger.error(f"Failed to generate square wave: {e}", exc_info=True)
        return {"error": f"Failed to generate square wave: {e}"}


@mcp.tool()
def generate_square_wave(
    frequency: float,
    duration: float,
    amplitude: float = 0.5,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Generate a square wave and save it as a WAV file.
    
    Args:
        frequency: Frequency in Hz
        duration: Duration in seconds
        amplitude: Amplitude (0.0 to 1.0, default 0.5)
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with file path and generation details
    """
    return _generate_square_wave(frequency, duration, amplitude, sample_rate)


def _generate_sawtooth_wave(
    frequency: float,
    duration: float,
    amplitude: float = 0.5,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Internal implementation: Generate a sawtooth wave and save it as a WAV file.
    
    Args:
        frequency: Frequency in Hz
        duration: Duration in seconds
        amplitude: Amplitude (0.0 to 1.0, default 0.5)
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with file path and generation details
    """
    try:
        # Validate parameters
        error = _validate_audio_params(frequency, duration, amplitude)
        if error:
            return {"error": error}
        
        # Generate time array
        t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
        
        # Generate sawtooth wave
        phase = (frequency * t) % 1.0
        waveform = amplitude * (2 * phase - 1)
        
        # Save to file
        output_path = _generate_timestamped_filename("generated_audio", "sawtooth", "wav")
        sf.write(str(output_path), waveform, sample_rate)
        
        logger.info(f"Generated sawtooth wave: {output_path}")
        return {
            "file_path": str(output_path),
            "frequency": frequency,
            "duration": duration,
            "amplitude": amplitude,
            "sample_rate": sample_rate,
        }
    except Exception as e:
        logger.error(f"Failed to generate sawtooth wave: {e}", exc_info=True)
        return {"error": f"Failed to generate sawtooth wave: {e}"}


@mcp.tool()
def generate_sawtooth_wave(
    frequency: float,
    duration: float,
    amplitude: float = 0.5,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Generate a sawtooth wave and save it as a WAV file.
    
    Args:
        frequency: Frequency in Hz
        duration: Duration in seconds
        amplitude: Amplitude (0.0 to 1.0, default 0.5)
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with file path and generation details
    """
    return _generate_sawtooth_wave(frequency, duration, amplitude, sample_rate)


def _generate_triangle_wave(
    frequency: float,
    duration: float,
    amplitude: float = 0.5,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Internal implementation: Generate a triangle wave and save it as a WAV file.
    
    Args:
        frequency: Frequency in Hz
        duration: Duration in seconds
        amplitude: Amplitude (0.0 to 1.0, default 0.5)
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with file path and generation details
    """
    try:
        # Validate parameters
        error = _validate_audio_params(frequency, duration, amplitude)
        if error:
            return {"error": error}
        
        # Generate time array
        t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
        
        # Generate triangle wave
        phase = (frequency * t) % 1.0
        waveform = amplitude * (2 * np.abs(2 * phase - 1) - 1)
        
        # Save to file
        output_path = _generate_timestamped_filename("generated_audio", "triangle", "wav")
        sf.write(str(output_path), waveform, sample_rate)
        
        logger.info(f"Generated triangle wave: {output_path}")
        return {
            "file_path": str(output_path),
            "frequency": frequency,
            "duration": duration,
            "amplitude": amplitude,
            "sample_rate": sample_rate,
        }
    except Exception as e:
        logger.error(f"Failed to generate triangle wave: {e}", exc_info=True)
        return {"error": f"Failed to generate triangle wave: {e}"}


@mcp.tool()
def generate_triangle_wave(
    frequency: float,
    duration: float,
    amplitude: float = 0.5,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Generate a triangle wave and save it as a WAV file.
    
    Args:
        frequency: Frequency in Hz
        duration: Duration in seconds
        amplitude: Amplitude (0.0 to 1.0, default 0.5)
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with file path and generation details
    """
    return _generate_triangle_wave(frequency, duration, amplitude, sample_rate)


def _generate_noise(
    noise_type: str,
    duration: float,
    amplitude: float = 0.5,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Internal implementation: Generate noise and save it as a WAV file.
    
    Args:
        noise_type: Type of noise ("white", "pink", or "brown")
        duration: Duration in seconds
        amplitude: Amplitude (0.0 to 1.0, default 0.5)
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with file path and generation details
    """
    try:
        # Validate parameters (no frequency for noise)
        error = _validate_audio_params(None, duration, amplitude)
        if error:
            return {"error": error}
        
        num_samples = int(sample_rate * duration)
        noise_type_lower = noise_type.lower()
        
        if noise_type_lower == "white":
            # White noise: uniform random values
            waveform = amplitude * np.random.uniform(-1, 1, num_samples)
        
        elif noise_type_lower == "pink":
            # Pink noise: 1/f noise (approximation using filtering)
            white = np.random.uniform(-1, 1, num_samples)
            # Simple pink noise filter (accumulator)
            b = np.array([0.049922035, -0.095993537, 0.050612699, -0.004408786])
            a = np.array([1, -2.494956002, 2.017265875, -0.522189400])
            from scipy import signal
            waveform = signal.lfilter(b, a, white)
            waveform = amplitude * waveform / np.max(np.abs(waveform))
        
        elif noise_type_lower == "brown":
            # Brown noise: integral of white noise
            white = np.random.uniform(-1, 1, num_samples)
            waveform = np.cumsum(white)
            waveform = amplitude * waveform / np.max(np.abs(waveform))
        
        else:
            return {"error": f"Invalid noise type: {noise_type}. Must be 'white', 'pink', or 'brown'"}
        
        # Save to file
        output_path = _generate_timestamped_filename("generated_audio", f"noise_{noise_type_lower}", "wav")
        sf.write(str(output_path), waveform.astype(np.float32), sample_rate)
        
        logger.info(f"Generated {noise_type} noise: {output_path}")
        return {
            "file_path": str(output_path),
            "noise_type": noise_type,
            "duration": duration,
            "amplitude": amplitude,
            "sample_rate": sample_rate,
        }
    except Exception as e:
        logger.error(f"Failed to generate noise: {e}", exc_info=True)
        return {"error": f"Failed to generate noise: {e}"}


@mcp.tool()
def generate_noise(
    noise_type: str,
    duration: float,
    amplitude: float = 0.5,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Generate noise and save it as a WAV file.
    
    Args:
        noise_type: Type of noise ("white", "pink", or "brown")
        duration: Duration in seconds
        amplitude: Amplitude (0.0 to 1.0, default 0.5)
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with file path and generation details
    """
    return _generate_noise(noise_type, duration, amplitude, sample_rate)


def _generate_additive_synth(
    partials: list[Dict[str, float]],
    duration: float,
    amplitude: float = 0.5,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Internal implementation: Generate additive synthesis by combining multiple sine waves (partials).
    
    Args:
        partials: List of dicts with 'frequency' and 'amplitude' keys (e.g., [{"frequency": 440, "amplitude": 1.0}, {"frequency": 880, "amplitude": 0.5}])
        duration: Duration in seconds
        amplitude: Overall amplitude multiplier (0.0 to 1.0, default 0.5)
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with file path and generation details
    """
    try:
        if not partials:
            return {"error": "At least one partial must be provided"}
        if duration <= 0 or duration > 300:
            return {"error": "Duration must be between 0 and 300 seconds"}
        if amplitude <= 0 or amplitude > 1.0:
            return {"error": "Amplitude must be between 0 and 1.0"}
        
        # Generate time array
        t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
        
        # Initialize waveform
        waveform = np.zeros_like(t)
        
        # Add each partial
        for partial in partials:
            if "frequency" not in partial or "amplitude" not in partial:
                return {"error": "Each partial must have 'frequency' and 'amplitude' keys"}
            
            freq = float(partial["frequency"])
            amp = float(partial["amplitude"])
            phase = float(partial.get("phase", 0.0))
            
            waveform += amp * np.sin(2 * np.pi * freq * t + phase)
        
        # Normalize and apply overall amplitude
        if np.max(np.abs(waveform)) > 0:
            waveform = amplitude * waveform / np.max(np.abs(waveform))
        
        # Save to file
        output_path = _generate_timestamped_filename("generated_audio", "additive", "wav")
        sf.write(str(output_path), waveform, sample_rate)
        
        logger.info(f"Generated additive synthesis: {output_path}")
        return {
            "file_path": str(output_path),
            "partials_count": len(partials),
            "duration": duration,
            "amplitude": amplitude,
            "sample_rate": sample_rate,
        }
    except Exception as e:
        logger.error(f"Failed to generate additive synth: {e}", exc_info=True)
        return {"error": f"Failed to generate additive synth: {e}"}


@mcp.tool()
def generate_additive_synth(
    partials: list[Dict[str, float]],
    duration: float,
    amplitude: float = 0.5,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Generate additive synthesis by combining multiple sine waves (partials).
    
    Args:
        partials: List of dicts with 'frequency' and 'amplitude' keys (e.g., [{"frequency": 440, "amplitude": 1.0}, {"frequency": 880, "amplitude": 0.5}])
        duration: Duration in seconds
        amplitude: Overall amplitude multiplier (0.0 to 1.0, default 0.5)
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with file path and generation details
    """
    return _generate_additive_synth(partials, duration, amplitude, sample_rate)


def _generate_fm_synth(
    carrier_freq: float,
    modulator_freq: float,
    mod_index: float,
    duration: float,
    amplitude: float = 0.5,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Internal implementation: Generate frequency modulation (FM) synthesis.
    
    Args:
        carrier_freq: Carrier frequency in Hz
        modulator_freq: Modulator frequency in Hz
        mod_index: Modulation index (controls intensity of modulation)
        duration: Duration in seconds
        amplitude: Amplitude (0.0 to 1.0, default 0.5)
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with file path and generation details
    """
    try:
        if carrier_freq <= 0 or carrier_freq > 20000:
            return {"error": "Carrier frequency must be between 0 and 20000 Hz"}
        if modulator_freq <= 0 or modulator_freq > 20000:
            return {"error": "Modulator frequency must be between 0 and 20000 Hz"}
        if duration <= 0 or duration > 300:
            return {"error": "Duration must be between 0 and 300 seconds"}
        if amplitude <= 0 or amplitude > 1.0:
            return {"error": "Amplitude must be between 0 and 1.0"}
        
        # Generate time array
        t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
        
        # FM synthesis: carrier modulated by modulator
        modulator = np.sin(2 * np.pi * modulator_freq * t)
        waveform = amplitude * np.sin(2 * np.pi * carrier_freq * t + mod_index * modulator)
        
        # Save to file
        output_path = _generate_timestamped_filename("generated_audio", "fm", "wav")
        sf.write(str(output_path), waveform, sample_rate)
        
        logger.info(f"Generated FM synthesis: {output_path}")
        return {
            "file_path": str(output_path),
            "carrier_freq": carrier_freq,
            "modulator_freq": modulator_freq,
            "mod_index": mod_index,
            "duration": duration,
            "amplitude": amplitude,
            "sample_rate": sample_rate,
        }
    except Exception as e:
        logger.error(f"Failed to generate FM synth: {e}", exc_info=True)
        return {"error": f"Failed to generate FM synth: {e}"}


@mcp.tool()
def generate_fm_synth(
    carrier_freq: float,
    modulator_freq: float,
    mod_index: float,
    duration: float,
    amplitude: float = 0.5,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Generate frequency modulation (FM) synthesis.
    
    Args:
        carrier_freq: Carrier frequency in Hz
        modulator_freq: Modulator frequency in Hz
        mod_index: Modulation index (controls intensity of modulation)
        duration: Duration in seconds
        amplitude: Amplitude (0.0 to 1.0, default 0.5)
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with file path and generation details
    """
    return _generate_fm_synth(carrier_freq, modulator_freq, mod_index, duration, amplitude, sample_rate)


def _generate_adsr_envelope(
    attack: float,
    decay: float,
    sustain: float,
    release: float,
    duration: float,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Internal implementation: Generate an ADSR (Attack, Decay, Sustain, Release) envelope.
    
    Args:
        attack: Attack time in seconds (time to reach peak amplitude)
        decay: Decay time in seconds (time to reach sustain level)
        sustain: Sustain level (0.0 to 1.0, amplitude during sustain phase)
        release: Release time in seconds (time to fade to zero)
        duration: Total duration in seconds
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with envelope array and parameters
    """
    try:
        if attack < 0 or decay < 0 or release < 0:
            return {"error": "Attack, decay, and release times must be non-negative"}
        if sustain < 0 or sustain > 1.0:
            return {"error": "Sustain level must be between 0.0 and 1.0"}
        if duration <= 0 or duration > 300:
            return {"error": "Duration must be between 0 and 300 seconds"}
        if (attack + decay + release) > duration:
            return {"error": "Sum of attack, decay, and release times cannot exceed duration"}
        
        # Calculate sample counts for each phase
        total_samples = int(sample_rate * duration)
        attack_samples = int(sample_rate * attack)
        decay_samples = int(sample_rate * decay)
        release_samples = int(sample_rate * release)
        sustain_samples = total_samples - attack_samples - decay_samples - release_samples
        
        if sustain_samples < 0:
            return {"error": "Not enough time for sustain phase"}
        
        # Create envelope
        envelope = np.zeros(total_samples)
        idx = 0
        
        # Attack phase: linear ramp from 0 to 1
        if attack_samples > 0:
            envelope[idx:idx + attack_samples] = np.linspace(0, 1, attack_samples)
            idx += attack_samples
        else:
            envelope[idx] = 1.0
        
        # Decay phase: linear ramp from 1 to sustain level
        if decay_samples > 0:
            envelope[idx:idx + decay_samples] = np.linspace(1, sustain, decay_samples)
            idx += decay_samples
        
        # Sustain phase: constant sustain level
        if sustain_samples > 0:
            envelope[idx:idx + sustain_samples] = sustain
            idx += sustain_samples
        
        # Release phase: linear ramp from sustain to 0
        if release_samples > 0:
            envelope[idx:idx + release_samples] = np.linspace(sustain, 0, release_samples)
        
        logger.info(f"Generated ADSR envelope: A={attack}s D={decay}s S={sustain} R={release}s")
        return {
            "envelope": envelope.tolist(),
            "attack": attack,
            "decay": decay,
            "sustain": sustain,
            "release": release,
            "duration": duration,
            "sample_rate": sample_rate,
            "samples": len(envelope)
        }
    except Exception as e:
        logger.error(f"Failed to generate ADSR envelope: {e}", exc_info=True)
        return {"error": f"Failed to generate ADSR envelope: {e}"}


@mcp.tool()
def generate_adsr_envelope(
    attack: float,
    decay: float,
    sustain: float,
    release: float,
    duration: float,
    sample_rate: int = 44100
) -> Dict[str, Any]:
    """Generate an ADSR (Attack, Decay, Sustain, Release) envelope.
    
    Args:
        attack: Attack time in seconds (time to reach peak amplitude)
        decay: Decay time in seconds (time to reach sustain level)
        sustain: Sustain level (0.0 to 1.0, amplitude during sustain phase)
        release: Release time in seconds (time to fade to zero)
        duration: Total duration in seconds
        sample_rate: Sample rate in Hz (default 44100)
    
    Returns:
        Dictionary with envelope array and parameters
    """
    return _generate_adsr_envelope(attack, decay, sustain, release, duration, sample_rate)


def _apply_envelope_to_waveform(
    waveform_file: str,
    attack: float = 0.01,
    decay: float = 0.1,
    sustain: float = 0.7,
    release: float = 0.2
) -> Dict[str, Any]:
    """Internal implementation: Apply an ADSR envelope to an existing waveform file.
    
    Args:
        waveform_file: Path to the input audio file
        attack: Attack time in seconds (default 0.01)
        decay: Decay time in seconds (default 0.1)
        sustain: Sustain level 0.0-1.0 (default 0.7)
        release: Release time in seconds (default 0.2)
    
    Returns:
        Dictionary with output file path and envelope parameters
    """
    try:
        # Read input file
        waveform, sample_rate = sf.read(waveform_file)
        duration = len(waveform) / sample_rate
        
        # Generate ADSR envelope using private implementation
        envelope_result = _generate_adsr_envelope(
            attack=attack,
            decay=decay,
            sustain=sustain,
            release=release,
            duration=duration,
            sample_rate=sample_rate
        )
        
        if "error" in envelope_result:
            return envelope_result
        
        # Apply envelope
        envelope = np.array(envelope_result["envelope"])
        enveloped_waveform = waveform * envelope
        
        # Save to new file
        output_path = _generate_timestamped_filename("generated_audio", "enveloped", "wav")
        sf.write(str(output_path), enveloped_waveform, sample_rate)
        
        logger.info(f"Applied ADSR envelope to {waveform_file}: {output_path}")
        return {
            "file_path": str(output_path),
            "input_file": waveform_file,
            "attack": attack,
            "decay": decay,
            "sustain": sustain,
            "release": release,
            "duration": duration,
            "sample_rate": sample_rate
        }
    except Exception as e:
        logger.error(f"Failed to apply envelope: {e}", exc_info=True)
        return {"error": f"Failed to apply envelope: {e}"}


@mcp.tool()
def apply_envelope_to_waveform(
    waveform_file: str,
    attack: float = 0.01,
    decay: float = 0.1,
    sustain: float = 0.7,
    release: float = 0.2
) -> Dict[str, Any]:
    """Apply an ADSR envelope to an existing waveform file.
    
    Args:
        waveform_file: Path to the input audio file
        attack: Attack time in seconds (default 0.01)
        decay: Decay time in seconds (default 0.1)
        sustain: Sustain level 0.0-1.0 (default 0.7)
        release: Release time in seconds (default 0.2)
    
    Returns:
        Dictionary with output file path and envelope parameters
    """
    return _apply_envelope_to_waveform(waveform_file, attack, decay, sustain, release)


def _generate_subtractive_synth(
    base_waveform: str,
    filter_type: str,
    cutoff: float,
    resonance: float = 1.0,
    amplitude: float = 0.5
) -> Dict[str, Any]:
    """Internal implementation: Generate subtractive synthesis by filtering a base waveform.
    
    Args:
        base_waveform: Type of base waveform ("sine", "square", "sawtooth", "triangle")
        filter_type: Filter type ("lowpass", "highpass", "bandpass")
        cutoff: Cutoff frequency in Hz
        resonance: Filter resonance/Q factor (1.0 = no resonance, higher = more resonant)
        amplitude: Output amplitude (0.0 to 1.0, default 0.5)
    
    Returns:
        Dictionary with file path and synthesis details
    """
    try:
        from scipy import signal
        
        if base_waveform not in ["sine", "square", "sawtooth", "triangle"]:
            return {"error": "base_waveform must be 'sine', 'square', 'sawtooth', or 'triangle'"}
        if filter_type not in ["lowpass", "highpass", "bandpass"]:
            return {"error": "filter_type must be 'lowpass', 'highpass', or 'bandpass'"}
        if cutoff <= 0 or cutoff > 20000:
            return {"error": "Cutoff frequency must be between 0 and 20000 Hz"}
        if resonance < 0.1:
            return {"error": "Resonance must be at least 0.1"}
        if amplitude <= 0 or amplitude > 1.0:
            return {"error": "Amplitude must be between 0 and 1.0"}
        
        # Generate base waveform (1 second at 220 Hz for a good starting point)
        sample_rate = 44100
        duration = 1.0
        frequency = 220.0
        t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
        
        if base_waveform == "sine":
            waveform = np.sin(2 * np.pi * frequency * t)
        elif base_waveform == "square":
            waveform = signal.square(2 * np.pi * frequency * t)
        elif base_waveform == "sawtooth":
            waveform = signal.sawtooth(2 * np.pi * frequency * t)
        elif base_waveform == "triangle":
            waveform = signal.sawtooth(2 * np.pi * frequency * t, width=0.5)
        
        # Design filter using butterworth
        nyquist = sample_rate / 2
        normalized_cutoff = cutoff / nyquist
        
        # Clamp normalized cutoff to valid range
        normalized_cutoff = max(0.001, min(0.999, normalized_cutoff))
        
        if filter_type == "lowpass":
            b, a = signal.butter(4, normalized_cutoff, btype='low')
        elif filter_type == "highpass":
            b, a = signal.butter(4, normalized_cutoff, btype='high')
        elif filter_type == "bandpass":
            # For bandpass, use a range around cutoff
            low_cutoff = max(0.001, normalized_cutoff * 0.8)
            high_cutoff = min(0.999, normalized_cutoff * 1.2)
            b, a = signal.butter(4, [low_cutoff, high_cutoff], btype='band')
        
        # Apply filter
        filtered_waveform = signal.filtfilt(b, a, waveform)
        
        # Apply resonance by boosting frequencies near cutoff (simple simulation)
        if resonance > 1.0:
            # Create a resonant peak
            b_res, a_res = signal.iirpeak(cutoff / nyquist, Q=resonance)
            filtered_waveform = signal.filtfilt(b_res, a_res, filtered_waveform)
        
        # Normalize and apply amplitude
        if np.max(np.abs(filtered_waveform)) > 0:
            filtered_waveform = amplitude * filtered_waveform / np.max(np.abs(filtered_waveform))
        
        # Save to file
        output_path = _generate_timestamped_filename("generated_audio", "subtractive", "wav")
        sf.write(str(output_path), filtered_waveform, sample_rate)
        
        logger.info(f"Generated subtractive synthesis: {output_path}")
        return {
            "file_path": str(output_path),
            "base_waveform": base_waveform,
            "filter_type": filter_type,
            "cutoff": cutoff,
            "resonance": resonance,
            "amplitude": amplitude,
            "duration": duration,
            "sample_rate": sample_rate
        }
    except Exception as e:
        logger.error(f"Failed to generate subtractive synth: {e}", exc_info=True)
        return {"error": f"Failed to generate subtractive synth: {e}"}


@mcp.tool()
def generate_subtractive_synth(
    base_waveform: str,
    filter_type: str,
    cutoff: float,
    resonance: float = 1.0,
    amplitude: float = 0.5
) -> Dict[str, Any]:
    """Generate subtractive synthesis by filtering a base waveform.
    
    Args:
        base_waveform: Type of base waveform ("sine", "square", "sawtooth", "triangle")
        filter_type: Filter type ("lowpass", "highpass", "bandpass")
        cutoff: Cutoff frequency in Hz
        resonance: Filter resonance/Q factor (1.0 = no resonance, higher = more resonant)
        amplitude: Output amplitude (0.0 to 1.0, default 0.5)
    
    Returns:
        Dictionary with file path and synthesis details
    """
    return _generate_subtractive_synth(base_waveform, filter_type, cutoff, resonance, amplitude)
