SF v18 Script to add more silence between every other phrase?

Unsounded wrote on 2/26/2026, 9:04 PM

I find Text to Speech very useful but one problem is the way it generates silence. Ideally TTS should have a method to control the amount of silence inserted per line. Sometimes I have more than one sentence per line in the TTS window. When the text is rendered the silence between each phrase is the same. This makes it impossible to use Auto Region to create regions per line. Currently, I can manually add extra silence to allow auto region to work per line but this is time consuming.

Is it possible to create a script that will add, say, half a second of silence between every other phrase? A phrase is measured as having silence less than 800 ms between word chunks. I'm wondering if it is possible to amend the word regions script. I find C# difficult to use. If it is possible any pointers would be appreciated. Thanks.

Comments

Unsounded wrote on 2/26/2026, 11:03 PM

I just realized if I have exactly two sentences per line and can get the Created Regions from Words script to skip every other area of detected silence it could create the regions I want automatically without the need to edit the silence length. I saved a copy as Create Regions from Phrases but haven't worked out how to skip every other region of silence. I hope this is trivial and someone can post how to do it.

Unsounded wrote on 2/27/2026, 12:34 AM

I found a solution. Here's the script in case it is useful for anyone else (Minimum word distance of 600 ms seems best):

/* ============================================================================
 *  Script Name: Create Regions From Phrases.cs
 *  Description: This script adds regions skipping every other region
 * ===========================================================================
*/

using System;
using System.IO;
using System.Windows.Forms;
using System.Collections;
using SoundForge;
using System.Reflection;
using System.Drawing;
using System.Threading;

public class EntryPoint
{
    //static bool finished = false;
    public string Begin(IScriptableApp app)
    {
        ISfFileHost file = app.CurrentFile;
        if (app.ActiveWindow == null)
            return "No file to process";

        // Prompt for the preset
        SpokenWordsWizard.Dialog(app.Win32Window, app);

        return null;
    }

    public static double strToDouble (string number)
    {
        double myDouble;
        bool isValid = double.TryParse(number, out myDouble); // check if possible
        if (isValid)
        {
            return myDouble;
        }
        else
        {
            DPF("ERROR: '{0}' is not a valid double.", number);
            return 0;
        }
    }

    public static string getDirectoryPath(string fileOrDir)
    {
        string dir = "";

        if (Directory.Exists(fileOrDir))
            dir = fileOrDir;
        else if (File.Exists(fileOrDir))
            dir = Path.GetDirectoryName(fileOrDir);
        else
            dir = Environment.GetEnvironmentVariable("userprofile"); //if fileOrDir is no path, return user directory

        return dir;
    }

    public void FromSoundForge(IScriptableApp app)
    {
        ForgeApp = app; //execution begins here
        app.SetStatusText(String.Format("Script '{0}' is running.", Script.Name));
        string msg = Begin(app);
        app.SetStatusText(msg != null ? msg : String.Format("Script '{0}' is done.", Script.Name));
    }

    public class SpokenWordsWizard
    {
        static IScriptableApp appInterface = null;

