Guitar neck diagram
:root {
–tp: #1a1a18; –ts: #5f5e5a; –tt: #888780;
–bg: #ffffff; –bg2: #f5f4f0;
–border: rgba(0,0,0,0.10);
–font: system-ui,-apple-system,sans-serif;
–tf: #E1F5EE; –ts2: #0F6E56; –tt2: #085041;
–cf: #FAECE7; –cs: #993C1D; –ct: #712B13;
}
@media (prefers-color-scheme:dark) {
:root {
–tp: #e8e6dc; –ts: #b4b2a9; –tt: #888780;
–bg: #1c1c1a; –bg2: #252523; –border: rgba(255,255,255,0.08);
–tf: #085041; –ts2: #5DCAA5; –tt2: #9FE1CB;
–cf: #712B13; –cs: #F0997B; –ct: #F5C4B3;
}
}
*{ box-sizing:border-box; margin:0; padding:0; }
body{ font-family:var(–font); background:var(–bg); color:var(–tp); padding:24px; min-height:100vh; }
h1{ font-size:18px; font-weight:500; margin-bottom:4px; }
.subtitle{ font-size:13px; color:var(–ts); margin-bottom:20px; line-height:1.6; }
.controls{ display:flex; flex-wrap:wrap; gap:14px; margin-bottom:20px; align-items:flex-end; }
.ctrl-group{ display:flex; flex-direction:column; gap:5px; }
.ctrl-group label{ font-size:12px; color:var(–ts); font-weight:500; }
select, input[type=text]{
font-family:var(–font); font-size:13px; background:var(–bg2); color:var(–tp);
border:1px solid var(–border); border-radius:6px; padding:6px 10px;
outline:none; cursor:pointer; min-width:220px;
}
select:focus, input:focus{ border-color:var(–ts2); }
.tuning-row{ display:flex; gap:6px; }
.tuning-row input{ width:52px !important; min-width:unset !important; text-align:center; font-weight:500; }
.err{ color:#c0392b; font-size:12px; margin-top:4px; display:none; }
.scroll-wrap{
overflow-x:auto; border:1px solid var(–border);
border-radius:8px; padding:12px 0 4px; background:var(–bg2);
}
svg .c-teal circle, svg .c-teal rect{ fill:var(–tf); stroke:var(–ts2); stroke-width:1; }
svg .c-teal text{ fill:var(–tt2); }
svg .c-coral circle, svg .c-coral rect{ fill:var(–cf); stroke:var(–cs); stroke-width:1; }
svg .c-coral text{ fill:var(–ct); }
svg .th{ font-family:var(–font); font-size:14px; font-weight:500; fill:var(–tp); }
svg .ts{ font-family:var(–font); font-size:12px; font-weight:400; fill:var(–ts); }
.legend{ display:flex; gap:24px; margin-top:14px; align-items:center; flex-wrap:wrap; }
.legend-item{ display:flex; align-items:center; gap:7px; font-size:13px; color:var(–ts); }
.swatch{ width:20px; height:20px; border-radius:50%; border:1.5px solid; flex-shrink:0; }
.sw-root{ background:var(–cf); border-color:var(–cs); }
.sw-scale{ background:var(–tf); border-color:var(–ts2); }
.notes-label{ margin-left:auto; font-size:12px; color:var(–tt); }
optgroup{ font-weight:600; }
/* ── Piano keyboard ── */
.piano-wrap{
margin-top:28px;
}
.piano-heading{
font-size:13px; font-weight:500; color:var(–ts); margin-bottom:10px;
}
.piano-scroll{
overflow-x:auto; border:1px solid var(–border);
border-radius:8px; padding:16px 20px 12px; background:var(–bg2);
display:inline-block; max-width:100%;
}
/* ── Flute fingerings ── */
.flute-wrap{
margin-top:32px;
}
.flute-heading{
font-size:13px; font-weight:500; color:var(–ts); margin-bottom:10px;
}
.flute-scroll{
overflow-x:auto; border:1px solid var(–border);
border-radius:8px; padding:20px; background:var(–bg2);
}
.flute-row{
display:flex; gap:18px; align-items:flex-start; flex-wrap:nowrap;
}
.flute-card{
display:flex; flex-direction:column; align-items:center; gap:6px; flex-shrink:0;
}
.flute-card .note-name{
font-size:12px; font-weight:600; color:var(–tp); text-align:center; line-height:1.2;
}
.flute-card .note-name.is-root{ color:var(–cs); }
.flute-card .octave-tag{
font-size:10px; color:var(–ts); text-align:center;
}
/* key circles inside flute SVG */
svg .fk-open { fill:var(–bg); stroke:var(–ts); stroke-width:1.5; }
svg .fk-closed{ fill:var(–tp); stroke:var(–ts); stroke-width:1.5; }
svg .fk-half { fill:url(#halfFill); stroke:var(–ts); stroke-width:1.5; }
svg .fk-trill { fill:#e8d88a; stroke:#9a8a20; stroke-width:1.5; }
svg .fk-oct { fill:var(–tp); stroke:var(–ts); stroke-width:1.2; }
svg .fk-oct-o { fill:var(–bg); stroke:var(–ts); stroke-width:1.2; }
svg .fl-body { fill:none; stroke:var(–ts); stroke-width:2; opacity:0.35; }
svg .fl-label { font-family:var(–font); font-size:8px; fill:var(–ts); text-anchor:middle; }
svg .wk{ fill:#f8f7f3; stroke:#c8c6bc; stroke-width:1; }
svg .bk{ fill:#f0eeea; stroke:#b0aea4; stroke-width:1; }
svg .wk-root{ fill:var(–cf); stroke:var(–cs); stroke-width:1.5; }
svg .bk-root{ fill:var(–cs); stroke:var(–ct); stroke-width:1.5; }
svg .wk-scale{ fill:var(–tf); stroke:var(–ts2); stroke-width:1.5; }
svg .bk-scale{ fill:var(–ts2); stroke:var(–tt2); stroke-width:1.5; }
svg .key-label{ font-family:var(–font); font-size:10px; fill:var(–ts); text-anchor:middle; }
svg .key-label-dark{ font-family:var(–font); font-size:9px; fill:var(–ts); text-anchor:middle; }
svg .key-label-active{ fill:var(–ct); }
svg .key-label-scale{ fill:var(–tt2); }
Guitar neck diagram
C
C#
D
D#
E
F
F#
G
G#
A
A#
B
Scale / mode
Chord
Messiaen mode 1 (whole tone)
Messiaen mode 2 (octatonic)
Messiaen mode 3
Messiaen mode 4
Messiaen mode 5
Messiaen mode 6
Messiaen mode 7
Major (Ionian)
Natural minor (Aeolian)
Dorian
Phrygian
Lydian
Mixolydian
Locrian
Harmonic minor
Melodic minor
Phrygian dominant
Lydian dominant
Major pentatonic
Minor pentatonic
Blues
Diminished (H-W)
Diminished (W-H)
Augmented
Chromatic
— custom —
Standard (E A D G B E)
Drop D (D A D G B E)
Open G (D G D G B D)
Open D (D A D F# A D)
Open E (E B E G# B E)
DADGAD (D A D G A D)
Half step down (Eb Ab Db Gb Bb Eb)
Full step down (D G C F A D)
Root
Scale tone
const CHROMATIC = [“C”, “C#”, “D”, “D#”, “E”, “F”, “F#”, “G”, “G#”, “A”, “A#”, “B”];
const SCALES = {“messiaen_1”: {“name”: “Messiaen mode 1 (whole tone)”, “desc”: “6 notes \u00b7 2 transpositions \u00b7 All whole steps”, “intervals”: [0, 2, 4, 6, 8, 10]}, “messiaen_2”: {“name”: “Messiaen mode 2 (octatonic)”, “desc”: “8 notes \u00b7 3 transpositions \u00b7 Semitone\u2013tone repeating \u2014 his most-used mode”, “intervals”: [0, 1, 3, 4, 6, 7, 9, 10]}, “messiaen_3”: {“name”: “Messiaen mode 3”, “desc”: “9 notes \u00b7 4 transpositions \u00b7 Tone\u2013semitone\u2013semitone repeating”, “intervals”: [0, 2, 3, 4, 6, 7, 8, 10, 11]}, “messiaen_4”: {“name”: “Messiaen mode 4”, “desc”: “8 notes \u00b7 6 transpositions \u00b7 Semitone\u2013semitone\u2013min3rd\u2013semitone repeating”, “intervals”: [0, 1, 2, 5, 6, 7, 8, 11]}, “messiaen_5”: {“name”: “Messiaen mode 5”, “desc”: “6 notes \u00b7 6 transpositions \u00b7 Semitone\u2013maj3rd\u2013semitone repeating”, “intervals”: [0, 1, 5, 6, 7, 11]}, “messiaen_6”: {“name”: “Messiaen mode 6”, “desc”: “8 notes \u00b7 6 transpositions \u00b7 Tone\u2013tone\u2013semitone\u2013semitone repeating”, “intervals”: [0, 2, 4, 5, 6, 8, 10, 11]}, “messiaen_7”: {“name”: “Messiaen mode 7”, “desc”: “10 notes \u00b7 6 transpositions \u00b7 Semitone\u2013semitone\u2013semitone\u2013tone\u2013semitone repeating”, “intervals”: [0, 1, 2, 3, 5, 6, 7, 8, 9, 11]}, “major”: {“name”: “Major (Ionian)”, “desc”: “7 notes \u00b7 W W H W W W H”, “intervals”: [0, 2, 4, 5, 7, 9, 11]}, “minor”: {“name”: “Natural minor (Aeolian)”, “desc”: “7 notes \u00b7 W H W W H W W”, “intervals”: [0, 2, 3, 5, 7, 8, 10]}, “dorian”: {“name”: “Dorian”, “desc”: “7 notes \u00b7 W H W W W H W”, “intervals”: [0, 2, 3, 5, 7, 9, 10]}, “phrygian”: {“name”: “Phrygian”, “desc”: “7 notes \u00b7 H W W W H W W”, “intervals”: [0, 1, 3, 5, 7, 8, 10]}, “lydian”: {“name”: “Lydian”, “desc”: “7 notes \u00b7 W W W H W W H”, “intervals”: [0, 2, 4, 6, 7, 9, 11]}, “mixolydian”: {“name”: “Mixolydian”, “desc”: “7 notes \u00b7 W W H W W H W”, “intervals”: [0, 2, 4, 5, 7, 9, 10]}, “locrian”: {“name”: “Locrian”, “desc”: “7 notes \u00b7 H W W H W W W”, “intervals”: [0, 1, 3, 5, 6, 8, 10]}, “harmonic_minor”: {“name”: “Harmonic minor”, “desc”: “7 notes \u00b7 Natural minor with raised 7th”, “intervals”: [0, 2, 3, 5, 7, 8, 11]}, “melodic_minor”: {“name”: “Melodic minor”, “desc”: “7 notes \u00b7 Natural minor with raised 6th & 7th”, “intervals”: [0, 2, 3, 5, 7, 9, 11]}, “phrygian_dom”: {“name”: “Phrygian dominant”, “desc”: “7 notes \u00b7 Phrygian with raised 3rd”, “intervals”: [0, 1, 4, 5, 7, 8, 10]}, “lydian_dom”: {“name”: “Lydian dominant”, “desc”: “7 notes \u00b7 Lydian with b7”, “intervals”: [0, 2, 4, 6, 7, 9, 10]}, “pentatonic”: {“name”: “Major pentatonic”, “desc”: “5 notes”, “intervals”: [0, 2, 4, 7, 9]}, “minor_pent”: {“name”: “Minor pentatonic”, “desc”: “5 notes”, “intervals”: [0, 3, 5, 7, 10]}, “blues”: {“name”: “Blues”, “desc”: “6 notes \u00b7 Minor pent + b5”, “intervals”: [0, 3, 5, 6, 7, 10]}, “diminished”: {“name”: “Diminished (H-W)”, “desc”: “8 notes \u00b7 Semitone\u2013tone alternating”, “intervals”: [0, 1, 3, 4, 6, 7, 9, 10]}, “dim_wh”: {“name”: “Diminished (W-H)”, “desc”: “8 notes \u00b7 Tone\u2013semitone alternating”, “intervals”: [0, 2, 3, 5, 6, 8, 9, 11]}, “augmented”: {“name”: “Augmented”, “desc”: “6 notes \u00b7 Min3rd\u2013semitone repeating”, “intervals”: [0, 3, 4, 7, 8, 11]}, “chromatic”: {“name”: “Chromatic”, “desc”: “12 notes”, “intervals”: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]}};
const CHORDS = {“major”: {“name”: “Major”, “desc”: “1 3 5”, “intervals”: [0, 4, 7]}, “minor”: {“name”: “Minor”, “desc”: “1 b3 5”, “intervals”: [0, 3, 7]}, “dim”: {“name”: “Diminished”, “desc”: “1 b3 b5”, “intervals”: [0, 3, 6]}, “aug”: {“name”: “Augmented”, “desc”: “1 3 #5”, “intervals”: [0, 4, 8]}, “sus2”: {“name”: “Sus2”, “desc”: “1 2 5”, “intervals”: [0, 2, 7]}, “sus4”: {“name”: “Sus4”, “desc”: “1 4 5”, “intervals”: [0, 5, 7]}, “power”: {“name”: “Power (5th)”, “desc”: “1 5”, “intervals”: [0, 7]}, “maj6”: {“name”: “Major 6”, “desc”: “1 3 5 6”, “intervals”: [0, 4, 7, 9]}, “min6”: {“name”: “Minor 6”, “desc”: “1 b3 5 6”, “intervals”: [0, 3, 7, 9]}, “six_nine”: {“name”: “6/9”, “desc”: “1 3 5 6 9”, “intervals”: [0, 2, 4, 7, 9]}, “maj7”: {“name”: “Major 7”, “desc”: “1 3 5 7”, “intervals”: [0, 4, 7, 11]}, “dom7”: {“name”: “Dominant 7”, “desc”: “1 3 5 b7”, “intervals”: [0, 4, 7, 10]}, “min7”: {“name”: “Minor 7”, “desc”: “1 b3 5 b7”, “intervals”: [0, 3, 7, 10]}, “minmaj7”: {“name”: “Minor/Major 7”, “desc”: “1 b3 5 7”, “intervals”: [0, 3, 7, 11]}, “half_dim”: {“name”: “Half-dim (m7b5)”, “desc”: “1 b3 b5 b7”, “intervals”: [0, 3, 6, 10]}, “dim7”: {“name”: “Diminished 7”, “desc”: “1 b3 b5 bb7”, “intervals”: [0, 3, 6, 9]}, “augmaj7”: {“name”: “Augmented Maj7”, “desc”: “1 3 #5 7”, “intervals”: [0, 4, 8, 11]}, “aug7”: {“name”: “Augmented 7 (7#5)”, “desc”: “1 3 #5 b7”, “intervals”: [0, 4, 8, 10]}, “dom7sus4”: {“name”: “Dom 7sus4”, “desc”: “1 4 5 b7”, “intervals”: [0, 5, 7, 10]}, “dom7b5”: {“name”: “Dom 7b5”, “desc”: “1 3 b5 b7”, “intervals”: [0, 4, 6, 10]}, “add9”: {“name”: “Add9”, “desc”: “1 3 5 9”, “intervals”: [0, 2, 4, 7]}, “madd9”: {“name”: “m(add9)”, “desc”: “1 b3 5 9”, “intervals”: [0, 2, 3, 7]}, “add11”: {“name”: “Add11”, “desc”: “1 3 5 11”, “intervals”: [0, 4, 5, 7]}, “maj9”: {“name”: “Major 9”, “desc”: “1 3 5 7 9”, “intervals”: [0, 2, 4, 7, 11]}, “dom9”: {“name”: “Dominant 9”, “desc”: “1 3 5 b7 9”, “intervals”: [0, 2, 4, 7, 10]}, “min9”: {“name”: “Minor 9”, “desc”: “1 b3 5 b7 9”, “intervals”: [0, 2, 3, 7, 10]}, “minmaj9”: {“name”: “Minor/Major 9”, “desc”: “1 b3 5 7 9”, “intervals”: [0, 2, 3, 7, 11]}, “dom7b9”: {“name”: “Dom 7b9”, “desc”: “1 3 5 b7 b9”, “intervals”: [0, 1, 4, 7, 10]}, “dom7s9”: {“name”: “Dom 7#9 (Hendrix)”, “desc”: “1 3 5 b7 #9”, “intervals”: [0, 3, 4, 7, 10]}, “dom7b9s9”: {“name”: “Dom 7b9#9”, “desc”: “1 3 5 b7 b9 #9”, “intervals”: [0, 1, 3, 4, 7, 10]}, “maj11”: {“name”: “Major 11”, “desc”: “1 3 5 7 9 11”, “intervals”: [0, 2, 4, 5, 7, 11]}, “dom11”: {“name”: “Dominant 11”, “desc”: “1 3 5 b7 9 11”, “intervals”: [0, 2, 4, 5, 7, 10]}, “min11”: {“name”: “Minor 11”, “desc”: “1 b3 5 b7 9 11”, “intervals”: [0, 2, 3, 5, 7, 10]}, “dom7s11”: {“name”: “Dom 7#11 (Lydian dom)”, “desc”: “1 3 5 b7 #11”, “intervals”: [0, 4, 6, 7, 10]}, “maj7s11”: {“name”: “Maj 7#11”, “desc”: “1 3 5 7 #11”, “intervals”: [0, 4, 6, 7, 11]}, “maj13”: {“name”: “Major 13”, “desc”: “1 3 5 7 9 11 13”, “intervals”: [0, 2, 4, 5, 7, 9, 11]}, “dom13”: {“name”: “Dominant 13”, “desc”: “1 3 5 b7 9 11 13”, “intervals”: [0, 2, 4, 5, 7, 9, 10]}, “min13”: {“name”: “Minor 13”, “desc”: “1 b3 5 b7 9 11 13”, “intervals”: [0, 2, 3, 5, 7, 9, 10]}, “dom13s11”: {“name”: “Dom 13#11”, “desc”: “1 3 5 b7 9 #11 13”, “intervals”: [0, 2, 4, 6, 7, 9, 10]}, “alt”: {“name”: “Altered (7alt)”, “desc”: “1 3 b7 b9 #9 b13”, “intervals”: [0, 1, 3, 4, 8, 10]}, “dom7b9b13”: {“name”: “Dom 7b9b13”, “desc”: “1 3 5 b7 b9 b13”, “intervals”: [0, 1, 4, 7, 8, 10]}, “dom7s9s11”: {“name”: “Dom 7#9#11”, “desc”: “1 3 b7 #9 #11”, “intervals”: [0, 3, 4, 6, 10]}, “dom9s11”: {“name”: “Dom 9#11”, “desc”: “1 3 5 b7 9 #11”, “intervals”: [0, 2, 4, 6, 7, 10]}, “dom9b13”: {“name”: “Dom 9b13”, “desc”: “1 3 5 b7 9 b13”, “intervals”: [0, 2, 4, 7, 8, 10]}};
const PRESETS = {“Standard (E A D G B E)”: “E,A,D,G,B,E”, “Drop D (D A D G B E)”: “D,A,D,G,B,E”, “Open G (D G D G B D)”: “D,G,D,G,B,D”, “Open D (D A D F# A D)”: “D,A,D,F#,A,D”, “Open E (E B E G# B E)”: “E,B,E,G#,B,E”, “DADGAD (D A D G A D)”: “D,A,D,G,A,D”, “Half step down (Eb Ab Db Gb Bb Eb)”: “Eb,Ab,Db,Gb,Bb,Eb”, “Full step down (D G C F A D)”: “D,G,C,F,A,D”};
const FRET_X = [72,152,232,310,386,460,532,602,670,736,800,862,922,1082];
const STRING_Y = [68,118,168,218,268,318];
const STR_WIDTHS = [3,2.4,1.9,1.4,1.0,0.7];
const DOT_R = 13;
const NECK_X0=72, NECK_X1=1082, NECK_Y0=32, NECK_Y1=348;
const ENHARMONIC = {Db:’C#’,Eb:’D#’,Fb:’E’,Gb:’F#’,Ab:’G#’,Bb:’A#’,Cb:’B’};
function norm(n) {
const s = n.trim();
const cap = s.charAt(0).toUpperCase() + s.slice(1).toLowerCase()
.replace(‘b’,’b’).replace(‘#’,’#’);
const cap2 = s.charAt(0).toUpperCase() + s.slice(1);
return ENHARMONIC[cap2] || cap2;
}
function fretMid(f) { return Math.floor((FRET_X[f]+FRET_X[f+1])/2); }
function noteAt(open, fret) { return CHROMATIC[(CHROMATIC.indexOf(open)+fret)%12]; }
function svgEl(tag, attrs) {
const el = document.createElementNS(‘http://www.w3.org/2000/svg’, tag);
for (const [k,v] of Object.entries(attrs)) el.setAttribute(k,v);
return el;
}
function svgTxt(attrs, text) {
const el = svgEl(‘text’, attrs);
el.textContent = text;
return el;
}
function buildNeck(tuning, noteSet, root) {
const svg = document.getElementById(‘neck’);
svg.innerHTML=”;
const a = el => svg.appendChild(el);
a(svgEl(‘rect’,{x:NECK_X0,y:NECK_Y0,width:NECK_X1-NECK_X0,height:NECK_Y1-NECK_Y0,rx:6,fill:’#C8A96A’,opacity:’0.15′}));
a(svgEl(‘rect’,{x:NECK_X0,y:NECK_Y0,width:8,height:NECK_Y1-NECK_Y0,rx:2,fill:’var(–tp)’,opacity:’0.3′}));
for (let f=1;f
a(svgEl(‘circle’,{cx:fretMid(f),cy,r:7,fill:’var(–tp)’,opacity:’0.10′})));
STRING_Y.forEach((sy,i)=>
a(svgEl(‘line’,{x1:NECK_X0,y1:sy,x2:NECK_X1,y2:sy,stroke:’var(–ts)’,’stroke-width’:STR_WIDTHS[i],opacity:’0.5′})));
// Open string labels
STRING_Y.forEach((sy,i)=>
a(svgTxt({x:56,y:sy,’text-anchor’:’middle’,’dominant-baseline’:’central’,’font-weight’:’500′,class:’ts’}, tuning[i])));
// Fret numbers
a(svgTxt({x:fretMid(0),y:372,’text-anchor’:’middle’,class:’ts’},’open’));
for (let f=1;f{
for (let fret=0;fretnorm(i.value));
}
function update() {
const key = document.getElementById(‘sel-key’).value;
const mode = document.getElementById(‘sel-mode’).value;
const tuning = getTuning();
const errEl = document.getElementById(‘tuning-err’);
if (tuning.some(n=>!CHROMATIC.includes(n))) { errEl.style.display=’block’; return; }
errEl.style.display=’none’;
const rootIdx = CHROMATIC.indexOf(key);
let entry, notes, label;
if (mode === ‘chord’) {
const ckey = document.getElementById(‘sel-chord’).value;
entry = CHORDS[ckey];
notes = entry.intervals.map(i=>CHROMATIC[(rootIdx+i)%12]);
label = key + ‘ ‘ + entry.name;
document.getElementById(‘subtitle’).textContent = label + ‘ · ‘ + entry.desc;
document.getElementById(‘leg-tone-label’).textContent = ‘Chord tone’;
} else {
const skey = document.getElementById(‘sel-scale’).value;
entry = SCALES[skey];
notes = entry.intervals.map(i=>CHROMATIC[(rootIdx+i)%12]);
label = entry.name;
document.getElementById(‘subtitle’).textContent = label + ‘ · ‘ + entry.desc;
document.getElementById(‘leg-tone-label’).textContent = ‘Scale tone’;
}
const noteSet = new Set(notes);
buildNeck(tuning, noteSet, key);
if (window.buildPiano) buildPiano(noteSet, key);
if (window.buildFlute) buildFlute(notes, key);
document.getElementById(‘leg-root’).textContent = key + ‘ (root)’;
document.getElementById(‘leg-notes’).textContent = ‘Notes: ‘ + notes.join(‘ ‘);
}
function buildTuningInputs(tuning) {
const row = document.getElementById(‘tuning-row’);
row.innerHTML=”;
const labels=[‘6 (low)’,’5′,’4′,’3′,’2′,’1 (high)’];
tuning.forEach((note,i)=>{
const inp = document.createElement(‘input’);
inp.type=’text’; inp.value=note; inp.maxLength=3; inp.title=labels[i]+’ string’;
inp.addEventListener(‘input’,()=>{
document.getElementById(‘sel-preset’).value=”;
update();
});
row.appendChild(inp);
});
}
// Preset tuning handler
document.getElementById(‘sel-preset’).addEventListener(‘change’, function(){
if (!this.value) return;
buildTuningInputs(this.value.split(‘,’).map(n=>norm(n.trim())));
update();
});
document.getElementById(‘sel-key’).addEventListener(‘change’, update);
document.getElementById(‘sel-scale’).addEventListener(‘change’, update);
document.getElementById(‘sel-chord’).addEventListener(‘change’, update);
document.getElementById(‘sel-mode’).addEventListener(‘change’, function() {
const isChord = this.value === ‘chord’;
document.getElementById(‘scale-ctrl’).style.display = isChord ? ‘none’ : ”;
document.getElementById(‘chord-ctrl’).style.display = isChord ? ” : ‘none’;
update();
});
// Initialise
const initKey = “C”;
const initScale = “major”;
const initChord = “major”;
const initMode = “scale”;
const initTuning = [“E”, “A”, “D”, “G”, “B”, “E”];
document.getElementById(‘sel-key’).value = initKey;
document.getElementById(‘sel-scale’).value = initScale;
document.getElementById(‘sel-chord’).value = initChord;
document.getElementById(‘sel-mode’).value = initMode;
if (initMode === ‘chord’) {
document.getElementById(‘scale-ctrl’).style.display = ‘none’;
document.getElementById(‘chord-ctrl’).style.display = ”;
}
buildTuningInputs(initTuning);
// Mark matching preset
for (const [,val] of Object.entries(PRESETS)) {
if (val === initTuning.join(‘,’)) {
document.getElementById(‘sel-preset’).value = val; break;
}
}
// ── Concert flute fingerings (Boehm system) ──────────────────────────────
//
// Key layout (top to bottom in diagram):
// OCT — octave/register key (left thumb, upper)
// TH — B♭ thumb lever (Briccialdi) — shown as small side key
// LH1 — left index finger (covers B hole)
// LH2 — left middle finger (covers A hole)
// LH3 — left ring finger (covers G hole)
// LH4 — left pinky (G#/Ab key)
// ··· — gap between hands
// RH1 — right index finger (covers F# hole)
// RH2 — right middle finger(covers E hole)
// RH3 — right ring finger (covers D hole)
// RH4 — right pinky (Eb/D# roller key)
//
// Each note’s fingering is encoded as an object:
// oct: bool — octave key depressed
// th: bool — Briccialdi Bb thumb lever
// lh1..lh4: bool — left fingers (true = key DOWN / hole covered)
// rh1..rh4: bool — right fingers (true = key DOWN / hole covered)
//
// Fingerings cover first octave (4th octave register = add oct key).
// Register 1: C4–B4, Register 2: C5–B5 (oct key), Register 3: C6+ (oct key + overblowing)
// We show register 1 as the canonical reference diagram, noting octave above.
(function() {
// Standard Boehm fingerings for all 12 chromatic pitches (register-1 reference).
// oct=false for all here; add oct=true for register 2, specific combos for reg 3.
// Keys: oct, th(Bb lever), lh1, lh2, lh3, lh4(G#key), rh1, rh2, rh3, rh4(Eb key)
//
// Convention: lh4 being TRUE means the G#/Ab key IS pressed (LH pinky down).
// On a standard closed-G# flute this key is NORMALLY UP (open), pressing it closes G#.
//
// Fingering reference: standard Boehm closed-G# system, C foot.
const BASE = {
// oct th lh1 lh2 lh3 lh4 rh1 rh2 rh3 rh4
‘C’ : { oct:false, th:false, lh1:true, lh2:true, lh3:true, lh4:false, rh1:true, rh2:true, rh3:true, rh4:false },
‘C#’: { oct:false, th:false, lh1:true, lh2:true, lh3:true, lh4:false, rh1:true, rh2:true, rh3:false, rh4:false },
‘D’ : { oct:false, th:false, lh1:true, lh2:true, lh3:true, lh4:false, rh1:true, rh2:false, rh3:false, rh4:false },
‘D#’: { oct:false, th:false, lh1:true, lh2:true, lh3:true, lh4:false, rh1:false, rh2:false, rh3:false, rh4:true },
‘E’ : { oct:false, th:false, lh1:true, lh2:true, lh3:true, lh4:false, rh1:false, rh2:false, rh3:false, rh4:false },
‘F’ : { oct:false, th:false, lh1:true, lh2:true, lh3:false, lh4:false, rh1:false, rh2:false, rh3:false, rh4:false },
‘F#’: { oct:false, th:false, lh1:true, lh2:false, lh3:false, lh4:false, rh1:false, rh2:false, rh3:false, rh4:false },
‘G’ : { oct:false, th:false, lh1:false, lh2:false, lh3:false, lh4:false, rh1:false, rh2:false, rh3:false, rh4:false },
‘G#’: { oct:false, th:false, lh1:false, lh2:false, lh3:false, lh4:true, rh1:false, rh2:false, rh3:false, rh4:false },
‘A’ : { oct:false, th:false, lh1:false, lh2:true, lh3:true, lh4:false, rh1:true, rh2:true, rh3:true, rh4:false },
‘A#’: { oct:false, th:true, lh1:true, lh2:false, lh3:false, lh4:false, rh1:false, rh2:false, rh3:false, rh4:false },
‘B’ : { oct:false, th:false, lh1:true, lh2:false, lh3:false, lh4:false, rh1:false, rh2:false, rh3:false, rh4:false },
};
// Compute register-specific fingering.
// Reg 1 = C4–B4 (base, no oct key)
// Reg 2 = C5–B5 (add oct key to all base fingerings)
// Reg 3 = C6–B6 (oct key + specific overblown fingerings — simplified: same as reg2 for display)
function fingeringFor(note, octave) {
const base = Object.assign({}, BASE[note]);
if (octave >= 2) base.oct = true;
return base;
}
// Choose which octave to show for a given chromatic note given the scale’s root.
// We default to register 2 (middle octave) as most practical for flutists.
function chooseOctave(/* unused for now */) { return 2; }
// ── SVG drawing ──────────────────────────────────────────────────────────
// Flute diagram is drawn top-to-bottom as a stylised side view.
// The body is a narrow vertical tube; keys appear as circles on the left side.
const CARD_W = 52; // SVG width per card
const KEY_R = 7; // main key circle radius
const OCT_R = 4; // octave key radius (small)
const TH_R = 4.5; // thumb Bb lever radius
const COL_X = 26; // x centre of key column
const BODY_X1 = 22; // tube left edge
const BODY_X2 = 30; // tube right edge
const TOP_Y = 18; // y of first key centre
const KEY_GAP = 18; // vertical gap between main keys
const HAND_GAP= 10; // extra gap between LH and RH groups
const OCT_Y = TOP_Y – 14; // octave key position above LH1
// Key vertical positions (y centres), relative to SVG top
// Order: OCT, LH1, LH2, LH3, LH4, [gap], RH1, RH2, RH3, RH4
function keyPositions() {
const ys = {};
ys.oct = OCT_Y;
ys.lh1 = TOP_Y;
ys.lh2 = TOP_Y + KEY_GAP;
ys.lh3 = TOP_Y + KEY_GAP * 2;
ys.lh4 = TOP_Y + KEY_GAP * 3;
ys.rh1 = TOP_Y + KEY_GAP * 3 + HAND_GAP + KEY_GAP;
ys.rh2 = TOP_Y + KEY_GAP * 3 + HAND_GAP + KEY_GAP * 2;
ys.rh3 = TOP_Y + KEY_GAP * 3 + HAND_GAP + KEY_GAP * 3;
ys.rh4 = TOP_Y + KEY_GAP * 3 + HAND_GAP + KEY_GAP * 4;
return ys;
}
const POS = keyPositions();
const TUBE_TOP = OCT_Y – OCT_R – 4;
const TUBE_BOTTOM= POS.rh4 + KEY_R + 6;
const SVG_H = TUBE_BOTTOM + 18; // extra for label below
function fEl(tag, attrs) {
const el = document.createElementNS(‘http://www.w3.org/2000/svg’, tag);
for (const [k,v] of Object.entries(attrs)) el.setAttribute(k,v);
return el;
}
function fTxt(attrs, text) {
const el = fEl(‘text’, attrs); el.textContent = text; return el;
}
function drawFluteCard(note, isRoot, octave) {
const f = fingeringFor(note, octave);
const svg = document.createElementNS(‘http://www.w3.org/2000/svg’,’svg’);
svg.setAttribute(‘width’, CARD_W);
svg.setAttribute(‘height’, SVG_H);
svg.setAttribute(‘viewBox’, `0 0 ${CARD_W} ${SVG_H}`);
svg.setAttribute(‘xmlns’,’http://www.w3.org/2000/svg’);
// Gradient def for half-hole (not needed here but good to include)
const defs = fEl(‘defs’,{});
const grad = fEl(‘linearGradient’,{id:`hf_${note.replace(‘#’,’s’)}`,x1:’0′,y1:’0′,x2:’1′,y2:’0′});
grad.appendChild(fEl(‘stop’,{offset:’50%’,’stop-color’:’var(–tp)’,’stop-opacity’:’1′}));
grad.appendChild(fEl(‘stop’,{offset:’50%’,’stop-color’:’var(–bg)’,’stop-opacity’:’1′}));
defs.appendChild(grad);
svg.appendChild(defs);
// Tube body (vertical rectangle)
svg.appendChild(fEl(‘rect’,{
x: BODY_X1, y: TUBE_TOP,
width: BODY_X2 – BODY_X1, height: TUBE_BOTTOM – TUBE_TOP,
rx: 3, class: ‘fl-body’
}));
// Hand separator dashes
const sepY = (POS.lh4 + POS.rh1) / 2;
for (let dx = 0; dx ORDER.indexOf(a) – ORDER.indexOf(b));
sorted.forEach(note => {
const octave = 2; // display register 2 (middle octave)
const isRoot = note === root;
const card = document.createElement(‘div’);
card.className = ‘flute-card’;
// Note name label
const nameEl = document.createElement(‘div’);
nameEl.className = ‘note-name’ + (isRoot ? ‘ is-root’ : ”);
nameEl.textContent = note;
card.appendChild(nameEl);
// Octave label
const octEl = document.createElement(‘div’);
octEl.className = ‘octave-tag’;
octEl.textContent = `(${octave + 3}th oct)`;
card.appendChild(octEl);
// SVG diagram
card.appendChild(drawFluteCard(note, isRoot, octave));
// Key legend below diagram
const legEl = document.createElement(‘div’);
legEl.style.cssText = ‘font-size:9px;color:var(–ts);text-align:center;line-height:1.5;margin-top:2px;’;
const f = fingeringFor(note, octave);
const parts = [];
if (f.oct) parts.push(‘oct’);
if (f.th) parts.push(‘B♭lv’);
[‘lh1′,’lh2′,’lh3′,’lh4′,’rh1′,’rh2′,’rh3′,’rh4’].forEach(k => {
if (f[k]) parts.push(k.toUpperCase());
});
legEl.textContent = parts.length ? parts.join(‘ ‘) : ‘open G’;
card.appendChild(legEl);
row.appendChild(card);
});
};
})();
(function() {
// Two full octaves of white/black keys (C–B repeated twice)
const OCTAVES = 2;
const WK_W = 32; // white key width px
const WK_H = 120; // white key height px
const BK_W = 20; // black key width px
const BK_H = 76; // black key height px
const BORDER_R = 4; // bottom-corner radius on white keys
// Within one octave (root = C):
const WHITE_SEMI = [0,2,4,5,7,9,11]; // semitones of white keys C D E F G A B
const WHITE_NAMES = [‘C’,’D’,’E’,’F’,’G’,’A’,’B’];
const BLACK_SEMI = [1,3,6,8,10]; // C# D# F# G# A#
const BLACK_BEFORE= [0,1,3,4,5]; // white-key index to the left of each black
const TOTAL_WHITE = 7 * OCTAVES;
const SVG_W = TOTAL_WHITE * WK_W + 2;
const SVG_H = WK_H + 30;
const svg = document.getElementById(‘piano’);
svg.setAttribute(‘width’, SVG_W);
svg.setAttribute(‘height’, SVG_H);
svg.setAttribute(‘viewBox’, `0 0 ${SVG_W} ${SVG_H}`);
function pEl(tag, attrs) {
const el = document.createElementNS(‘http://www.w3.org/2000/svg’, tag);
for (const [k,v] of Object.entries(attrs)) el.setAttribute(k,v);
return el;
}
function pTxt(attrs, text) {
const el = pEl(‘text’, attrs); el.textContent = text; return el;
}
window.buildPiano = function(noteSet, root) {
svg.innerHTML = ”;
svg.appendChild(pEl(‘rect’,{x:0,y:0,width:SVG_W,height:SVG_H,fill:’transparent’}));
// White keys (draw first so black keys render on top)
for (let oct=0; oct {
const note = CHROMATIC[semi];
const x = octX + wi * WK_W;
const isRoot = note === root;
const inScale = noteSet.has(note);
const cls = isRoot ? ‘wk-root’ : inScale ? ‘wk-scale’ : ‘wk’;
const d = `M${x},0 L${x+WK_W-1},0 L${x+WK_W-1},${WK_H-BORDER_R} ` +
`Q${x+WK_W-1},${WK_H} ${x+WK_W-1-BORDER_R},${WK_H} ` +
`L${x+BORDER_R},${WK_H} Q${x},${WK_H} ${x},${WK_H-BORDER_R} Z`;
svg.appendChild(pEl(‘path’,{d,class:cls}));
// Note name label beneath key (first octave only)
if (oct === 0) {
const lCls = isRoot ? ‘key-label key-label-active’
: inScale ? ‘key-label key-label-scale’
: ‘key-label’;
svg.appendChild(pTxt({x:x+WK_W/2, y:WK_H+18, class:lCls}, WHITE_NAMES[wi]));
}
});
}
// Black keys (drawn on top)
for (let oct=0; oct {
const note = CHROMATIC[semi];
const x = octX + BLACK_BEFORE[bi] * WK_W + WK_W – BK_W/2;
const isRoot = note === root;
const inScale = noteSet.has(note);
const cls = isRoot ? ‘bk-root’ : inScale ? ‘bk-scale’ : ‘bk’;
svg.appendChild(pEl(‘rect’,{x,y:0,width:BK_W,height:BK_H,rx:3,class:cls}));
// Label inside black keys (first octave only)
if (oct === 0) {
const labelStyle = isRoot ? ‘fill:var(–ct)’
: inScale ? ‘fill:var(–tt2)’
: ‘fill:var(–ts)’;
svg.appendChild(pTxt({
x: x+BK_W/2, y: BK_H-9, class:’key-label-dark’,
style: labelStyle
}, note));
}
});
}
};
})();
update();