A PowerShell-script on Windows for the zeroing.
I’m sharing it as-is, with no guarantees. If you find a better approach, I’d love to read about it. (I use the PowerShell ISE.)
<#
WAV Sample Zeroing Tool (PowerShell + Embedded C#)
--------------------------------------------------
This script processes WAV audio files and zeros out a specified number
of samples at the beginning and end of the audio data.
Features:
- Supports PCM 16‑bit, 24‑bit, 32‑bit, and IEEE Float 32‑bit WAV files
- Automatically detects number of channels (mono, stereo, etc.)
- Automatically detects bit depth and calculates frame size
- Uses a dynamically generated C# class name to avoid Add-Type collisions
- Safely loads the generated type from the dynamic assembly
- Accepts "samples per channel" as input (not frames)
Notes:
- A "frame" = one sample for each channel at a given time.
Example: Stereo = 2 samples per frame (L + R).
- Zeroing frames ensures both channels stay aligned.
Date: 2026-01-23
Language: English
#>
# ---------------------------------------------------------
# Generate a unique class name to avoid Add-Type conflicts
# ---------------------------------------------------------
$unique = [Guid]::NewGuid().ToString("N")
$classname = "WavTools_$unique"
# ---------------------------------------------------------
# Embed C# code that performs the WAV processing
# ---------------------------------------------------------
Add-Type -TypeDefinition @"
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
public static class $classname
{
// Helper class to store WAV chunk metadata
public class ChunkInfo
{
public string Id;
public long Offset;
public long Size;
}
// Main processing function
public static void ZeroSamplesToNewFile(string inputPath, string outputPath, int samplesStart, int samplesEnd)
{
if (!File.Exists(inputPath))
throw new FileNotFoundException("Input file not found.", inputPath);
if (samplesStart < 0 || samplesEnd < 0)
throw new ArgumentException("Sample values must be >= 0.");
byte[] riffHeader = new byte[12];
using (FileStream fsIn = new FileStream(inputPath, FileMode.Open, FileAccess.Read))
using (BinaryReader br = new BinaryReader(fsIn))
{
// ---------------------------------------------------------
// Validate RIFF/WAVE header
// ---------------------------------------------------------
if (fsIn.Read(riffHeader, 0, 12) != 12)
throw new InvalidDataException("File too short for RIFF header.");
if (Encoding.ASCII.GetString(riffHeader, 0, 4) != "RIFF")
throw new InvalidDataException("Not a RIFF file.");
if (Encoding.ASCII.GetString(riffHeader, 8, 4) != "WAVE")
throw new InvalidDataException("Not a WAVE file.");
// ---------------------------------------------------------
// Read all chunks (fmt, data, etc.)
// ---------------------------------------------------------
List<ChunkInfo> chunks = new List<ChunkInfo>();
while (fsIn.Position + 8 <= fsIn.Length)
{
byte[] idBytes = br.ReadBytes(4);
byte[] sizeBytes = br.ReadBytes(4);
if (idBytes.Length < 4 || sizeBytes.Length < 4)
break;
string id = Encoding.ASCII.GetString(idBytes);
int size = BitConverter.ToInt32(sizeBytes, 0);
long chunkStart = fsIn.Position;
chunks.Add(new ChunkInfo
{
Id = id,
Offset = chunkStart,
Size = size
});
long nextPos = chunkStart + size;
if (nextPos > fsIn.Length)
break;
fsIn.Seek(size, SeekOrigin.Current);
}
// ---------------------------------------------------------
// Locate fmt chunk
// ---------------------------------------------------------
ChunkInfo fmtChunk = chunks.Find(c => c.Id == "fmt ");
if (fmtChunk == null)
throw new InvalidDataException("fmt chunk not found.");
fsIn.Seek(fmtChunk.Offset, SeekOrigin.Begin);
byte[] fmtData = br.ReadBytes((int)fmtChunk.Size);
ushort audioFormat = BitConverter.ToUInt16(fmtData, 0);
ushort numChannels = BitConverter.ToUInt16(fmtData, 2);
uint sampleRate = BitConverter.ToUInt32(fmtData, 4);
ushort blockAlign = BitConverter.ToUInt16(fmtData, 12);
ushort bitsPerSample = BitConverter.ToUInt16(fmtData, 14);
int bytesPerSample = bitsPerSample / 8;
int bytesPerFrame = bytesPerSample * numChannels;
// ---------------------------------------------------------
// Convert "samples per channel" → "frames"
// (1 frame = all channels at a given time)
// ---------------------------------------------------------
int framesStart = samplesStart;
int framesEnd = samplesEnd;
// ---------------------------------------------------------
// Locate data chunk
// ---------------------------------------------------------
ChunkInfo dataChunk = chunks.Find(c => c.Id == "data");
if (dataChunk == null)
throw new InvalidDataException("data chunk not found.");
long dataStart = dataChunk.Offset;
long dataSize = dataChunk.Size;
long bytesStart = (long)framesStart * bytesPerFrame;
long bytesEnd = (long)framesEnd * bytesPerFrame;
if (dataSize < bytesStart + bytesEnd)
throw new InvalidDataException("Audio data too short for requested zeroing.");
// ---------------------------------------------------------
// Create output file
// ---------------------------------------------------------
using (FileStream fsOut = new FileStream(outputPath, FileMode.Create, FileAccess.Write))
using (BinaryWriter bwOut = new BinaryWriter(fsOut))
{
fsIn.Seek(0, SeekOrigin.Begin);
byte[] buffer = new byte[8192];
// Copy everything before the data chunk
long copyUntil = dataStart - 8; // minus header size
long remaining = copyUntil;
while (remaining > 0)
{
int toRead = (int)Math.Min(buffer.Length, remaining);
int read = fsIn.Read(buffer, 0, toRead);
if (read == 0) break;
bwOut.Write(buffer, 0, read);
remaining -= read;
}
// Copy data chunk header
fsIn.Seek(dataStart - 8, SeekOrigin.Begin);
bwOut.Write(br.ReadBytes(8));
// ---------------------------------------------------------
// Write zeroed frames at the beginning
// ---------------------------------------------------------
byte[] zeroFrame = new byte[bytesPerFrame];
for (int i = 0; i < framesStart; i++)
bwOut.Write(zeroFrame);
// ---------------------------------------------------------
// Copy middle section (untouched audio)
// ---------------------------------------------------------
long middleStart = dataStart + bytesStart;
long middleEnd = dataStart + dataSize - bytesEnd;
long middleLength = middleEnd - middleStart;
fsIn.Seek(middleStart, SeekOrigin.Begin);
remaining = middleLength;
while (remaining > 0)
{
int toRead = (int)Math.Min(buffer.Length, remaining);
int read = fsIn.Read(buffer, 0, toRead);
if (read == 0) break;
bwOut.Write(buffer, 0, read);
remaining -= read;
}
// ---------------------------------------------------------
// Write zeroed frames at the end
// ---------------------------------------------------------
for (int i = 0; i < framesEnd; i++)
bwOut.Write(zeroFrame);
// ---------------------------------------------------------
// Copy any chunks after the data chunk
// ---------------------------------------------------------
long afterData = dataStart + dataSize;
fsIn.Seek(afterData, SeekOrigin.Begin);
while (fsIn.Position < fsIn.Length)
{
int read = fsIn.Read(buffer, 0, buffer.Length);
if (read == 0) break;
bwOut.Write(buffer, 0, read);
}
}
}
}
}
"@
# ---------------------------------------------------------
# PowerShell section: Process all WAV files in a folder
# ---------------------------------------------------------
$folder = 'I:\xxx'
$outputFolder = Join-Path $folder 'Output'
if (-not (Test-Path $outputFolder)) {
New-Item -ItemType Directory -Path $outputFolder | Out-Null
}
# User input: number of samples per channel to zero
$samplesStart = 80
$samplesEnd = 100
$files = Get-ChildItem -Path $folder -Filter *.wav
foreach ($f in $files) {
$out = Join-Path $outputFolder ($f.BaseName + '_mod.wav')
Write-Host "Processing: $out"
try {
# ---------------------------------------------------------
# Locate the dynamically generated assembly containing the class
# ---------------------------------------------------------
$asm = [AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object { $_.GetTypes().Name -contains $classname }
if (-not $asm) {
throw "Could not locate assembly for type $classname."
}
# Retrieve the type from the assembly
$type = $asm.GetType($classname)
if (-not $type) {
throw "Could not load type $classname."
}
# Call the static C# method
$type::ZeroSamplesToNewFile($f.FullName, $out, $samplesStart, $samplesEnd)
Write-Host " ✔ Success"
}
catch {
Write-Host " ✖ Error: $($_.Exception.Message)"
}
}
Write-Host ""
Write-Host "Done."
Write-Host "Output files are located in:"
Write-Host " $outputFolder"