Voice conversion on the emotion trajectories — what was done, and what it measures
The pages in this build are a corrected copy of the emotion tier pages. This page says exactly what the correction is, and reports whether it worked — including where it did not.
1. What voice conversion is, in plain language
Every chain on this site is several separate
recordings of what the mining believed was one speaker, played one after another.
They were only ever filtered for speaker similarity, never made into one voice,
so as a chain plays you can hear the voice itself shift between segments.
Voice conversion takes a clip and re-speaks it in someone else's voice.
The words, the timing, the pauses and most of the delivery come from the original clip;
the vocal identity — the thing that makes a voice recognisably one person —
comes from a short reference recording of the target. Here the target is always
segment 1 of that same chain. So the chain ends up sounding like segment 1's
speaker throughout, saying what each of the later clips actually said.
What it fixes. Only one thing: who the voice sounds like. If two
clips of a chain sounded like two different people, they should now sound like one.
What it does not fix. It does not make the clips one continuous
performance — they are still separate takes, recorded at different moments, with
different room and microphone, and the joins are still joins. It does not correct the
mining: if the wrong clips were chained together, they are still the wrong clips.
What it can break. Delivery and identity are not cleanly separable. A
converted clip is re-synthesised, so its pacing, emphasis and emotional colour can move
— which matters enormously here, because the only thing these chains exist
to demonstrate is an emotional change. If conversion flattened the emotion, these pages
would be worse than the ones they copy and there would be no reason to ship them. That is
the first thing section 5 measures, and it is the reason the emotion re-scoring runs
on every single segment rather than on a sample.
2. Exactly what was run
For each chain, segment 1 was loudness-prepared
(EBU R128 integrated −23 LUFS, peak ceiling 0.95) and used as the target
voice. Segments 2…k were tokenised and re-synthesised onto it with
ChatterboxVC (the s3gen voice-conversion path, one candidate, seed derived from
the chain id so the run is reproducible), then restored with SIDON, which also
takes the output to 48 kHz. TF32 is enabled for s3gen (measured free) and
explicitly disabled around SIDON (measured ~15 dB cost).
Then, exactly as the uncorrected pages do, every segment is scaled to
−20 dBFS RMS before the chain is concatenated, a
0.15 s silence marks each
boundary, and the peak guard is applied once to the finished chain so the level
relationship between segments survives.
1,304 chains and 3,438 segments
were converted onto a new voice (4,742 segments passed
through the pipeline in total, counting the anchors used for the third render below), in
1.5 GPU-hours. 0 chains failed.
Two things are deliberately NOT in this
build.
• The
VoiceNet (
VN1) tiers are untouched — the request
was for the emotion pages only.
• The
voice-profile (
vprof_vc) tiers are untouched, because
they have no problem to fix: each of those chains is already a single cloned voice by
construction. No tier mixes profile chains with mined ones, so this is a clean cut and no
page is half-converted.
Both remain available in full on
the original site.
3. What changed in this build, and why
The first version of these pages converted only
segments 2…k and left segment 1 exactly as recorded. Listening found
the problem the measurement had also found: the chain then glues one raw 24 kHz
recording to k−1 neural 48 kHz re-syntheses, and the
acoustic — most audibly the reverb — changes at the first join. The cosine
across that first join measured
0.836 before and
0.728 after, the single
worst thing conversion did anywhere in the build.
So segment 1 is now converted too, with itself as the target, and
that render is the default on every card. The earlier one is kept behind an expander on
each card, because it retains more of the emotional move
(96 % against
90 %) — a real trade-off rather than a
strict improvement, and the choice stays visible.
Crossfades replace the silence. Segments are now
joined with an equal-power crossfade — cos/sin, so
that the two sides sum to constant energy. A linear crossfade sums to less than unit
energy at its midpoint and dips audibly on uncorrelated material, which two different
recordings always are.
The nominal length is 150 ms, shortened where it would do damage:
never more than a quarter of either neighbouring segment, and cut to 100 ms wherever
the incoming segment starts loud or the outgoing one ends loud, since a
crossfade attenuates the incoming signal exactly where a word-initial consonant would be.
Across 6,995 joins: 5,088 took the
full 150 ms, 1,382 were shortened for a hot onset and
525 for a hot tail. The shortest fade anywhere in the build
was 100 ms — the 25 % duration cap never bound,
because the shortest segment here is 3 s. Each card prints its own fade lengths.
The chain is normalised as one signal.
Previously every segment was scaled to −20 dBFS RMS independently,
which forces them all to the same loudness and erases the dynamics between them. Now a
single gain is applied to the finished chain and the peak guard is applied once
after it.
That would have been unsafe on the original recordings — those carry up to
8 dB of level steps from the MOSS encoder's own per-clip normalisation, an artifact
of the pipeline rather than the performance. It is safe here because SIDON returns every
converted segment at a consistent level, and the result is measured rather than assumed:
the largest step between adjacent segments is a median
1.17 dB, 3.39 dB at the 95th
percentile. For comparison the raw, un-normalised chains reach 17.96 dB. What is
left is the conversion's own dynamics, not a per-clip reset.
4. Did it unify the voice? — speaker identity
Measured with Orange/Speaker-wavLM-id, the
250-dimensional speaker verification embedding — the same space the
min_cos_* numbers on the original pages live in, and the space in which the
mining's 0.80 threshold is meaningful. (Not -tbr, which is a 128-d
timbre embedding on an unrelated scale.) For every chain: the worst
cosine between any segment and segment 1, and the worst between any two neighbours.
Higher is more like one person. All figures are over the same
1,304 chains.
| worst cosine to segment 1 | p05 | p25 | median | p75 | p95 |
|---|
| original | 0.069 | 0.502 | 0.756 | 0.861 | 0.935 |
| corrected (anchor kept raw) | 0.376 | 0.555 | 0.674 | 0.755 | 0.840 |
| uniform (anchor converted too) | 0.455 | 0.679 | 0.781 | 0.857 | 0.923 |
| worst cosine between neighbours | p05 | p25 | median | p75 | p95 |
|---|
| original | 0.069 | 0.522 | 0.765 | 0.866 | 0.937 |
| corrected | 0.415 | 0.604 | 0.706 | 0.785 | 0.859 |
| uniform | 0.459 | 0.689 | 0.785 | 0.858 | 0.925 |
40 %of chains at or above the 0.80 identity threshold, original
13 %… corrected
44 %… uniform
4a. The result depends entirely on how loose the chain already was
The overall before/after average is close to
useless here, because it averages two opposite effects. The conversion pulls every chain
towards a common level of identity agreement: it rescues chains whose segments were
plainly different people, and it damages chains that were already one voice. Splitting the
chains by their original worst-to-anchor cosine shows it directly. Median values;
“improved” is the share of chains in that band whose number went up at
all.
| original worst-to-anchor cosine | chains | share | before | corrected | change | improved | uniform | change | improved | emotion kept |
|---|
| below 0.50 - clearly different voices | 326 | 25 % | 0.221 | 0.519 | +0.296 | 96 % | 0.653 | +0.413 | 97 % | 96 % |
| 0.50-0.70 - audibly different | 190 | 15 % | 0.643 | 0.578 | -0.055 | 29 % | 0.682 | +0.045 | 68 % | 96 % |
| 0.70-0.80 - close but under threshold | 272 | 21 % | 0.756 | 0.671 | -0.083 | 14 % | 0.762 | +0.003 | 52 % | 96 % |
| 0.80 and above - already one voice | 516 | 40 % | 0.879 | 0.761 | -0.117 | 2 % | 0.865 | -0.015 | 34 % | 95 % |
Read as a recommendation. For the
25 % of chains that started below 0.50 — segments that
really are different people — the conversion is a large, near-universal improvement:
0.221 → 0.519, and
96 % of them go up. For the
40 % that were already at or above 0.80, it is a loss
(-0.117, only 2 % improved) and those
chains are better listened to on the original site. The uniform render is the
better general-purpose correction on this measure: it is a large gain where there was a
problem (+0.413) and close to neutral where there was not
(-0.015).
The corrected render does not improve the identity number, and on the
median it makes it worse (-0.069). That is a real result and it has a
mechanical explanation: the converted segments are compared against a raw
recording of segment 1. Neural re-synthesis moves a clip in this embedding space
even when the voice it imitates is right, so a converted segment scores further from a
raw anchor than another raw recording of the same speaker would. The number is punishing
the resynthesis, not only the voice.
The uniform render is the processing-matched comparison —
converted audio against a converted anchor — and it is the one that answers
“did the conversion make these sound like one person?”. There the median
change is +0.015, and the share of chains reaching the 0.80
threshold goes 40 % →
44 %.
4b. Separating “different person” from “re-synthesised”
The number above mixes two things a listener does not
hear the same way: how well the conversion unified the voice, and how far neural
re-synthesis moves a clip in this embedding space at all — which it does
even when the imitated voice is right. In the shipped render the second effect is
concentrated in one place: the first join, where a raw recording meets a
re-synthesis.
So the full segment×segment cosine matrix was recomputed for every one of
the 1,304 chains and split up. seam 1 is that first join.
after the first join is the worst agreement among segments 2…k
measured against each other — note that this is identical for the corrected
and uniform renders by construction, since both contain the same converted segments 2
onward. spread is how far the identity wanders across the whole chain, max minus
min over every pair.
| first join only (seam 1) | p05 | p25 | median | p75 | p95 |
|---|
| original | 0.166 | 0.711 | 0.836 | 0.910 | 0.959 |
| corrected | 0.449 | 0.623 | 0.728 | 0.797 | 0.867 |
| uniform | 0.526 | 0.754 | 0.840 | 0.900 | 0.944 |
| after the first join — segments 2…k against each other | p05 | p25 | median | p75 | p95 |
|---|
| original | 0.107 | 0.627 | 0.805 | 0.889 | 0.953 |
| converted | 0.507 | 0.711 | 0.804 | 0.870 | 0.935 |
| identity wander across the chain (lower is steadier) | p05 | p25 | median | p75 | p95 |
|---|
| original | 0.000 | 0.043 | 0.131 | 0.277 | 0.765 |
| corrected | 0.000 | 0.095 | 0.179 | 0.280 | 0.433 |
| uniform | 0.000 | 0.045 | 0.106 | 0.201 | 0.379 |
The awkward result, stated plainly.
Among the converted segments themselves, the conversion changed essentially nothing:
worst agreement of segments 2…k against each other was a median
0.805 before and 0.804 after, and the share reaching the 0.80 threshold went 51.5 % → 51.4 %.
So the conversion is not making the later segments sound more like one
another. What it does is pull each of them towards segment 1, which is why
the chains that started far from segment 1 improve so much and the chains that started
close to it do not. The residual differences between the converted segments survive the
conversion — the speech tokens the vocoder is driven from evidently still carry
some of each source speaker.
The one identity measure that improves across the board is spread, and
only for the uniform render: 0.131 →
0.106. The corrected render makes spread worse
(0.179), because it adds the raw-to-synthetic step at seam 1 on
top of whatever variation was already there. On identity, the uniform render is the
better artefact on every measure taken here.
5. Did it destroy the emotion? — the point of the exercise
Every segment, original and converted, was re-scored
through the same emotion stack the corpus was annotated with
(laion/BUD-E-Whisper + Empathic-Insight-Voice-Plus) and put on
the same tie-aware corpus-percentile scale the mining used
(ecdfm over the exact 127 M-utterance global histograms). Then
d_b — the end-to-end move on the axis the chain was mined for
— was recomputed exactly as mine5.py defines it.
Both sides are scored with the same scorer, so any offset between this scorer and
the stored corpus annotation cancels. As a check that it does: re-scoring the
originals reproduces the mined d_a/d_b to three
decimals.
Retention is d_b(after) / d_b(before), kept
signed: a conversion that reverses a trajectory has destroyed it, and must not
be able to score 1.0 by flipping. Chains whose original delta is under 0.02 are excluded,
because a ratio against zero is noise.
The check, run first. Re-scoring the
original audio and comparing the recomputed d_b against the value
stored by the miner: median absolute error 0.0003, 95th percentile
0.0215, over 1,304 chains — on a scale where a tier threshold is 0.20.
The re-scoring reproduces the mining, so the before/after numbers below are a measurement
of the conversion and not of a scorer mismatch.
| end-to-end move on the mined axis (d_b) | p05 | p25 | median | p75 | p95 |
|---|
| as mined (from the parquet) | +0.202 | +0.268 | +0.337 | +0.515 | +0.686 |
| re-scored, original audio | +0.201 | +0.267 | +0.341 | +0.518 | +0.706 |
| re-scored, corrected | -0.035 | +0.218 | +0.304 | +0.457 | +0.660 |
| re-scored, uniform | -0.098 | +0.167 | +0.307 | +0.506 | +0.717 |
| fraction of the delta retained | p05 | p25 | median | p75 | p95 |
|---|
| corrected | +0.000 | +0.805 | +0.956 | +1.000 | +1.111 |
| uniform | -0.077 | +0.565 | +0.903 | +1.086 | +1.927 |
93 %corrected chains that keep the DIRECTION of the move
87 %… that keep at least half of it
91 %uniform: direction kept
78 %uniform: at least half kept
6. What it cost in quality
The Empathic-Insight overall quality head, per
segment, on the same audio the players serve. A cheap proxy, not a listening test, and it
is scored on 48 kHz SIDON output against a 24 kHz original — so part of
any gain here is bandwidth, not conversion. Read it as “the conversion did not
wreck the audio”, not as “the conversion improved it”.
| predicted overall quality, per segment | p05 | p25 | median | p75 | p95 |
|---|
| original | 2.428 | 2.726 | 2.922 | 3.051 | 3.221 |
| corrected | 2.578 | 2.894 | 3.090 | 3.237 | 3.413 |
| uniform | 2.687 | 2.979 | 3.148 | 3.278 | 3.431 |
| change, corrected − original | +0.000 | +0.000 | +0.152 | +0.268 | +0.478 |
7. By corpus — where the correction had something to fix
The corpora differ enormously in how well
“speaker” was ever defined. mls ships real speaker identities;
everywhere else it meant “same source recording”. podcast is the
loose one — only about 54.6 % of consecutive clips inside one nominal podcast
speaker track are actually the same person — so it is where a conversion pass has
the most to prove.
| corpus | chains | cos→seg1 orig | corrected | uniform | ≥0.80 orig | ≥0.80 corr | d_b orig | d_b corr | retention | quality |
|---|
emolia | 746 | 0.775 | 0.675 | 0.771 | 40 % | 12 % | +0.401 | +0.353 | 95 % | 2.85 → 3.01 |
eurospeech | 109 | 0.868 | 0.731 | 0.876 | 56 % | 26 % | +0.300 | +0.270 | 89 % | 3.05 → 3.25 |
evasnippets | 63 | 0.757 | 0.696 | 0.843 | 43 % | 13 % | +0.272 | +0.266 | 98 % | 3.10 → 3.25 |
mls | 79 | 0.926 | 0.786 | 0.909 | 96 % | 38 % | +0.281 | +0.273 | 98 % | 3.11 → 3.27 |
podcast | 232 | 0.573 | 0.618 | 0.748 | 21 % | 5 % | +0.303 | +0.270 | 96 % | 2.79 → 3.02 |
snippets | 75 | 0.149 | 0.456 | 0.570 | 7 % | 0 % | +0.344 | +0.318 | 95 % | 2.72 → 2.85 |
8. By rule
| rule | chains | cos→seg1 orig | corrected | uniform | d_b orig | d_b corr | retention |
|---|
AB2 | 300 | 0.784 | 0.673 | 0.799 | +0.304 | +0.277 | 94 % |
B1 | 380 | 0.745 | 0.660 | 0.769 | +0.331 | +0.283 | 95 % |
B1 UNION VN1 | 20 | 0.732 | 0.673 | 0.768 | +0.238 | +0.228 | 89 % |
BASE | 20 | 0.753 | 0.722 | 0.797 | +0.347 | +0.353 | 100 % |
PXR | 404 | 0.752 | 0.661 | 0.774 | +0.364 | +0.337 | 96 % |
S1 | 20 | 0.688 | 0.676 | 0.776 | +0.042 | +0.022 | 48 % |
S2 | 20 | 0.733 | 0.639 | 0.748 | +0.552 | +0.516 | 95 % |
S3 | 100 | 0.749 | 0.704 | 0.787 | +0.551 | +0.524 | 96 % |
S4 | 20 | 0.778 | 0.757 | 0.824 | +0.475 | +0.462 | 95 % |
mixed | 20 | 0.910 | 0.745 | 0.860 | +0.006 | -0.024 | 108 % |
9. Intelligibility — did the words survive?
1,140 segments from
300 chains, stratified over the six corpora, transcribed with
openai/whisper-large-v3-turbo. Character error rate, not word error rate: these
chains include Korean, Japanese and Chinese, where word error rate is not defined in any
useful way.
The headline number uses the ASR of the original clip as the reference,
not the corpus transcript. Whisper's own error on this material is large and multilingual
and would swamp the effect being measured; comparing converted-ASR against original-ASR
cancels it and answers the actual question — did the conversion change what is
said? The language detected on the original is forced on the converted side, so a
language flip cannot masquerade as a transcription difference.
| comparison | CER |
|---|
| converted vs. ASR of the original — how much the conversion changed the words | 6.7 % |
| original vs. the corpus transcript (context: Whisper's own error here) | 10.0 % |
| converted vs. the corpus transcript | 12.3 % |
The conversion costs about
2.3 character-error points. Against the corpus transcript the error rate goes
10.0 % →
12.3 %. That is a real degradation and it is the
clearest price in this whole build: the speech is still the same speech, but it is
measurably less cleanly articulated. It is small enough that the chains remain
listenable and large enough that this audio should not be treated as a
transcription-grade rendering of the originals.
| corpus | segments | converted vs original-ASR | vs transcript, original | vs transcript, converted |
|---|
emolia | 177 | 5.0 % | 6.4 % | 8.1 % |
eurospeech | 197 | 9.8 % | 13.6 % | 19.9 % |
evasnippets | 200 | 4.9 % | 7.1 % | 6.7 % |
mls | 182 | 4.6 % | 2.7 % | 6.3 % |
podcast | 192 | 9.4 % | 20.9 % | 22.7 % |
snippets | 192 | 7.0 % | 11.2 % | 13.5 % |
10. Do we need voice conversion at all?
The alternative to converting a mismatched chain is
to not ship it: apply a hard identity cut and keep only chains that already sound
like one person. Whether that is better is answerable from what is already measured here,
because the conversion's effect is known separately for the chains each cut would keep
and the chains it would drop.
| hard cut | chains kept | of the grid | median cosine kept | VC on those KEPT | improved | VC on those DROPPED | improved |
|---|
| ≥ 0.70 | 788 | 60.4 % | 0.841 | -0.108 | 6 % | +0.173 | 71 % |
| ≥ 0.75 | 675 | 51.8 % | 0.858 | -0.110 | 3 % | +0.076 | 62 % |
| ≥ 0.80 | 516 | 39.6 % | 0.879 | -0.117 | 2 % | +0.006 | 51 % |
| ≥ 0.85 | 365 | 28.0 % | 0.905 | -0.125 | 1 % | -0.026 | 44 % |
The table answers the question, and the
answer is mostly “no”. Wherever a cut would keep a chain,
conversion makes that chain worse on identity — at
≥ 0.80 it costs 0.117 and helps only
2 % of them. And at ≥ 0.80 the chains that
would be dropped gain almost nothing from conversion either
(+0.006, 51 % improved
— a coin flip).
Conversion only clearly earns its place further down: below 0.70 it is
worth +0.173 on the 516 chains a 0.70 cut would
discard, improving 71 % of them. Those are exactly
the chains that are beyond rescue by filtering — and they are also the ones where
the listener hears the conversion happen as a stylistic break, because the voices being
reconciled were audibly different to begin with.
The two embedding spaces, on the same
chains. The corpus-wide counts below come from two different populations, so
their percentages cannot be compared with each other. The grid is the one place both
numbers exist for the same chains: on 298 of them (all
emolia, the only corpus here carrying a stored timbre anchor),
-id ≥ 0.80 is best matched by
-tbr ≥ 0.788
(85.9 % agreement), and the standing calibration of
0.795 agrees on 84.9 %. That
confirms the calibration rather than revising it, and it means a timbre cut in the
0.75–0.80 range is slightly more permissive than the identity test at 0.80,
not less. Correlation between the two spaces is r = 0.923.
This has to be done store-against-store: the identity cosine recomputed for this
build is measured on the rendered concatenation and sits a median
-0.0184 from the corpus store
(r = 0.967). Pairing across
the two sources instead moves the apparent equivalent threshold by nearly 0.1 — a
measurement offset masquerading as a disagreement between the models.
| corpus-wide hard cut | -id chains kept | of its own population | -tbr chains kept | of its own population |
|---|
| ≥ 0.70 | 1,099,149 | 40.1 % | 4,312,661 | 86.0 % |
| ≥ 0.75 | 980,781 | 35.8 % | 4,037,045 | 80.5 % |
| ≥ 0.80 | 811,690 | 29.6 % | 3,520,070 | 70.2 % |
| ≥ 0.85 | 571,975 | 20.9 % | 2,630,528 | 52.5 % |
Read the two columns separately. They
are measured over different sets of chains — -id on
2,737,699 and -tbr on 5,012,091 of the
9,744,940 mined chains, with different corpus mixes. The gap between
40 % and 86 % at the same nominal threshold is not evidence that the
timbre space is more lenient; it is mostly a different denominator. The paired test above
is the comparison that controls for that.
The number that matters for planning is the absolute one: at
-tbr ≥ 0.80 there are
3,520,070
chains that need no conversion at all.
Where the build lives
Corrected audio is under vcaudio/
(the corrected render) and vcaudio_u/ (the uniform render); the untouched
originals stay under audio/. Every number on these pages is in
vc_grid.json, per chain, machine-readable. Bulk artefacts — the
converted segments as float16, the per-shard measurement files — are on the cluster
under traj2/vcgrid/out.