Skip to content
C.W.K.
Stream
Lesson 03 of 04 · published

Noisy Place Mode: Three Layers

~15 min · speaker-verification, speech-enhancement, noise, expert-mode

Level 0Muted
0 XP0/57 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete
"Not something I'd use always on... a specialist feature I can switch on only when there's a lot of tangled noise around... even if it costs latency... keep it off normally, and let me turn it on when I choose." — Dad

One Second Under the Music

A loud bar, music playing right over Dad's head. Mom said one quiet line, about a second long, and then Dad spoke. The stored turn was all his. Measured on that recording, four things had gone wrong at once:

  • Her line shared a chunk with his. The ear cuts speech into two-second chunks, so her second sat in the same chunk as his first words and took his name.
  • The music masked her. Cropped and scored alone, her second came out 0.08 against Dad and 0.13 against Mom: nobody's. Scribe's batch pass over the recording didn't hear her words at all; only the phone's realtime socket had.
  • Her profile couldn't grow. Mom was one clean quiet-room sample, and a correction couldn't help here: the turn was ruled all Dad's, so there was no other voice to name, and her stretch was under the two seconds a sample needed.
  • Some turns could never be cut. 36 of 202 checked turns were kept as .m4a (the Watch, the composer), and the window pass and corrections had been skipping them without a word. They are decoded to 16 kHz now.

Dad's Principle, and His Condition

Dad's direction came in two parts. The principle: sounds add up like transparent layers, so peel them apart before asking whose each one is. The condition: as an expert mode he turns on himself for noisy places, never always on, latency accepted. So it is a system setting, voice_noisy_place_mode, off by default. He switches it with the phone's Noisy toggle beside Cut-in on the voice screen, in Admin → Voice Mode, through POST /api/voice/noisy-place-mode, or by voice from anywhere through voice_check noisy_mode, which a turn ruled someone else's can never use. With it off, nothing in the path changed: the same calls, the same timeouts.

Three Layers

With the mode on, cwkBellows hears the recording three ways:

  • As it came, exactly the way the default path hears it, in two-second chunks.
  • Cleaned by DPDFNet-4, a speech-enhancement model run through sherpa-onnx, which takes out music and crowd noise. The cleaned speech is named in one-second chunks, so a one-second interjection gets a chunk of its own.
  • Residual: the recording minus the cleaned one, what the cleaner took out. The cleaner keeps the voice nearest the phone and counts a farther one as noise, so Mom across a restaurant table landed here.

The cleaner was picked on the bar recording itself, scoring Mom's cropped second against Dad and Mom:

LayerDad / MomScribe heard her words
The recording0.08 / 0.13no
Spleeter vocals0.00 / 0.06no
UVR MDX-Net vocals0.11 / 0.14not measured
GTCRN-0.04 / 0.02not measured
DPDFNet-40.03 / 0.26yes

Music separation kept the music's voice-band harmonics in with her; speech enhancement took them out. That's the whole difference.

The Mode Only Ever Adds

joined_layers keeps every voice the plain answer found, untouched, and only adds a voice that a layer alone holds: 0.8 seconds or more of its own, carved out of the time around it. From the cleaned layer, a voice named for someone other than Dad, or one that leans toward an enrolled voice (0.15 or more for it and 0.10 above Dad), which reads Mom?. From the residual, only a voice named for an enrolled person, since what the cleaner took out is mostly noise. Never Dad's from a layer: the recording as it came already holds him.

Strangers stay the plain path's business, because the cleaned layer's short chunks also turn Dad's own first words into short voices of nobody, and counting those would doubt him. When the added voice came from the cleaned layer, Scribe transcribes the cleaned recording, the only place her words were heard. A noisy ear that fails falls back to the plain one, and the turn's voice_meta.speaker.noisy says so. And an unconfirmed voice still never counts as Dad's. A carved voice joins the turn's voices, and the verdict is ruled on that joined list by the same one-second line as always: a carved voice of a second or more beside Dad's makes the turn mixed and its words are labeled, while one of 0.8 to 1 second is recorded but too short to move the verdict, so the turn can stay dad. And the voice_check door still demands a turn ruled dad.

Two more pieces landed with it. The lean label works in both modes: a voice named nobody's that leans toward Mom reads [Mom?] instead of [someone else]. It decides nothing more than before; it is what Dad corrects. And a correction on a turn ruled all Dad's now listens again through the noisy ear, so the bar turn's second can finally be filed into the short pool of the last lesson.

Measured, and Still Open

The replay took the 210 newest checked turns and skipped any whose recording could no longer be found, so 204 were heard twice, plain and noisy: 183 stayed dad, 9 stayed mixed, 9 stayed uncertain, 2 others unchanged, and exactly 1 went from dad to mixed: the bar turn, with Mom? carved from the cleaned layer for 1.03 seconds. No turn of Dad's alone gained a voice, and no voice the plain check found was lost. The price: 2.4 seconds more per turn at the median, 7.4 at the 95th percentile, 17 at most (a 120-second turn), about 0.12 to 0.14 seconds per second of recording.

A first version ruled the turn from the cleaned layer alone. It caught the bar and lost five genuine mixed turns, Mom cleaned away as noise, and doubted three of Dad's. That is why the layers only add. And be honest about the status: the work item behind this mode is still open, every number above is an offline replay through the live ear, and the mode's field use isn't on record as verified yet.

Code

