What if your application generated sound from state instead of maintaining a WAV file for every possible variation?
Games and interactive applications are full of events.
A player collects an item.
A mission completes.
Health drops.
A warning appears.
A build succeeds.
A device enters an error state.
A message arrives.
The traditional architecture is straightforward:
Event
↓
Audio Asset
↓
Play
And for many products, that's exactly the right solution.
But sometimes sound isn't static.
Sometimes it depends on runtime state.
In those cases, a growing library of audio files can become increasingly difficult to maintain.
Instead of:
warning-1.wav
warning-2.wav
warning-3.wav
warning-4.wav
what if the application could derive the cue from the state itself?
That's where SoundScript becomes interesting.
Application State
↓
Musical Rules
↓
SoundScript
↓
WAV / MIDI
Rather than selecting every sound from an asset library, your application generates controlled musical behaviour from data.
Let's build one.
Install SoundScript
SoundScript 13 targets .NET 10.
dotnet add package SoundScript --version 13.0.0
We'll start with a small set of application events.
Define Event Cues
Imagine a game or desktop application supports these events:
- Collect
- Achievement
- Warning
- Failure
Create a simple model:
record AudioCue(
int Tempo,
string Dynamic,
string Motif);
Now define your musical rules:
var cues =
new Dictionary<string, AudioCue>
{
["collect"] =
new(
132,
"mf",
"C4 e E4 e G4 q"),
["achievement"] =
new(
120,
"f",
"C4 e E4 e G4 e C5 q"),
["warning"] =
new(
104,
"f",
"A3 e A3 e E4 q A3 e"),
["failure"] =
new(
72,
"ff",
"C3 e C3 e G2 q C3 e")
};
Notice what the application owns:
- Tempo
- Dynamics
- Musical motifs
Not:
- Audio samples
- DSP code
- MIDI event streams
- Waveform generation
The application describes intent.
Turn an Event into Audio
Choose an event:
var cue =
cues["achievement"];
Build a SoundScript program:
var source = $"""
tempo {cue.Tempo}
track eventCue {{
instrument piano
{cue.Dynamic}
{cue.Motif}
}}
""";
Render it:
using SoundScript;
var wav =
SoundScriptEngine
.Compile(source)
.RenderWave();
File.WriteAllBytes(
"achievement.wav",
wav);
The entire pipeline becomes:
Achievement Event
↓
Lookup Musical Rule
↓
SoundScript
↓
WAV
Why Not Just Use WAV Files?
A fair question.
If your product genuinely has four final sounds:
collect.wav
achievement.wav
warning.wav
failure.wav
then static assets are probably simpler.
Procedural generation becomes more compelling when runtime state influences the cue.
For example:
Warning
+
Severity 1
may not need to sound identical to:
Warning
+
Severity 4
Instead of storing:
warning-1.wav
warning-2.wav
warning-3.wav
warning-4.wav
you can derive variation from application state.
Let Severity Control Dynamics
Suppose severity ranges from 1 to 4.
Map it into musical dynamics:
var dynamic =
severity switch
{
1 => "mp",
2 => "mf",
3 => "f",
_ => "ff"
};
Conceptually:
Severity 1 → mp
Severity 2 → mf
Severity 3 → f
Severity 4 → ff
The business meaning remains entirely inside the application.
SoundScript simply renders the resulting musical behaviour.
Let Progression Influence Tempo
Games often evolve over time.
A simple rule:
var tempo =
80 + level * 4;
Produces:
Level 1 → Tempo 84
Level 5 → Tempo 100
Level 10 → Tempo 120
Your cue can evolve naturally without requiring dozens of manually authored audio assets.
Let State Choose the Musical Motif
You can also map application state directly into motifs.
var motif =
status switch
{
"healthy" =>
"C4 e E4 e G4 q",
"degraded" =>
"A3 e E4 e A3 q",
"critical" =>
"C3 e C3 e G2 q",
_ =>
"C4 q"
};
Conceptually:
Healthy
↓
Bright Motif
Degraded
↓
Warning Motif
Critical
↓
Failure Motif
This keeps the relationship between state and sound explicit.
Combine Multiple Pieces of State
Most real applications have more than one variable.
Imagine:
record RuntimeState(
string Status,
int Severity,
int Level);
Now derive each musical property independently.
var tempo =
80 + state.Level * 4;
var dynamic =
state.Severity switch
{
1 => "mp",
2 => "mf",
3 => "f",
_ => "ff"
};
var motif =
state.Status switch
{
"healthy" =>
"C4 e E4 e G4 q",
"warning" =>
"A3 e A3 e E4 q",
"critical" =>
"C3 e C3 e G2 q",
_ =>
"C4 q"
};
Generate the final source:
var source = $"""
tempo {tempo}
track stateCue {{
instrument piano
{dynamic}
{motif}
}}
""";
Render:
var wav =
SoundScriptEngine
.Compile(source)
.RenderWave();
One event can now produce meaningful variation from multiple dimensions of state.
Procedural Audio Without DSP
The term procedural audio often makes developers think about:
- Oscillators
- Filters
- Envelopes
- Mixers
- Modulation
- Sample buffers
That's one kind of procedural audio.
SoundScript operates at a higher level.
Instead of thinking in samples, you're thinking in:
- Tempo
- Notes
- Dynamics
- Instruments
- Motifs
- Musical phrases
The question becomes:
What should this state sound like?
rather than:
How do I generate every sample?
Static vs Procedural Architectures
Static Assets
Event
↓
Asset Key
↓
WAV
↓
Play
Advantages
- Simple
- Predictable
- Easy to preview
- Production-friendly
Trade-off
More variations typically require more assets.
Procedural Audio
Event + State
↓
Rules
↓
Musical Source
↓
Render
↓
Play
Advantages
- State-driven variation
- Versionable rules
- Reproducible output
- Fewer asset variants
Trade-off
More application logic
Neither approach is universally superior.
The right choice depends on the problem.
The Best Architecture Is Often Hybrid
Most games shouldn't generate everything.
A practical hybrid might use static assets for:
- Main themes
- Voice acting
- Hero moments
- Cinematics
- Signature character sounds
And procedural generation for:
- Status cues
- Notifications
- Temporary UI sounds
- Alerts
- Monitoring events
- Prototype content
Hand Crafted Audio
+
Procedural Utility Audio
The two approaches complement each other well.
Cache Generated Audio
If the same state appears repeatedly, avoid rendering identical output over and over.
Build a cache key:
var key =
$"{eventName}:{severity}:{level}";
Check the cache:
if (cache.TryGetValue(
key,
out var existing))
{
return existing;
}
Otherwise render:
var wav =
SoundScriptEngine
.Compile(source)
.RenderWave();
cache[key] = wav;
return wav;
The flow becomes:
Event
↓
Build Key
↓
Cache?
↙ ↘
Hit Miss
↓ ↓
Play Render
↓
Cache
↓
Play
Deterministic generation makes caching extremely natural.
Procedural Doesn't Have to Mean Runtime
Another pattern is build-time generation.
Imagine your game supports:
3 Statuses
×
4 Severity Levels
×
3 Environments
That's:
36 Variations
A build step could generate:
success-1-dev.wav
success-2-dev.wav
success-3-dev.wav
warning-1-prod.wav
warning-2-prod.wav
failure-4-prod.wav
...
Runtime remains:
State
↓
Lookup
↓
Play
But the assets were generated from version-controlled rules.
This hybrid approach is often very attractive.
Example: Health Warning System
Imagine a player currently has:
var health = 24;
Map health into severity:
var severity =
health switch
{
> 70 => 1,
> 40 => 2,
> 20 => 3,
_ => 4
};
Then dynamics:
var dynamic =
severity switch
{
1 => "mp",
2 => "mf",
3 => "f",
_ => "ff"
};
Result:
Health 90 → Quiet Cue
Health 50 → Medium Cue
Health 24 → Strong Cue
Health 8 → Very Strong Cue
The gameplay state now influences sound directly.
Example: Achievement Rarity
Suppose achievements have rarity levels:
Common
Rare
Epic
Legendary
Map them into different motifs:
var motif =
rarity switch
{
"common" =>
"C4 e E4 q",
"rare" =>
"C4 e E4 e G4 q",
"epic" =>
"C4 e E4 e G4 e C5 q",
"legendary" =>
"C4 e E4 e G4 e C5 e E5 h",
_ =>
"C4 q"
};
Now the musical phrase itself communicates rarity.
Rarity
↓
Known Rule
↓
Known Musical Behaviour
No AI required.
No randomness required.
Example: Monitoring Applications
The same concept works outside games.
Suppose a monitoring system tracks:
Healthy
Degraded
Critical
Map them into motifs:
var motif =
status switch
{
"healthy" =>
"C4 e E4 e G4 q",
"degraded" =>
"A3 e A3 e E4 q",
"critical" =>
"C3 e C3 e G2 q",
_ =>
"C4 q"
};
Now system health can have an auditory representation.
Not as a replacement for dashboards.
As an additional information channel.
Procedural Rules Are Reviewable
Suppose you modify a warning cue:
- A3 e A3 e E4 q
+ A3 e C4 e E4 e A4 q
That's reviewable in Git.
Compare that to:
warning.wav changed
where the most detailed review may simply be:
binary file modified
Developer-owned audio behaviour benefits from being represented as text.
Generated Audio Can Be Tested
Deterministic generation enables automated testing.
var first =
RenderEventCue(
"warning",
severity: 3,
level: 8);
var second =
RenderEventCue(
"warning",
severity: 3,
level: 8);
Console.WriteLine(
first.AsSpan()
.SequenceEqual(second));
Or generate a hash:
using System.Security.Cryptography;
var hash =
Convert.ToHexString(
SHA256.HashData(first));
Console.WriteLine(hash);
Your audio behaviour becomes part of your testable system.
A Reusable Renderer
You can wrap the entire approach into a helper:
using SoundScript;
static byte[] RenderEventCue(
string eventName,
int severity,
int level)
{
var dynamic =
severity switch
{
1 => "mp",
2 => "mf",
3 => "f",
_ => "ff"
};
var motif =
eventName switch
{
"collect" =>
"C4 e E4 e G4 q",
"achievement" =>
"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 tempo =
80 + level * 4;
var source = $"""
tempo {tempo}
track eventCue {{
instrument piano
{dynamic}
{motif}
}}
""";
return SoundScriptEngine
.Compile(source)
.RenderWave();
}
Usage:
var wav =
RenderEventCue(
"warning",
severity: 3,
level: 8);
File.WriteAllBytes(
"warning.wav",
wav);
The application interface remains simple and expressive.
What the Application Owns
The application owns:
- Event semantics
- State
- Severity rules
- Progression systems
- Cache strategy
- Playback decisions
SoundScript owns:
- Musical representation
- Compilation
- Rendering
- WAV generation
- MIDI generation
That's a healthy separation.
Where Procedural Audio Fits Best
🎮 Game Prototypes
Generate useful audio before final sound design exists.
🔔 Application Notifications
Create state-driven event cues.
🚦 Monitoring Systems
Sonify changing system health.
♿ Accessibility
Provide additional non-visual feedback.
🏭 Industrial Applications
Represent process and machine states.
🧪 Automated Testing
Generate deterministic audio fixtures.
🎓 Education
Demonstrate state-to-music mappings.
🛠 Developer Tooling
Generate build and status cues programmatically.
When Static Assets Are Still Better
Static assets remain the right choice when you need:
- Professional sound design
- Recorded instruments
- Voice acting
- Branded audio
- Cinematic sequences
- Ambient environments
- Mastered production quality
Procedural audio doesn't replace professionally authored media.
It complements it.
One Simple Rule
Ask a single question:
Does the sound meaningfully depend on application state?
If the answer is:
No
Use a static asset.
If the answer is:
Yes
Consider procedural generation.
That's usually the right architectural test.
The Bigger Idea
Applications already generate many outputs from state.
State
↓
UI
State
↓
Notification
State
↓
API Response
State
↓
Report
SoundScript adds another possibility:
State
↓
Musical Behaviour
↓
Audio
That's the core idea behind procedural application audio.
Try It Yourself
Install SoundScript:
dotnet add package SoundScript --version 13.0.0
Start with:
var source = """
tempo 120
track eventCue {
instrument piano
mf
C4 e
E4 e
G4 q
}
""";
Render it:
var wav =
SoundScriptEngine
.Compile(source)
.RenderWave();
File.WriteAllBytes(
"cue.wav",
wav);
Then make the source depend on:
- Event
- Severity
- Level
- Status
Change one variable at a time and observe how the output evolves.
The workflow becomes:
Application State
↓
Derive Musical Intent
↓
Compile
↓
Render
↓
Cache or Store
↓
Play
That's procedural audio from a developer's perspective.
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 Background Music from JSON in .NET
In This Series
- Generate MIDI and WAV in a Few Lines of C#
- Deterministic Audio for Automated Testing in .NET
- Text-to-Music with SoundScript: Deterministic Composition Instead of Prompting
- Generate Background Music from JSON in .NET
- Procedural Game and Application Audio in .NET
SoundScript
Write audio and media like code.
Top comments (0)