DEV Community

Anup Jayant Dharangutti
Anup Jayant Dharangutti

Posted on

Deterministic Audio Fixtures for Automated Testing in .NET

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Treat Audio Like Generated Test Data

Instead of this:

fixture.wav
Enter fullscreen mode Exit fullscreen mode

your workflow becomes:

Test Intent
      ↓
SoundScript Source
      ↓
Compile
      ↓
WAV / MIDI Fixture
      ↓
System Under Test
Enter fullscreen mode Exit fullscreen mode

This is the same pattern developers already use elsewhere.

Schema
   ↓
Generated Client

Template
   ↓
Document

Source Code
   ↓
Executable

SoundScript
   ↓
Audio Fixture
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Or make it a test:

Assert.True(
    first.AsSpan().SequenceEqual(second));
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Conceptually:

SoundScript Source
        ↓
    Render WAV
        ↓
      SHA-256
        ↓
Expected Hash?
    ↙       ↘
 PASS   Investigate
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then load them:

using SoundScript;

var compilation =
    SoundScriptEngine.CompileFile(
        "tests/fixtures/notification.ss");

var wav =
    compilation.RenderWave();
Enter fullscreen mode Exit fullscreen mode

Now audio changes show up naturally in pull requests.

Instead of:

Binary files differ
Enter fullscreen mode Exit fullscreen mode

you review:

-tempo 100
+tempo 120

-track notification {
-    C4 q E4 q
+track notification {
+    C4 e
+    E4 e
+    G4 q
}
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

The same idea applies to MIDI:

var firstMidi =
    compilation.RenderMidi();

var secondMidi =
    compilation.RenderMidi();

Assert.True(
    firstMidi.AsSpan()
        .SequenceEqual(secondMidi));
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Conceptually:

tempo 120

track passed {
    mf

    G4 e
    G4 e
    G4 e
    G4 e
}

track failed {
    f

    C3 e
    C3 e
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

you have:

human-readable source
        ↓
deterministic build
        ↓
binary artifact
Enter fullscreen mode Exit fullscreen mode

Developers already embrace this model everywhere else:

Source Code → Executable

Schema → Client

Template → Document

Build Script → Package

SoundScript → Audio
Enter fullscreen mode Exit fullscreen mode

SoundScript applies the same engineering principle to sound.


Try It Yourself

Install SoundScript:

dotnet add package SoundScript --version 13.0.0
Enter fullscreen mode Exit fullscreen mode

Create a simple fixture:

tempo 120

track fixture {
    instrument piano
    mf

    C4 e
    E4 e
    G4 q
}
Enter fullscreen mode Exit fullscreen mode

Render twice:

var compilation =
    SoundScriptEngine.Compile(source);

var a =
    compilation.RenderWave();

var b =
    compilation.RenderWave();

Console.WriteLine(
    a.AsSpan().SequenceEqual(b));
Enter fullscreen mode Exit fullscreen mode

Then compute a hash:

var hash =
    Convert.ToHexString(
        SHA256.HashData(a));

Console.WriteLine(hash);
Enter fullscreen mode Exit fullscreen mode

You now have an audio fixture that can be:

Written
   ↓
Reviewed
   ↓
Generated
   ↓
Hashed
   ↓
Tested
   ↓
Regenerated
Enter fullscreen mode Exit fullscreen mode

That's a very software-engineering way to think about audio.


Try SoundScript

dotnet add package SoundScript --version 13.0.0
Enter fullscreen mode Exit fullscreen mode

Resources


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.

📧 info@dharangutti.in

🌐 https://www.dharangutti.in/

Top comments (0)