        public static void Dialog(System.Windows.Forms.IWin32Window hOwner, IScriptableApp app) 
        {
            Form dlg = new Form();

            dlg.Text = "Word Regions - '" + app.ActiveWindow.Title + "'";

            dlg.FormBorderStyle = FormBorderStyle.FixedDialog;
            dlg.MaximizeBox = false;
            dlg.MinimizeBox = false;

            dlg.StartPosition = FormStartPosition.CenterScreen;
            dlg.ClientSize = new Size(420, 160);

            Point pt = new Point(10,10);
            Size sOff = new Size(10, 10);

            Label description = null;
            description = new Label();
            description.Size = new Size (400, 50);
            description.Location = pt;
            description.AutoSize = false;
            description.Text = "Select a silent area in the Data Window to measure the noise level. The initial value of 'RMS noise level' is calculated from the quitest 500ms in the audio file. To restore this value, measure the noise level without making a selection in the data window.";
            dlg.Controls.Add(description);

            pt.Y += 55;
            TextBox textBox1 = null;
            textBox1 = new TextBox();
            textBox1.Size = new Size(150, 21);
            textBox1.Location = pt;
            double noiseFloorRMS = estimateNoiseFloor(app);
            textBox1.Text = SfHelpers.RatioTodB(noiseFloorRMS).ToString("F");;
            dlg.Controls.Add(textBox1);

            pt.X += 160;
            pt.Y += 3;
            Label label1 = new Label();
            label1.Text = "RMS noise level [dB]";
            label1.Size = new Size (200, 21);
            label1.Location = pt;
            dlg.Controls.Add(label1);

            pt.X -= 160;
            pt.Y += 30;
            TextBox textBox2 = null;
            textBox2 = new TextBox();
            textBox2.Size = new Size(150, 21);
            textBox2.Location = pt;
            textBox2.Text = "200";
            dlg.Controls.Add(textBox2);

            pt.X += 160;
            pt.Y += 3;
            Label label2 = new Label();
            label2.Text = "Minimal word distance [ms]";
            label2.Size = new Size (200, 21);
            label2.Location = pt;
            dlg.Controls.Add(label2);

            pt = (Point)dlg.ClientSize;
            pt -= sOff;

            Button btn = new Button();
            pt -= btn.Size;
            btn.Text = "Cancel";
            btn.Location = pt;
            btn.Click += new EventHandler(OnCancel_Click);
            dlg.Controls.Add(btn);
            dlg.CancelButton = btn;
            pt.X -= (btn.Width + 10);

            btn = new Button();
            btn.Text = "Run";
            btn.Location = pt;
            btn.Click += new EventHandler(OnRun_Click);
            dlg.Controls.Add(btn);
            dlg.AcceptButton = btn;
            pt.X = 10;

            btn = new Button();
            btn.Width = 150;
            btn.Text = "Measure noise level";
            btn.Location = pt;
            btn.Click += new EventHandler(OnMeasure_Click);
            dlg.Controls.Add(btn);
            dlg.AcceptButton = btn;

            appInterface = app;

            dlg.Show(hOwner);
        }

        private static void OnRun_Click(object sender, System.EventArgs e) 
        {
            Button btn  = (Button)sender;
            Form form = (Form)btn.Parent;
            TextBox rms = (TextBox)form.Controls[1];
            TextBox silLength = (TextBox)form.Controls[3];

            double rmsSilence = strToDouble(rms.Text);
            double minSecWordDistance = strToDouble(silLength.Text) / 1000;

            if (minSecWordDistance.CompareTo(0.0) <= 0)
            {
                MessageBox.Show("Maximum silence length value is not valid");
            }
            else if (rmsSilence.CompareTo(0.0) == 0)
            {
                MessageBox.Show("Noise level value is not valid");
            }
            else
            {
                rmsSilence = SfHelpers.dBToRatio(rmsSilence + 3);
                placeRegionsAtSpokenWords(appInterface, rmsSilence, minSecWordDistance);
                form.Close();
            }
        }

        private static void OnMeasure_Click(object sender, System.EventArgs e) 
        {
            Button btn  = (Button)sender;
            Form form = (Form)btn.Parent;
            form.Text = "Word Regions - '" + appInterface.ActiveWindow.Title + "'";
            double rms = 0.0;
            TextBox tbRms = (TextBox)form.Controls[1];
            SfAudioSelection currentSelection = appInterface.ActiveWindow.Selection;
            if (currentSelection.Length > 0)
            {
                rms = getCurrentRMS(appInterface);
                tbRms.Text = rms.ToString("F");
            }
            else
            {
                rms = SfHelpers.RatioTodB(estimateNoiseFloor(appInterface));
                tbRms.Text = rms.ToString("F");
            }
        }

        private static void OnCancel_Click(object sender, System.EventArgs e) 
        {
            Button btn = (Button)sender;
            Form form = (Form)btn.Parent;

            form.Close();
        }
    }

    public static double estimateNoiseFloor(IScriptableApp app)
    {
        double rms = 0.0;
        double rmsSilence = 1.0;
        ISfFileHost file = app.CurrentFile;
        long stepSize = file.SecondsToPosition(0.5);
        SfAudioSelection selection = new SfAudioSelection(0, stepSize);

        while (true)
        {
            // Calculate RMS
            for (uint ii = 0; ii < file.Channels; ++ii)
            {
                rms += file.ComputeRMS(selection, ii) / file.Channels;
            }

            if (rms < rmsSilence)
                rmsSilence = rms;

            rms = 0.0;
            selection.Start += stepSize;

            if ((selection.Start + stepSize) > file.Length)
                break;
        }

        return rmsSilence;
    }

