Community Forums Archive

Go Back

Subject:REQ script like auto trim/crop
Posted by: AnotherSoundGuy
Date:8/27/2012 8:06:22 PM

Greetings,

I'm new to the scripting forum, even though I have used Sound Forge since (well forever) but never played with the scripting. Now that I've found it, I'd like to work on a script for a feature that I have wanted for a looong time. I have searched through all the samples and all the posts on this forum and haven't found anything that is exactly what I am looking for.

I wish that Process-->Auto Trim/Crop would reduce the length of gaps, but not eliminate them. Here's why! I work with long form spoken word recordings that need to go on a CD. Don't ask, it's historical! I wish there was a way of stepping through a recording a reducing every "silent" gap over 2 seconds to 2 seconds, so that maximum "silent" gap would be 2 seconds. Unfortunately, Process-->Auto Trim/Crop finds all these gaps, but reduces them to 0 seconds. This sounds really weird in a spoken word recording. A very short pause is preserved, a slightly longer pause is preserved, but any really long pause is just eliminated --- that's just wrong. Hope that makes sense!

It seems like I should be able to invoke Process-->Auto Trim/Crop and then step through each of the regions and increase them to the minimum length. But would it be -128db silent, that would sound weird!

Sorry for being wordy. If you can help, or point me to something I missed, or tell me I'm crazy, it's all good. Thanks for listening, have a good day.

Regards,
David

Message last edited on8/29/2012 6:51:13 PM byAnotherSoundGuy.
Subject:RE: REQ script like auto trim/crop
Reply by: AnotherSoundGuy
Date:8/28/2012 4:39:34 PM

I apologize for being a newbie and I threw together the following script to step through a file 0.5 seconds at a time and if that 0.5 second area is (RMS<-40) then insert .25 second region into the middle. Afterwards I can run the Special-->Playlist/Cutlist-->Treat as Cutlist to remove the silence.

Unfortunately, it doesn't work, and double unfortunately, the only message I get is almost totally useless. Is there anyway to get better debug information than…

System.ApplicationException: 1 compiler errors.
at SoundForge.ScriptHost.CodeDomScriptManager.Compile(String szFile, String szSourceText)
at SoundForge.ScriptHost.RunScript(String szFile, String szSourceText, EngineType eType, Boolean fCompileOnly, Boolean fDebug)



double minus40db = dBToRatio(-40.0);

ISfDataWnd wnd = app.ActiveWindow;
if (wnd == null)
{
return "Open a file before running this script.";
}
ISfFileHost file = app.CurrentFile;
SfAudioStatistics[] stat = new SfAudioStatistics[file.Channels];

Int64 ccStart = 0;
Int64 ccStep = file.SecondsToPosition(0.5);
Int64 ccLength = file.Length;

int idUndo = file.BeginUndo("Silence To Regions");

// Step through open file 0.5 seconds at a time.
// If the selected area is RMS < -40db then
// Create a 0.25 second region centered in the selection
for (Int64 ccPos = ccStart; ccPos + ccStep < ccLength; ccPos += ccStep)
{
// Select area (ccPos, ccStep)
SfAudioSelection asel = new SfAudioSelection(ccPos, ccStep);

// Update statistics for this selection
file.UpdateStatistics(asel);
file.WaitForDoneOrCancel();
if ( ! file.StatisticsAreUpToDate)
return "Something went wrong."; // error

// If RMS of all channels < -40db then
if ((file.Channels==1 && stat[0].RMSLevel < minus40db)
|| (file.Channels==2 && stat[0].RMSLevel < minus40db && stat[1].RMSLevel < minus40db))
{
// Create region ccPos+0.125 to ccPos +0.375
SfAudioMarker mk = new(ccPos + file.SecondsToPosition(0.125), ccStep/2);
file.Markers.Add(mk);
}

}

file.EndUndo(idUndo, false);
return null;


Any thoughts?

Message last edited on8/28/2012 4:45:38 PM byAnotherSoundGuy.
Subject:RE: REQ script like auto trim/crop
Reply by: roblesinge
Date:8/28/2012 10:56:49 PM

I'm giving this one a good look. I'm thinking you would need to analyze a selection starting at 0,1. If that selection is < 0dB, then you would look at selection 0,2 and so on. Once the selection is > 0dB, you would test to see if it is > 2000ms. If it is, you would overwrite the selection with 2000ms of silence. Then begin testing again at the end of the previous selection.

I'm working out how to get this into code.

Rob.

Subject:RE: REQ script like auto trim/crop
Reply by: roblesinge
Date:8/29/2012 12:20:55 PM

This seems to do what you're asking. It's not as exact as I would like, since I'm scanning through in such large chunks, but it gets within a few milliseconds of 2 seconds between each silence longer than 2 seconds. I'm still going to look through it and add an undo wrapper, as well as make sure exceptions are handled.

I'll keep working on it when I have time.