Which layer voices may join the turn, verbatim (cwkPippa backend/services/speaker_check.py:662-677)·python
def _layer_voice_counts(voice: Mapping[str, Any], layer: str) -> bool:
    """Whether a voice the ear heard in a layer under the recording joins
    the turn. Never Dad's (the recording as it came already holds him). In
    the cleaned layer, a voice the ear named for someone else or that leans
    toward an enrolled voice (Mom's quiet line under the music); strangers
    are the plain path's, and on 210 of Dad's kept turns the cleaned layer's
    one-second chunks also turned his own first words into short voices of
    nobody (2026-10-11). In the residual layer, only a voice named for an
    enrolled person: what the cleaner took out is mostly noise."""
    profile = voice.get("profile_id")
    if profile == DAD_PROFILE:
        return False
    named = bool(profile) and voice.get("band") in ("match", "uncertain")
    if layer == "cleaned":
        return named or leans_toward(voice) is not None
    return named
A minimal sketch: the layer rules, the lean and the carving from joined_layers, run on a bar-like turn·python
# Noisy-place mode in miniature: the recording as it came, plus voices that
# only a layer under it holds. The rules follow cwkPippa's speaker check
# (leans_toward, _layer_voice_counts, the carving in joined_layers).
DAD = "dad"
LEAN_LINE, LEAN_MARGIN = 0.15, 0.10
LAYER_MIN_SECONDS = 0.8
NAMES = {"dad": "Dad", "mom": "Mom"}


def leans_toward(voice):
    scores = voice.get("scores") or {}
    others = {k: v for k, v in scores.items() if k != DAD}
    if not others:
        return None
    best = max(others, key=others.get)
    if others[best] >= LEAN_LINE and others[best] - scores.get(DAD, -1.0) >= LEAN_MARGIN:
        return best
    return None


def layer_voice_counts(voice, layer):
    if voice.get("profile_id") == DAD:
        return False            # the recording as it came already holds him
    named = bool(voice.get("profile_id")) and voice["band"] in ("match", "uncertain")
    if layer == "cleaned":
        return named or leans_toward(voice) is not None
    return named                # residual: only an enrolled person, the rest is noise


def subtract(spans, taken):
    out = []
    for start, end in spans:
        pieces = [(start, end)]
        for t0, t1 in taken:
            cut = []
            for p0, p1 in pieces:
                if t1 <= p0 or t0 >= p1:
                    cut.append((p0, p1))
                    continue
                if t0 > p0:
                    cut.append((p0, t0))
                if t1 < p1:
                    cut.append((t1, p1))
            pieces = cut
        out.extend(p for p in pieces if p[1] - p[0] > 0.05)
    return out


def join(plain, layers):
    """Every voice the plain answer found stays; a layer can only add one."""
    voices = [dict(v) for v in plain]
    for layer, found in layers.items():
        for v in found:
            if not layer_voice_counts(v, layer):
                continue
            # Time a voice other than Dad's already holds is not found twice,
            # and that includes a voice this loop added a moment ago.
            held = [s for kept in voices if kept.get("profile_id") != DAD for s in kept["spans"]]
            spans = subtract(v["spans"], held)
            seconds = round(sum(b - a for a, b in spans), 2)
            if seconds < LAYER_MIN_SECONDS:
                continue
            for kept in voices:                 # carved out of the time around it
                kept["spans"] = subtract(kept["spans"], spans)
            who = v.get("profile_id") or leans_toward(v)
            label = NAMES[who] + ("" if v.get("profile_id") and v["band"] == "match" else "?")
            voices.append({**v, "spans": spans, "label": label, "layer": layer})
    return voices


# The bar under a loud speaker: the plain ear heard one voice, Dad's.
plain = [{"profile_id": DAD, "band": "match", "spans": [(0.0, 9.5)], "label": "Dad"}]
layers = {
    "cleaned": [
        {"profile_id": None, "band": "none", "spans": [(0.6, 1.63)], "scores": {DAD: -0.02, "mom": 0.20}},
        {"profile_id": DAD, "band": "match", "spans": [(1.7, 9.5)], "scores": {DAD: 0.71, "mom": 0.12}},
        {"profile_id": None, "band": "none", "spans": [(1.7, 2.4)], "scores": {DAD: 0.09, "mom": 0.05}},
    ],
    "residual": [
        {"profile_id": None, "band": "none", "spans": [(0.0, 9.5)], "scores": {DAD: 0.03, "mom": 0.04}},
    ],
}
for v in join(plain, layers):
    spans = ", ".join(f"{a:.2f}-{b:.2f}s" for a, b in v["spans"])
    print(f"{v['label']:<5} {v.get('layer', 'as it came'):<11} {spans}")
# A voice of 1 s or more beside Dad's makes the turn mixed (verdict_of).
voices = join(plain, layers)
others = [v for v in voices if v["label"] != "Dad" and sum(b - a for a, b in v["spans"]) >= 1.0]
print("verdict:", "mixed" if others else "dad")

# Output:
# Dad   as it came  0.00-0.60s, 1.63-9.50s
# Mom?  cleaned     0.60-1.63s
# verdict: mixed

External links

Exercise

Run the sketch, then break it on purpose three ways and predict each output first: give the residual layer a voice named mom with band uncertain for 1.5 s at 3.0-4.5 s; give the cleaned layer a stranger with no lean at 5.0-7.0 s; and shrink the cleaned Mom? voice to 0.6-1.3 s. Finally, change join so the cleaned layer replaces the plain answer instead of adding to it, and find the input that makes it lose a voice the plain answer had.
Hint
The residual Mom is named for an enrolled person, so it counts and is carved out of Dad's time. The stranger in the cleaned layer neither is named nor leans, so it never joins. The shrunk Mom? is 0.7 s, under the 0.8 s floor, so the turn stays all Dad's. For the replacement version, put a plain Mom voice somewhere the cleaned layer has no voice: replacing the answer drops her, which is the five lost mixed turns in miniature.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.