    public static double getCurrentRMS(IScriptableApp app)
    {
        double rmsSilence = 0.0;
        SfAudioStatistics[] statistics = new SfAudioStatistics[20];
        ISfFileHost currentFile = app.CurrentFile;
        ISfDataWnd wnd = app.ActiveWindow;
        SfAudioSelection currentSelection = wnd.Selection;

        // Update statistics for this selection
        currentFile.UpdateStatistics(currentSelection);
        currentFile.WaitForDoneOrCancel();
        if (!currentFile.StatisticsAreUpToDate)
        {
            MessageBox.Show("Statistics calculation failed");
            return 0; //error
        }

        // Calculate RMS
        for (uint ii = 0; ii < currentFile.Channels; ++ii)
        {
            statistics[ii] = currentFile.GetStatistics(ii);
            rmsSilence += statistics[ii].RMSLevel / currentFile.Channels;
        }
        rmsSilence = SfHelpers.RatioTodB(rmsSilence);

        return rmsSilence;
    }

    public static void placeRegionsAtSpokenWords(IScriptableApp app, double rmsSilence, double minSecWordDistance)
    {
        ISfFileHost file = app.CurrentFile;
        ISfDataWnd wnd = app.ActiveWindow;

        int undoID = file.BeginUndo("Word Regions");

        // Get maximum silence length in samples
        long minSamplesWordDistance = file.SecondsToPosition(minSecWordDistance);
        Int64 stepSize = 2048;

        // Initialize variables
        SfAudioStatistics[] statistics = new SfAudioStatistics[20];
        Int64 fileStart    = file.SecondsToPosition(0.0);
        Int64 minimumWordLength = file.SecondsToPosition(0.2);
        Int64 fileLength   = file.Length;
        SfAudioSelection selection = new SfAudioSelection(wnd);
        SfAudioMarker    aRegion    = new SfAudioMarker(fileStart, stepSize);
        Int64 currentSilenceLength = 0;
        Int64 currentWordLength = 0;
        bool isSilence = true;
        int regionNum = 1;
		  int addCount = -1;
        bool regionWasPlaced = false;

        //Now step through the File & find Regions with RMS > 'rmsSilence ' considering minumWordDistance & minimumWordLength.
        Int64 ccPos = 0;
        for (ccPos = 0; ccPos + stepSize < fileLength; ccPos += stepSize)
        {
            app.SetStatusText("Script is Processing");

            // Select area (ccPos, stepSize)
            selection.Start  = ccPos;
            selection.Length = stepSize;

            // If RMS of all channels > rmsSilence then ...
            bool overThreshold = false;
            float rmsLevel = 0.0f;
            for (uint ii = 0; ii < file.Channels; ++ii)
            {
                rmsLevel = file.ComputeRMS(selection, ii);
                if (rmsLevel > rmsSilence)
                    overThreshold = true;
            }

            if (overThreshold)
            {
                if (isSilence)
                {
                    aRegion.Start = ccPos;
                    isSilence = false;
                    currentWordLength = 0;
                }
                currentSilenceLength = 0;
					 currentWordLength +=stepSize;
            }
            else if (isSilence == false)
            {
               currentSilenceLength += stepSize;
					currentWordLength +=stepSize;
            }

            if (currentSilenceLength >= minSamplesWordDistance && isSilence == false)
            {
               if (addCount == 2)
					{ 
					 aRegion.Name = String.Format("Word_{0}", regionNum);
                aRegion.Length = ccPos - aRegion.Start + stepSize - currentSilenceLength;
                if (aRegion.Length > minimumWordLength)
                {
                    file.Markers.Add(aRegion);
                    regionWasPlaced = true;
                }
                file.WaitForDoneOrCancel();
                isSilence = true;
                currentSilenceLength = 0;
                currentWordLength = 0;
					 addCount = 0;
					 //MessageBox.Show(addCount.ToString());
                regionNum++;
					 //addCount = false;
					}
					else
					addCount += 1;
            }
				
        }

        if (isSilence == false && regionWasPlaced == true) // get the last word at the end of file
        {
            aRegion.Name = String.Format("Word_{0}", regionNum);
            aRegion.Length = file.Length - aRegion.Start;
            if (aRegion.Length > minimumWordLength  && regionNum % 2 == 0)
                file.Markers.Add(aRegion);
            file.WaitForDoneOrCancel();
        }

        file.EndUndo(undoID, false);

        if (regionWasPlaced)
            DPF("Regions where successfully placed");
        else
            DPF("SOUND FORGE did not find any words");
    }

    public static IScriptableApp ForgeApp = null;
    public static void DPF(string sz) { ForgeApp.OutputText(sz);}
    public static void DPF(string fmt, object o) { string msg = String.Format(fmt, o); ForgeApp.OutputText(msg); }
}