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

Only Dad Cuts Her Off, When the Ear Can Tell

~16 min · voice, barge-in, speaker-recognition, fail-safe, swift

Level 0Lost
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

A talk inside the navigator

Tap her face on the map and the talk begins. It is cwkPippa's voice mode carried inside Pathfinder: PippaTalk runs the loop, and CwkKitVoice, the family's shared Swift voice parts, supplies the pieces. The brain stays cwkPippa's. A talk is an ordinary cwkPippa conversation with origin_surface: pathfinder, made of dictated turns answered for the ear, and every turn carries Dad's position and goes out right after a fresh navigation report, so she answers from the route itself.

  • The ears are ElevenLabs Scribe realtime, reached directly on a single-use socket that cwkPippa's listening grant builds. The phone never holds a speech key.
  • The mouth is her voice streamed on the microphone's own engine, so echo cancellation takes her out of what the mic hears.
  • The patience. A quiet minute ends the talk, and a server restart is ridden out for 45 seconds before the talk stops and says why.
  • Dad's voice is kept (cwkPippa todo #309). Each utterance's recording goes up to cwkPippa twice, the 16 kHz stream the socket heard and the same stretch at the microphone's own rate, so a conversation can later become a video with his real voice in it. Nothing of it stays on the phone. The words wait for that upload; if it fails, the note "The recording was not kept." appears and the words still go.
  • The talk can see (todo #318). With the camera Dad turned on for that talk, what it saw while he spoke goes with his words as the turn's pictures. It is video only, never touches the talk's audio, and runs only while the talk holds the microphone with the app in front.

How a turn is heard, composed for the ear and read aloud is the family's shared machinery, taught in voice-mode-quest, whose barge-in track this lesson builds on. Here the question is narrower: in a moving car, who is allowed to stop her?

The detector hears loudness

Barge-in means Dad talks over her and she stops. It is on by default (the talkCutIn switch in Settings), and it starts listening only after 0.3 s of her voice has sounded. Underneath is a detector of loud runs well above the room's floor, and that simplicity broke twice.

Her own voice (#314). Echo cancellation lets her through at full loudness for about half a second where she starts again after a pause, and the detector took it for Dad. On the Pippa phone app it stopped her mid-answer and Dad had to say "계속해." to have her go on. The fix, which Pathfinder's talk adopted, asks each loud chunk for its pitch (VoicePitch, an autocorrelation between 70 and 400 Hz). Her register is 205-360 Hz, Dad speaks at 100-180 Hz, and a loud chunk in her register does not count. The cost was measured on the phone's recordings: Dad's raised voice, when it reaches her register, cuts in later, once by 0.9 s.

Everybody else (#339). A door, the street, a passenger or Mom trips a loudness detector as surely as Dad does. So a trip no longer cuts. CutInHold lowers her reading to a quarter of its volume while the mic keeps every chunk. One second of audio after the trip, the kept stretch goes to POST /api/conversations/{id}/cut-in-check, where cwkPippa's speaker ear compares it with the enrolled voices. Only Dad cuts: a voice the ear names as his, or one scoring at least 0.20 against his voice with no other enrolled voice scoring higher. No speech, Mom, a stranger or her own voice coming back leave her reading at full volume, and for one second after a "not Dad" new trips are ignored, so a noisy cabin is not asked about every half second.

The numbers are the kit's: duckedVolume = 0.25, after = 1.0, send = 2.3, timeout = 2.5, cooldown = 1.0. On the phone the kept audio is shorter than send: about 0.6 s before the trip (six 100 ms chunks) plus the second after, so the ear hears roughly 1.6 s. Measured on 132 cut-ins on the web, a full second after the trip caught 91 % of Dad's real cut-ins on the first ask, and half a second 68 %.

When the check cannot say

The honest part is what happens when the ear is not there. Every one of these paths cuts her off:

  • the ear answers later than 2.5 s (timeout);
  • the request fails or the server answers an error status (unreachable);
  • the answer cannot be read, or its cut is anything but false: missing, null, true, a string. (Foundation's bridge reads a JSON 0 as false too, so the decoder's comment, "anything but an explicit false", is a shade stricter than its code. cwkPippa only ever sends a boolean);
  • the speaker check is switched off or down on the server, or no voice of Dad's is enrolled: the server itself answers cut: true with reason unchecked;
  • the talk has no conversation to ask about yet (unchecked).

So in each of those cases a loud run still cuts her, exactly as every trip did before the ear existed. The asymmetry is deliberate. A noise that cuts her by mistake costs a sentence, and cwkPippa knows where Dad stopped hearing, so she can resume. A Dad who cannot stop her costs his trust in talking to her at the wheel. Even when the check works it has a price: by cwkPippa's measurement on the web, Dad's cut-in now stops her about 1.5 s after he starts instead of about 0.4 s. His words from the start are kept and replayed into the next listen, so nothing he said is lost to the wait.

One rule ties this back to the road: a guidance line that cuts her drops any hold being asked about. The road is not Dad talking, and a turn instruction never waits for a speaker check.

A guard that can fail must fail toward the person it protects. The check exists so noise stops silencing Pippa, but Dad's power to interrupt her is the one thing it may never take away. So "not sure" means cut, and the only way she keeps reading is a positive answer that the voice was someone else's.

Code

A cut-in on the phone, step by step (a timeline drawn for this lesson from CutInHold and PippaTalk)·text
t = 0.0 s    a loud run on the open mic while she reads, not in her register
             -> CutInHold.trip: her reading drops to 0.25 volume, every mic chunk is kept
                (already kept: ~0.6 s before the trip, six 100 ms chunks, the run among them)

t = 1.0 s    one second of audio heard since the trip
             -> the kept audio (~1.6 s; never more than the latest 2.3 s)
                goes to POST /api/conversations/{id}/cut-in-check

t <= 3.5 s   the ruling (the ear has 2.5 s)
             dad                         -> cut: she stops, the talk listens,
                                            the kept words replay into the new listen
             not_dad / no_speech         -> keep: full volume again, trips ignored for 1.0 s
             late, error, unreadable,
             unchecked (off, down,
             Dad not enrolled)           -> cut, as before the ear existed

any time     a guidance line cuts her    -> the hold is dropped: the road is not Dad talking
cwkSiblingKit, CwkKit/Sources/CwkKitVoice/CutInCheck.swift: the hold's volume, the ruling's decoder and the hold's timings (trimmed: lines 29-31, 41-54 and 148-164)·swift
  /// Her reading's volume while the ear is asked (``CutInHold``; the WebUI's
  /// `DUCKED_VOLUME`).
  public static let duckedVolume: Float = 0.25

  // …

  /// The server's answer. Anything but an explicit `"cut": false` cuts: a ruling that
  /// cannot be read must not keep Dad from cutting in.
  public static func decode(_ data: Data) -> CutInCheck {
    guard let object = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else {
      return CutInCheck(cut: true, reason: "unreadable")
    }
    return CutInCheck(
      cut: (object["cut"] as? Bool) != false,
      reason: object["reason"] as? String ?? "unknown",
      ms: (object["ms"] as? NSNumber)?.intValue,
      detail: object["detail"] as? String,
      answer: data
    )
  }

  // …

  public struct Options: Sendable, Equatable {
    /// Heard after the trip before the ear is asked.
    public var after: Double
    /// What the ear is sent: the latest stretch of the kept chunks, the run among them.
    public var send: Double
    /// An ear slower than this cuts.
    public var timeout: Double
    /// After a "not Dad", trips are ignored this long.
    public var cooldown: Double

    public init(after: Double = 1.0, send: Double = 2.3, timeout: Double = 2.5, cooldown: Double = 1.0) {
      self.after = after
      self.send = send
      self.timeout = timeout
      self.cooldown = cooldown
    }
  }

External links

Exercise

Find a guard in your own system that can block a person from doing something: a spam filter on a support form, a rate limiter on a login, a confirmation check on a destructive command, a classifier in front of a human. List every way the guard itself can fail (timeout, outage, unreadable answer, not configured). For each, write down whether the person is let through or stopped today, and whether that is the right direction for the cost of being wrong.
Hint
Compare the two mistakes in the units the person feels. A false block that the person cannot get around is usually the expensive one; a false pass that can be undone cheaply is usually not. Then check that the unconfigured state, the one nobody tests, fails in the same direction as the outage.

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.