← Nachtelijke Maker

Versta Duo → RaveChat

Een gesproken groepschat voor plekken waar je elkaar niet kunt verstaan. Elke telefoon ondertitelt z'n eigen drager dichtbij de mond, en stuurt alleen de tekst via Bluetooth naar de schermen van de anderen. Geen internet, geen account.

PoC werkt — 11 jun 2026 RaveChat — werknaam Spin-off van Versta 100% on-device
~15 min
stabiel getest
tekst
i.p.v. audio relay
0
internet / server
1-op-1
MVP-scope

De frustratie die alles aanstuurt

Midden op de dansvloer van een technofeest. Een leuke klik met iemand die op je afkomt, iets in je oor probeert te roepen — en je verstaat er geen klap van. De bas dreunt door je borst, de zaal zit op ~100 dB. Dit is het ijkpunt waar Versta aan opgehangen is, en het hardste probleem dat er bestaat voor live verstaan.

Het inzicht: bijna altijd probeert iemand iets dicht bij je oor te zeggen. Dáár, vlak bij de mond van de spreker, is het geluid nog schoon — het is de afstand over de dansvloer die het kapotmaakt. Vang je de stem op precies waar 'ie wordt uitgesproken, dan is de halve strijd al gewonnen.

Externe validatie: kchau's "Loud Restaurants"

Op 11 juni 2026 postte Kevin Chau (@kchau) een iOS-app tegen luide restaurants — onafhankelijk dezelfde architectuur die Versta als geparkeerde v1.2 had liggen. Elke persoon heeft een iPhone + AirPods; de oortjes blokkeren de zaalruis én leveren een microfoon dicht bij de mond; de twee telefoons praten peer-to-peer over wifi (AirDrop-achtig, geen router). Effectief een lokale intercom waarbij je oren van de herrie geïsoleerd zijn.

"It's p2p wifi." · "Each person has an iPhone and AirPods." · "I found AirPods to perform well in blocking out noise... the rest is up to how well the microphone can do." · "Latency is horrible." — @kchau

Vier onafhankelijke testers hadden dezelfde paired-phone-behoefte al gesignaleerd (Tycho, Jan Nico, festival-jongeren, Olivia). kchau bouwde het en bevestigde: dit werkt, en zijn grootste open probleem is audio-latency.

De variant: transcribe-at-source

kchau relayt audio — je moet nog steeds horen, dus elke milliseconde latency telt. Versta Duo zet om naar tekst, en dat is strikt robuuster:

  • Werkt ongeacht je gehoor — je leest in plaats van te strijden om te horen (de differentiator voor slechthorenden).
  • Latency-tolerant: 1-2 seconden ondertitel-vertraging is prima. kchau's hardste probleem verdwijnt grotendeels.
  • Werkt over talen heen, en een verloren seconde tekst resynct onzichtbaar — een verloren seconde audio is een gemist woord.
Kern-idee

Elke telefoon transcribeert z'n eigen drager. Jouw near-field mic (paar cm van je mond, hoge SNR ook in 100 dB) → jouw iPhone transcribeert jouw schone spraak on-device → alleen de tekst gaat naar de schermen van de anderen. Niemands telefoon hoeft ooit nog een stem uit de zaalherrie te vissen over een tafel of dansvloer heen. Dat lost het moeilijkste probleem — far-field stem in lawaai — volledig op, niet half.

De mic die al in je oor zit

Voor Onno is er een gratis near-field mic die kchau's AirPods evenaart: zijn hoorapparaten. Ingesteld als volledige Bluetooth-headset (niet alleen koptelefoon) levert het hoortoestel de microfoon precies bij het oor — exact waar iemand op de dansvloer in roept. Wat Tycho als "bug" meldde (de mic-sessie kaapt de Bluetooth-output) blijkt juist de beste feature: capture op de plek waar het woord valt.

Kanttekening uit de eerste test: iOS gebruikt steeds maar één van beide toestellen als headset-mic (10 jun: rechts), en het is onzeker of dat stabiel hetzelfde toestel blijft. Een aparte zelftest meet welke mic het beste oppikt — zie de Microfoon-Test.

Hoe het loopt

