Skip to main content

Command Palette

Search for a command to run...

Generate Background Music from JSON in .NET

Updated
โ€ข11 min readโ€ขView as Markdown
A
Automation Architect and .NET developer with 16 years of software engineering and quality engineering experience. Creator of SoundScript โ€” an open-source, deterministic language and .NET toolkit for generating MIDI, WAV, vocals, temporal visuals, and synchronized audio/video from code. I write about .NET, developer tooling, deterministic media generation, test automation, procedural audio, and software engineering. ๐ŸŒ soundscript.net ๐ŸŒ dharangutti.in ๐Ÿ’ป GitHub: @dharangutti

What if application data could drive sound without your application owning an entire audio engine?

Modern applications already use JSON to describe behaviour.

  • Configuration
  • User preferences
  • Application state
  • Notifications
  • Workflow status
  • Environment settings
  • Feature flags

That data typically controls things like:

JSON
  โ†“
UI

JSON
  โ†“
Feature Behaviour

JSON
  โ†“
API Configuration

JSON
  โ†“
Workflow Logic

But JSON can also drive audio.

With SoundScript, a .NET application can transform ordinary structured data into musical intent and render deterministic WAV or MIDI output.

The key idea is that JSON is not SoundScript's language.

Your application owns the JSON schema.

Your application decides what the data means.

SoundScript simply performs the rendering.

JSON
  โ†“
Application Mapping
  โ†“
SoundScript Source
  โ†“
Compile
  โ†“
WAV / MIDI

That separation turns out to be surprisingly useful.

Business data remains business data.

Audio remains programmable.

Let's build it.


Install SoundScript

SoundScript 13 targets .NET 10.

dotnet add package SoundScript --version 13.0.0

We'll also use the JSON functionality built into .NET.


Start with a Simple Configuration

Imagine an application has a configuration file like this:

{
  "name": "checkout-success",
  "tempo": 120,
  "dynamic": "mf",
  "notes": "C4 e E4 e G4 e C5 q"
}

Create a matching model:

record AudioCue(
    string Name,
    int Tempo,
    string Dynamic,
    string Notes);

Load the JSON:

using System.Text.Json;

var json =
    File.ReadAllText("cue.json");

var cue =
    JsonSerializer.Deserialize<AudioCue>(
        json)
    ?? throw new InvalidOperationException(
        "Invalid audio configuration.");

At this stage there's nothing audio-specific happening.

The application simply has structured state.


Map JSON into SoundScript

Now transform that state into musical intent.

var source = $"""
    tempo {cue.Tempo}

    track cue {{
        instrument piano

        {cue.Dynamic}

        {cue.Notes}
    }}
    """;

Compile and render:

using SoundScript;

var compilation =
    SoundScriptEngine.Compile(source);

var wav =
    compilation.RenderWave();

var midi =
    compilation.RenderMidi();

File.WriteAllBytes(
    $"{cue.Name}.wav",
    wav);

File.WriteAllBytes(
    $"{cue.Name}.mid",
    midi);

That's the entire pipeline.

cue.json
    โ†“
Deserialize
    โ†“
Application Model
    โ†“
Map to SoundScript
    โ†“
Compile
    โ†“
checkout-success.wav
checkout-success.mid

Why Not Just Store a WAV File?

A reasonable question.

For a single fixed cue:

checkout-success.wav

is often the best solution.

Static assets are:

  • Easy to preview
  • Professionally designed
  • Simple to deploy
  • Simple to maintain

You shouldn't replace static audio just because dynamic generation is possible.

The value appears when application state influences the sound.


Example: Severity-Driven Audio

Suppose the application receives:

{
  "state": "warning",
  "severity": 3
}

Instead of exposing raw audio controls, define a simple business model:

record StatusCue(
    string State,
    int Severity);

Map severity into known dynamics:

var dynamic =
    cue.Severity switch
    {
        1 => "mp",
        2 => "mf",
        3 => "f",
        _ => "ff"
    };

Map state into known musical motifs:

var motif =
    cue.State switch
    {
        "success" =>
            "C4 e E4 e G4 e C5 q",

        "warning" =>
            "A3 e A3 e E4 q A3 e",

        "failure" =>
            "C3 e C3 e G2 q C3 e",

        _ =>
            "C4 q"
    };

Create SoundScript:

var source = $"""
    tempo 110

    track status {{
        instrument piano

        {dynamic}
        {motif}
    }}
    """;

And render:

var wav =
    SoundScriptEngine
        .Compile(source)
        .RenderWave();

Notice the separation of responsibilities:

State + Severity
         โ†“
Application Rules
         โ†“
Musical Intent
         โ†“
SoundScript
         โ†“
Audio

Keep Business Logic Outside the Audio Engine

This is an important architectural boundary.

You probably don't want SoundScript deciding:

Severity 4 = Emergency

That's an application concern.

Likewise, you probably don't want your business configuration describing:

MIDI Channel 1
Velocity 112
Tick Position 480

That's audio implementation detail.

