fix my c# class there's still issues with the fading logic as it's still making sox error out please fix and return in full tysm

using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;

public class SoxVolumeFader
{
    /// <summary>
    /// Applies fade in and fade out volume effect to an audio file using SoX.
    /// Automatically calculates the fade-in and fade-out ensuring no overlap.
    /// </summary>
    /// <param name="inputFile">Full path to the input audio file.</param>
    /// <param name="soxExePath">Full path to SoX executable.</param>
    public static async Task ApplyFadeAsync(string inputFile, string soxExePath)
    {
        if (string.IsNullOrWhiteSpace(inputFile))
            throw new ArgumentException("Input file path is required.", nameof(inputFile));
        if (string.IsNullOrWhiteSpace(soxExePath))
            throw new ArgumentException("SoX executable path is required.", nameof(soxExePath));

        inputFile = Path.GetFullPath(inputFile);
        soxExePath = Path.GetFullPath(soxExePath);

        if (!File.Exists(inputFile))
            throw new FileNotFoundException("Input file not found.", inputFile);
        if (!File.Exists(soxExePath))
            throw new FileNotFoundException("SoX executable not found.", soxExePath);

        if (!IsFileReadable(inputFile))
            throw new UnauthorizedAccessException($"Input file is not readable: {inputFile}");
        if (!IsFileExecutable(soxExePath))
            throw new UnauthorizedAccessException($"SoX executable is not executable: {soxExePath}");

        double durationSeconds = await GetAudioDurationAsync(inputFile, soxExePath);
        if (durationSeconds <= 0)
            throw new InvalidOperationException("Audio duration must be positive.");

        /*
         * Fade calculation strategy:
         * - Use perceptually logarithmic scaling for fadeIn duration (0.1s to 5s).
         * - fadeOut duration = fadeIn * 1.2 (slightly longer fade out).
         * - If fadeIn + fadeOut > duration, scale both fades down proportionally.
         * - Calculate stayDuration (middle segment) as duration - (fadeIn + fadeOut).
         * - All values are >= 0, no overlaps or negative durations.
         */

        const double MIN_FADE_IN = 0.1;    // minimum fade-in in seconds
        const double MAX_FADE_IN = 5.0;    // maximum fade-in in seconds
        const double FADE_OUT_RATIO = 1.2; // fadeOut duration multiplier relative to fadeIn

        // Normalize length on 0-1 scale (consider 1 minute max for scaling)
        double normalizedLength = Math.Min(durationSeconds / 60.0, 1.0);

        // Logarithmic scaling for natural fade length growth:
        // map normalizedLength in [0,1] to a factor f in [0,1]
        // f = log10(9 * normalizedLength + 1), (log10(1) == 0, log10(10) == 1)
        double fadeInFactor = Math.Log10(9 * normalizedLength + 1);

        // Calculate fadeIn duration scaled logarithmically
        double fadeIn = MIN_FADE_IN + (MAX_FADE_IN - MIN_FADE_IN) * fadeInFactor;

        // Calculate unscaled fadeOut relative to fadeIn
        double fadeOut = fadeIn * FADE_OUT_RATIO;

        // Check total fade duration
        double totalFade = fadeIn + fadeOut;

        if (totalFade > durationSeconds)
        {
            // Scale fades proportionally so they fit exactly into duration (no overlap)
            double scale = durationSeconds / totalFade;
            fadeIn *= scale;
            fadeOut *= scale;
            totalFade = durationSeconds; // now fades exactly consume entire duration
        }

        // Calculate stay duration (middle full-volume section)
        double stayDuration = durationSeconds - totalFade;

        // Safety clamps (should never be < 0 due to scaling, but just in case)
        fadeIn = Math.Max(0, fadeIn);
        fadeOut = Math.Max(0, fadeOut);
        stayDuration = Math.Max(0, stayDuration);

        // Round to milliseconds precision accepted by SoX
        fadeIn = Math.Round(fadeIn, 3);
        fadeOut = Math.Round(fadeOut, 3);
        stayDuration = Math.Round(stayDuration, 3);

        string inputDir = Path.GetDirectoryName(inputFile) ?? Directory.GetCurrentDirectory();
        string tempOutputFile = Path.Combine(
            inputDir,
            Path.GetFileNameWithoutExtension(inputFile) + "_fade_temp" + Path.GetExtension(inputFile));

        if (File.Exists(tempOutputFile))
        {
            try { File.Delete(tempOutputFile); }
            catch (Exception ex)
            {
                throw new IOException($"Failed to delete existing temp file: {tempOutputFile}", ex);
            }
        }

        // SoX fade command:
        // fade t <fadeIn> <stayDuration> <fadeOut>
        string args = $"\"{inputFile}\" \"{tempOutputFile}\" fade t {fadeIn:F3} {stayDuration:F3} {fadeOut:F3}";

        var processInfo = new ProcessStartInfo
        {
            FileName = soxExePath,
            Arguments = args,
            CreateNoWindow = true,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
        };

        using (var process = new Process { StartInfo = processInfo })
        {
            var tcs = new TaskCompletionSource<bool>();
            process.EnableRaisingEvents = true;
            process.Exited += (s, e) => tcs.TrySetResult(true);

            try
            {
                if (!process.Start())
                    throw new Exception("Failed to start SoX process.");
            }
            catch (Exception ex)
            {
                throw new Exception("Could not start SoX process.", ex);
            }

            Task<string> stdOutTask = process.StandardOutput.ReadToEndAsync();
            Task<string> stdErrTask = process.StandardError.ReadToEndAsync();

            await tcs.Task;

            string stdOut = await stdOutTask.ConfigureAwait(false);
            string stdErr = await stdErrTask.ConfigureAwait(false);

            if (process.ExitCode != 0)
            {
                TryDeleteFile(tempOutputFile);

                string errorMsg = $"SoX process failed with exit code {process.ExitCode}.";
                if (!string.IsNullOrWhiteSpace(stdErr))
                    errorMsg += $" Error: {stdErr.Trim()}";

                throw new Exception(errorMsg);
            }
        }

        // Replace original file atomically, with backup
        try
        {
            string backupFile = inputFile + ".bak";

            if (File.Exists(backupFile))
                TryDeleteFile(backupFile);

            File.Move(inputFile, backupFile); // Backup original

            try
            {
                File.Move(tempOutputFile, inputFile); // Replace with faded file
                TryDeleteFile(backupFile); // Delete backup if successful
            }
            catch
            {
                // Restore backup if replacement failed
                TryDeleteFile(inputFile);
                File.Move(backupFile, inputFile);
                throw;
            }
        }
        catch (Exception ex)
        {
            TryDeleteFile(tempOutputFile);
            throw new IOException("Failed to replace the original audio file with faded version.", ex);
        }
    }

