Sound Forge 16: making sample chains

amaury-groc wrote on 3/22/2022, 9:55 AM

I have folders with samples and would like to either:

  • Make one sample with all samples in the folder, one after the other, AND have a marker at each boundary (I found a way to open all samples in one data window, but not for putting markers at each file end)
  • Or, make one sample with all samples in the folder with equal length (so, adding silence to the shorter ones so that all "slices" have the same length)

Any convenient solution?

Comments

SP. wrote on 3/22/2022, 4:59 PM

@amaury-groc

Yes, this can be done with a script.

Here I have a fully working script, that will combine all files in a given folder and its subfolders and place a marker at the beginning of each new file.

Open Sound Forge and go into View > Script Editor. Copy the full script down below into the script editor.

Click on Compile Script in the upper right corner of the editor Window. You should read the message "Compile OK" at the bottom. Then click on Run Script in the upper right corner. The script will now start. It will ask for a folder where your files are stored. Be careful not to select the wrong folder. I added a "Are you sure?" question box, just as a fail save.

Disclaimer: As always I need to state, that you should have a backup of all your files before doing this. I will take no responsibility in case something gets deleted, corrupted, damaged etc. Use at your own risk.

using System;

using System.IO;

using System.Windows.Forms;

using SoundForge;

 

public class EntryPoint {

public void Begin(IScriptableApp app) {

 

    string folder = SfHelpers.ChooseDirectory("Choose a folder to process files from", @"C:\"); //The default folder is C:\, change it to something different if you want

 

    DialogResult dialogResult = MessageBox.Show("Are you sure to process " + folder + " and all its subfolders?", "WARNING", MessageBoxButtons.YesNo);

   

    if(dialogResult == DialogResult.Yes)

    {

        DirectoryInfo di = new DirectoryInfo(folder);

 

        ISfFileHost combinedFile = null;

 

        int markerNumber = 1;

       

        foreach (FileInfo file in di.GetFiles("*", SearchOption.AllDirectories))

        {

           ISfFileHost openedFile = app.OpenFile(file.FullName, false, true);

       

           if(null == openedFile)

           {

               DPF("Could not open {0}", file.FullName);

               MessageBox.Show("Could not open " + file.FullName + "\n\nThe script will now stop.", "Error", MessageBoxButtons.OK);

               return;

           }

 

           SfAudioSelection selectAll = new SfAudioSelection(openedFile);

 

           if(combinedFile == null)

           {

               combinedFile = openedFile.NewFile(selectAll);

               SfAudioMarker firstMarker = new SfAudioMarker(0);

               firstMarker.Name = String.Format("{0}", markerNumber);

               combinedFile.Markers.Add(firstMarker);

               markerNumber++;

           }

           else

           {              

              SfAudioMarker marker = new SfAudioMarker(combinedFile.Length);                            

              combinedFile.OverwriteAudio(combinedFile.Length, 0, openedFile, selectAll);

              marker.Name = String.Format("{0}", markerNumber);

              combinedFile.Markers.Add(marker);

              markerNumber++;

           }          

 

           openedFile.Close(CloseOptions.DiscardChanges);

 

           File.Delete(Path.ChangeExtension(file.FullName, ".sfk")); //This command deletes all the sfk-files created by Sound Forge          

       

        }

    }

 

}

 

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

rraud wrote on 3/22/2022, 5:22 PM

I do not fully understated your query @amaury-groc.

If you want to combine all the pertinent files to one. Open and launch the first file. Click the 'Open' dialog again and check-mark, "Append to current data window" Select the rest of the files and click 'open'. You can separate the clips by markers or regions.

If you want all the files to be the same duration, I would use 'elastique timestreach' in SFP's 'Process> Time' menu.

amaury-groc wrote on 3/23/2022, 3:22 AM

@amaury-groc

Yes, this can be done with a script.

Here I have a fully working script, that will combine all files in a given folder and its subfolders and place a marker at the beginning of each new file.

Open Sound Forge and go into View > Script Editor. Copy the full script down below into the script editor.

Click on Compile Script in the upper right corner of the editor Window. You should read the message "Compile OK" at the bottom. Then click on Run Script in the upper right corner. The script will now start. It will ask for a folder where your files are stored. Be careful not to select the wrong folder. I added a "Are you sure?" question box, just as a fail save.

Disclaimer: As always I need to state, that you should have a backup of all your files before doing this. I will take no responsibility in case something gets deleted, corrupted, damaged etc. Use at your own risk.

using System;

using System.IO;

using System.Windows.Forms;

using SoundForge;

 

public class EntryPoint {

public void Begin(IScriptableApp app) {

 

    string folder = SfHelpers.ChooseDirectory("Choose a folder to process files from", @"C:\"); //The default folder is C:\, change it to something different if you want

 

    DialogResult dialogResult = MessageBox.Show("Are you sure to process " + folder + " and all its subfolders?", "WARNING", MessageBoxButtons.YesNo);

   

    if(dialogResult == DialogResult.Yes)

    {

        DirectoryInfo di = new DirectoryInfo(folder);

 

        ISfFileHost combinedFile = null;

 

        int markerNumber = 1;

       

        foreach (FileInfo file in di.GetFiles("*", SearchOption.AllDirectories))

        {

           ISfFileHost openedFile = app.OpenFile(file.FullName, false, true);

       

           if(null == openedFile)

           {

               DPF("Could not open {0}", file.FullName);

               MessageBox.Show("Could not open " + file.FullName + "\n\nThe script will now stop.", "Error", MessageBoxButtons.OK);

               return;

           }

 

           SfAudioSelection selectAll = new SfAudioSelection(openedFile);

 

           if(combinedFile == null)

           {

               combinedFile = openedFile.NewFile(selectAll);

               SfAudioMarker firstMarker = new SfAudioMarker(0);

               firstMarker.Name = String.Format("{0}", markerNumber);

               combinedFile.Markers.Add(firstMarker);

               markerNumber++;

           }

           else

           {              

              SfAudioMarker marker = new SfAudioMarker(combinedFile.Length);                            

              combinedFile.OverwriteAudio(combinedFile.Length, 0, openedFile, selectAll);

              marker.Name = String.Format("{0}", markerNumber);

              combinedFile.Markers.Add(marker);

              markerNumber++;

           }          

 

           openedFile.Close(CloseOptions.DiscardChanges);

 

           File.Delete(Path.ChangeExtension(file.FullName, ".sfk")); //This command deletes all the sfk-files created by Sound Forge          

       

        }

    }

 

}

 

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

Thank you so much! I will make sure to backup my data before trying and take full responsibility. Before I try, I'm not sure I specified: I want to create one file per folder, not one file will all files from all sub-folders.

2 additional points:

- would it be easy to specify an output folder? I'd be happy if all created combined file would be saved in a single folder

- If I can ask more from your generous contribution, would it be easy to make a script that creates a combined file with samples equally spaced? Cherry on top would be to append the number of files combined at the end of the created file name.

 

What I need in the end is /if number of samples in folder =<32, create a file with combined samples and markers for each sample start, else, create combined files with all samples equally spaced.

Just sharing that for completion, I do not expect you put more time in it. If it's fun for you I won't complain. Thanks again for this!

amaury-groc wrote on 3/23/2022, 3:24 AM

I do not fully understated your query @amaury-groc.

If you want to combine all the pertinent files to one. Open and launch the first file. Click the 'Open' dialog again and check-mark, "Append to current data window" Select the rest of the files and click 'open'. You can separate the clips by markers or regions.

If you want all the files to be the same duration, I would use 'elastique timestreach' in SFP's 'Process> Time' menu.

I need a combined file with markers at each slice start, made from all files in a folder. When opening samples in a single data window, I see no easy way to also have markers at the right place (apart from doing this manually)

 

For the equally spaced, I want the samples not stretched, so Elastique wouldn't help.

 

Hope it's clearer now :)

rraud wrote on 3/23/2022, 10:18 AM

For the equally spaced, I want the samples not stretched, so Elastique wouldn't help.

> Without 'time stretch' I am not aware of a way to make the files of equal duration without deleting or adding data.. (silence or copy/paste)

I need a combined file with markers at each slice

After appending the slice files to the data window (as I previously mentioned), there is a script to create CD track/regions which would ID the slices nicely, You can view the 'CD track' playlist and even edit names and comments, which would give you much more information than the 'one trick pony' markers/regions. Selecting and navigating to the CD tracks/slices would be faster and easier than markers/regions as well. With the CD tool set, a specified silent pause can be added between the track/slices .. there is also insert silence script.

amaury-groc wrote on 3/23/2022, 10:51 AM

For the equally spaced, I want the samples not stretched, so Elastique wouldn't help.

> Without 'time stretch' I am not aware of a way to make the files of equal duration without deleting or adding data.. (silence or copy/paste)

I need a combined file with markers at each slice

After appending the slice files to the data window (as I previously mentioned), there is a script to create CD track/regions which would ID the slices nicely, You can view the 'CD track' playlist and even edit names and comments, which would give you much more information than the 'one trick pony' markers/regions. Selecting and navigating to the CD tracks/slices would be faster and easier than markers/regions as well. With the CD tool set, a specified silent pause can be added between the track/slices .. there is also insert silence script.

The purpose of my aim is to use the resulting combined files with a hardware device, the Dirtywave m8.

  • It supports slicing for files having up to 32 markers. No need for additional information, just markers embedded in the wav file as Sound Forge does it
  • It otherwise support slicing wav file in up to 128 slices of same size, so without needed any metadata. For that the slices have to be equally spaced and then yes, there is a need to add silence to the shorter samples so that all samples are the same length as the longest before combining them in a sinlge wav file.

Hope I didn't confuse you more now :) Thanks for replying!

SP. wrote on 3/23/2022, 2:25 PM

@amaury-groc

I have here modified script that should do what you asked for.

You can select the input folder. The script will then walk through all subfolders and create an output file in an output folder you can specify. The output filename will have the foldername and an underscore followed by the number of sampled used. Subfolders without files will be skipped. After the file is generated it will be automatically saved. You can get a pop up dialog asking you, if you want to open the saved file. Select no to save RAM.

Some warnings 💀😁

  • Run the script without any files open.
  • If you have input subfolders with identical names and numbers of files inside the output files will be overwritten.
  • If you have files inside your folders that Sound Forge cannot open, the script will fail.
  • The script will fail if you mix mono and stereo files.
  • The script can fail if you have corrupted audio files.
  • Do not load files that already have markers inside or they will be copied and create a mess.
  • Do not mix audio files with different sample rates and bit depths.
  • Do not run this script on millions of files. I was able to crash Sound Forge multiple times. 😁

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

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

    string inputFolder = SfHelpers.ChooseDirectory("Choose the input folder to process files from", @"C:\"); //The default folder is C:\, change it to something different if you want

    DialogResult dialogResultInput = MessageBox.Show("Are you sure to process " + inputFolder + " and all its subfolders?", "WARNING", MessageBoxButtons.YesNo);

    string outputFolder = SfHelpers.ChooseDirectory("Choose the output folder to save the combined files to", @"C:\"); //The default folder is C:\, change it to something different if you want

    DialogResult dialogResultOutput = MessageBox.Show("Are you sure to save the files to " + outputFolder +"?", "WARNING", MessageBoxButtons.YesNo);
    
    if(dialogResultInput == DialogResult.Yes && dialogResultOutput == DialogResult.Yes)
    {
        ProcessDirectory(inputFolder, outputFolder);         
    }
}

public int ProcessDirectory(string inputFolder, string outputFolder) 
{
    // Process the list of files found in the directory.
    if(ProcessFile(inputFolder, outputFolder) == -1)
    {
        return -1; //Error
    };
    
    // Recurse into subdirectories of this directory.
    string [] subdirectoryEntries = Directory.GetDirectories(inputFolder);
    foreach(string subdirectory in subdirectoryEntries)
    {
        if(ProcessDirectory(subdirectory, outputFolder) == -1)
        {
            return -1; //Error
        }
    }

    return 0; //OK
}

public int ProcessFile(string inputFolder, string outputFolder) 
{
    DirectoryInfo di = new DirectoryInfo(inputFolder);
    ISfFileHost combinedFile = null;
    int markerNumber = 1;
    int fileCounter = 0;
    long maxLength = 0;

    foreach (FileInfo file in di.GetFiles("*"))
    {
       ISfFileHost openedFile = ForgeApp.OpenFile(file.FullName, false, true);
       Application.DoEvents();
    
       if(null == openedFile)
       {
           DPF("Could not open {0}", file.FullName);
           MessageBox.Show("Could not open " + file.FullName + "\n\nThe script will now stop.", "Error", MessageBoxButtons.OK);
           return -1;
       }

       if(openedFile.Length > maxLength)
       {
           maxLength = openedFile.Length;
       }

       openedFile.Close(CloseOptions.DiscardChanges);
       Application.DoEvents();

       fileCounter++;
    }
    
    foreach (FileInfo file in di.GetFiles("*"))
    {
       ISfFileHost openedFile = ForgeApp.OpenFile(file.FullName, false, true);
       Application.DoEvents();
    
       if(null == openedFile)
       {
           DPF("Could not open {0}", file.FullName);
           MessageBox.Show("Could not open " + file.FullName + "\n\nThe script will now stop.", "Error", MessageBoxButtons.OK);
           return -1;
       }

       if(fileCounter > 32)
       {
           if(openedFile.Length < maxLength)
           {
               openedFile.InsertSilence(openedFile.Length, maxLength - openedFile.Length);
               Application.DoEvents();
           }
       }

       SfAudioSelection selectAll = new SfAudioSelection(openedFile);
       if(combinedFile == null)
       {
           combinedFile = openedFile.NewFile(selectAll);
           SfAudioMarker firstMarker = new SfAudioMarker(0);
           firstMarker.Name = String.Format("{0}", markerNumber);
           combinedFile.Markers.Add(firstMarker);
           markerNumber++;
       }
       else
       {              
          SfAudioMarker marker = new SfAudioMarker(combinedFile.Length);                            
          combinedFile.OverwriteAudio(combinedFile.Length, 0, openedFile, selectAll);
          marker.Name = String.Format("{0}", markerNumber);
          combinedFile.Markers.Add(marker);
          markerNumber++;
       }           
       openedFile.Close(CloseOptions.DiscardChanges);
       Application.DoEvents();
       File.Delete(Path.ChangeExtension(file.FullName, ".sfk")); //This command deletes all the sfk-files created by Sound Forge  
    }

    if(combinedFile != null)
    {
        combinedFile.SaveAs(outputFolder + @"\" + di.Name + "_" + fileCounter + ".wav", combinedFile.SaveFormat.Guid, "Default Template", RenderOptions.WaitForDoneOrCancel);        
        Application.DoEvents();
        combinedFile.Close(CloseOptions.SaveChanges);    
    }

    return 0;
}


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

amaury-groc wrote on 3/24/2022, 1:45 AM

@amaury-groc

I have here modified script that should do what you asked for.

You can select the input folder. The script will then walk through all subfolders and create an output file in an output folder you can specify. The output filename will have the foldername and an underscore followed by the number of sampled used. Subfolders without files will be skipped. After the file is generated it will be automatically saved. You can get a pop up dialog asking you, if you want to open the saved file. Select no to save RAM.

Some warnings 💀😁

  • Run the script without any files open.
  • If you have input subfolders with identical names and numbers of files inside the output files will be overwritten.
  • If you have files inside your folders that Sound Forge cannot open, the script will fail.
  • The script will fail if you mix mono and stereo files.
  • The script can fail if you have corrupted audio files.
  • Do not load files that already have markers inside or they will be copied and create a mess.
  • Do not mix audio files with different sample rates and bit depths.
  • Do not run this script on millions of files. I was able to crash Sound Forge multiple times. 😁

Thanks so much! I'll give it a go. There are folders in that folders tree that contain non-audio files. I'll see about copying the relevant folders somewhere else, or deleting folders I don't need (on a copy of the data), Amazing, I appreciate your help a lot!

amaury-groc wrote on 3/25/2022, 1:07 PM

@amaury-groc

I have here modified script that should do what you asked for.

You can select the input folder. The script will then walk through all subfolders and create an output file in an output folder you can specify. The output filename will have the foldername and an underscore followed by the number of sampled used. Subfolders without files will be skipped. After the file is generated it will be automatically saved. You can get a pop up dialog asking you, if you want to open the saved file. Select no to save RAM.

Some warnings 💀😁

  • Run the script without any files open.
  • If you have input subfolders with identical names and numbers of files inside the output files will be overwritten.
  • If you have files inside your folders that Sound Forge cannot open, the script will fail.
  • The script will fail if you mix mono and stereo files.
  • The script can fail if you have corrupted audio files.
  • Do not load files that already have markers inside or they will be copied and create a mess.
  • Do not mix audio files with different sample rates and bit depths.
  • Do not run this script on millions of files. I was able to crash Sound Forge multiple times. 😁

I have one more thing. I tried to find where in the script you specify the output file name and didn't even understand it unfortunately. What I would like it that the output file name contains all folder names (not the top folder that I specify as a root) separated by a "-" sign.

The reason is that many of the subfolders that contain wav files are named the same, but the paths are of course unique.

I'd then deal with removing redundant parts in the file names, that, I know how to do.

If you don't have time for that I will understand, just let me know. In any case, I wish I could buy you a coffee! Maybe there's a way?

amaury-groc wrote on 3/25/2022, 1:15 PM

Ah, I see now the line where you create the file name here:

    if(combinedFile != null)
    {
        combinedFile.SaveAs(outputFolder + @"\" + di.Name + "_" + fileCounter + ".wav", combinedFile.SaveFormat.Guid, "Default Template", RenderOptions.WaitForDoneOrCancel);        
        Application.DoEvents();
        combinedFile.Close(CloseOptions.SaveChanges);    

So, I'm looking for a way to add each directory name in the path here (it's OK if the root directory is included, it would be easy to batch remove later)

Also, seeing that code, I understand it would use a "default template" for saving the file (sample rate, bit depth) right? Is that specified as the one I use when saving a wav file in Sound Forge?

SP. wrote on 3/25/2022, 6:20 PM

@amaury-groc I can change the naming part.

 

This is from the manual. If you want a different format I could change the SaveAs command to RenderAs

SaveAs

Save the current file with the given name and file format.

void SaveAs( Filename, FormatNameorGUID, TemplateNameOrData, RenderOptions )

Parameters

  • Filename

The full pathname to save to.

  • FormatNameorGUID

Identifies the output format. Use ISfFileHost.SaveFormat.Guid to save the file back to its original format.

  • TemplateNameOrData

Identifies the template to use with the output format. Use "Default Template" to save the file back to its original format.

  • RenderOptions

Options to control the save operation.

Remarks

If the AndClose option is used, the file will be closed after is has been saved. Otherwise the file will be re-opened after saving, and the Filename and SaveFormat properties of the current file will be changed to that of the new file. The IsModified property will also be set to false.

Use RenderAs() to save the file to a new format without these side effects.

amaury-groc wrote on 3/26/2022, 2:51 AM

Perfect to use the original format to save, I believe all samples in a given folder have the same format (they come from a serious vendor), and I can batch process later to convert all to 16bit 44.1.

 

Thanks for the offer to change the name part. I'm not trained in programming. When you propose the change, I'm ready to process the data, I've made sure that there are no files Sound Forge can't use and kept only relevant folders in my root etc..

amaury-groc wrote on 3/28/2022, 12:57 PM

@amaury-groc I can change the naming part.

I'm not sure if you've got a notification for my answer above. I would be very grateful if you guide me on how to get all folders names in the path, for the output sample name.

SP. wrote on 3/29/2022, 5:14 AM

@amaury-groc I don't think I have time to do this this week. Maybe next week.

amaury-groc wrote on 3/29/2022, 7:45 AM

@amaury-groc I don't think I have time to do this this week. Maybe next week.

No problem of course! That you consider it at all is already a great gift, and when it comes I'll be very happy.

amaury-groc wrote on 4/10/2022, 12:43 PM

I thought I wrote something, and it didn't take. I was wondering if you'd find time to look at my naming thing, just to know. No pressure and no problem of course when it's a "no". Thank you!

SP. wrote on 4/10/2022, 12:52 PM

@amaury-groc I will very likely modify the code in over the next weeks. I have some different things to do at the moment and not the time to sit down after work and change the code. And I need time to test the modified code and create test sets of audio files because my samples are all stored in folders with different names, so I don't have the same problem like you encountered. Next week are easter holidays and I'm not at home then.

If you split your samples into different chunks and process them one after another everything should work fine. I think the problem is you want to do everything in one go?

amaury-groc wrote on 4/10/2022, 1:42 PM

Thanks, understood, I'm grateful you intend to look at it.

 

My problem is not only that I want to do it all at once, it's that most folder names (the parent folders containing the samples) are often useless, and the useful name is somewhere else in the path.

 

I could do them one by one and rename the files but my goal here is to avoid too much manual work :)

SP. wrote on 4/11/2022, 7:17 AM

@amaury-groc How about batch renaming the files before batch processing them? I use this tool to rename many files https://www.bulkrenameutility.co.uk/

amaury-groc wrote on 4/11/2022, 8:49 AM

Thanks for the suggestion @SP..

 

I may miss a part of it though. With the script, output files would be named like [ParentFolderA_12.wav, ParentFolderA_16.wav, ParentFolderB_48.wav, etc...], If I'd want to rename files after running the script, the file names don't include more information than the parent folder's name (in the example, 2 files are named ParentFolderA), so I'd not be able to batch rename them meaningfully.

 

That's the reason why I'd like the files to have names containing all folder names. Then, I would use a batch renamer to remove irrelevant parts of the names.

 

Let me know if I missed something in the suggestion :)

SP. wrote on 4/11/2022, 9:27 AM

@amaury-groc Ahh, yes, you are correct. I forgot that the script combines the single files into one. In this case changing the filename to something different before batch converting is, of course, useless.

 

rraud wrote on 4/11/2022, 9:44 AM

How about a script for renaming the file/regions? That might make the segments easier to ID.

amaury-groc wrote on 4/11/2022, 10:03 AM

@amaury-groc Ahh, yes, you are correct. I forgot that the script combines the single files into one. In this case changing the filename to something different before batch converting is, of course, useless.

 

Indeed! And the script you wrote seems to do what I need it to do beautifully, minus giving the files a verbose file name that I can then batch rename. Great we understand one another :)

amaury-groc wrote on 4/11/2022, 10:04 AM

How about a script for renaming the file/regions? That might make the segments easier to ID.

I don't really care about the file names; the files are combined into a single sample file. The name of that resulting single file is what matters though. Thanks for suggesting!