A cleaner architecture is:

Business State
      โ†“
Application Rules
      โ†“
Musical Intent
      โ†“
SoundScript
      โ†“
Audio

Each layer has a single responsibility.


Avoid Passing Arbitrary Source from JSON

A tempting approach is:

{
  "source": "tempo 120 track x { ... }"
}

followed by:

SoundScriptEngine.Compile(
    config.Source);

For trusted developer-authored configuration, that's perfectly valid.

For user data or external input, a better pattern is:

{
  "state": "warning",
  "intensity": 2
}

Then map it explicitly:

Known Input
      โ†“
Validation
      โ†“
Application Rules
      โ†“
Known Musical Structures

Rather than:

Arbitrary String
        โ†“
Compiler

The same principle applies to many configurable systems.


A More Realistic Example

Imagine a deployment monitoring system producing:

{
  "event": "deployment",
  "result": "failed",
  "severity": 4,
  "environment": "production"
}

Create a model:

record DeploymentEvent(
    string Event,
    string Result,
    int Severity,
    string Environment);

Now define rules.

Tempo

var tempo =
    data.Environment switch
    {
        "production" => 116,
        "staging" => 104,
        _ => 96
    };

Dynamic

var dynamic =
    data.Severity switch
    {
        <= 1 => "mp",
        2 => "mf",
        3 => "f",
        _ => "ff"
    };

Motif

var motif =
    data.Result switch
    {
        "success" =>
            "C4 e E4 e G4 e C5 q",

        "failed" =>
            "C3 e C3 e G2 q C3 e",

        _ =>
            "C4 q"
    };

Assemble the cue:

var source = $"""
    tempo {tempo}

    track deployment {{
        instrument piano

        {dynamic}
        {motif}
    }}
    """;

The same deployment state always produces the same intended musical behaviour.

That's one of the biggest advantages of deterministic audio.


JSON Is Control Data, Not Music

Notice the difference between these two representations.

Application state:

{
  "result": "failed",
  "severity": 4
}

Musical intent:

tempo 116

track deployment {
    instrument piano
    ff

    C3 e
    C3 e
    G2 q
    C3 e
}

They serve different purposes.

Trying to make them identical often creates unnecessary coupling.

Keeping them separate tends to produce cleaner designs.


Why Not Use an AI Music API?

Let's compare the goals.

Imagine sending:

{
  "state": "warning",
  "severity": 3
}

to a generative music system using a prompt like:

Create a short urgent notification sound for severity level 3.

That can work very well if you want creative variation.

But application audio often requires different guarantees.

You may want:

Severity 3
      โ†“
Same Rules
      โ†“
Same Output

You may want to:

  • Test it
  • Cache it
  • Hash it
  • Version it
  • Reproduce it

Deterministic generation is often a better fit for those requirements.


Comparing Audio Architectures

Static Assets

State
  โ†“
Lookup
  โ†“
Existing WAV

Advantages

  • Simple
  • Fast
  • Professional audio quality

Trade-off

Every variation usually requires another asset.


Generative Music Service

State
  โ†“
Prompt
  โ†“
Model
  โ†“
Generated Audio

Advantages

  • Creative
  • Rich output
  • Large stylistic range

Trade-off

  • Probabilistic behaviour
  • External dependencies
  • Different reproducibility model

DSP / Custom Synthesis

State
  โ†“
DSP Code
  โ†“
Oscillators
Envelopes
Mixers
  โ†“
PCM Audio

Advantages

  • Maximum control

Trade-off

  • Significant audio-engine complexity

SoundScript

State
  โ†“
Application Rules
  โ†“
Musical Intent
  โ†“
SoundScript
  โ†“
WAV / MIDI

This occupies a useful middle ground.

  • More dynamic than static assets
  • Higher level than DSP
  • More deterministic than prompt generation

User-Configurable Notification Profiles

Imagine users can select:

{
  "notificationStyle": "gentle",
  "speed": "normal"
}

Your application maps:

gentle
   โ†“
piano
mp

and:

normal
   โ†“
tempo 100

Another profile might be:

{
  "notificationStyle": "urgent",
  "speed": "fast"
}

Mapping to:

urgent
  โ†“
stronger dynamic
different motif

fast
  โ†“
tempo 132

Notice that you're exposing meaningful product concepts, not audio-engine internals.

That's generally easier for both developers and users.


Game State as Audio

Imagine:

{
  "health": 24,
  "danger": true,
  "level": 7
}

Application rules derive:

danger = true
      โ†“
warning motif

health < 30
      โ†“
stronger dynamic

level = 7
      โ†“
tempo adjustment

Result:

Game State
     โ†“
Application Rules
     โ†“
Musical State
     โ†“
Audio

This pattern is particularly useful during prototyping when sound design is still evolving.


Cache Repeated States

Not every cue needs to be rendered repeatedly.

Generate a cache key:

var key =
    $"{state}:{severity}:{tempo}";

Then:

if (cache.TryGetValue(
        key,
        out var existing))
{
    return existing;
}