Symmetrisch: beide kanten doen exact hetzelfde. Capture dichtbij de mond, transcribeer lokaal, stuur alleen tekst over de korte Bluetooth-link, lees op het scherm.

🎧Jouw near-field michoortoestel / AirPods, ~cm van de mond
📝On-device transcriptienl-NL, geport uit Versta 1.3
📱Hun schermjouw woorden verschijnen
📝Hun transcriptiehun eigen mic, lokaal

Bidirectioneel over één verbinding: jouw tekst gaat heen, die van hen komt terug. Geen audio verlaat ooit je toestel — alleen korte tekstpakketjes.

De PoC die het bewees

Op 11 juni gebouwd en door Onno getest: Versta Duo (com.terwisscha.verstaduo). ~15 minuten onafgebroken transcriberen op de iPhone, daarna de iPad-app geopend → in één klap alle opgespaarde tekst zichtbaar (de outbox flushte instant), daarna stabiel heen-en-weer.

Twee dingen maakten het verschil na een lange debug-grind:

  • Spraak-laag geport uit Versta's productie-SpeechManager — committed+current, generation-guard, commit-vóór-reset bij stilte, fout = committen niet wissen. De from-scratch-versie crashte en wiste tekst.
  • Ontkoppeling via een durable outbox (Onno's idee): finals in een queue met ack + flush-on-reconnect, partials best-effort daarbuiten. Daardoor flushte een 15-minuten-backlog ineens toen de tweede kant verscheen.

Transport: van MultipeerConnectivity naar Bluetooth

De PoC draait op MultipeerConnectivity (AWDL — de AirDrop-radio). Dat werkte, maar bleek fragiel: koude discovery sliep tot AirDrop AWDL wakker maakte, geen reconnect na backgrounden, en het kan principieel niet met de wifi-radio uit. Het ontwerp verschuift daarom naar Core Bluetooth, achter dezelfde durable outbox — alleen het bezorg-mechanisme wisselt.

Aspectkchau (audio)Versta Duo (tekst)
Wat reistLive audio-streamKorte tekstpakketjes
Latency-eisHard — elke ms teltTolerant — 1-2 s prima
Drop overleeftGemist woordRessynct onzichtbaar
Werkt zonder gehoorNee, je luistertJa, je leest
Wifi-radio uitKan met Core Bluetooth

Laag-scheiding (zodat beide transports naast elkaar kunnen)

Eén abstractie houdt de spraak-, queue- en UI-laag ongemoeid; alleen de byte-link wisselt — zo zijn MC en BLE A/B-testbaar.

  • LinkTransport — protocol: start/stop, send(Data, reliable), callbacks onConnected / onDisconnected / onReceive.
  • RelayCore — transport-agnostisch: de outbox + seq + ack + flush + remoteFinals / remotePartial.
  • MCLink (bestaand) en BLELink (nieuw) implementeren alleen de byte-link. Een toggle kiest welke.

BLE-transport in het kort

  • Eén custom Service-UUID. Elk toestel draait dubbele rol: peripheral (adverteert) én central (scant).
  • Tiebreak → kleinste id wordt central en initieert; voorkomt dubbele links.
  • Twee characteristics: rx (write, central → peripheral) en tx (notify, peripheral → central) — beide richtingen over één verbinding.
  • MTU onderhandelen (~185–512 byte); finals groter dan de MTU splitsen met mini-header (id + index + totaal) en herassembleren.
  • Reconnect op didDisconnect: meteen opnieuw connect → iOS herverbindt zodra in bereik. Van nature persistenter dan MC.

Valkuil die het stil sloopt: zonder de juiste Info.plist-sleutels (lokale-netwerk- of Bluetooth-permissie + service-declaratie) vind je nul peers — zonder foutmelding.

De make-or-break: koppelen in een menigte

Kernspanning: wrijvingsloos (vanzelf verbinden) versus ondubbelzinnig (juist op een druk feest mogen er geen vreemden per ongeluk gekoppeld worden — en dáár wil je 'm gebruiken). Pure auto-connect breekt precies waar de app voor bedoeld is. De oplossing is adaptief, met een vriendelijke handle als sleutel.

Probeer de handle

iOS-toestelnamen zijn generiek ("iPhone"), dus elk toestel toont een automatisch gegenereerde, botsing-resistente handle: kleur + dier + emoji. Daarboven mag een vrije alias. Dit is een echte spec uit het ontwerp — genereer er een paar:

🦊
Blauwe Vos
Optionele alias voorop:
Weergave: 🦊 Blauwe Vos

~16 kleuren × ~22 dieren = honderden combinaties — genoeg variatie als het viraal gaat. De handle is stabiel-by-default (regenereren = bewuste reset), wat straks her-ontmoeting mogelijk maakt.

Adaptieve flow

Bij openen meteen adverteren + scannen — geen "start"-knop. BLE-signaalsterkte (RSSI) filtert: alleen mensen op ~arm-afstand tonen, verre vreemden in de zaal vallen weg.

Dichtbij — jouw scherm
🦦Gouden Otter ▮▮▮ dichtbij
🦉Groene Uil ▮▮ nabij
🐺Rode Wolf ▮ te ver
De ander bevestigt
🦦
"Gouden Otter wil verbinden — OK?"
  • Eén kandidaat dichtbij → grote knop "Verbind met 🦦 Gouden Otter", de ander bevestigt. De gewone 1-op-1.
  • Meerdere kandidaten → lijstje gesorteerd op nabijheid; tik de handle die je op hun scherm ziet.
  • Zekerheid / menigte-vangnet → "Toon code" geeft een QR; de ander scant en is direct gekoppeld, lijst overgeslagen.

De QR is ook de groei-lus

Dezelfde QR is een Universal Link. Heeft de ander de app niet, dan opent de App Store; na installeren leidt dezelfde code direct naar koppelen. "Laat je code zien → zij installeren → meteen verbonden" is één doorlopende flow — het menigte-vangnet en de virale onboarding in één.

Bewust niet

  • Stil auto-connecten — koppelt de verkeerde persoon in een menigte.
  • Alleen-QR — te veel wrijving voor de simpele 1-op-1 en vereist altijd de camera.

MVP-aanbeveling: nabijheid + handle + tik-met-bevestiging eerst (camera-loos, wrijvingsarm). QR als sterke tweede — het is zowel het vangnet als de groei-lus, dus vroeg waardevol.

Gesproken groepschat

De waardevolste case: iedereen praat in z'n eigen mic, iedereen léést een gedeelde live-transcriptie. Een groepsgesprek in lawaai is precies waar gehoor afhaakt. Geïnspireerd op Bitchat's BLE-mesh, opgebouwd in drie tiers.

11-op-1
Directe linkMVP — PoC werkt

Twee toestellen, directe BLE-verbinding. De huidige target en bewezen fundering.

2ster
Kleine groep, sterVolgende stap

Eén toestel is host/kamer; anderen joinen via kamer-QR; de host herzendt naar allen. Werkt als iedereen in bereik van de host is — een tafel of vriendencluster. Geen multi-hop.

3mesh
Mesh (Bitchat-stijl)Ambitieus

Multi-hop relay met TTL + dedup; berichten hoppen via tussenliggende mensen, dus niet iedereen hoeft direct in bereik. Bereik-robuust, schaalt naar grotere groepen — maar zelf een substantieel project.

Nu al een groep-klare envelop

Ook al doet v1 alleen 1-op-1, het bericht wordt meteen mesh-klaar ontworpen — zo is groep een additieve feature, geen rewrite later.

{ roomId, sender (stableId + handle + alias),
  msgId, kind (final/partial/presence),
  seq, text, ttl }
  • 1-op-1: roomId = het paar, ttl = 1 (geen relay), twee leden.
  • Groep: finals floodt de mesh (ttl = N, relay met dedup op msgId); partials alleen naar directe buren (ttl = 1, best-effort) — anders verzuipt de mesh in updates.
  • Dedup = seen-set van msgId. Relay = ttl > 0 en nog niet gezien → opnieuw uitzenden met ttl − 1.

Eerlijke grens: BLE houdt ~8 gelijktijdige verbindingen per toestel. Sweet spot = kleine co-locatie groep (tafel / vriendencluster, ~4–6), niet de hele club.

De sociale laag — Onno's brainwave

Op een dancefeest ben je een avond beste vrienden maar wissel je geen contactgegevens uit. Júíst omdat de app anoniem is (handle, geen telefoonnummer) is de drempel laag — je "maakt contact via de app". De anonimiteit is de feature, niet een beperking.

Her-ontmoeting als magisch moment

Omdat de handle / senderId stabiel is, ziet Bluetooth op een vólgend feest dezelfde persoon: "hé, 🦊 Blauwe Vos is er weer — daar kan ik wat tegen zeggen." Dat verandert het product van gereedschap naar een sociaal object: een lichte, anonieme, terugkerende band.

Klein toe te voegen, hoge delight: een lokale "eerder ontmoet"-rol (set van senderId's die je sprak) + bij her-ontdekking een highlight "Blauwe Vos is in de buurt". Blijft volledig on-device.

Verder weg, expliciet later

  • Een opt-in berichtje ná het feest — async, op termijn óók over internet — gekoppeld aan de anonieme id (geen telefoonnummer). De bridge van anoniem-lokaal naar optioneel-persistent-online.
  • Privacy-kanttekening: een stabiele broadcast-id maakt je herkenbaar voor iedereen met de app. Dat ís de feature, maar overweeg een "ghost mode" (tijdelijke id) voor wie onzichtbaar wil zijn.

Positionering: RaveChat

De werknaam RaveChat verschuift het frame: niet "Versta Duo, een ondertitel-utility", maar een sociaal object voor ravers — de app voor het feest waar je elkaar niet kunt verstaan, en waar je rave-vrienden via Bluetooth terugkomen. Zelfde transcribe-at-source-tech, sterkere viral-framing. (App heet technisch nog Versta Duo / com.terwisscha.verstaduo; hernoemen is een latere stap.)

Waar het staat

Fundering staat. De spraak-naar-tekst is gefixt en de relay werkt bidirectioneel; de durable outbox vangt een drop op. De overstap naar BLE-transport is ontworpen maar nog niet gebouwd. 1-op-1 is de MVP-scope.

Roadmap

PoC: transcribe-at-source over MultipeerConnectivityKlaar
11 jun — ~15 min stabiel, bidirectioneel, outbox flusht een backlog ineens. Spraak-laag uit Versta-productie geport.
Robuustheid afmakenNu
Test wifi-aan-maar-ander-netwerk (= dansvloer, verwacht werkend via AWDL — Airplane-mode + wifi-aan-test); gedrag bij echte drops / wifi-toggle.
WER-meting: eigen near-field vs Versta far-fieldTe doen
Ground-truth-protocol (15 vaste zinnen) in babble, voorlezer op ~5 cm met de hoorapparaat-mic. Bewijst of near-field-relay de fout materieel verlaagt. Beslist of dit kern-feature of leuke theorie is.
BLE-transport bouwenTe doen
BLELink achter de bestaande outbox; toggle MC ↔ BLE voor A/B. Werkt met wifi uit, persistenter.
Koppel-UX: nabijheid + handle + tikTe doen
MVP camera-loos; QR (verbinden + installeren) als sterke tweede.
Tier 2 — ster-groepLater
Eerste groep-stap, kamer-QR. Envelop is al groep-klaar.

Open vragen

  • Verbetert near-field-mic-over-p2p de WER materieel op onze stack? Akoestisch logisch, nog onbewezen voor ons.
  • Gaat de near-field-relay-laag terug naar Versta's geparkeerde v1.2 (multipeer rooms), of blijft het een eigen app/product?
  • Hoe hard willen we reconnect-in-achtergrond (kost batterij), of accepteren we "app voor = verbonden"?
  • Handle-stijl bevestigen (kleur + dier + emoji) en of 'ie regenereerbaar/instelbaar moet zijn.
  • Hernoemen naar RaveChat — wanneer, en met welke positionering naar de App Store.

Scope-waarschuwing uit het ontwerp: dit is een nieuw product-mode (netwerklaag bovenop een nu schone on-device single-app), geen kleine feature. Hoort als eigen spoor, niet ingejat in lopend Versta v2-engine-werk.

Code: ~/Developer/useful-apps/VerstaDuo/ · bundle com.terwisscha.verstaduo · iOS 17, universeel. Spin-off van Versta (live in de App Store).