|
This version is still in development and is not considered stable yet. For the latest stable version, please use dengjen-nvda 4.x! |
Domain layer
The modules under addon/synthDrivers/dengjen_neural_voices/domain/, plus the two persistence
modules beside them. The domain layer talks to engines only through the TTSBackend port (see
Speech pipeline); the NVDA driver and the gRPC adapter sit on either side.
| Module | Owns |
|---|---|
|
Voices, speech options and the system that ties them together. |
|
Which tuning sliders a model type supports, and the engine key behind each. |
|
The |
|
Per-application voice settings. |
|
Splitting text so quoted or bracketed passages use a second speaker. |
|
Normalisation, night mode and stereo panning of PCM. |
|
The PCM cache, described in Speech pipeline. |
|
NVDA configuration for the add-on. |
|
Moving voices across the 4.0.0 rename. |
Voices and the speech system
DengjenVoice represents one installed voice.
-
DengjenVoice.from_path(path, backend)takes the key from the directory name and the rest of its identity from the voice’svoice.json. For Piper voices it reads the quality from the last segment of a<language>-<name>-<quality>key. -
begin_load()picks the voice’s JSON config file, preferringconfig.jsonand ignoringvoice.json, and asks the backend to load it on a worker thread. It returns a future that concurrent callers share; a failed load is not cached, so the next call retries.load()waits on that future, and reading a load-dependent attribute such assample_ratedoes the same.is_loadedis true once the result is applied. The result supplies the backend voice id, sample rate, speakers, whether streaming output is supported, and the default scales (frommodel_profiles). -
synthesize()forwards to the backend. A voice never calls the engine directly.
SpeechOptions holds the voice and the rate, volume, pitch and sentence silence for one utterance.
Assigning a voice starts its load (voice.begin_load()) and returns without waiting. DengjenTextToSpeechSystem.create_speech_provider(text)
hands each SpeechProvider a copy of the current options, so changing the rate, volume, pitch or
selected voice after a task is queued does not alter that task. The copy is shallow: the voice object
itself is shared, which is why SpeechTask sets and restores the speaker around its own run.
create_break_provider(ms) returns a SilenceProvider for the voice’s sample rate.
Voices are discovered by load_all_voices_from_nvda_config_dir(backend). It first runs
migrate_voices_directory(), then scans the Piper voices directory and the Kokoro voices directory
and returns the voices sorted by key. Directories that do not parse as a voice are skipped.
Model profiles
profile_for(model_type) returns a ModelProfile listing which of length_scale, noise_scale
and noise_w apply, and which engine key each maps to. piper and melotts tune all three;
MeloTTS sends noise_w as the backend parameter noise_scale_w. Any other model type is not
tunable, so its sliders have no effect. Add a profile here when a new backend exposes tunables.
Voice metadata
voice.json holds model_type, name, language and description, all strings. It exists so
NVDA-facing identity does not depend on an engine’s own config format or on Piper’s directory-name
convention. read_or_migrate() reads the sidecar; when it is missing or malformed, it falls back to
parsing a Piper-style directory name and writes the sidecar so later reads skip the fallback. A name
that does not parse raises ValueError.
App profiles
AppProfileManager stores per-application settings (voice, variant, speaker, rate, volume,
pitch) in NVDA’s configuration under speech/dengjen_neural_voices/app_profiles, keyed by the
lower-cased executable name. Rate, volume and pitch are clamped to 0-100.
The global plugin applies them: on focus change it looks up the profile for the focused application, first restores the settings it previously overrode, then applies the new profile and remembers the values it replaced. The domain module only reads, writes and applies profile data; it does not watch focus.
Structural reading
split_into_segments(text, default_speaker, alt_speaker) finds passages inside ( ), [ ],
" " or 「 」 of up to 200 characters, merges overlapping spans, and returns segments that
alternate between the two speakers. The driver uses it only when the Structural reading setting is
on and the current voice has at least two speakers.
Audio processing
AudioStreamProcessor post-processes PCM chunks while keeping state across an utterance, so
gain and filter changes do not click at chunk boundaries. It does the following:
-
Normalisation towards an RMS of 0.20 with a peak ceiling of 0.95.
-
Night mode: a gain of 0.55 and damping of high frequencies.
-
Spatial audio: stereo output with an equal-power pan by
pan, a value from -1 to 1 that the driver derives from the focused object’s horizontal position on screen.
flush() returns whatever the processor still holds at the end of an utterance.
Configuration and migration
DengjenConfig (_config.py) extends NVDA’s speech configuration section with
dengjen_neural_voices. Its spec defines per-voice variant, speaker and the three scales
(0-100, default 50), per-language preferred voices, whether the user declined the language offer,
and the download URLs under hosting.
voice_migration.py carries voices across the 4.0.0 rename from Sonata Neural Voices. When Sonata
is no longer installed it moves its voices folder to the new location, retrying on every voice scan
until the move succeeds. When Sonata is still installed it leaves the folder alone, and the voice
manager copies voices on request. The module imports nothing from its own package, because
installTasks.py loads it by file path.