// Singing parts. The selected part sets the highlighted range on the keyboard and where // the keyboard opens. Chosen on the About page, stored in this browser's localStorage. export type SingingPart = { id: string label: string low: number // MIDI note number high: number // MIDI note number lowName: string highName: string } export const PARTS: SingingPart[] = [ { id: 'soprano', label: 'Soprano', low: 60, high: 81, lowName: 'C4', highName: 'A5' }, { id: 'alto', label: 'Alto', low: 53, high: 74, lowName: 'F3', highName: 'D5' }, { id: 'tenor', label: 'Tenor', low: 48, high: 69, lowName: 'C3', highName: 'A4' }, { id: 'baritone', label: 'Baritone', low: 45, high: 65, lowName: 'A2', highName: 'F4' }, { id: 'bass', label: 'Bass', low: 40, high: 64, lowName: 'E2', highName: 'E4' }, ] export const DEFAULT_PART_ID = 'tenor' /** localStorage key. Same `pocket-piano:` prefix and JSON encoding as the sustain setting. */ export const PART_STORAGE_KEY = 'pocket-piano:part' // Browser helpers shared by the piano and About pages (inlined before their scripts). // Plain JS in a raw string: no backticks or template placeholders inside. export const PARTS_JS = 'window.pocketPianoParts = (function () {\n' + "\t'use strict'\n" + '\tvar PARTS = ' + JSON.stringify(PARTS) + '\n\tvar DEFAULT_ID = ' + JSON.stringify(DEFAULT_PART_ID) + '\n\tvar KEY = ' + JSON.stringify(PART_STORAGE_KEY) + '\n' + String.raw` function byId(id) { for (var i = 0; i < PARTS.length; i++) if (PARTS[i].id === id) return PARTS[i] return null } function read() { var id = null try { var raw = localStorage.getItem(KEY) id = raw === null ? null : JSON.parse(raw) } catch (e) {} return byId(id) || byId(DEFAULT_ID) } function write(id) { var part = byId(id) if (!part) return read() try { localStorage.setItem(KEY, JSON.stringify(part.id)) } catch (e) {} return part } // Where the keyboard opens for a part: pos is the white-key index at the left edge, // span is how many white keys are visible. Prefer a window that starts on a C and still // shows the whole part (closest to centered), otherwise center the part. function homePos(whites, part, span) { var maxPos = Math.max(0, whites.length - span) var lo = whites.indexOf(part.low) var hi = whites.indexOf(part.high) if (lo < 0) lo = whites.indexOf(part.low - 1) if (hi < 0) hi = whites.indexOf(part.high + 1) var centered = Math.round((lo + hi + 1) / 2 - span / 2) var best = null for (var p = Math.max(0, hi - span + 1); p <= Math.min(lo, maxPos); p++) { if (whites[p] % 12 !== 0) continue if (best === null || Math.abs(p - centered) < Math.abs(best - centered)) best = p } return Math.min(maxPos, Math.max(0, best === null ? centered : best)) } return { list: PARTS, defaultId: DEFAULT_ID, key: KEY, byId: byId, read: read, write: write, homePos: homePos } })() `