fix my c# SOX class I need you to write hardcore code to calculate the proper fading needed

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 a medium fade in and quicker fade out based on audio length.
    /// </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)
    {
        // Basic validation
        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));

        // Normalize paths
        inputFile = Path.GetFullPath(inputFile);
        soxExePath = Path.GetFullPath(soxExePath);

        // Check file existence and access permissions
        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.");

        // Parameters for fade
        const double minFadeIn = 0.5; // minimum fade in length (sec)
        const double maxFadeIn = 5.0; // maximum fade in length (sec)

        // Calculate fadeIn duration as 10% of total length, clamped
        double fadeIn = Math.Clamp(durationSeconds * 0.1, minFadeIn, maxFadeIn);

        // Fade out quicker than before - 50% of fadeIn duration
        double fadeOut = fadeIn * 0.5;

        double totalFades = fadeIn + fadeOut;
        double stayDuration;

        if (totalFades >= durationSeconds)
        {
            // If fades longer than audio, scale them proportionally to fit total duration
            double scale = durationSeconds / totalFades;
            fadeIn *= scale;
            fadeOut *= scale;
            stayDuration = 0;
        }
        else
        {
            stayDuration = durationSeconds - totalFades;
        }

        // Round to 3 decimals to ensure SoX accepts it correctly
        fadeIn = Math.Round(Math.Max(fadeIn, 0), 3);
        fadeOut = Math.Round(Math.Max(fadeOut, 0), 3);
        stayDuration = Math.Round(Math.Max(stayDuration, 0), 3);

        string inputDir = Path.GetDirectoryName(inputFile) ?? Directory.GetCurrentDirectory();

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

        // Ensure temp output file does not already exist and is writable/deletable
        if (File.Exists(tempOutputFile))
        {
            try
            {
                File.Delete(tempOutputFile);
            }
            catch (Exception ex)
            {
                throw new IOException($"Failed to delete existing temp file: {tempOutputFile}", ex);
            }
        }

        // Prepare SoX arguments for fade effect
        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
        try
        {
            string backupFile = inputFile + ".bak";

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

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

            try
            {
                File.Move(tempOutputFile, inputFile); // move faded file to original path
                TryDeleteFile(backupFile); // delete backup if all is well
            }
            catch
            {
                // Attempt restore from 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);
        }
    }

    /// <summary>
    /// Get audio duration using SoX.
    /// </summary>
    /// <param name="inputFile">Input audio file path.</param>
    /// <param name="soxExePath">Path to SoX executable.</param>
    /// <returns>Duration in seconds.</returns>
    private static async Task<double> GetAudioDurationAsync(string inputFile, string soxExePath)
    {
        var processInfo = new ProcessStartInfo
        {
            FileName = soxExePath,
            Arguments = $"--i -D \"{inputFile}\"",
            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 for duration.");
            }
            catch (Exception ex)
            {
                throw new Exception("Could not start SoX process to get audio duration.", ex);
            }

            Task<string> outputTask = process.StandardOutput.ReadToEndAsync();
            Task<string> 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.");
        }
    }

    /// <summary>
    /// Check if file is readable.
    /// </summary>
    private static bool IsFileReadable(string path)
    {
        try
        {
            using (var stream = File.Open(path, FileMode.Open, FileAccess.Read))
            {
                return true;
            }
        }
        catch
        {
            return false;
        }
    }

    /// <summary>
    /// Check if file is executable.
    /// Works by basic check of extensions (.exe, .bat, .cmd, etc.) on Windows.
    /// In Unix, checks execute permission bit.
    /// </summary>
    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
        {
            // On Unix, check execute permission
            try
            {
                var fileInfo = new FileInfo(path);
                // No direct API in .NET to check unix perms—we try to execute a process to verify permissions
                // For simplicity, just assume file exists means executable (warning: very simple check)
                return fileInfo.Exists && (fileInfo.Attributes & FileAttributes.Directory) == 0;
            }
            catch
            {
                return false;
            }
        }
    }

    /// <summary>
    /// Attempt to delete a file, ignoring exceptions.
    /// </summary>
    private static void TryDeleteFile(string filePath)
    {
        try
        {
            if (File.Exists(filePath))
                File.Delete(filePath);
        }
        catch
        {
            // intentionally ignore cleanup errors
        }
    }
}