    // === Support Methods ===

    private static async Task<double> GetAudioDurationAsync(string inputFile, string soxExePath)
    {
        var psi = new ProcessStartInfo
        {
            FileName = soxExePath,
            Arguments = $"--i -D \"{inputFile}\"",
            CreateNoWindow = true,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
        };

        using (var process = new Process { StartInfo = psi })
        {
            var tcs = new TaskCompletionSource<bool>();
            process.EnableRaisingEvents = true;
            process.Exited += (s, e) => tcs.TrySetResult(true);

            if (!process.Start())
                throw new Exception("Failed to start SoX process for duration.");

            var outputTask = process.StandardOutput.ReadToEndAsync();
            var errorTask = process.StandardError.ReadToEndAsync();

            await tcs.Task;

            string output = await outputTask.ConfigureAwait(false);
            string error = await errorTask.ConfigureAwait(false);

            if (process.ExitCode != 0)
            {
                string errorMsg = $"SoX failed to get audio duration. Exit code {process.ExitCode}.";
                if (!string.IsNullOrWhiteSpace(error))
                    errorMsg += $" Error: {error.Trim()}";
                throw new Exception(errorMsg);
            }

            if (double.TryParse(output.Trim(), out double duration))
                return duration;

            throw new Exception("Failed to parse audio duration from SoX output.");
        }
    }

    private static bool IsFileReadable(string path)
    {
        try
        {
            using (var stream = File.Open(path, FileMode.Open, FileAccess.Read)) { }
            return true;
        }
        catch
        {
            return false;
        }
    }

    private static bool IsFileExecutable(string path)
    {
        if (OperatingSystem.IsWindows())
        {
            string ext = Path.GetExtension(path).ToLowerInvariant();
            return ext == ".exe" || ext == ".bat" || ext == ".cmd" || ext == ".com";
        }
        else
        {
            try
            {
                FileInfo fileInfo = new FileInfo(path);
                return fileInfo.Exists && (fileInfo.Attributes & FileAttributes.Directory) == 0;
            }
            catch
            {
                return false;
            }
        }
    }

    private static void TryDeleteFile(string filePath)
    {
        try
        {
            if (File.Exists(filePath))
                File.Delete(filePath);
        }
        catch
        {
            // ignore cleanup errors
        }
    }
}