Store the parameters, not the WAV. That single decision determines whether a voice blip repository is still usable after two years, because a parameter file can be diffed, merged, reviewed, and re-rendered, while a WAV can only be stored and hoped over. Everything below — folder layout, naming, manifest, git settings, rollback — follows from treating the template as the source of truth and the rendered audio as a build artifact.
If you are still deciding what goes inside the template itself, start with the voice blip template system guide. This article assumes the template already exists and covers where it lives, how its output is named, and what has to be recorded so a rendered file can be traced back to the exact parameters that produced it.
A Folder Layout That Survives a Recast
Use an opaque character ID assigned once, and keep the display name in metadata where it can change without touching a single file path. Display names are the most common cause of broken references in a long project: a faction is renamed, an honorific changes during localization, and every path that encodes the old name becomes wrong at the same moment.
blips/
ch_0042/
manifest.json
idle/
ln_0117.wav
ln_0117.json
injured/
ln_0117.wav
ln_0117.json
ch_0043/
manifest.json
idle/
ln_0201.wav
ln_0201.json
templates/
tpl_authority_soft/
v1.json
v2.json
v3.json
The character directory is the only place the character identity appears. The state directory uses a small closed vocabulary the engine already understands — idle, injured, exhausted, transformed — so adding a state does not require a new convention. Line IDs are stable and never reused, even after a line is cut, because a reused ID silently reassigns old metadata to new audio.
Templates live in their own tree with one file per version.
This is the part most pipelines get wrong: they keep a single
template.json that is edited in place, which
means the previous version exists only in git history and
cannot be referenced by a rendered line. Versioned files make
the reference explicit — a line points at
tpl_authority_soft/v3, not at "the current
template."
Name Files by Stable ID, Keep the Version in the Metadata
The filename pattern that holds up:
ln_0117.wav
ln_0117.json
No template version in the filename. Encoding it there forces every reference in the engine, in dialogue scripts, and in localization tables to be rewritten whenever the template is bumped, which in practice means nobody bumps the template. The version belongs in the JSON beside the WAV and in the manifest.
The per-line JSON is small and contains only what is needed to reproduce or verify the file:
{
"lineId": "ln_0117",
"state": "injured",
"template": "tpl_authority_soft",
"templateVersion": 3,
"seed": 42017,
"renderer": "blipgen 1.4.2",
"sampleRate": 48000,
"bitDepth": 16,
"sha256": "9f2c...a41d",
"renderedAt": "2026-10-11T09:14:22Z"
}
Four of these fields are routinely omitted and each omission causes a specific failure. Template version omitted means a rendered line cannot be tied to the parameters that produced it. Renderer version omitted means a renderer upgrade silently changes output with no record of which lines were affected. Sample rate and bit depth omitted means a re-export at a different format produces the same filename with different content; the sample rate and format guide covers why that difference is not cosmetic. SHA-256 omitted means a hand-edited WAV is indistinguishable from a pipeline-rendered one.
The Character Manifest Is a Flat Index, Not a Document
The per-character manifest exists to answer queries quickly: which lines used template version 2, which lines were rendered by an older renderer, and which files no longer match their recorded hash. Keep it flat, one entry per line, and generate it from the per-line JSON rather than maintaining it by hand.
{
"character": "ch_0042",
"displayName": "Commander Vey",
"lines": [
{ "lineId": "ln_0117", "state": "injured", "templateVersion": 3,
"sha256": "9f2c...a41d" },
{ "lineId": "ln_0118", "state": "idle", "templateVersion": 2,
"sha256": "3b81...c07e" }
]
}
A generated manifest stays truthful because it cannot drift from the files it indexes. A hand-maintained manifest drifts the first time someone is in a hurry, and from then on it is a document describing what the project used to look like. The query that matters — "list every line still on template v2" — is a one-line filter over this file.
Deterministic Rendering, and Where It Stops Being Deterministic
Version-controlling the parameters only works if the same parameters produce the same WAV. For a blip synthesizer this holds under four conditions: the same template file, the same seed, the same renderer build, and the same output format (sample rate and bit depth). Change any one and the output changes.
It also stops holding in three specific places, and each needs a policy rather than an assumption. Floating-point arithmetic can differ across CPU architectures and library versions, so a renderer running on a different machine may not produce bit-identical output. Resampling implementations are not standardized, so a 44.1 kHz render resampled to 48 kHz by two different tools will differ. And a renderer bug fix will change output for existing templates by definition.
The practical rule: pin the renderer version in the per-line JSON, treat a renderer upgrade as a deliberate migration (re-render affected lines, diff the hashes, commit the result as a single reviewable change), and never let a renderer upgrade land silently in the middle of a milestone. A pinned version plus recorded hashes makes the migration visible; an unpinned renderer makes it invisible until a playtest.
Git Settings That Keep the Repository Usable
Two strategies work; mixing them does not.
Commit the parameters and generate WAVs in CI. The repository stays small, history stays readable, and the audio folder is a build artifact. This requires a deterministic renderer (see above) and a build step that reads the manifests and produces the WAVs. It is the cleanest option for a project that ships from CI.
Commit the WAVs with Git LFS. Choose this when the renderer is not fully deterministic or when artists need to audition files without running the renderer. LFS stores a pointer in the repository and the payload on a remote, which keeps clone times reasonable. The cost is a hard dependency on the LFS remote and a client that must be installed before the first commit; a team that forgets will commit full binaries at least once.
Either way, add a .gitattributes entry so WAVs
are routed consistently and do not fall back to plain git by
accident:
*.wav filter=lfs diff=lfs merge=lfs -text
*.json text eol=lf
For teams that need file locking on binaries, Perforce or SVN with locks is the standard alternative, at the cost of a heavier workflow. A file-locking system solves the merge problem that LFS does not; it does not solve the "which template produced this WAV" problem, which is what the JSON and manifest are for. The guide to reducing sound effect file size without losing quality matters here because smaller blips make every storage strategy cheaper, and a 40 ms mono blip has no reason to be a 400 KB file.
Write the chosen policy into the repository README with the exact day-one commands. A binary policy that lives only in the audio lead's head is not a policy.
Rolling Back to "The Build That Sounded Right"
The scenario that tests the whole scheme: a build from two weeks ago sounded correct, the current build does not, and the difference needs to be identified without listening to every line.
With the scheme above, the steps are concrete:
- Check out the commit that produced the older build.
- Re-run the renderer at the pinned version recorded in each line's JSON.
- Compare manifest hashes between the old build and the current build; the diff names the specific lines and templates that changed.
- Cross-reference the changed templates against git history to read what was edited and by whom.
If step 3 produces a list of line IDs, the scheme works. If it produces a wall of binary file changes with no parameter history behind them, the repository is storing audio but not the reasons for the audio, and the next voice mismatch will be just as hard to explain as the last one.
Run this drill once before a milestone, not after. The cost of the folder layout, the per-line JSON, the generated manifest, and the pinned renderer is a few days at the start of a project. The cost of skipping them is paid every time a character's voice changes and nobody can say why.