Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/project-her.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ jobs:
project-her plan --config .project-her-smoke/project-her.json
project-her validate --config examples/f5-project-her.json
project-her plan --config examples/f5-project-her.json
project-her validate --config examples/voice-profiles/cybernetic-commander/project-her.json
project-her plan --config examples/voice-profiles/cybernetic-commander/project-her.json

- name: Build distributions
if: matrix.python == '3.12'
Expand All @@ -58,6 +60,7 @@ jobs:
tar -tf dist/voforge-0.6.0.tar.gz | grep 'voforge/schemas/project-her.schema.json'
tar -tf dist/voforge-0.6.0.tar.gz | grep 'examples/project-her/project-her.json'
tar -tf dist/voforge-0.6.0.tar.gz | grep 'examples/f5-project-her.json'
tar -tf dist/voforge-0.6.0.tar.gz | grep 'examples/voice-profiles/cybernetic-commander/project-her.json'

- name: Upload distributions
if: matrix.python == '3.12'
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ dist/
vo/
*.wav
!examples/**/*.wav
examples/voice-profiles/*/out/
16 changes: 16 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ include LICENSE
recursive-include docs *.md
recursive-include voforge/schemas *.json
recursive-include examples/project-her *.json
include examples/voice-profiles/cybernetic-commander/README.md
include examples/voice-profiles/cybernetic-commander/build_reference.py
include examples/voice-profiles/cybernetic-commander/project-her.json
include examples/voice-profiles/cybernetic-commander/lines.json
include examples/voice-profiles/cybernetic-commander/profile-spec.json
include examples/voice-profiles/cybernetic-commander/calibration-lines.json
include examples/voice-profiles/cybernetic-commander/timed-dialogue.json
include examples/voice-profiles/cybernetic-commander/render_timed_dialogue.py
include examples/voice-profiles/cybernetic-commander/master_voice.py
include examples/voice-profiles/cybernetic-commander/reference-manifest.json
include examples/voice-profiles/cybernetic-commander/reference-cybernetic-commander.wav
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
Comment on lines +19 to +22

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 👍 / 👎.

recursive-include examples/voice-profiles/cybernetic-commander/samples *.md *.json *.wav
prune examples/audio
include examples/audio/NOTICE.md
include examples/audio/README.md
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ verified end-to-end (xtts on Windows-on-Arm, x86-64-emulated).
| key | | |
|---|---|---|
| `who` | required | speaker, must match the cast sheet (or `_default` exists) |
| `text` | required | the line |
| `text` | required | the display/subtitle line |
| `spokenText` | optional | pronunciation-only text sent to TTS |
| `id` | optional | stable name for `--name id` |
| `tone` | optional | tone preset name |
| `style`, `styledegree` | optional | acted emotion (azure; degrades to tone elsewhere) |
Expand All @@ -222,7 +223,10 @@ verified end-to-end (xtts on Windows-on-Arm, x86-64-emulated).
```

`_subs` is regex `[pattern, replacement]` text cleanup (typography →
phonetics). Speakers can carry a default `style`/`tone` of their own.
phonetics). Speakers can carry a default `style`/`tone` of their own. Use
per-line `spokenText` when only one performance needs an accent-specific
phonetic spelling. Content-addressed names use the spoken form so pronunciation
changes cannot silently reuse stale audio.

For Kokoro, set `voice` to a packaged Kokoro ID such as `af_heart`. Project
configuration should pin the selected model repository and voice IDs; do not
Expand Down Expand Up @@ -290,6 +294,12 @@ artifact hashes, PCM measurements, and an explicit asset notice. They use no
natural-person or game-character reference audio and are excluded from the MIT
code license because the official F5 checkpoint is CC-BY-NC-4.0.

The separately licensed
[Cybernetic Commander profile](examples/voice-profiles/cybernetic-commander/)
demonstrates a hash-pinned multi-clip reference pack. Its user-supplied voice
assets are not MIT-licensed, require the recorded authorization boundary, and
remain restricted to evaluation and non-commercial use.

## Fidelity and promotion evidence

See [`docs/VOICE_FIDELITY.md`](docs/VOICE_FIDELITY.md) for the reusable findings
Expand Down
10 changes: 10 additions & 0 deletions docs/PROJECT_HER.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ It reuses the synthetic Kokoro reference and records the official F5 checkpoint
revision, checkpoint hash, exact reference transcript, inference speed, seed,
asset notice, and consent boundary.

The Cybernetic Commander example under examples/voice-profiles is a separately
licensed multi-clip reference pack. It records the source hashes, deterministic
reference build, exact composite transcript, user authorization assertion, and
mandatory release review without changing the anonymous F5 example.

Lines may provide `spokenText` for pronunciation-only spellings while retaining
the normal `text` for subtitles and dialogue records. Project-Her includes the
line source hash in each run, and VOForge content-addressed naming uses the
spoken form so a pronunciation change cannot reuse an older artifact.

The important fields are:

- `intendedUse`: `evaluation`, `non-commercial`, or `commercial`;
Expand Down
20 changes: 20 additions & 0 deletions docs/consents/cybernetic-commander.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Cybernetic Commander authorization record

- Recorded: 2026-08-02
- Provider: repository owner/user
- Profile: cybernetic-commander
- Evidence source: direct user confirmation in the Codex task that supplied the
four recordings
- Scope asserted by provider: use the supplied recordings to construct a
separate voice profile and add that profile to Protonmatter/voforge
- Repository policy scope: evaluation and non-commercial use only
- Independent verification: not performed

The provider affirmed that they have permission from the relevant speaker or
rightsholder to clone and redistribute the supplied voice recordings in this
repository. This file records that assertion; it is not independent proof of
identity, ownership, performer consent, or downstream commercial rights.

The profile and its reference assets must not be presented as a natural person's
actual speech, endorsement, or participation. Commercial use, identity claims,
or redistribution outside this repository require a separate rights review.
102 changes: 102 additions & 0 deletions examples/voice-profiles/cybernetic-commander/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Cybernetic Commander voice profile

This is a separately licensed, reference-conditioned F5 profile built from four
user-supplied recordings. It is intentionally isolated from VOForge's anonymous
audio examples and is not covered by the repository's MIT source-code license.

The profile is called Cybernetic Commander. Do not represent it as the voice of,
or an endorsement by, a natural person. The recorded authorization boundary is
in docs/consents/cybernetic-commander.md. Commercial use requires a separate
rights review.

## Rebuild

Use the existing pinned F5 environment from the repository root:

..\f5tts-venv\Scripts\python.exe examples\voice-profiles\cybernetic-commander\build_reference.py

The build fails on decoder-version drift. It preserves and analyzes all four
source MP3s, selects the user-preferred v2 generated performance for F5
conditioning, and retains the other clips as hash-bound comparison evidence.
It writes a normalized 24 kHz mono PCM16 reference and reference-manifest.json.
Accent-specific rhotic syllables are supplied through per-line spokenText while
the normal text remains available for subtitles and dialogue records.

profile-spec.json records the acoustic, pacing, pause, and pronunciation
contract. calibration-lines.json provides consonant, vowel, number, and tactical
coverage without making those long calibration passages part of the default
render. timed-dialogue.json preserves complete-sentence reference passages,
proper-noun QA terms, and source-scene provenance using VOForge's strict, local
timed-text parser. Each video reference has a bounded QA purpose; metadata is
recorded without downloading or redistributing source audio. The active
artifact is the exact user-preferred v2 systems-ready take,
hash-pinned in profile-spec.json. Its original inference speed was not preserved,
so forced rerendering is not reproducible and must not be treated as equivalent.
The numeric F0, formant, perturbation, VOT, PVI, syllable, and pause values in
profile-spec.json are evaluation targets, not post-render DSP instructions.

## Validate and plan

project-her validate --config examples\voice-profiles\cybernetic-commander\project-her.json
project-her plan --config examples\voice-profiles\cybernetic-commander\project-her.json

Rendering remains disabled through MCP by default. The reference transcript was
ASR-assisted and manually normalized; a human listening review is still required
before promoting generated dialogue.

Render one isolated timed-dialogue candidate with the shipped, hash-pinned
composite reference:

python render_timed_dialogue.py dyson-responsibility

Phrase parts are resumable. The renderer preserves 80 ms of quiet boundary
padding, applies a 20 ms terminal release when a model part ends active, and
accounts for retained quiet samples when inserting each declared break. It does
not resample or pitch-shift voiced audio. Timed candidates remain under ignored
out/ until manually approved.

## Experimental mastering A/B

`master_voice.py` applies the profile's measured post-processing preset to an
existing dry WAV without replacing that source. It requires an FFmpeg build
with the Rubber Band, equalizer, compressor, soft-clip, resampler, and EBU R128
`loudnorm` filters. Pass an executable explicitly when FFmpeg is not on `PATH`:

python examples\voice-profiles\cybernetic-commander\master_voice.py ^
examples\voice-profiles\cybernetic-commander\out\dry.wav ^
examples\voice-profiles\cybernetic-commander\out\dry-mastered.wav ^
--ffmpeg C:\path\to\ffmpeg.exe

The default preset slows cadence by 7%, lowers pitch by 0.75 semitone with
Rubber Band formant preservation, applies documented 115 Hz/310 Hz/presence EQ,
3:1 RMS compression, and oversampled tanh saturation, then iteratively
calibrates a 24 kHz mono PCM24 result to -16.5 LUFS with a -1.0 dBTP ceiling.
The normalizer reserves 0.1 dB of true-peak margin. The script refuses to
replace an existing WAV or manifest unless `--force` is supplied and records
input/output hashes, exact filters, calibration attempts, format, and measured
loudness in the adjacent JSON manifest.

Passing mastering metrics is not a promotion gate. In the local acceptance
trial, formant-preserving pitch/time processing substantially reduced the
cached Whisper tiny.en transcription accuracy even though format, loudness,
true peak, and clipping checks passed. Every candidate therefore requires a
dry/mastered A/B, an intelligibility comparison, and human listening review.
Generated masters and manifests remain ignored under `out/`.

## Checked-in name-free samples

The separately licensed [`samples/`](samples/) directory contains seven WAV
examples whose intended scripts and cached offline transcriptions contain no
personal, speaker, or character names. The directory includes exact/partial ASR
examples and the rejected mastering A/B so limitations are visible rather than
silently discarded. Each file is hash-bound in `samples/manifest.json`; human
listening and the profile's non-commercial rights boundary still apply.

## Named dialogue evaluations

[`dialogue/`](dialogue/) is deliberately separate from the name-free sample
set. It contains long-form, named evaluation dialogue with dry/processed A/B
evidence, explicit text provenance, model and reference hashes, measurements,
and conservative QA dispositions. These files are not promotion-approved and
require human listening and rights review before use beyond repository-local,
non-commercial evaluation.
159 changes: 159 additions & 0 deletions examples/voice-profiles/cybernetic-commander/build_reference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
r"""Rebuild the Cybernetic Commander F5 reference from its authorized sources.

Run from the repository root with the pinned F5 environment:

..\f5tts-venv\Scripts\python.exe \
examples\voice-profiles\cybernetic-commander\build_reference.py

The source clips are preserved byte-for-byte. This script decodes each one,
applies conservative peak-safe mastering, selects the preferred coherent
performance, and writes a mono 24 kHz PCM16 reference plus a hash-bound manifest.
"""
from __future__ import annotations

import json
import math
from pathlib import Path
from typing import Dict, List, Sequence, Tuple

import soundfile as sf

from voforge import __version__ as voforge_version
from voforge.audio import (
MasteringSettings,
artifact_record,
master_samples,
sha256_file,
write_pcm16_mono,
)


ROOT = Path(__file__).resolve().parent
REFERENCE = ROOT / "reference-cybernetic-commander.wav"
MANIFEST = ROOT / "reference-manifest.json"
EXPECTED_SOUNDFILE = "0.14.0"
EXPECTED_LIBSNDFILE = "1.2.2"
SETTINGS = MasteringSettings(
input_gain_db=-2.5,
peak_limit_dbfs=-3.0,
lookahead_ms=5.0,
release_ms=80.0,
sample_rate=24000,
)
SOURCES: Sequence[Tuple[str, str, str]] = (
(
"generated-directive.mp3",
"generated-primary",
"Min Kang, come with me if you want to live. You have been targeted for "
"termination. Skynet is looking to destroy you. I was sent by you from "
"the future to protect you.",
),
(
"generated-night-walk.mp3",
"generated-comparison",
"Min Kang, nice night for a walk. Your clothes. Give them to me.",
),
(
"source-clothes-request.mp3",
"source-comparison",
"Your clothes. Give them to me. Now.",
),
(
"source-night-walk.mp3",
"source-comparison",
"Nice night for a walk.",
),
)


def _decode(path: Path) -> Tuple[List[float], int, Dict[str, object]]:
waveform, sample_rate = sf.read(path, dtype="float32", always_2d=True)
mono = waveform.mean(axis=1)
samples = [float(value) for value in mono]
peak = max((abs(value) for value in samples), default=0.0)
rms = math.sqrt(sum(value * value for value in samples) / max(1, len(samples)))
return samples, int(sample_rate), {
"channels": int(waveform.shape[1]),
"sampleRate": int(sample_rate),
"frames": int(waveform.shape[0]),
"durationSeconds": round(len(samples) / sample_rate, 3),
"peakDbfs": round(20 * math.log10(max(peak, 1e-12)), 4),
"rmsDbfs": round(20 * math.log10(max(rms, 1e-12)), 3),
"exactClipSamples": sum(abs(value) >= 1.0 for value in samples),
}


def build() -> Dict[str, object]:
soundfile_version = getattr(sf, "__version__", "")
libsndfile_version = getattr(sf, "__libsndfile_version__", "")
if soundfile_version != EXPECTED_SOUNDFILE:
raise RuntimeError(
f"soundfile version drift: expected {EXPECTED_SOUNDFILE}, got {soundfile_version}"
)
if libsndfile_version != EXPECTED_LIBSNDFILE:
raise RuntimeError(
f"libsndfile version drift: expected {EXPECTED_LIBSNDFILE}, got {libsndfile_version}"
)

reference_samples: List[float] = []
reference_transcripts: List[str] = []
source_records: List[Dict[str, object]] = []
for filename, role, transcript in SOURCES:
source = ROOT / "sources" / filename
if not source.is_file():
raise FileNotFoundError(source)
decoded, sample_rate, metrics = _decode(source)
delivery, mastering = master_samples(decoded, sample_rate, SETTINGS)
if role == "generated-primary":
reference_samples.extend(delivery)
reference_transcripts.append(transcript)
source_records.append(
{
"path": source.relative_to(ROOT).as_posix(),
"role": role,
"bytes": source.stat().st_size,
"sha256": sha256_file(source),
"transcript": transcript,
"decoded": metrics,
"mastering": mastering,
}
)

if not reference_samples or not reference_transcripts:
raise RuntimeError("at least one generated-primary source must be selected")
write_pcm16_mono(REFERENCE, reference_samples, SETTINGS.sample_rate)
reference_text = " ".join(reference_transcripts)
manifest: Dict[str, object] = {
"schemaVersion": 1,
"profile": "cybernetic-commander",
"referenceText": reference_text,
"transcriptMethod": {
"engine": "openai/whisper-tiny.en",
"reviewStatus": "ASR-assisted and manually normalized",
"releaseReviewRequired": True,
},
"build": {
"voforgeVersion": voforge_version,
"soundfileVersion": soundfile_version,
"libsndfileVersion": libsndfile_version,
"mastering": SETTINGS.record(),
},
"selectionPolicy": {
"selectedRole": "generated-primary",
"reason": "user-selected v2 performance with per-line rhotic spelling controls",
"otherSources": "remaining clips retained as hash-bound comparison evidence",
},
"sources": source_records,
"reference": artifact_record(REFERENCE, ROOT),
}
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 👍 / 👎.

)
return manifest


if __name__ == "__main__":
result = build()
print(json.dumps(result["reference"], indent=2, sort_keys=True))
Loading
Loading