* fix(engines): downmix along the channel axis, not axis 0 (#1328)
Found while reviewing #1328. Every subprocess sidecar guards its PCM
conversion with a defensive `arr.mean(axis=0)`, which is correct only for
channels-first audio. For a channels-last (N, 2) array `squeeze()` keeps
both axes and the mean runs across TIME: every output sample becomes the
mean of two neighbouring samples and the render collapses to 2 samples.
That is not a downmix, it is a destroyed waveform played back as noise.
Unreachable in all five today because every engine returns mono -- which
is precisely why it could sit there being wrong. Nothing runs it, so
nothing reports it, and the first engine or SDK version to emit stereo
gets noise with no error anywhere.
Pick the channel axis instead of assuming it, and loop so a stray extra
axis reduces the whole way to mono; previously a (2, N, 2) array stayed
2-D after one mean and produced a PCM buffer whose length disagreed with
the n_samples in the frame -- a desynchronized audio frame rather than a
merely wrong-sounding one.
Downmixing correctly rather than raising (the choice PocketTTS made on
#1328): these five are shipping engines, and turning a render that works
today into an error is a regression risk that the actual defect -- the
wrong axis -- does not require taking.
The sidecars run under different interpreters (confucius4 and dots.tts
each have their own venv), so they cannot import a shared helper and the
duplication cannot be refactored away. The recurrence guard is therefore
a test that holds all five to the same behaviour at once, so a sixth copy
pasted into a new sidecar fails there rather than shipping: 20 of its 30
cases fail before this change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(engines): let a broken sidecar fail instead of skipping
CodeRabbit Major on #1366: the blanket `except Exception -> pytest.skip`
turned a syntax error or an import-time regression in any of the five
sidecars into a skip, so this regression suite could pass CI while
running nothing.
All five are stdlib-only at import (torch and the model load lazily on
the first synthesize), so there is no optional dependency to tolerate --
an import failure here is a real defect in a shipping engine. Unguarded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: debpalash <nizam4103@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>