[object Object]

← back to Dw Photo Capture

Scan audio: outcome-based — yoo-hoo on resolve, 'uh-oh' on no-match

610fc3076ab924900a88a0503506e27d875dca1f · 2026-06-25 17:49:56 -0700 · Steve

Moved the success chime from lock-in to the resolve branch (only plays when the
code actually finds a product) and added a distinct low descending square-wave
'uh-oh' when a code locks but matches nothing (or nothing is read). Refactored
into a shared tone() player.

Files touched

Diff

commit 610fc3076ab924900a88a0503506e27d875dca1f
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Jun 25 17:49:56 2026 -0700

    Scan audio: outcome-based — yoo-hoo on resolve, 'uh-oh' on no-match
    
    Moved the success chime from lock-in to the resolve branch (only plays when the
    code actually finds a product) and added a distinct low descending square-wave
    'uh-oh' when a code locks but matches nothing (or nothing is read). Refactored
    into a shared tone() player.
---
 data/build.json   |  6 ++++--
 public/index.html | 23 ++++++++++++-----------
 2 files changed, 16 insertions(+), 13 deletions(-)

diff --git a/data/build.json b/data/build.json
index dd5f436..97d26f4 100644
--- a/data/build.json
+++ b/data/build.json
@@ -1,5 +1,5 @@
 {
-  "next": 42,
+  "next": 44,
   "map": {
     "63863152": 27,
     "578af86f": 2,
@@ -40,6 +40,8 @@
     "331a91f4": 38,
     "dee91484": 39,
     "2e98c767": 40,
-    "7d42b31f": 41
+    "7d42b31f": 41,
+    "260c91c6": 42,
+    "3b47a090": 43
   }
 }
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index c61f5e5..3fd0f90 100644
--- a/public/index.html
+++ b/public/index.html
@@ -851,21 +851,22 @@ async function startScanStream(){   // (re)acquire the live stream on the curren
 // ── lock-in "yoo-hoo" cue (Web Audio, zero asset; must be unlocked by a user gesture on iOS) ──
 let _audio=null;
 function unlockAudio(){ try{ if(!_audio) _audio=new (window.AudioContext||window.webkitAudioContext)(); if(_audio.state==='suspended')_audio.resume(); }catch(e){} }
-function playYoohoo(){
+function tone(seq,type,vol){               // seq = [[freq,startOffset,dur],…]; one-shot Web Audio blip
   try{
     if(!_audio) return;
-    const ac=_audio, t=ac.currentTime;
-    // two-note falling minor third "yoo-HOO" (G5 -> E5) — the classic sing-song attention call
-    [[784,0.00,0.17],[659,0.15,0.34]].forEach(([f,off,dur])=>{
+    const ac=_audio, t=ac.currentTime, V=vol||0.3;
+    seq.forEach(([f,off,dur])=>{
       const o=ac.createOscillator(), g=ac.createGain();
-      o.type='triangle'; o.frequency.setValueAtTime(f,t+off);
+      o.type=type||'triangle'; o.frequency.setValueAtTime(f,t+off);
       g.gain.setValueAtTime(0.0001,t+off);
-      g.gain.exponentialRampToValueAtTime(0.3,t+off+0.03);
+      g.gain.exponentialRampToValueAtTime(V,t+off+0.03);
       g.gain.exponentialRampToValueAtTime(0.0001,t+off+dur);
       o.connect(g).connect(ac.destination); o.start(t+off); o.stop(t+off+dur+0.03);
     });
   }catch(e){}
 }
+function playYoohoo(){ tone([[784,0.00,0.17],[659,0.15,0.34]],'triangle',0.30); }  // ✓ FOUND: bright falling "yoo-HOO"
+function playUhoh(){   tone([[311,0.00,0.20],[233,0.20,0.36]],'square',0.22); }     // ✗ NOT RIGHT: low descending "uh-oh"
 async function openLiveScan(){
   unlockAudio();                              // we're inside the user's tap → unlock audio for the lock-in cue
   try{ await startScanStream(); }
@@ -910,8 +911,7 @@ async function scanTick(){
       $('#scanStatus').textContent=(locked?'🔒 LOCKED · ':'👀 ')+vtag+d.top;
       if(locked){
         $('#scanlive').classList.add('locked'); // turn the frame + pill RED — "locked in"
-        try{navigator.vibrate&&navigator.vibrate(70);}catch(e){}
-        playYoohoo();                         // 🔊 audible "yoo-hoo" the instant the number locks
+        try{navigator.vibrate&&navigator.vibrate(70);}catch(e){}   // haptic on lock; audio waits for the resolve outcome
         if(scanTimer){clearInterval(scanTimer);scanTimer=null;}   // freeze ticks on the red frame
         const hit=d;
         setTimeout(()=>{ closeLiveScan(); applyScan(hit); },420);  // hold the red a beat, then GO
@@ -938,13 +938,14 @@ async function scanResolve(cands){
 }
 async function applyScan(d){
   const cands=(d&&d.candidates&&d.candidates.length)?d.candidates:(d&&d.top?[d.top]:[]);
-  if(!cands.length){ toast('No SKU/name found — try a closer, flatter shot'); return; }
+  if(!cands.length){ playUhoh(); toast('No SKU/name found — try a closer, flatter shot'); return; }
   toast('🔎 Matching '+cands.length+' read'+(cands.length>1?'s':'')+'…');
   const hit=await scanResolve(cands);
   collapsed=false;
-  if(!hit){                               // nothing matched anywhere → drop the top read into All-Shopify search
-    chipTo('shop'); $('#q').value=cands[0]; toast('No match — searching all Shopify for '+cands[0]); return doShopSearch();
+  if(!hit){                               // locked a code but nothing matched anywhere → "not right"
+    playUhoh(); chipTo('shop'); $('#q').value=cands[0]; toast('✗ No match for '+cands[0]+' — searching all Shopify'); return doShopSearch();
   }
+  playYoohoo();                           // ✓ resolved to a real product — the happy lock
   $('#q').value=hit.q;
   if(hit.via==='any'){ chipTo('any'); toast('✓ '+hit.q+' — '+hit.n+' on TWIL'); doLookup(); }
   else { chipTo('shop'); toast('✓ '+hit.q+' — '+hit.n+' in Shopify'); doShopSearch(); }

← a045b5f Scan: brand-aware fast-lock + RED locked-in state  ·  back to Dw Photo Capture  ·  Voice search: mic button → speak vendor/SKU/what-to-look-for 3948d6a →