Join Waitlist

Finally, a Tuner Built for the Harmonica

Stop fighting chromatic tuners that measue one reed at a time

Start Tuning Accurately
The Solution

How it works

1

Real-time reed-by-reed frequency analysis with cents-accurate deviation display

2

Switchable tuning standards: Just Intonation, Equal Temperament, and 19-limit for custom tunings

3

Overblow/overdraw detection mode that isolates harmonic content chromatic tuners misread as noise

Get early access

Be among the first to try it. Limited spots available.

Starting at $12/mo

We respect your privacy. No spam, ever.

able to break out (audit issue 17). const ORIGIN = "woodshed-harmonicatech"; const KIND = "waitlist"; const SLUG = "woodshed-harmonicatech"; const form = document.getElementById('waitlist-form'); const dialog = document.getElementById('name-dialog'); const successMsg = document.getElementById('success-msg'); let pendingEmail = ''; // Step 1: capture the email, then ask for an optional name before submitting. form.addEventListener('submit', (e) => { e.preventDefault(); pendingEmail = form.email.value.trim(); if (!pendingEmail) return; dialog.classList.remove('hidden'); dialog.classList.add('flex'); document.getElementById('first-name').focus(); }); // Step 2: Finish (with name) or Skip (without) — either way, one POST completes it. async function completeSignup(firstName, lastName) { dialog.classList.add('hidden'); dialog.classList.remove('flex'); const btn = form.querySelector('button'); btn.textContent = 'Joining...'; btn.disabled = true; try { const payload = { email: pendingEmail, origin: ORIGIN, kind: KIND }; if (firstName) payload.firstName = firstName; if (lastName) payload.lastName = lastName; const res = await fetch('/api/landing/' + encodeURIComponent(SLUG) + '/signup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const data = await res.json(); if (!res.ok) { if (data.error === 'Already signed up') { successMsg.textContent = "You're already on the list!"; } else { throw new Error(data.error); } } successMsg.classList.remove('hidden'); form.style.display = 'none'; } catch (err) { btn.textContent = 'Try again'; btn.disabled = false; console.error('Signup error:', err); } } document.getElementById('name-finish').addEventListener('click', () => { completeSignup( document.getElementById('first-name').value.trim(), document.getElementById('last-name').value.trim() ); }); document.getElementById('name-skip').addEventListener('click', () => completeSignup('', ''));