Otherwise:

var wav =
    SoundScriptEngine
        .Compile(source)
        .RenderWave();

cache[key] = wav;

return wav;

The architecture becomes:

Application State
         โ†“
      Key
    โ†™     โ†˜
 Hit     Miss
  โ†“        โ†“
Reuse   Render
           โ†“
        Cache

A simple optimisation with substantial benefits.


Generate Assets at Build Time

Dynamic audio doesn't have to mean runtime rendering.

Suppose you support:

3 States
ร— 4 Severity Levels

That's only:

12 Outputs

A build pipeline could generate:

success-1.wav
success-2.wav
success-3.wav
success-4.wav

warning-1.wav
warning-2.wav
warning-3.wav
warning-4.wav

failure-1.wav
failure-2.wav
failure-3.wav
failure-4.wav

At runtime, the application simply loads files.

You keep deterministic generation while avoiding runtime rendering entirely.

This hybrid model is often very practical.


Audio Mappings Can Be Tested

Because generation is deterministic, it can participate in automated testing.

var first =
    BuildCue(config);

var second =
    BuildCue(config);

Assert.True(
    first.AsSpan()
         .SequenceEqual(second));

Or use hashes:

using System.Security.Cryptography;

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

Console.WriteLine(hash);

Your configuration-to-sound rules become testable software.


A Simple Helper

You can encapsulate the entire workflow:

using SoundScript;

static byte[] RenderCue(
    string state,
    int severity)
{
    var dynamic =
        severity switch
        {
            1 => "mp",
            2 => "mf",
            3 => "f",
            _ => "ff"
        };

    var motif =
        state switch
        {
            "success" =>
                "C4 e E4 e G4 e C5 q",

            "warning" =>
                "A3 e A3 e E4 q A3 e",

            "failure" =>
                "C3 e C3 e G2 q C3 e",

            _ =>
                "C4 q"
        };

    var source = $"""
        tempo 110

        track cue {{
            instrument piano

            {dynamic}
            {motif}
        }}
        """;

    return SoundScriptEngine
        .Compile(source)
        .RenderWave();
}

Usage:

var wav =
    RenderCue(
        "warning",
        3);

File.WriteAllBytes(
    "warning.wav",
    wav);

Your application stays focused on:

State
Severity

rather than audio implementation details.


Where This Approach Fits

๐Ÿ”” Application Notifications

Generate controlled cues from application events.

๐ŸŽฎ Games

Derive audio behaviour from game state.

๐Ÿšฆ Monitoring

Map system status into repeatable cues.

โ™ฟ Accessibility Experiments

Create structured non-visual feedback.

๐Ÿญ Industrial Applications

Represent machine and process states.

๐ŸŽ“ Education

Demonstrate how structured data can drive music.

๐Ÿ”„ CI/CD

Turn build and deployment status into deterministic audio.

๐Ÿงช Automated Testing

Generate reproducible audio fixtures.


When Not to Use It

Don't adopt this architecture simply because your application already uses JSON.

If the requirement is:

Play one professionally designed notification sound.

Then:

notification.wav

may be the ideal solution.

Dynamic generation becomes valuable when audio meaningfully depends on application state.

That's the real decision point.


The Bigger Idea

Modern software already transforms data into many outputs.

JSON
  โ†“
UI

JSON
  โ†“
HTML

JSON
  โ†“
API Behaviour

JSON
  โ†“
Workflow Execution

Programmable audio adds another possibility:

JSON
  โ†“
Application Rules
  โ†“
Musical Intent
  โ†“
Audio

The important part isn't the JSON.

The important part is that sound becomes another output of application logic.


Try It Yourself

Create:

{
  "name": "success",
  "tempo": 120,
  "dynamic": "mf",
  "notes": "C4 e E4 e G4 e C5 q"
}

Map it to SoundScript:

var source = $"""
    tempo {cue.Tempo}

    track cue {{
        instrument piano

        {cue.Dynamic}
        {cue.Notes}
    }}
    """;

Render it:

var wav =
    SoundScriptEngine
        .Compile(source)
        .RenderWave();

File.WriteAllBytes(
    $"{cue.Name}.wav",
    wav);

Then experiment.

Change:

"tempo": 120

to:

"tempo": 80

Or:

"dynamic": "mf"

to:

"dynamic": "ff"

Render again and compare the result.

The workflow is straightforward:

Configure
     โ†“
Map
     โ†“
Compile
     โ†“
Render
     โ†“
Listen

Try SoundScript

dotnet add package SoundScript --version 13.0.0

Resources


SoundScript Developer Series

Previous

Text-to-Music with SoundScript: Deterministic Composition Instead of Prompting

Coming Next

Procedural Game and Application Audio in .NET

We'll move from configuration-driven cues to event-driven procedural audio and explore when generated sound makes more sense than maintaining large collections of static assets.


SoundScript
Write audio and media like code.

๐Ÿ“ง info@dharangutti.in
๐ŸŒ https://www.dharangutti.in/