Architecture
This page shows dengjen-piper-rs’s own structure as a C4 Container, Component, and Code diagram. For how it fits into the wider ZirekHQ speech stack, see the org-wide System Context diagram. It has no dependency relationship, in either direction, with dengjen-tts — these are two independent, parallel Piper-format inference implementations in the org, not one wrapping the other.
Container
dengjen-piper-rs is a plain library crate: no CLI, no FFI boundary, no
Python/Java bindings. It’s consumed the ordinary way, via cargo add.
C4Container
Person(dev, "Rust developer", "Depends on the crate via Cargo")
System_Boundary(piperrs, "dengjen-piper-rs") {
Container(lib, "dengjen-piper-rs", "Rust library crate (rlib)", "Published to crates.io")
}
System_Ext(voices, "Piper voice catalogue", "e.g. huggingface.co/rhasspy/piper-voices")
Rel(dev, lib, "cargo add dengjen-piper-rs")
Rel(lib, voices, "Loads Piper-format ONNX voices")
Component
The workspace actually contains two coexisting designs. The one
below, under src/, is what’s actually published and consumed. A second,
hexagonal (ports-and-adapters) design lives in crates/piper-core — but it’s
pulled in only as a dev-dependency of the published crate, exercised via
examples/hexagonal_pipeline.rs, not the crate’s real public surface yet.
Both are shown so this doesn’t read as more settled than it is.
|
C4Component
Container_Boundary(published, "Published crate (src/) -- the real public API") {
Component(model, "model.rs", "Rust module", "ModelConfig, infer(), calls ort::Session directly")
Component(errors, "lib.rs", "Rust module", "PiperError and the crate's public entry points")
}
Container_Boundary(reference, "In-progress hexagonal architecture -- separate workspace crates, dev-dependency only, not yet promoted") {
Component(domain, "piper-core::domain", "Value types", "audio, errors, inference, phoneme")
Component(ports, "piper-core::ports", "Trait", "InferenceEngine -- the port")
Component(usecases, "piper-core::use_cases", "Orchestration")
Component(ortadapter, "ort-adapter", "Adapter crate", "Implements InferenceEngine via ort::Session")
Component(espeakadapter, "espeak-rs-adapter", "Adapter crate", "Grapheme-to-phoneme via espeak")
Component(voicerepo, "fs-voice-repo", "Adapter crate", "Loads voice files from disk")
}
Rel(errors, model, "Uses")
Rel(usecases, ports, "Depends on")
Rel(ortadapter, ports, "Implements")
Rel(usecases, domain, "Uses")
Code
The one real, verified port from the in-progress piper-core side: an
InferenceEngine: Send + Sync trait depending only on domain value types.
OrtInferenceEngine (in the ort-adapter crate) is its only production
implementor today; a private FixedOutputEngine test double also exists,
but it’s defined inline in the port’s own #[cfg(test)] module, not in
stub-adapter (that crate implements a different port, Phonemizer, not
this one).
classDiagram
class InferenceEngine {
<<trait>>
+infer(ids, params) Result
+validate_arity(expects_speaker_tensor) Result
}
class OrtInferenceEngine
InferenceEngine <|.. OrtInferenceEngine
Versioning
The workspace shares one version.workspace = true across all 6 members,
the root crate, and the non-member crates/espeak-rs-sys — with no
exceptions. This is a deliberate convention, not an accident of the
workspace template.
espeak-rs-sys and espeak-rs were briefly carved out to version
independently, since they repeatedly change alone (cross-compile fixes, FFI
hardening, leak fixes) while piper-core and its adapters change together.
That split was reverted: a single shared version is simpler to reason about — "these N crates at version X work together" holds for the whole workspace
at a glance, with no need to cross-reference a compatibility matrix to know
which espeak-rs/espeak-rs-sys version a given dengjen-piper-rs release
was built and tested against. The unrelated-republish cost of lockstep is
accepted as the tradeoff for that simplicity.
See #115.
Want to help? Learn how to contribute to the ZirekHQ docs ›