A script that fades at a constant dB rate

James-Lo wrote on 2/26/2020, 2:09 PM

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

 

Comments

rraud wrote on 2/26/2020, 3:24 PM

@James-Lo, Thanks for your contribution

Your constant fade script works in the 32 bit version of SF Pro 13.. sort of.. the selected waveform disappears though, but the audio is faded The waveform shows the fade when rebuilt.

SCS used to have a Sound Forge scripting forum, Magix has the archives available as I recall, don't know the URL offhand.

 

 

James-Lo wrote on 2/26/2020, 4:08 PM

Ha, that's crazy. I keep forgetting that there is a 32 bit version of SF Pro 13, thanks for testing. Yeah, I poked around that scripting archive a bit when I was struggling with readAudio and writeAudio, but being an archive I didn't find anyone who had advice on the issues I was confronting in SF Pro 13-64. I've always been impressed that SF had a scripting interface so it's sad that I'm learning it just as entropy is taking over. 😀

rraud wrote on 2/26/2020, 5:15 PM

@James-Lo, I deleted the Constant Fade <.cs> file from the 64 bit version, since it did not work anyway and noticed it was no longer available in the 32 bit version either. So the <.cs> file must be located in the SF 64 bit Script folder. I do not know why, except the 64 and 32 bit versions share many of the settings and such. There are separate script folders though..

James-Lo wrote on 2/26/2020, 7:48 PM

Yeesh