Skip to content

Add Cybernetic Commander voice, timing, and mastering workflows - #4

Merged
Protonmatter merged 4 commits into
mainfrom
agent/cybernetic-commander-profile
Aug 4, 2026
Merged

Protonmatter merged 4 commits into
mainfrom
agent/cybernetic-commander-profile

Conversation

@Protonmatter

@Protonmatter Protonmatter commented Aug 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • add a separately licensed, hash-pinned Cybernetic Commander F5 voice profile with source provenance, consent boundary, deterministic reference rebuild, calibration contract, and Project-Her configuration
  • add per-line spokenText pronunciation support while preserving display text and invalidating stale content-addressed output names
  • add a strict local timed-SSML parser plus resumable phrase rendering with measured boundary padding and explicit pause accounting
  • add an experimental, evidence-producing mastering utility for dry/mastered A/B evaluation
  • add seven checked-in WAV demonstrations whose intended scripts and offline transcriptions contain no personal, speaker, or character names
  • package and validate the profile, timing tools, mastering workflow, sample audio, reference, sources, and provenance records in CI

Why

VOForge needed a reproducible way to carry accent-specific pronunciation and explicit dramatic timing without changing subtitles or treating local generated output as source truth. The profile also needed a runnable fresh-clone contract.

The mastering experiments showed that format, loudness, and true-peak compliance are not sufficient promotion gates: formant-preserving pitch/time processing can reduce intelligibility. This PR makes that limitation explicit and requires dry/mastered comparison, ASR comparison, and human listening approval.

Checked-in name-free WAV samples

The separately licensed examples/voice-profiles/cybernetic-commander/samples/ directory includes:

  • AC-789 flight path
  • API version and secure protocol
  • quarterly revenue and currency
  • conversational coffee invitation
  • daddy question
  • air-conditioner dry source
  • air-conditioner mastered A/B

Every file is hash-bound with intended text, cached Whisper tiny.en transcription, PCM measurements, known or unavailable seed status, and QA disposition. Intended scripts and offline transcriptions contain no personal, speaker, or character names. Human listening has not been completed, so this is a bounded automated screen rather than a general identity or privacy guarantee.

The mastered air-conditioner clip is deliberately retained as rejected A/B evidence: it passes PCM24, loudness, true-peak, and clipping gates but materially regresses transcription accuracy.

Mastering workflow

master_voice.py:

  • preserves the dry input and refuses overwrite unless --force is explicit
  • validates that FFmpeg provides Rubber Band, EQ, compressor, soft-clip, resampler, and EBU R128 filters
  • applies 0.93x tempo, -0.75 semitone pitch with formant preservation, documented 115 Hz/310 Hz/presence EQ, 3:1 RMS compression, and oversampled tanh saturation
  • iteratively calibrates short-form output to -16.5 LUFS within 0.05 LU and a -1.0 dBTP ceiling
  • exports 24 kHz mono PCM24 and validates the RIFF/WAVE_FORMAT_EXTENSIBLE contract before atomic publication
  • records relative paths, hashes, exact filters, FFmpeg identity, calibration attempts, measured format, LUFS, true peak, and QA requirements

The local ignored acceptance artifact measured -16.46 LUFS and -1.10 dBTP.

Licensing and safety boundary

  • the profile and audio assets are restricted to evaluation and non-commercial use
  • the repository records the provider's authorization assertion but does not claim independent verification of identity or rights
  • the profile must not be represented as a natural person's speech, endorsement, or participation
  • personal examples/audio/clone-f5-min-kang-*.wav files are not included
  • technically compliant masters remain experimental until intelligibility and human-listening gates pass

Validation

  • python -m compileall -q voforge tests examples/voice-profiles/cybernetic-commander
  • python -m ruff check examples/voice-profiles/cybernetic-commander/master_voice.py tests/test_master_voice.py
  • python -m unittest discover -s tests - 61 tests passed on Python 3.14.3
  • Python 3.12.13 full suite - 61 tests passed
  • seven sample hashes, PCM formats, zero-clip gates, and name-screening records validated offline
  • cached Whisper tiny.en transcription recorded for all seven samples
  • project-her validate --config examples/voice-profiles/cybernetic-commander/project-her.json
  • project-her plan --config examples/voice-profiles/cybernetic-commander/project-her.json
  • python -m build - source distribution and wheel built successfully
  • source distribution contains all seven screened samples and no personal-name WAVs
  • end-to-end mastering acceptance run - 24 kHz mono PCM24, -16.46 LUFS, -1.10 dBTP, both metric gates passed
  • pinned reference rebuild reproduced SHA-256 1f6e88b2961e1a8ec1221b663d26a1f1c1d88b6d78ee5be215fce48e0a41792e
  • git diff --check

Named dialogue evaluation

