whatever you're doing with duration length is perfect but not quite but it would be if you take exactly what you did and half it that will normalize it a bit ty send full script

using System;
using System.Text.RegularExpressions;

public class ValuesBasedOnString
{
    /// <summary>
    /// Maximum characters to count as very short input (exclusive upper bound)
    /// </summary>
    private const int VeryShortCharMax = 30;

    /// <summary>
    /// Maximum words to count as very short input (exclusive upper bound)
    /// </summary>
    private const int VeryShortWordMax = 5;

    /// <summary>
    /// Minimum characters to count as long/complex input (inclusive)
    /// </summary>
    private const int LongCharMin = 70;

    /// <summary>
    /// Minimum words to count as long/complex input (inclusive)
    /// </summary>
    private const int LongWordMin = 15;

    /// <summary>
    /// The original input string to analyze
    /// </summary>
    public string Input { get; }

    /// <summary>
    /// Formatted speak output, with commas replaced by newlines or periods split into lines when applicable
    /// </summary>
    public string FormattedSpeak { get; private set; }

    /// <summary>
    /// Computed quality value, typically between 5 and 30
    /// </summary>
    public int Quality { get; private set; }

    /// <summary>
    /// Computed duration value, typically between 5 and 30
    /// </summary>
    public int Duration { get; private set; }

    /// <summary>
    /// Mixed output data preceded by a "[verse]" tag for downstream use or logging
    /// </summary>
    public string MixLine => "[verse]" + Environment.NewLine + FormattedSpeak;

    /// <summary>
    /// Initializes a new instance analyzing input
    /// </summary>
    /// <param name="input">Input string to analyze</param>
    public ValuesBasedOnString(string input)
    {
        Input = input ?? string.Empty;
        ProcessInput();
    }

    /// <summary>
    /// Re-process the current input string (for example after updating parameters)
    /// </summary>
    public void Reprocess()
    {
        ProcessInput();
    }

    private void ProcessInput()
    {
        string cleanedInput = (Input ?? string.Empty).Trim();

        int charLength = cleanedInput.Length;
        int wordCount = CountWords(cleanedInput);

        // If input is empty, set minimal default values and return early
        if (wordCount == 0)
        {
            Quality = 5;
            Duration = 5;
            FormattedSpeak = string.Empty;
            return;
        }

        // If input contains commas, treat as a special case with structured output and fixed parameters
        if (cleanedInput.Contains(","))
        {
            Quality = 20;
            Duration = 27;
            FormattedSpeak = Regex.Replace(cleanedInput, @",\s*", Environment.NewLine);
            return;
        }

        FormattedSpeak = cleanedInput;

        // Fix boundary conditions:
        // Very short = strictly less than thresholds (exclusive)
        bool isVeryShort = (charLength < VeryShortCharMax) && (wordCount < VeryShortWordMax);

        // Long or complex = greater or equal than threshold (inclusive)
        bool isLong = (charLength >= LongCharMin) || (wordCount >= LongWordMin);

        if (isVeryShort)
        {
            // Very short inputs = fixed quality 12 (middle of previous 11-13)
            Quality = 12;
            Duration = 5;
            return;
        }
        if (isLong)
        {
            // Long inputs = fixed quality 24 (middle of 22-25), duration fixed 23 (middle of 20-27)
            Quality = 24;
            Duration = 23;
            return;
        }

        // Medium length inputs:
        // Quality fixed 14 (higher side of previous 13 or 14)
        Quality = 14;

        // Duration smoothly interpolated between 6 and 19 based on length

        // Normalized interpolation factor (0..1) for duration between very short upper and long lower bounds
        double tWords = NormalizeT(wordCount, VeryShortWordMax, LongWordMin);
        double tChars = NormalizeT(charLength, VeryShortCharMax, LongCharMin);

        double t = 0.7 * tWords + 0.3 * tChars;

        // Duration min/max for medium lengths range:
        const int minDuration = 6;
        const int maxDuration = 19;

        double baseDuration = Lerp(minDuration, maxDuration, t);

        // Round to nearest int (without randomness)
        Duration = ClampInt((int)Math.Round(baseDuration), minDuration, maxDuration);

        // Optionally split FormattedSpeak on periods if longer than 8 words for readability
        if (wordCount > 8 && cleanedInput.Contains('.'))
        {
            FormattedSpeak = Regex.Replace(cleanedInput, @"\.\s*", Environment.NewLine);
        }
    }

    private static int CountWords(string input)
    {
        if (string.IsNullOrWhiteSpace(input))
            return 0;

        string cleaned = Regex.Replace(input, @"\s+", " ").Trim();
        return cleaned.Split(' ').Length;
    }

    private static double NormalizeT(int val, int minExclusive, int maxInclusive)
    {
        if (val <= minExclusive) return 0.0;
        if (val >= maxInclusive) return 1.0;
        return (val - minExclusive) / (double)(maxInclusive - minExclusive);
    }

    private static double Lerp(double a, double b, double t) => a + t * (b - a);

    private static int ClampInt(int val, int min, int max)
    {
        if (val < min) return min;
        if (val > max) return max;
        return val;
    }
}