using System;
using System.IO;
using System.Windows.Forms;
using SoundForge;
using System.Collections;
using System.Collections.Generic;

public class EntryPoint
{
public void Begin(IScriptableApp app)
{
ISfDataWnd wnd = app.ActiveWindow;
if (wnd == null)
{
MessageBox.Show("Open a file before running this script.");
return;
}

ISfFileHost file = app.CurrentFile;

//Sets our max length for the silence.
long maxWanted = file.SecondsToPosition(2);
long step = 5000;

double minDb = SfHelpers.dBToRatio(-80.0);

//get length of the file in samples.
SfAudioSelection asel = new SfAudioSelection(file);
long sampHold = asel.Length;

long ccStart = sampHold - step;
long ccEnd = sampHold;

SfAudioSelection temp = new SfAudioSelection(ccEnd, ccEnd - ccStart);

while (ccStart > 0)
{
DPF(ccStart + " " + ccEnd);

temp.Start = ccStart;
temp.Length = ccEnd - ccStart;

file.UpdateStatistics(temp);
file.WaitForDoneOrCancel();
if (!file.StatisticsAreUpToDate)
{
MessageBox.Show("There was an error!");
return; //error
}

SfAudioStatistics[] statLR = new SfAudioStatistics[2];
statLR[0] = file.GetStatistics(0);
statLR[1] = file.GetStatistics(1);

DPF(statLR[0].RMSLevel.ToString() + " " + statLR[1].RMSLevel.ToString());

if (statLR[0].RMSLevel < minDb && statLR[1].RMSLevel < minDb)
{
ccStart -= step;
temp.Length = 0;
}

else
{
DPF("temp length is: {0} ", temp.Length.ToString() + ". Audio over -75dB RMS");

if (temp.Length > maxWanted)
{
DPF("Selection over 2000ms");

//do our replacement.
temp.Start += step;
temp.Length -= step;
file.DeleteAudio(temp);
file.InsertSilence(temp.Start, maxWanted - step);
}

DPF("audio not over 2000ms, or we're resetting");

//We prep to start the search further up the chain.
ccEnd = ccStart;
ccStart = ccEnd - step;
temp.Length = 0;
}

}

temp = null;
}

public void FromSoundForge(IScriptableApp app)
{
ForgeApp = app; //execution begins here
app.SetStatusText(String.Format("Script '{0}' is running.", Script.Name));
Begin(app);
app.SetStatusText(String.Format("Script '{0}' is done.", Script.Name));
}
public static IScriptableApp ForgeApp = null;
public static void DPF(string sz) { ForgeApp.OutputText(sz); }
public static void DPF(string fmt, params object[] args) { ForgeApp.OutputText(String.Format(fmt, args)); }
} //EntryPoint



Rob.

Message last edited on8/29/2012 12:36:01 PM byroblesinge.
Subject:RE: REQ script like auto trim/crop
Reply by: roblesinge
Date:8/29/2012 12:23:42 PM

BTW, it only works on Stereo files. If you're using mono, that will be a different ball of wax.

Rob.

Subject:RE: REQ script like auto trim/crop
Reply by: AnotherSoundGuy
Date:8/29/2012 6:48:27 PM

Greetings Rob,

Your script is much more complicated than mine. Thank you for your insights. The one thing I wanted to do was to shorten long quiet gaps, like auto trim/crop, but not to get rid of the gaps completely because that sounds so unnatural. Between your script and the samples in the SDK, I came up with the following solution. In a test, it eliminated 15 minutes out of a typical one and a half hour spoken word recording but left it sounding completely natural, because the gaps are there, just not as long as originally. There are a couple of manual steps after running this script: copy the regions to the play list, select treat as cut list, delete cut list. Voila! I am totally happy.


ISfDataWnd wnd = app.ActiveWindow;
if (wnd == null)
{
return "Open a file before running this script.";
}

ISfFileHost file = wnd.File;
SfAudioStatistics[] stat = new SfAudioStatistics[file.Channels];

Int64 ccStart = file.SecondsToPosition(0.0);
Int64 ccStep = file.SecondsToPosition(0.5);
Int64 ccLength = file.Length;
double minus40db = SfHelpers.dBToRatio(-40.0);
bool LastWasSilent = GETARG("false",false);

SfAudioSelection aSelection = new SfAudioSelection(wnd);
SfAudioMarker aRegion = new SfAudioMarker(ccStart, ccStep);

int idUndo = file.BeginUndo("Silence To Regions");

