(function () { 'use strict'; function freqToMidi(f) { return 69 + 12 * Math.log2(f / 440); } function midiToFreq(m) { return Math.pow(2, (m - 69) / 12) * 440; } const _NOTE_NAMES = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B']; const _NOTE_FLAT = ['C','Db','D','Eb', 'E','F','Gb','G','Ab','A','Bb', 'B']; // Returns true when the tuning name implies flat notation (e.g. "Eb Standard", "Bb Standard"). function preferFlats(tuningName) { return typeof tuningName === 'string' && /\b[A-G]b\b/.test(tuningName); } function midiToNote(m, useFlats) { return (useFlats ? _NOTE_FLAT : _NOTE_NAMES)[((Math.round(m) % 12) + 12) % 12]; } // Per-string-count standard open-string MIDI arrays const _BASE_MIDI = { 4: [28, 33, 38, 43], // bass 4: E1 A1 D2 G2 5: [23, 28, 33, 38, 43], // bass 5: B0 E1 A1 D2 G2 6: [40, 45, 50, 55, 59, 64], // guitar 6: E2 A2 D3 G3 B3 E4 7: [35, 40, 45, 50, 55, 59, 64], // guitar 7: B1 E2 A2 D3 G3 B3 E4 8: [30, 35, 40, 45, 50, 55, 59, 64], // guitar 8: F#1 B1 E2 A2 D3 G3 B3 E4 }; function offsetsToFreqs(offsets, isBass) { const len = offsets.length; let base; if (len === 4 || len === 5) { base = isBass ? _BASE_MIDI[len] : _BASE_MIDI[6]; } else { base = _BASE_MIDI[len] || _BASE_MIDI[6]; } return offsets.map((offset, i) => { const root = i < base.length ? base[i] : base[base.length - 1]; return midiToFreq(root + offset); }); } function getTuningName(offsets) { if (!offsets || offsets.length === 0) return 'Unknown'; const len = offsets.length; if (len < 4 || len > 8) return offsets.join(' '); // First-string open MIDI for this length (same table as _BASE_MIDI column 0). const firstStringMidi = (_BASE_MIDI[len] || _BASE_MIDI[6])[0]; const noteNames = ['C','C#','D','Eb','E','F','F#','G','Ab','A','A#','B']; // All-equal: name by the note the lowest string becomes at this offset. if (offsets.every(o => o === offsets[0])) { const noteIdx = ((firstStringMidi + offsets[0]) % 12 + 12) % 12; return noteNames[noteIdx] + ' Standard'; } // Drop tuning: first string is exactly 2 semitones below the rest (all equal). if (offsets[0] === offsets[1] - 2 && offsets.slice(1).every(o => o === offsets[1])) { const noteIdx = ((firstStringMidi + offsets[0]) % 12 + 12) % 12; return 'Drop ' + noteNames[noteIdx]; } // Named lookup table for specific patterns const named = { // 6-string '-2,0,0,0,0,0': 'Drop D', '-4,-2,-2,-2,-2,-2': 'Drop C', '-2,-2,0,0,0,0': 'Double Drop D', '0,0,0,-1,0,0': 'Open G', '-2,-2,0,0,-2,-2': 'Open D', '-2,0,0,0,-2,0': 'DADGAD', '0,2,2,1,0,0': 'Open E', '-2,0,0,2,3,2': 'Open D (alt)', // 7-string '-2,0,0,0,0,0,0': 'Drop A', // 8-string '-2,0,0,0,0,0,0,0': 'Drop E', }; const key = offsets.join(','); if (named[key]) return named[key]; return offsets.join(' '); } window._tunerUtils = { freqToMidi, midiToFreq, midiToNote, offsetsToFreqs, getTuningName, preferFlats }; })();