I want to share my first SF script! It fades out the selection at a constant dB rate. On long fades, e.g. a minute or more, you may feel that the various SF fade curves stall or speed up at points. In the past I've painstakingly crafted a constant-ish fade by piecing together line and curve segments in the graphic fade tool, but it's a lot of effort. This one feels pretty good to me, but I wouldn't be surprised if, on dynamic material, certain regions still need to be adjusted for subjective loudness. You can also make it fade in and change the dB range of the fade by editing the code in the function currentAttenuation().
If any of that was good news to you, then that was the good news. The bad news is that it doesn't run on SF Pro 13 because for some reason the readAudio and writeAudio methods don't work. I've tested this on SF Pro 10 builds 491 and 507. I also have some questions about the way certain methods work, which you can see in my capitalized comments. I've read in this forum that scripting expertise is "scarcer than hen's teeth" but it doesn't hurt to ask I guess. I'm also open to suggestions on how to improve things. Thanks for reading and I hope you find this useful.
/* Constant dB Fade.cs James Lo Feb 2020 This file implements a Sound Forge 10.0 (build 507 or 491) constant dB-rate fade script. It may work on other versions, but it has not been tested on them. If the normal SF fade tools make fades that seem to stall or speed up at points, give this script a try. Modify the code in the function "currentAttenuation" to set how much it fades and whether it fades up or down. NB: since it is constant dB-rate, the audio won't reach (or start at) true silence unless the currentAttenuation reduces the signal to a level that's below the precision of the output bit depth. In practice, you'll probably want to settle for about 48dB to 60dB of fade, and then use normal SF fade tools to get to (or start at) silence. */ using System; using System.IO; using System.Windows.Forms; using SoundForge; public class EntryPoint { public string Begin(IScriptableApp app) { ISfDataWnd wnd = app.ActiveWindow; if (null == wnd) return "No file open"; //get selection info ISfFileHost file = app.CurrentFile; SfAudioSelection asel = file.Window.EditRegion; DPF(" length = " + asel.Length); DPF(" start = " + asel.Start); DPF(" channel mask = " + asel.ChanMask); DPF(" nrChannels = " + file.Channels); //read audio into buffer float[,] selectionData = new float[asel.Length, file.Channels]; //WE HAVE TO READ ALL CHANNELS, NOT JUST THE SELECTED ONES, OR SF THROWS AN EXCEPTION int nrSamples = file.ReadAudio(selectionData, asel.Start); DPF(" nrSamples read = " + nrSamples); double maxSampleIndex = nrSamples - 1; //process the audio in the buffer uint initialChannelMask = asel.ChanMask; if (initialChannelMask == 0) initialChannelMask = 0xFF; for (int sampleIndex = 0; sampleIndex < asel.Length; sampleIndex++) { float attenuation = currentAttenuation(sampleIndex, maxSampleIndex); //for each channel uint channelMask = initialChannelMask; for (int channelIndex = 0; channelIndex < file.Channels; channelIndex++) { //if the channel is selected if ((channelMask & 0x01) == 1) { selectionData[sampleIndex, channelIndex] *= attenuation; } channelMask >>= 1; } } //write it back to the file ISfWriteAudioStream writeAudioStream = file.OpenWriteAudioStream(SfSampleType.Wav16, 0, 0); writeAudioStream.Append(selectionData); //FILE.WRITEAUDIO ASSUMES THAT THE INPUT STREAM ONLY CONTAINS THE SELECTED CHANNELS //SO WE WRITE ALL CHANNELS OUT SINCE WE WERE FORCED TO READ THEM EARLIER ANYWAY SfAudioSelection outpSel = new SfAudioSelection(asel); outpSel.ChanMask = 0; file.WriteAudio(outpSel, writeAudioStream); writeAudioStream.Close(CloseOptions.DiscardChanges); //WHY ARE "UNRECOGNIZED TEMP FILES" LEFT AFTER THIS SCRIPT FINISHES? return null; } private float currentAttenuation(double sampleIndex, double maxSampleIndex) { const double nrPowersOf2 = 10.0; //max volume change expressed in # of 6.02dB, may be fractional //FADE OUT return (float) Math.Pow(2.0, (sampleIndex/maxSampleIndex) * -nrPowersOf2); //FADE IN //return (float) Math.Pow(2.0, ((sampleIndex/maxSampleIndex) * nrPowersOf2) - nrPowersOf2); } 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 static IScriptableApp ForgeApp = null; public static void DPF(string sz) { ForgeApp.OutputText(sz); } public static void DPF(string fmt, object o) { ForgeApp.OutputText(String.Format(fmt,o)); } public static void DPF(string fmt, object o, object o2) { ForgeApp.OutputText(String.Format(fmt,o,o2)); } public static void DPF(string fmt, object o, object o2, object o3) { ForgeApp.OutputText(String.Format(fmt,o,o2,o3)); } public static string GETARG(string k, string d) { string val = Script.Args.ValueOf(k); if (val == null || val.Length == 0) val = d; return val; } public static int GETARG(string k, int d) { string s = Script.Args.ValueOf(k); if (s == null || s.Length == 0) return d; else return Script.Args.AsInt(k); } public static bool GETARG(string k, bool d) { string s = Script.Args.ValueOf(k); if (s == null || s.Length == 0) return d; else return Script.Args.AsBool(k); } } //EntryPoint