The separately licensed examples/voice-profiles/cybernetic-commander/dialogue/ directory now includes dry and husky-EQ A/B files for a user-supplied long-form narration. This content intentionally contains declared fictional character names and is isolated from the name-free samples/ contract.

  • both WAVs are hash-bound with byte counts, PCM metadata, pitch measurements, processing details, and zero-clip evidence
  • the raw source-film conditioning audio is not redistributed; only its filename and SHA-256 are recorded
  • the husky A/B uses reversible EQ and static level adjustment without pitch shifting, time stretching, compression, saturation, denoising, reverb, or background audio
  • cached Whisper tiny.en transcription is partial and confuses the accented Skynet; human listening and rights review remain required
  • the artifacts are explicitly marked not approved for promotion and evaluation/non-commercial only

Current local validation: manifest JSON, SHA-256, byte counts, PCM metadata, documentation targets, compileall, and 62 offline unit tests passed.

@Protonmatter
Protonmatter marked this pull request as ready for review August 3, 2026 05:22

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b9b2562ab5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread voforge/core.py
if mode == "seq":
return f"{seq:04d}"
return djb2(line["who"] + "|" + line["text"])
return djb2(line["who"] + "|" + line.get("spokenText", line["text"]))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make spoken-text hashes available to runtime players

When a hash-named line supplies spokenText, this now writes the artifact under djb2(who|spokenText), but all shipped runtime consumers still accept only the display text and compute djb2(who|text) (for example, runtime/voforge.js lines 44 and 64, with the same behavior in the Unity and Godot players). Consequently, any caller preserving the subtitle text as intended will request a filename that was never generated and fall back instead of playing the rendered audio; the runtime API or a display-to-artifact mapping must expose the new lookup key.

Useful? React with 👍 / 👎.

Comment on lines +68 to +70
fade = round(sample_rate * fade_ms / 1000)
fade_in = min(fade, leading_padding)
fade_out = min(fade, len(extracted))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Limit the release fade to trailing padding

When an F5 phrase ends with less than 20 ms of quiet material after the last active frame, fade_out is still set to the full 20 ms because it is bounded by the entire extracted phrase rather than trailing_padding. The loop therefore attenuates active phrase-final audio—potentially consonants—even though this helper promises to fade only within retained padding; bound the fade by the measured trailing padding unless modifying voiced samples is explicitly intended.

Useful? React with 👍 / 👎.

