[{"data":1,"prerenderedAt":485},["ShallowReactive",2],{"sideproject-en-tuner-of-albert-guitar":3},{"id":4,"title":5,"body":6,"date":441,"description":442,"extension":443,"featured":444,"image":445,"locale":446,"meta":447,"navigation":474,"order":475,"path":476,"repo":477,"seo":478,"status":479,"stem":480,"technologies":481,"url":477,"__hash__":484},"sideprojects\u002Fen\u002Fsideprojects\u002Ftuner-of-albert-guitar.md","Albert's Tuner (2025)",{"type":7,"value":8,"toc":422},"minimark",[9,13,34,39,42,60,65,89,100,109,118,127,131,161,170,174,178,192,196,206,210,232,236,264,273,277,303,307,340,344,372,376,379,394,397,406],[10,11,5],"h1",{"id":12},"alberts-tuner-2025",[14,15,16,17,20,22,23,33],"p",{},"A chromatic tuner for string instruments with an integrated voice-activated chord finder. Everything runs in the browser: no backend, no API keys, no audio data leaves your device.",[18,19],"br",{},[18,21],{},"\nDeployed as an installable PWA at ",[24,25,26],"strong",{},[27,28,32],"a",{"href":29,"rel":30},"https:\u002F\u002Fafinador.albertsarle.com",[31],"nofollow","afinador.albertsarle.com"," and also packaged as a native Android app (not published on Google Play yet).",[35,36,38],"h2",{"id":37},"what-does-it-do","What does it do?",[14,40,41],{},"Two things:",[43,44,45,53],"ul",{},[46,47,48,52],"li",{},[27,49,51],{"href":50},"#chromatic-tuner","Tune your instrument",": like any tuning app but without ads, registration, or clutter.",[46,54,55,59],{},[27,56,58],{"href":57},"#chord-finder-by-voice","Find chords by speaking",": can't remember a chord? Say it out loud and get the fingering diagram and variations. Much faster than scrolling through chord apps.",[61,62,64],"h3",{"id":63},"chromatic-tuner","🎸 Chromatic Tuner",[43,66,67,74,77,80,83,86],{},[46,68,69,70,73],{},"Real-time pitch detection using the ",[24,71,72],{},"YIN algorithm",", running in a Web Worker so audio processing doesn't block the UI or animations.",[46,75,76],{},"Supports guitar (standard and Drop D tunings), bass, and ukulele.",[46,78,79],{},"Visual precision dial with per-string indicator: tap a string to hear its reference note.",[46,81,82],{},"Adjustable A4 reference frequency (432–445 Hz).",[46,84,85],{},"Optional spectrum analyzer and light\u002Fdark mode.",[46,87,88],{},"Success notification (sound + modal) when all strings are in tune.",[14,90,91,96],{},[92,93],"img",{"alt":94,"src":95},"Tuner in dark mode tuning the F string","https:\u002F\u002Fres.cloudinary.com\u002Fdnyvmvkqi\u002Fimage\u002Fupload\u002Fc_scale,f_auto,q_auto,w_700\u002Fv1784021938\u002Fafinador_fa_dark_qgwevu.jpg",[97,98,99],"em",{},"Precision dial tuning the F string",[14,101,102,106],{},[92,103],{"alt":104,"src":105},"Tuner in dark mode tuning the D string","https:\u002F\u002Fres.cloudinary.com\u002Fdnyvmvkqi\u002Fimage\u002Fupload\u002Fc_scale,f_auto,q_auto,w_700\u002Fv1784021938\u002Fafinador_re_dark_th4gcu.jpg",[97,107,108],{},"The same dial responding to the D string",[14,110,111,115],{},[92,112],{"alt":113,"src":114},"All strings in tune, dark mode","https:\u002F\u002Fres.cloudinary.com\u002Fdnyvmvkqi\u002Fimage\u002Fupload\u002Fc_scale,f_auto,q_auto,w_700\u002Fv1784021938\u002Fafinador_tot_afinat_dark_genjgl.jpg",[97,116,117],{},"Visual feedback when all strings are in tune",[14,119,120,124],{},[92,121],{"alt":122,"src":123},"Tuner in light mode tuning C note","https:\u002F\u002Fres.cloudinary.com\u002Fdnyvmvkqi\u002Fimage\u002Fupload\u002Fc_scale,f_auto,q_auto,w_700\u002Fv1784021938\u002Fafinador_do_light_vbe1bc.jpg",[97,125,126],{},"Same tuner in light mode",[61,128,130],{"id":129},"chord-finder-by-voice","🎤 Chord Finder by Voice",[43,132,133,136,143,146,154],{},[46,134,135],{},"Press-and-speak button: say a chord name aloud (e.g., \"D minor seven\" or \"D minor seventh\") to see the guitar fingering diagram.",[46,137,138,139,142],{},"Recognizes ",[24,140,141],{},"Catalan and Spanish"," in the same voice dictionary.",[46,144,145],{},"Multiple positions per chord (open, barre, etc.) that you can navigate through.",[46,147,148,149,153],{},"Recent chord history, persistent between sessions (",[150,151,152],"code",{},"localStorage",").",[46,155,156,157,160],{},"Requires a browser with ",[150,158,159],{},"SpeechRecognition"," support (Chrome, Edge, Opera, Samsung Internet, Safari 14.1+\u002F14.5+); unsupported browsers like Firefox show a clear notice instead of failing silently.",[14,162,163,167],{},[92,164],{"alt":165,"src":166},"B minor seventh chord diagram, dark mode","https:\u002F\u002Fres.cloudinary.com\u002Fdnyvmvkqi\u002Fimage\u002Fupload\u002Fc_scale,f_auto,q_auto,w_700\u002Fv1784021938\u002Facords_si_menor_septima_1_dark_ngb3g4.jpg",[97,168,169],{},"Fingering diagram generated by saying \"B minor seventh\"",[35,171,173],{"id":172},"technical-architecture","Technical Architecture",[61,175,177],{"id":176},"pitch-detection-yin-in-a-web-worker","Pitch Detection: YIN in a Web Worker",[14,179,180,181,183,184,187,188,191],{},"The tuner's core is an implementation of the ",[24,182,72],{}," (autocorrelation with accumulated error correction), processing 4096-sample blocks at 44.1 kHz to estimate the fundamental frequency of sound from the microphone. This computation runs inside a ",[24,185,186],{},"Web Worker"," (",[150,189,190],{},"workers\u002FpitchWorker.ts",") so that audio analysis, which runs multiple times per second, doesn't compete with the main thread rendering the dial and animations.",[61,193,195],{"id":194},"voice-chord-custom-linguistic-parser","Voice → Chord: Custom Linguistic Parser",[14,197,198,201,202,205],{},[150,199,200],{},"services\u002FchordParser.ts"," converts Web Speech API transcription into a structured chord (",[150,203,204],{},"{root, quality}",") without relying on any NLP or external service — it's a pure function, testable without a microphone. The first word must be a valid note (C, D, E, F, G, A, B), which elegantly resolves the ambiguity of certain words like \"si\" (note vs. affirmation). Quality phrases (\"minor seven\", \"minor seventh\", \"diminished\"...) are matched ordered by word count, longest-first, so \"minor seven\" is recognized before \"minor\" leaves \"seven\" hanging. The same dictionary covers Catalan and Spanish simultaneously.",[61,207,209],{"id":208},"music-theory-engine-and-fingering-database","Music Theory Engine and Fingering Database",[14,211,212,215,216,219,220,223,224,231],{},[150,213,214],{},"services\u002FchordTheory.ts"," defines 9 basic qualities (major, minor, 7, m7, maj7, dim, aug, sus2, sus4) as interval tables and derives each chord's notes. ",[150,217,218],{},"data\u002FchordFingerings.ts"," is a static database of ",[24,221,222],{},"108 combinations"," (12 roots × 9 qualities), each with one or two positions (open and, when applicable, barre), adapted from ",[27,225,228],{"href":226,"rel":227},"https:\u002F\u002Fgithub.com\u002Ftombatossals\u002Fchords-db",[31],[150,229,230],{},"@tombatossals\u002Fchords-db",".",[61,233,235],{"id":234},"clean-separation-of-concerns","Clean Separation of Concerns",[14,237,238,239,242,243,187,246,249,250,249,253,249,256,259,260,263],{},"The codebase follows strict separation: ",[150,240,241],{},"services\u002F"," contains only pure logic (pitch detection, audio engine, parser, music theory) with no React or DOM dependencies, making it easy to test in isolation; ",[150,244,245],{},"hooks\u002F",[150,247,248],{},"useTuner",", ",[150,251,252],{},"useVoiceChord",[150,254,255],{},"useModalA11y",[150,257,258],{},"useOnline",") encapsulate lifecycle and state, reusing a ref pattern to avoid stale closures inside single-registered listeners; ",[150,261,262],{},"components\u002F"," just renders.",[14,265,266,270],{},[92,267],{"alt":268,"src":269},"Settings panel, dark mode","https:\u002F\u002Fres.cloudinary.com\u002Fdnyvmvkqi\u002Fimage\u002Fupload\u002Fc_scale,f_auto,q_auto,w_700\u002Fv1784021938\u002Fconfiguracio_dark_1_demtw2.jpg",[97,271,272],{},"Settings: instrument selection, reference frequency, and visual preferences",[35,274,276],{"id":275},"from-pwa-to-native-android-app","From PWA to Native Android App",[14,278,279,280,283,284,287,288,291,292,295,296,299,300,302],{},"The app is built as a ",[24,281,282],{},"Progressive Web App"," with ",[150,285,286],{},"vite-plugin-pwa"," (manifest, icons, and service worker with automatic background updates), installable directly from the browser. On top of that foundation, it's also packaged as a ",[24,289,290],{},"native Android app"," via a ",[24,293,294],{},"Trusted Web Activity (TWA)"," generated with Bubblewrap: signed with its own keystore, verified with an ",[150,297,298],{},"assetlinks.json"," file confirming to the OS that the app and the domain ",[150,301,32],{}," belong to the same owner, and accompanied by its own privacy policy page (a Google Play publication requirement).",[35,304,306],{"id":305},"quality-accessibility-and-resilience","Quality, Accessibility, and Resilience",[43,308,309,315,324],{},[46,310,311,314],{},[24,312,313],{},"Tests (Vitest)",": 5 test files cover pitch detection, audio engine, voice parser, and music theory — all sensitive logic lives in pure functions easy to test without mocking the DOM or microphone.",[46,316,317,320,321,323],{},[24,318,319],{},"Accessibility",": ",[150,322,255],{}," implements focus trapping in modals; the mic button also responds to spacebar for keyboard users.",[46,325,326,329,330,333,334,336,337,339],{},[24,327,328],{},"Resilience",": retry button when audio access fails, error messages in Catalan and Spanish over ",[150,331,332],{},"DOMException"," codes, connectivity detection (",[150,335,258],{},") with UI indicators when offline, and a clear message (not silent failure) on browsers without ",[150,338,159],{}," support.",[35,341,343],{"id":342},"tech-stack","Tech Stack",[43,345,346,349,352,355,358,361,363,366,369],{},[46,347,348],{},"React 19 & TypeScript",[46,350,351],{},"Vite 6",[46,353,354],{},"Tailwind CSS",[46,356,357],{},"Web Audio API",[46,359,360],{},"Web Speech API",[46,362,286],{},[46,364,365],{},"Vitest",[46,367,368],{},"Docker & Nginx",[46,370,371],{},"TWA \u002F Bubblewrap (Android)",[35,373,375],{"id":374},"deployment","Deployment",[14,377,378],{},"Multi-stage Docker image, served with Nginx:",[380,381,382,388],"ol",{},[46,383,384,387],{},[24,385,386],{},"Stage 1",": builds the React\u002FVite app with Node.",[46,389,390,393],{},[24,391,392],{},"Stage 2",": copies only the resulting static assets and serves them with Nginx, with cache headers tuned for a SPA and service worker.",[395,396],"hr",{},[14,398,399,402,403],{},[24,400,401],{},"Try it yourself:"," ",[27,404,29],{"href":29,"rel":405},[31],[14,407,408],{},[97,409,410,411,416,417,231],{},"Chord fingerings adapted from ",[27,412,414],{"href":226,"rel":413},[31],[150,415,230],{}," 0.5.1, Copyright (c) 2016 David Rubert, licensed under the ",[27,418,421],{"href":419,"rel":420},"https:\u002F\u002Fgithub.com\u002Ftombatossals\u002Fchords-db\u002Fblob\u002Fmaster\u002FLICENSE",[31],"MIT License",{"title":423,"searchDepth":424,"depth":424,"links":425},"",2,[426,431,437,438,439,440],{"id":37,"depth":424,"text":38,"children":427},[428,430],{"id":63,"depth":429,"text":64},3,{"id":129,"depth":429,"text":130},{"id":172,"depth":424,"text":173,"children":432},[433,434,435,436],{"id":176,"depth":429,"text":177},{"id":194,"depth":429,"text":195},{"id":208,"depth":429,"text":209},{"id":234,"depth":429,"text":235},{"id":275,"depth":424,"text":276},{"id":305,"depth":424,"text":306},{"id":342,"depth":424,"text":343},{"id":374,"depth":424,"text":375},"2026-07-14T12:00:00Z","Chromatic tuner for string instruments with voice-activated chord finder (Catalan\u002FSpanish), 100% in the browser and packaged as an Android app.","md",false,"https:\u002F\u002Fres.cloudinary.com\u002Fdnyvmvkqi\u002Fimage\u002Fupload\u002Fc_fill,ar_2:1,g_auto,f_auto,q_auto,w_1200\u002Fv1784022134\u002Fdemo_screenshots_pszgvu.png","en",{"slug":448,"type":449,"categories":450,"tags":451,"achievements":455,"metrics":461,"liveUrl":29,"githubUrl":423},"tuner-of-albert-guitar","sideproject",[449],[452,357,360,282,453,454,354,365],"React & TypeScript","Android (TWA)","Vite",[456,457,458,459,460],"Real-time pitch detection using the YIN algorithm inside a Web Worker","Voice-activated chord finder in Catalan and Spanish (Web Speech API)","Custom database of 108 chord fingerings (12 roots × 9 qualities)","Packaged as a native Android app via Trusted Web Activity","Full test suite (Vitest) for audio, voice, and music theory logic",[462,465,468,471],{"label":463,"value":464},"Chord Fingerings","108",{"label":466,"value":467},"Instruments","3",{"label":469,"value":470},"A4 Reference Range","432–445 Hz",{"label":472,"value":473},"Voice Languages","CA \u002F ES",true,1,"\u002Fen\u002Fsideprojects\u002Ftuner-of-albert-guitar",null,{"title":5,"description":442},"published","en\u002Fsideprojects\u002Ftuner-of-albert-guitar",[348,351,354,482,360,286,365,368,483],"Web Audio API (YIN pitch detection)","TWA (Bubblewrap) for Android","Qa3Zq6gOaG1nVHh0KITxlB9tGboOMnIiV0YMDGCrX3U",1790587323234]