You'll be shocked to find out that there isn't one already available. I wrote a couple of different batch merge scripts. This one I call a Q and A merge. It takes all of the files in a user selected folder and then merges them together in a new file, with 500 milliseconds of silence between each file and then saves this Q/A file to the parent directory of the selected folder. It could be adapted to do just a simple merge with no silence.
using System;
using System.IO;
using System.Windows.Forms;
using SoundForge;
using System.Collections;
using System.Collections.Generic;
using System.Text;
public class EntryPoint
{
//Method to evaluate and sort our string array of filenames
public class AlphanumComparatorFast : IComparer
{
public int Compare(object x, object y)
{
string s1 = x as string;
if (s1 == null)
{
return 0;
}
string s2 = y as string;
if (s2 == null)
{
return 0;
}
int len1 = s1.Length;
int len2 = s2.Length;
int marker1 = 0;
int marker2 = 0;
// Walk through two the strings with two markers.
while (marker1 < len1 && marker2 < len2)
{
char ch1 = s1[marker1];
char ch2 = s2[marker2];
// Some buffers we can build up characters in for each chunk.
char[] space1 = new char[len1];
int loc1 = 0;
char[] space2 = new char[len2];
int loc2 = 0;
// Walk through all following characters that are digits or
// characters in BOTH strings starting at the appropriate marker.
// Collect char arrays.
do
{
space1[loc1++] = ch1;
marker1++;
if (marker1 < len1)
{
ch1 = s1[marker1];
}
else
{
break;
}
} while (char.IsDigit(ch1) == char.IsDigit(space1[0]));
do
{
space2[loc2++] = ch2;
marker2++;
if (marker2 < len2)
{
ch2 = s2[marker2];
}
else
{
break;
}
} while (char.IsDigit(ch2) == char.IsDigit(space2[0]));
// If we have collected numbers, compare them numerically.
// Otherwise, if we have strings, compare them alphabetically.
string str1 = new string(space1);
string str2 = new string(space2);
int result;
if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
{
int thisNumericChunk = int.Parse(str1);
int thatNumericChunk = int.Parse(str2);
result = thisNumericChunk.CompareTo(thatNumericChunk);
}
else
{
result = str1.CompareTo(str2);
}
if (result != 0)
{
return result;
}
}
return len1 - len2;
}
}
public void Begin(IScriptableApp app)
{
//Gets the folder with the merge files and counts them to int fileCount
//Then sets the loopCount by dividing the fileCount by the mergeNum
//Then takes care of remainder for final paste-to with leftOver
string strFolder = SfHelpers.ChooseDirectory("Choose a folder to process files from", @"S:\Audio");
//gets our output directory by going up one folder from the files.
DirectoryInfo dirNew = new DirectoryInfo(strFolder);
DirectoryInfo parFolder = dirNew.Parent;
//Creates an array to hold filenames for the paste operation
string[] fileHolder;
fileHolder = Directory.GetFiles(strFolder, "*.wav");
//Let's sort the array so our files are pasted in the correct order
Array.Sort(fileHolder, new AlphanumComparatorFast());
int fileCount = fileHolder.Length;
//Sets amt of silence btw files.
double amtSilence = .5;
//Sets the target filename.
string targFileName = "_QA_File.wav";
//Here is the part where we start the Merge!
//get a reference (syncStat) for the specs of new file (mergedFile).
ISfFileHost syncStat = app.OpenFile(fileHolder[0], true, true);
if (null == syncStat)
{
DPF("Could not open {0}", fileHolder[0]);
return;
}
//creates a new file (mergedFile) for us to paste into.
ISfFileHost mergedFile = app.NewFile(syncStat.DataFormat, true);
mergedFile.UndosAreEnabled = false;
syncStat.Close(CloseOptions.DiscardChanges);
for (int x = 0; x < fileCount; x++)
{
//paste to mergedFile with fileHolder[x]
ISfFileHost temp = app.OpenFile(fileHolder[x], true, true);
temp.Markers.Clear();
mergedFile.OverwriteAudio(mergedFile.Length, 0, temp, new SfAudioSelection(temp));
//adds a marker to label each part of the mergedFile with filename
long mPos = mergedFile.Length - temp.Length;
string mName = Path.GetFileNameWithoutExtension(fileHolder[x]);
mergedFile.Markers.AddMarker(mPos, mName);
if (amtSilence != 0)
{
//Inserts requested silence (amtSilence) onto the end of mergedFile
long secToSamp = mergedFile.SecondsToPosition(amtSilence);
mergedFile.InsertSilence(mergedFile.Length, secToSamp);
}
temp.Close(CloseOptions.DiscardChanges);
}
//save merged file
string strFileName = Path.Combine(parFolder.FullName, targFileName);
DPF("The filename is {0}", strFileName);
mergedFile.SaveAs(strFileName, mergedFile.SaveFormat.Guid, "Default Template", RenderOptions.WaitForDoneOrCancel);
mergedFile.Close(CloseOptions.SaveChanges);
app.OpenFile(strFileName, false, false);
}
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
The following is a more in-depth file merging script that lets the user select the folder containing the files to be merged, How many of those files to merge at a time, where the resulting merged files will output, how much (if any) silence to put in between the files and then what output format to save the files as.
using System;
using System.IO;
using System.Windows.Forms;
using SoundForge;
using System.Collections;
using System.Collections.Generic;
public class EntryPoint
{
//Method to evaluate and sort our string array of filenames
public class AlphanumComparatorFast : IComparer
{
public int Compare(object x, object y)
{
string s1 = x as string;
if (s1 == null)
{
return 0;
}
string s2 = y as string;
if (s2 == null)
{
return 0;
}
int len1 = s1.Length;
int len2 = s2.Length;
int marker1 = 0;
int marker2 = 0;
// Walk through two the strings with two markers.
while (marker1 < len1 && marker2 < len2)
{
char ch1 = s1[marker1];
char ch2 = s2[marker2];
// Some buffers we can build up characters in for each chunk.
char[] space1 = new char[len1];
int loc1 = 0;
char[] space2 = new char[len2];
int loc2 = 0;
// Walk through all following characters that are digits or
// characters in BOTH strings starting at the appropriate marker.
// Collect char arrays.
do
{
space1[loc1++] = ch1;
marker1++;
if (marker1 < len1)
{
ch1 = s1[marker1];
}
else
{
break;
}
} while (char.IsDigit(ch1) == char.IsDigit(space1[0]));
do
{
space2[loc2++] = ch2;
marker2++;
if (marker2 < len2)
{
ch2 = s2[marker2];
}
else
{
break;
}
} while (char.IsDigit(ch2) == char.IsDigit(space2[0]));
// If we have collected numbers, compare them numerically.
// Otherwise, if we have strings, compare them alphabetically.
string str1 = new string(space1);
string str2 = new string(space2);
int result;
if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
{
int thisNumericChunk = int.Parse(str1);
int thatNumericChunk = int.Parse(str2);
result = thisNumericChunk.CompareTo(thatNumericChunk);
}
else
{
result = str1.CompareTo(str2);
}
if (result != 0)
{
return result;
}
}
return len1 - len2;
}
}
public void Begin(IScriptableApp app)
{
//Gets the folder with the merge files and counts them to int fileCount
//Then sets the loopCount by dividing the fileCount by the mergeNum
//Then takes care of remainder for final paste-to with leftOver
string strFolder = SfHelpers.ChooseDirectory("Choose a folder to process files from", @"S:\Audio");
//Creates an array to hold filenames for the paste operation
string[] fileHolder;
fileHolder = Directory.GetFiles(strFolder, "*.wav");
//Let's sort the array so our files are pasted in the correct order
Array.Sort(fileHolder, new AlphanumComparatorFast());
//Print out our Array to check
foreach (string strTest in fileHolder)
DPF(strTest);
int fileCount = fileHolder.Length;
//Gets number of files to merge at a time.
string strMergeNum = SfHelpers.WaitForInputString("How many of these " + fileCount + " files do you want per MP3?", "50");
int mergeNum = Convert.ToInt16(strMergeNum);
//Checks to make sure mergeNum is not 0. If it is, it asks for mergeNum again.
if (mergeNum == 0)
{
int mm = 0;
while (mm == 0)
{
string strErrMergeNum = SfHelpers.WaitForInputString("You must merge more than 0 files, try again", "50");
int ErrmergeNum = Convert.ToInt16(strErrMergeNum);
mm = ErrmergeNum;
}
mergeNum = mm;
return;
}
else if (fileCount < mergeNum)
mergeNum = fileCount;
//Gets amt of silence btw files.
string strAmtSilence = SfHelpers.WaitForInputString("How much silence btw files (in SECONDS) use 0 for no silence", "2");
double amtSilence = Convert.ToDouble(strAmtSilence);
// get renderer and template
int cRend = app.Renderers.Count;
string[] renderers = new string[cRend];
for (int ix = 0; ix < cRend; ix++)
renderers[ix] = app.Renderers[ix].Name;
object item = SfHelpers.ChooseItemFromList("Select destination file type:", renderers);
ISfRenderer rend = app.FindRenderer(item.ToString(), null);
if (null == rend)
DPF("No renderer.");
ISfGenericPreset tpl = rend.ChooseTemplate(IntPtr.Zero, 0);
if (null == tpl)
DPF("No template.");
//Gets the target folder and the prefix for the merged filenames.
string targFolder = SfHelpers.ChooseDirectory("Where do you want to save the merged " + rend.Extension + " file?", strFolder);
string targFileName = SfHelpers.WaitForInputString("Enter the prefix for your " + rend.Extension + " filename (i.e. PR# or Preview", "Preview_");
//Math to determine the number of loops to go through
int loopCount = 0;
int leftOver = 0;
if (fileCount == mergeNum)
loopCount = 1;
else
{
loopCount = (int)(fileCount / mergeNum);
leftOver = fileCount - (mergeNum * loopCount);
}
//Here is the part where we start the Merge!
int carryO = 0;
int y = 0;
int z = mergeNum;
for (int x = 0; x < loopCount; x++)
{
//get a reference (syncStat) for the specs of new file (mergedFile).
ISfFileHost syncStat = app.OpenFile(fileHolder[0], true, true);
if (null == syncStat)
{
DPF("Could not open {0}", fileHolder[0]);
return;
}
//creates a new file (mergedFile) for us to paste into.
ISfFileHost mergedFile = app.NewFile(syncStat.DataFormat, true);
while (y < z)
{
//paste to mergedFile with fileHolder[y]
ISfFileHost temp = app.OpenFile(fileHolder[y], true, false);
mergedFile.OverwriteAudio(mergedFile.Length, 0, temp, new SfAudioSelection(temp));
DPF(fileHolder[y]);
//adds a marker to label each part of the mergedFile with filename
long mPos = mergedFile.Length - temp.Length;
string mName = Path.GetFileNameWithoutExtension(fileHolder[y]);
mergedFile.Markers.AddMarker(mPos, mName);
if (amtSilence != 0)
{
//Inserts requested silence (amtSilence) onto the end of mergedFile
long secToSamp = mergedFile.SecondsToPosition(amtSilence);
mergedFile.InsertSilence(mergedFile.Length, secToSamp);
}
temp.Close(CloseOptions.DiscardChanges);
y++;
}
syncStat.Close(CloseOptions.DiscardChanges);
//save merged file
string fileNumber = x.ToString();
string fileName1 = (targFileName + fileNumber + "." + rend.Extension);
string strFileName = Path.Combine(targFolder, fileName1);
DPF("The filename is {0}", strFileName);
mergedFile.RenderAs(strFileName, null, tpl, null, RenderOptions.AndClose);
z = z + mergeNum;
carryO = x + 1;
}
//Next section deals with leftover files
if (leftOver != 0)
{
//get a reference (syncStat) for the specs of new file (mergedFile).
ISfFileHost lastsyncStat = app.OpenFile(fileHolder[0], true, true);
if (null == lastsyncStat)
{
DPF("Could not open {0}", fileHolder[0]);
return;
}
ISfFileHost lastmergedFile = app.NewFile(lastsyncStat.DataFormat, true);
for (int ii = leftOver; ii > 0; ii--)
{
//paste to mergedFile with fileHolder[y]
ISfFileHost temp = app.OpenFile(fileHolder[y], true, false);
lastmergedFile.OverwriteAudio(lastmergedFile.Length, 0, temp, new SfAudioSelection(temp));
DPF(fileHolder[y]);
//adds a marker to label each part of the mergedFile with filename
long mPos = lastmergedFile.Length - temp.Length;
string lName = Path.GetFileNameWithoutExtension(fileHolder[y]);
lastmergedFile.Markers.AddMarker(mPos, lName);
if (amtSilence != 0)
{
//Inserts requested silence (amtSilence) onto the end of mergedFile
long secToSamp = lastmergedFile.SecondsToPosition(amtSilence);
lastmergedFile.InsertSilence(lastmergedFile.Length, secToSamp);
}
temp.Close(CloseOptions.DiscardChanges);
y++;
}
lastsyncStat.Close(CloseOptions.DiscardChanges);
//save merged file
string lastfileNumber = carryO.ToString();
string lastfileName1 = (targFileName + lastfileNumber + "." + rend.Extension);
string laststrFileName = Path.Combine(targFolder, lastfileName1);
DPF("The filename is {0}", laststrFileName);
lastmergedFile.RenderAs(laststrFileName, null, tpl, null, RenderOptions.AndClose);
}
}
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, 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)); }
} //EntryPoint
Hopefully you can alter one of these to suit your needs. I'm hoping that Sony implements a merge function in a later version of SF.
Rob.