Comment on lines +154 to +158
requested_break = round(sample_rate * phrase.break_after_ms / 1000)
next_leading_padding = processed[index + 1][5] if index + 1 < len(processed) else 0
inserted_silence = max(
0, requested_break - trailing_padding - next_leading_padding
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor breaks shorter than retained boundary padding

For an accepted short break such as <break time="100ms"/>, the two phrases can retain 80 ms of trailing and leading padding each; this expression clamps inserted silence to zero but leaves roughly 160 ms between detected speech boundaries. Thus valid 0–150 ms breaks are silently lengthened instead of realizing their declared duration. The renderer should trim the retained padding as needed or reject break values it cannot honor.

Useful? React with 👍 / 👎.

Comment on lines +16 to +18
"revision": "84e5a410d9cead4de2f847e7c9369a6440bdfaca",
"source": "https://huggingface.co/SWivid/F5-TTS",
"sha256": "670900fd14e6c458b95da6e9ed317cdb20dbaf7a1c02ac06a05475a9d32b6a38",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce the declared checkpoint pin during rendering

Rendering this profile does not actually use or verify the declared revision and SHA-256: ProjectHerHarness._make_backend() passes only model.options (device here), so F5Backend constructs the default F5TTS_v1_Base checkpoint without a pinned ckpt_file or hash check. If the upstream/default cached checkpoint differs, the generated audio changes while the run summary still reports this revision and hash, invalidating the profile's reproducibility and provenance claims.

Useful? React with 👍 / 👎.

@@ -0,0 +1,8 @@
[
{
"id": "commander-systems-ready-v2-260803",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep new renders away from the immutable baseline path

With output.name set to id and the output directory set to out, this ID resolves to the exact out/commander-systems-ready-v2-260803.wav path that profile-spec.json labels as the immutable historical baseline whose inference settings are unrecovered. On a fresh clone the documented render command creates a non-equivalent file under the baseline identity, and on a machine retaining the historical file project-her render --force overwrites it; use a distinct candidate ID/path or add a hash-enforced overwrite guard.

Useful? React with 👍 / 👎.

Comment thread MANIFEST.in
Comment on lines +18 to +21
include examples/voice-profiles/cybernetic-commander/sources/generated-directive.mp3
include examples/voice-profiles/cybernetic-commander/sources/generated-night-walk.mp3
include examples/voice-profiles/cybernetic-commander/sources/source-clothes-request.mp3
include examples/voice-profiles/cybernetic-commander/sources/source-night-walk.mp3

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Exclude restricted recordings from distribution archives

These explicit includes place the user-supplied source MP3s and cloned reference WAV into the source distribution that the workflow builds and uploads, even though docs/consents/cybernetic-commander.md limits the recorded authorization to adding the recordings to this repository and requires a separate rights review for redistribution outside it. Publishing or sharing the built archive therefore redistributes the raw recordings without the required recorded review; keep them out of distributions or document that additional authorization before packaging them.

Useful? React with 👍 / 👎.

Comment on lines +99 to +102
seed = 261000 + rows.index(row) * 100 + index
text_hash = hashlib.sha256(phrase.text.encode("utf-8")).hexdigest()[:8]
part = part_dir / f"{index:02d}-{seed}-{text_hash}.wav"
if force_parts or not part.is_file():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include synthesis provenance in the part cache key

The resumable filename depends only on phrase index, seed, and an eight-character text hash. If the reference artifact, reference transcript, F5 checkpoint, or inference/mastering settings are revised, existing WAVs still satisfy part.is_file() and are reused, while the newly written manifest attributes them to the current reference and configuration. This can silently mix stale audio into a new experiment; key or validate cached parts against the complete synthesis provenance rather than text and seed alone.

Useful? React with 👍 / 👎.

Comment on lines +209 to +217
if args.dialogue_id == "all":
document = json.loads((ROOT / "timed-dialogue.json").read_text(encoding="utf-8"))
shared_backend = F5Backend(device="cpu")
for row in document["dialogues"]:
print(await render(
row["id"], force_parts=args.force_parts, backend=shared_backend
))
elif len(selected_ids) > 1:
shared_backend = F5Backend(device="cpu")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Defer shared model loading when all phrase parts are cached

For all and comma-separated selections, the CLI constructs the heavyweight CPU F5Backend before checking whether any phrase needs synthesis. A fully cached resumable run therefore still loads or downloads the F5 model and can fail for missing weights or memory even though assembling the existing WAV parts requires no backend. Preserve a lazily initialized shared backend so cached-only reassembly remains lightweight and offline.

Useful? React with 👍 / 👎.

@Protonmatter Protonmatter changed the title Add Cybernetic Commander voice profile and timed dialogue tooling Add Cybernetic Commander voice, timing, and mastering workflows Aug 3, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c9e07b50b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +387 to +391
output = output.resolve()
manifest = manifest.resolve()
if not source.is_file():
raise FileNotFoundError("input WAV does not exist: {}".format(source))
if source == output:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject manifest paths that alias either audio file

When --manifest resolves to the output path, the WAV is successfully installed at line 505 and then silently replaced with JSON by _write_json_atomic; with --force, choosing the input path similarly destroys the source audio. Since the CLI accepts an arbitrary manifest path, validate that it differs from both source and output before invoking FFmpeg.

Useful? React with 👍 / 👎.

or output_format["bitsPerSample"] != 24
):
raise RuntimeError("mastered output does not satisfy 24 kHz mono PCM24")
os.replace(str(candidate), str(output))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Publish the WAV and manifest as one guarded operation

When the manifest directory is unwritable, its creation fails, or its write runs out of space, this replacement has already published the new WAV and discarded any prior output even though master_voice then raises. With --force, the old manifest can remain beside the new, mismatched audio, defeating the recorded provenance and the function's atomic-publication contract; stage or validate the manifest before replacing the destination artifacts, and avoid leaving a partially published pair.

Useful? React with 👍 / 👎.

]:
raise ValueError("display and spoken SSML must have identical break structure")
part_dir = ROOT / "out" / "timed-parts" / dialogue_id
part_dir.mkdir(parents=True, exist_ok=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep timed renders at the profile's 24 kHz rate

On every uncached CLI render, this constructs F5Backend with its default MasteringSettings, whose delivery rate is 22,050 Hz (voforge/audio.py line 31); F5Backend.synth then resamples the model waveform to that rate before the timed assembler reads it (voforge/backends.py lines 162 and 190). Consequently the assembled candidate is 22.05 kHz rather than the profile/reference's 24 kHz, and the renderer's stated no-resampling contract is false; supply a 24 kHz mastering setting to the backend or explicitly change the profile contract.

Useful? React with 👍 / 👎.

"profile": "cybernetic-commander",
"input": {
"path": _manifest_path(source, manifest.parent),
"sha256": sha256_file(source),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bind the manifest to the input bytes actually processed

If the dry input is atomically replaced or rewritten while the potentially long FFmpeg and loudness-calibration passes are running, FFmpeg can finish processing the original open file while this late hash reads the new path contents. The resulting manifest then attributes the master to bytes that did not produce it, undermining the evidence record; hash the input before processing and verify that it is unchanged before publication.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 746d5779ff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

MANIFEST.write_text(
json.dumps(manifest, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
newline="\n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve Python 3.9 support in the reference builder

When the documented rebuild is run on Python 3.9, which the package and CI matrix explicitly support, Path.write_text() does not accept the newline argument, so this raises TypeError. Because the reference WAV has already been written at line 124, the command can leave a newly rebuilt reference beside the old manifest; write through open(..., newline="\n") or omit this Python 3.10-only argument.

Useful? React with 👍 / 👎.

@Protonmatter
Protonmatter merged commit 0ac52c8 into main Aug 4, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant