← 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
M data/build.jsonM public/index.html
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 →