We generate JSON fixtures, images, database records, and API payloads for tests. Why not audio?
Most engineering teams already treat test data as code.
We generate:
- JSON fixtures
- Database records
- API payloads
- Configuration files
- Images
Yet audio is often handled differently.
A WAV file gets copied into a test directory and remains there as a binary asset that nobody wants to touch.
tests/
└── fixtures/
└── notification.wav
The test passes.
The build is green.
But a future developer reviewing the repository may have no idea:
- What sound does this contain?
- Which notes are being played?
- Why was it generated?
- Can it be reproduced?
- Was that binary change intentional?
In many projects, the WAV file becomes the only source of truth.
What if the source of the sound was the artifact you reviewed instead?
That's where SoundScript becomes interesting.
The Problem with Binary Audio Fixtures
Consider a pull request containing:
notification.wav changed
What exactly changed?
- Tempo?
- Pitch?
- Instrument?
- Duration?
- Volume?
- Entire musical phrase?
Without specialised tooling, the answer is usually:
"I don't know. Someone changed the WAV."
Compare that to a textual audio definition:
tempo 120
track notification {
instrument piano
mf
C4 e
E4 e
G4 q
}
Now the intent is visible.
A code reviewer can understand the change before listening to anything.
Install SoundScript
SoundScript 13 targets .NET 10.
dotnet add package SoundScript --version 13.0.0
Create a deterministic audio fixture directly from C#:
using SoundScript;
var source = """
tempo 120
track fixture {
instrument piano
C4 e
E4 e
G4 e
B4 e
D5 e
G4 e
}
""";
var compilation =
SoundScriptEngine.Compile(source);
var wav = compilation.RenderWave();
var midi = compilation.RenderMidi();
File.WriteAllBytes("fixture.wav", wav);
File.WriteAllBytes("fixture.mid", midi);
The key idea isn't that we generated a WAV file.
The important part is that the fixture now has a readable, reviewable definition:
tempo 120
track fixture {
instrument piano
C4 e
E4 e
G4 e
B4 e
D5 e
G4 e
}
Treat Audio Like Generated Test Data
Instead of this:
fixture.wav
your workflow becomes:
Test Intent
↓
SoundScript Source
↓
Compile
↓
WAV / MIDI Fixture
↓
System Under Test
This is the same pattern developers already use elsewhere.
Schema
↓
Generated Client
Template
↓
Document
Source Code
↓
Executable
SoundScript
↓
Audio Fixture
Audio stops being a mysterious binary asset and becomes another generated artifact.
Verify Deterministic Rendering
One of the most useful properties for testing is reproducibility.
Render the same source twice:
var compilation =
SoundScriptEngine.Compile(source);
var first =
compilation.RenderWave();
var second =
compilation.RenderWave();
var identical =
first.AsSpan().SequenceEqual(second);
Console.WriteLine(identical);
Or make it a test:
Assert.True(
first.AsSpan().SequenceEqual(second));
If the source and rendering conditions remain unchanged, SoundScript is designed to produce repeatable output.
That means the generated audio itself can participate in automated regression testing.
Add SHA-256 Regression Checks
In many cases, you don't need to store expected byte arrays.
A hash is enough.
using System.Security.Cryptography;
var wav =
SoundScriptEngine
.Compile(source)
.RenderWave();
var hash =
Convert.ToHexString(
SHA256.HashData(wav));
Console.WriteLine(hash);
Conceptually:
SoundScript Source
↓
Render WAV
↓
SHA-256
↓
Expected Hash?
↙ ↘
PASS Investigate
Generated audio can now be validated much like any other build artifact.
Example: Testing Audio Upload Pipelines
Suppose you're testing an audio upload endpoint.
Many teams would commit a WAV fixture:
upload-test.wav
Instead, generate one during test execution.
var wav =
SoundScriptEngine
.Compile("""
tempo 100
track upload {
instrument piano
C4 q
E4 q
G4 h
}
""")
.RenderWave();
await File.WriteAllBytesAsync(
"upload-test.wav",
wav);
Now the fixture is:
✅ Generated
✅ Readable
✅ Reproducible
✅ Versionable
✅ Easy to modify
The musical intent lives inside the test itself.
Store Fixture Sources in Files
For larger test suites, keep the musical definitions separate.
tests/
└── fixtures/
├── notification.ss
├── warning.ss
└── failure.ss
Then load them:
using SoundScript;
var compilation =
SoundScriptEngine.CompileFile(
"tests/fixtures/notification.ss");
var wav =
compilation.RenderWave();
Now audio changes show up naturally in pull requests.
Instead of:
Binary files differ
you review:
-tempo 100
+tempo 120
-track notification {
- C4 q E4 q
+track notification {
+ C4 e
+ E4 e
+ G4 q
}
That's significantly easier to reason about.
A Simple Deterministic Fixture Test
A complete test might look like this:
using SoundScript;
var source = """
tempo 120
track fixture {
instrument piano
C4 e
E4 e
G4 q
}
""";
var compilation =
SoundScriptEngine.Compile(source);
var first =
compilation.RenderWave();
var second =
compilation.RenderWave();
Assert.True(
first.AsSpan().SequenceEqual(second));
The same idea applies to MIDI:
var firstMidi =
compilation.RenderMidi();
var secondMidi =
compilation.RenderMidi();
Assert.True(
firstMidi.AsSpan()
.SequenceEqual(secondMidi));
The tests aren't just verifying behaviour.
They're also verifying reproducibility.
Should Every Test Compare Exact Bytes?
No.
And that's an important distinction.
Exact byte comparisons are useful when renderer stability matters.
Many audio tests care about different guarantees:
- Is the WAV valid?
- Is the duration correct?
- Is the sample rate correct?
- Is the channel count correct?
- Does the MIDI contain expected notes?
- Is the file size reasonable?
Choose assertions based on what the test is actually promising.
Think in Testing Layers
A useful strategy is to separate validation into layers.
Level 1: Format Validation
Generated Bytes
↓
Format Parser
↓
Valid?
Is the output a valid WAV or MIDI file?
Level 2: Structural Validation
Verify characteristics such as:
- Duration
- Sample rate
- Channels
- Tracks
- Notes
- Tempo
Level 3: Deterministic Regression
Verify exact reproducibility.
Expected Hash
=
Actual Hash
SoundScript makes this level possible without forcing every test to use it.
CI/CD Gets Interesting Too
This pattern isn't limited to testing.
Build and deployment results can also be represented as sound.
Imagine:
0 failures
↓
Success motif
Few failures
↓
Warning motif
Many failures
↓
Failure motif
Conceptually:
tempo 120
track passed {
mf
G4 e
G4 e
G4 e
G4 e
}
track failed {
f
C3 e
C3 e
}
The same test results produce the same audio.
That's deterministic sonification.
Why Sonify CI?
Not as a replacement for dashboards.
As an additional information channel.
Potential use cases include:
- Long-running test environments
- Monitoring stations
- Accessibility experiments
- Engineering demonstrations
- Industrial control systems
- Background build awareness
The goal isn't to add sound everywhere.
The goal is to use sound when sound communicates useful information.
Where Deterministic Audio Fixtures Fit Best
🧪 Media Upload Testing
Generate valid WAV files during setup.
🔔 Notification Systems
Verify generated notification sounds.
🎮 Games
Create repeatable audio scenarios.
♿ Accessibility Testing
Produce controlled audio feedback.
🚦 Monitoring Applications
Validate state-to-sound mappings.
🏭 Industrial Systems
Test machine-state signalling.
🔄 CI/CD Experiments
Generate repeatable build sonification.
📦 Demo Data Generation
Create reproducible audio assets during builds.
Audio as Source Code
The bigger idea isn't WAV generation.
It's moving intent into something developers can inspect.
Instead of:
mysterious binary asset
you have:
human-readable source
↓
deterministic build
↓
binary artifact
Developers already embrace this model everywhere else:
Source Code → Executable
Schema → Client
Template → Document
Build Script → Package
SoundScript → Audio
SoundScript applies the same engineering principle to sound.
Try It Yourself
Install SoundScript:
dotnet add package SoundScript --version 13.0.0
Create a simple fixture:
tempo 120
track fixture {
instrument piano
mf
C4 e
E4 e
G4 q
}
Render twice:
var compilation =
SoundScriptEngine.Compile(source);
var a =
compilation.RenderWave();
var b =
compilation.RenderWave();
Console.WriteLine(
a.AsSpan().SequenceEqual(b));
Then compute a hash:
var hash =
Convert.ToHexString(
SHA256.HashData(a));
Console.WriteLine(hash);
You now have an audio fixture that can be:
Written
↓
Reviewed
↓
Generated
↓
Hashed
↓
Tested
↓
Regenerated
That's a very software-engineering way to think about audio.
Try SoundScript
dotnet add package SoundScript --version 13.0.0
Resources
- 🌐 Website: https://soundscript.net/
- 📦 NuGet: https://www.nuget.org/packages/SoundScript
- 💻 GitHub: https://github.com/dharangutti/sound-script
- 📚 Documentation: https://soundscript.net/doc.html?p=documentation.md
- 🚀 V13 Release: https://github.com/dharangutti/sound-script/releases/tag/v13.0.0
SoundScript Developer Series
Previous
Generate MIDI and WAV from the Same C# Source
Coming Next
Text-to-Music with SoundScript: Deterministic Composition Instead of Prompting
We'll take ordinary text, transform it into musical structures, and explore why deterministic composition differs fundamentally from probabilistic AI-generated music.
SoundScript
Write audio and media like code.
Top comments (0)