// Step through open file 0.5 seconds at a time.
// If the selected area is RMS < -40db then
// Create a 0.25 second region centered in the selection
for (Int64 ccPos = 0; ccPos + ccStep < ccLength; ccPos += ccStep)
{
// Select area (ccPos, ccStep)
aSelection.Start = ccPos;
aSelection.Length = ccStep;

// Update statistics for this selection
file.UpdateStatistics(aSelection);
file.WaitForDoneOrCancel();
if ( ! file.StatisticsAreUpToDate)
{
return "Something went wrong.";
}
for (uint ii = 0; ii < file.Channels; ++ii)
{
stat[ii] = file.GetStatistics(ii);
}

// If RMS of all channels < -40db then
if ((file.Channels==1 && stat[0].RMSLevel < minus40db)
|| (file.Channels==2 && stat[0].RMSLevel < minus40db && stat[1].RMSLevel < minus40db))
{
// If this is the second or more silent region then
// Create region ccPos to ccPos*3/4
if (LastWasSilent)
{
aRegion.Start = ccPos;
aRegion.Length = ccStep*3/4;
file.Markers.Add(aRegion);
}
LastWasSilent = true;
}
else
{
LastWasSilent = false;
}
}

file.EndUndo(idUndo, false);
return null;

Subject:RE: REQ script like auto trim/crop
Reply by: josecotes
Date:10/10/2012 11:43:44 AM

Can you post your entire code-block?
I am a newbie scripting for SF and can't get this one to run.

Thanks!

Subject:RE: REQ script like auto trim/crop
Reply by: roblesinge
Date:10/10/2012 2:17:56 PM

Try this:


using System;
using System.IO;
using System.Windows.Forms;
using SoundForge;
using System.Collections;
using System.Collections.Generic;

public class EntryPoint
{
public void Begin(IScriptableApp app)
{

ISfDataWnd wnd = app.ActiveWindow;
if (wnd == null)
{
return "Open a file before running this script.";
}

ISfFileHost file = wnd.File;
SfAudioStatistics[] stat = new SfAudioStatistics[file.Channels];

Int64 ccStart = file.SecondsToPosition(0.0);
Int64 ccStep = file.SecondsToPosition(0.5);
Int64 ccLength = file.Length;
double minus40db = SfHelpers.dBToRatio(-40.0);
bool LastWasSilent = GETARG("false",false);

SfAudioSelection aSelection = new SfAudioSelection(wnd);
SfAudioMarker aRegion = new SfAudioMarker(ccStart, ccStep);

int idUndo = file.BeginUndo("Silence To Regions");

// Step through open file 0.5 seconds at a time.
// If the selected area is RMS < -40db then
// Create a 0.25 second region centered in the selection
for (Int64 ccPos = 0; ccPos + ccStep < ccLength; ccPos += ccStep)
{
// Select area (ccPos, ccStep)
aSelection.Start = ccPos;
aSelection.Length = ccStep;

// Update statistics for this selection
file.UpdateStatistics(aSelection);
file.WaitForDoneOrCancel();
if ( ! file.StatisticsAreUpToDate)
{
return "Something went wrong.";
}
for (uint ii = 0; ii < file.Channels; ++ii)
{
stat[ii] = file.GetStatistics(ii);
}

// If RMS of all channels < -40db then
if ((file.Channels==1 && stat[0].RMSLevel < minus40db)
|| (file.Channels==2 && stat[0].RMSLevel < minus40db && stat[1].RMSLevel < minus40db))
{
// If this is the second or more silent region then
// Create region ccPos to ccPos*3/4
if (LastWasSilent)
{
aRegion.Start = ccPos;
aRegion.Length = ccStep*3/4;
file.Markers.Add(aRegion);
}
LastWasSilent = true;
}
else
{
LastWasSilent = false;
}
}

file.EndUndo(idUndo, false);
return null;

public void FromSoundForge(IScriptableApp app)
{
ForgeApp = app; //execution begins here
app.SetStatusText(String.Format("Script '{0}' is running.", Script.Name));
Begin(app);
app.SetStatusText(String.Format("Script '{0}' is done.", Script.Name));
}
public static IScriptableApp ForgeApp = null;
public static void DPF(string sz) { ForgeApp.OutputText(sz); }
public static void DPF(string fmt, params object[] args) { ForgeApp.OutputText(String.Format(fmt, args)); }
} //EntryPoint



For future reference, you can use this to start your scripts


using System;
using System.IO;
using System.Windows.Forms;
using SoundForge;
using System.Collections;
using System.Collections.Generic;

public class EntryPoint
{
public void Begin(IScriptableApp app)
{

//Your code here.

}


public void FromSoundForge(IScriptableApp app)
{
ForgeApp = app; //execution begins here
app.SetStatusText(String.Format("Script '{0}' is running.", Script.Name));
Begin(app);
app.SetStatusText(String.Format("Script '{0}' is done.", Script.Name));
}
public static IScriptableApp ForgeApp = null;
public static void DPF(string sz) { ForgeApp.OutputText(sz); }
public static void DPF(string fmt, params object[] args) { ForgeApp.OutputText(String.Format(fmt, args)); }
} //EntryPoint


Message last edited on10/10/2012 2:18:20 PM byroblesinge.

Go Back