diff --git a/flute-data.js b/flute-data.js index 7fb0f66..fe51e17 100644 --- a/flute-data.js +++ b/flute-data.js @@ -1,5 +1,67 @@ -// Flute fingering data and note mappings +// Flute fingering data based on professional fingering chart +// Mapping keyboard keys to flute holes: A=LH1, S=LH2, D=LH3, F=RH1, G=RH2, H=RH3, J=RH4 +// Additional keys: K=Thumb, L=Bb lever, ;=C# trill const FLUTE_FINGERINGS = { + // First octave (low register) + 'C': ['K', 'A', 'S', 'D', 'F', 'G', 'H', 'J'], // Thumb + all fingers + 'C#': ['K', 'A', 'S', 'D', 'F', 'G', 'H'], // Thumb + all except RH4 + 'Db': ['K', 'A', 'S', 'D', 'F', 'G', 'H'], // Same as C# + 'D': ['K', 'A', 'S', 'D', 'F', 'G', 'H'], // Thumb + all except RH4 + 'D#': ['K', 'A', 'S', 'D', 'F', 'G'], // Thumb + first 5 + 'Eb': ['K', 'A', 'S', 'D', 'F', 'G'], // Same as D# + 'E': ['K', 'A', 'S', 'D', 'F', 'G'], // Thumb + first 5 + 'F': ['K', 'A', 'S', 'D', 'F'], // Thumb + first 4 + 'F#': ['K', 'A', 'S', 'D'], // Thumb + first 3 + 'Gb': ['K', 'A', 'S', 'D'], // Same as F# + 'G': ['K', 'A', 'S', 'D'], // Thumb + first 3 + 'G#': ['K', 'A', 'S'], // Thumb + first 2 + 'Ab': ['K', 'A', 'S'], // Same as G# + 'A': ['K', 'A', 'S'], // Thumb + first 2 + 'A#': ['K', 'A', 'L'], // Thumb + LH1 + Bb lever + 'Bb': ['K', 'A', 'L'], // Same as A# + 'B': ['K', 'A'], // Thumb + LH1 + + // Second octave (middle register) + 'C2': ['K'], // Thumb only + 'C#2': ['A', 'S', 'D', 'F', 'G', 'H'], // All fingers, no thumb + 'Db2': ['A', 'S', 'D', 'F', 'G', 'H'], // Same as C#2 + 'D2': ['A', 'S', 'D', 'F', 'G', 'H'], // All fingers, no thumb + 'D#2': ['A', 'S', 'D', 'F', 'G'], // First 5, no thumb + 'Eb2': ['A', 'S', 'D', 'F', 'G'], // Same as D#2 + 'E2': ['A', 'S', 'D', 'F', 'G'], // First 5, no thumb + 'F2': ['A', 'S', 'D', 'F'], // First 4, no thumb + 'F#2': ['A', 'S', 'D'], // First 3, no thumb + 'Gb2': ['A', 'S', 'D'], // Same as F#2 + 'G2': ['A', 'S', 'D'], // First 3, no thumb + 'G#2': ['A', 'S'], // First 2, no thumb + 'Ab2': ['A', 'S'], // Same as G#2 + 'A2': ['A', 'S'], // First 2, no thumb + 'A#2': ['A', 'L'], // LH1 + Bb lever, no thumb + 'Bb2': ['A', 'L'], // Same as A#2 + 'B2': ['A'], // LH1 only, no thumb + + // Third octave (high register) + 'C3': [], // Open (no keys) + 'C#3': ['S', 'D', 'F', 'G', 'H'], // All except LH1 and thumb + 'Db3': ['S', 'D', 'F', 'G', 'H'], // Same as C#3 + 'D3': ['S', 'D', 'F', 'G', 'H'], // All except LH1 and thumb + 'D#3': ['S', 'D', 'F', 'G'], // First 4 except LH1, no thumb + 'Eb3': ['S', 'D', 'F', 'G'], // Same as D#3 + 'E3': ['S', 'D', 'F', 'G'], // First 4 except LH1, no thumb + 'F3': ['S', 'D', 'F'], // First 3 except LH1, no thumb + 'F#3': ['S', 'D'], // First 2 except LH1, no thumb + 'Gb3': ['S', 'D'], // Same as F#3 + 'G3': ['S', 'D'], // First 2 except LH1, no thumb + 'G#3': ['S'], // LH2 only + 'Ab3': ['S'], // Same as G#3 + 'A3': ['S'], // LH2 only + 'A#3': ['L'], // Bb lever only + 'Bb3': ['L'], // Same as A#3 + 'B3': [], // Open (no keys) +}; + +// Simplified fingerings for basic learning (used in current songs) +const BASIC_FINGERINGS = { 'C': ['A', 'S', 'D', 'F', 'G', 'H', 'J'], // All keys down 'D': ['A', 'S', 'D', 'F', 'G', 'H'], // All except J 'E': ['A', 'S', 'D', 'F', 'G'], // First 5 keys diff --git a/flute-svg.js b/flute-svg.js index 1a836dc..c58f18f 100644 --- a/flute-svg.js +++ b/flute-svg.js @@ -1,297 +1,539 @@ -// SVG Flute Component Generator -// Creates a detailed SVG representation of an orchestral flute with interactive fingering indicators +// Professional Flute Fingering Chart SVG Generator +// Creates fingering chart diagrams matching standard flute fingering charts class FluteSVG { constructor(containerId) { this.container = document.getElementById(containerId); - this.keyStates = { - 'A': false, - 'S': false, - 'D': false, - 'F': false, - 'G': false, - 'H': false, - 'J': false - }; - // Animation constants - this.ANIMATION_DURATION_MS = 300; + this.currentNote = 'C'; - // Cache for DOM elements - this.keyElements = {}; - this.previewElements = {}; - this.highlightElements = {}; + // Complete fingering chart data matching the game's note names + // Now includes both thumb keys: thumb (main) and thumbBb (Bb lever) + this.fingeringData = { + 'C': { thumb: true, thumbBb: false, lh1: true, lh2: true, lh3: true, lh4: false, rh1: true, rh2: true, rh3: true, rh4: false, trill1: false, trill2: false }, + 'D': { thumb: true, thumbBb: false, lh1: true, lh2: true, lh3: true, lh4: false, rh1: true, rh2: true, rh3: false, rh4: false, trill1: false, trill2: false }, + 'E': { thumb: true, thumbBb: false, lh1: true, lh2: true, lh3: true, lh4: false, rh1: true, rh2: false, rh3: false, rh4: false, trill1: false, trill2: false }, + 'F': { thumb: true, thumbBb: false, lh1: true, lh2: true, lh3: true, lh4: false, rh1: false, rh2: false, rh3: false, rh4: false, trill1: false, trill2: false }, + 'G': { thumb: true, thumbBb: false, lh1: true, lh2: true, lh3: false, lh4: false, rh1: false, rh2: false, rh3: false, rh4: false, trill1: false, trill2: false }, + 'A': { thumb: true, thumbBb: false, lh1: true, lh2: false, lh3: false, lh4: false, rh1: false, rh2: false, rh3: false, rh4: false, trill1: false, trill2: false }, + 'B': { thumb: true, thumbBb: false, lh1: false, lh2: false, lh3: false, lh4: false, rh1: false, rh2: false, rh3: false, rh4: false, trill1: false, trill2: false }, + 'C2': { thumb: false, thumbBb: false, lh1: false, lh2: false, lh3: false, lh4: false, rh1: false, rh2: false, rh3: false, rh4: false, trill1: false, trill2: false }, + + // Additional notes for extended songs - Bb lever used for A#/Bb + 'C#': { thumb: true, thumbBb: false, lh1: true, lh2: true, lh3: true, lh4: false, rh1: true, rh2: true, rh3: false, rh4: false, trill1: false, trill2: false }, + 'D#': { thumb: true, thumbBb: false, lh1: true, lh2: true, lh3: true, lh4: false, rh1: true, rh2: false, rh3: false, rh4: false, trill1: false, trill2: false }, + 'F#': { thumb: true, thumbBb: false, lh1: true, lh2: true, lh3: false, lh4: false, rh1: false, rh2: false, rh3: false, rh4: false, trill1: false, trill2: false }, + 'G#': { thumb: true, thumbBb: false, lh1: true, lh2: false, lh3: false, lh4: false, rh1: false, rh2: false, rh3: false, rh4: false, trill1: false, trill2: false }, + 'A#': { thumb: true, thumbBb: true, lh1: false, lh2: false, lh3: false, lh4: false, rh1: false, rh2: false, rh3: false, rh4: false, trill1: false, trill2: false } + }; - this.createFluteSVG(); - this.cacheKeyElements(); - } - - cacheKeyElements() { - // Cache DOM elements to avoid repeated queries - const keys = ['A', 'S', 'D', 'F', 'G', 'H', 'J']; - keys.forEach(key => { - const keyGroup = this.container.querySelector(`[data-key="${key}"]`); - if (keyGroup) { - this.keyElements[key] = keyGroup; - this.previewElements[key] = keyGroup.querySelector('.key-preview'); - this.highlightElements[key] = keyGroup.querySelector('.key-highlight'); - } - }); + this.createFingeringChart(); } - - createFluteSVG() { + createFingeringChart() { + // Clear existing content + this.container.innerHTML = ''; + // Create SVG element const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); - svg.setAttribute('viewBox', '0 0 800 200'); - svg.setAttribute('id', 'flute-svg'); + svg.setAttribute('viewBox', '0 0 550 150'); + svg.setAttribute('id', 'flute-fingering-chart'); svg.setAttribute('role', 'img'); - svg.setAttribute('aria-label', 'Interactive flute diagram showing finger positions for notes'); + svg.setAttribute('aria-label', 'Flute fingering chart showing finger positions'); svg.style.width = '100%'; svg.style.height = 'auto'; - - // Create flute body (main tube) - const fluteBody = this.createFluteBody(); - svg.appendChild(fluteBody); - - // Create finger holes/keys representing each keyboard key - // Flute keys from left to right: A, S, D, F, G, H, J - const keyPositions = [ - { id: 'A', x: 150, y: 100, label: 'A' }, // Left hand index finger - { id: 'S', x: 220, y: 100, label: 'S' }, // Left hand middle finger - { id: 'D', x: 290, y: 100, label: 'D' }, // Left hand ring finger - { id: 'F', x: 380, y: 100, label: 'F' }, // Right hand index finger - { id: 'G', x: 450, y: 100, label: 'G' }, // Right hand middle finger - { id: 'H', x: 520, y: 100, label: 'H' }, // Right hand ring finger - { id: 'J', x: 590, y: 100, label: 'J' } // Right hand pinky finger - ]; - - keyPositions.forEach(keyPos => { - const keyGroup = this.createKey(keyPos); - svg.appendChild(keyGroup); - }); - - // Add embouchure (mouthpiece) on the left - const embouchure = this.createEmbouchure(); - svg.appendChild(embouchure); - - // Clear container and add SVG - this.container.innerHTML = ''; + svg.style.maxHeight = '150px'; + + // Create the fingering chart diagram exactly like the professional chart + this.createChartElements(svg); + + // Add to container this.container.appendChild(svg); + + // Initialize with default note + this.updateFingering(this.currentNote); } - createFluteBody() { - const g = document.createElementNS('http://www.w3.org/2000/svg', 'g'); - g.setAttribute('id', 'flute-body'); - - // Main tube (cylindrical body) - const tube = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); - tube.setAttribute('x', '100'); - tube.setAttribute('y', '85'); - tube.setAttribute('width', '600'); - tube.setAttribute('height', '30'); - tube.setAttribute('rx', '15'); - tube.setAttribute('fill', 'url(#fluteGradient)'); - tube.setAttribute('stroke', '#999'); - tube.setAttribute('stroke-width', '2'); - - // Add gradient for metallic look - const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs'); - const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient'); - gradient.setAttribute('id', 'fluteGradient'); - gradient.setAttribute('x1', '0%'); - gradient.setAttribute('y1', '0%'); - gradient.setAttribute('x2', '0%'); - gradient.setAttribute('y2', '100%'); - - const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); - stop1.setAttribute('offset', '0%'); - stop1.setAttribute('style', 'stop-color:#e8e8e8;stop-opacity:1'); - - const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); - stop2.setAttribute('offset', '50%'); - stop2.setAttribute('style', 'stop-color:#d4d4d4;stop-opacity:1'); + createChartElements(svg) { + // Left side - Both thumb keys + const thumbGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + thumbGroup.setAttribute('id', 'thumb-keys'); + + // Main Thumb button (upper, larger) + const thumbPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + thumbPath.setAttribute('d', 'M15 35 L15 25 Q15 20 20 20 L35 20 Q40 20 40 25 L40 65 Q40 70 35 70 L20 70 Q15 70 15 65 Z'); + thumbPath.setAttribute('fill', 'white'); + thumbPath.setAttribute('stroke', '#333'); + thumbPath.setAttribute('stroke-width', '2'); + thumbPath.setAttribute('id', 'thumb-fill'); + + // Thumb Bb lever (lower, smaller) + const thumbBbPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + thumbBbPath.setAttribute('d', 'M20 75 L20 70 Q20 68 22 68 L33 68 Q35 68 35 70 L35 85 Q35 87 33 87 L22 87 Q20 87 20 85 Z'); + thumbBbPath.setAttribute('fill', 'white'); + thumbBbPath.setAttribute('stroke', '#333'); + thumbBbPath.setAttribute('stroke-width', '2'); + thumbBbPath.setAttribute('id', 'thumb-bb-fill'); + + // Thumb labels + const thumbLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + thumbLabel.setAttribute('x', '27'); + thumbLabel.setAttribute('y', '110'); + thumbLabel.setAttribute('text-anchor', 'middle'); + thumbLabel.setAttribute('font-family', 'Arial, sans-serif'); + thumbLabel.setAttribute('font-size', '9'); + thumbLabel.setAttribute('fill', '#666'); + thumbLabel.textContent = 'Thumb'; + + const thumbBbLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + thumbBbLabel.setAttribute('x', '27'); + thumbBbLabel.setAttribute('y', '120'); + thumbBbLabel.setAttribute('text-anchor', 'middle'); + thumbBbLabel.setAttribute('font-family', 'Arial, sans-serif'); + thumbBbLabel.setAttribute('font-size', '9'); + thumbBbLabel.setAttribute('fill', '#666'); + thumbBbLabel.textContent = 'Bb lever'; + + thumbGroup.appendChild(thumbPath); + thumbGroup.appendChild(thumbBbPath); + thumbGroup.appendChild(thumbLabel); + thumbGroup.appendChild(thumbBbLabel); + svg.appendChild(thumbGroup); + + // Main tone holes - Left hand (LH1, LH2, LH3) with different sizes + const leftHoles = [ + { id: 'lh1', x: 80, y: 70, r: 12, label: 'LH1' }, // Smallest + { id: 'lh2', x: 130, y: 70, r: 16, label: 'LH2' }, // Medium + { id: 'lh3', x: 180, y: 70, r: 16, label: 'LH3' } // Medium + ]; - const stop3 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); - stop3.setAttribute('offset', '100%'); - stop3.setAttribute('style', 'stop-color:#c0c0c0;stop-opacity:1'); + leftHoles.forEach(hole => { + const holeGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + holeGroup.setAttribute('id', hole.id); + + const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); + circle.setAttribute('cx', hole.x); + circle.setAttribute('cy', hole.y); + circle.setAttribute('r', hole.r); + circle.setAttribute('fill', 'white'); + circle.setAttribute('stroke', '#333'); + circle.setAttribute('stroke-width', '2'); + circle.setAttribute('id', hole.id + '-fill'); + + const label = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + label.setAttribute('x', hole.x); + label.setAttribute('y', hole.y + hole.r + 15); + label.setAttribute('text-anchor', 'middle'); + label.setAttribute('font-family', 'Arial, sans-serif'); + label.setAttribute('font-size', '10'); + label.setAttribute('fill', '#666'); + label.textContent = hole.label; + + holeGroup.appendChild(circle); + holeGroup.appendChild(label); + svg.appendChild(holeGroup); + }); - gradient.appendChild(stop1); - gradient.appendChild(stop2); - gradient.appendChild(stop3); - defs.appendChild(gradient); + // LH4 key - curved shape like in diagram + const lh4Group = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + lh4Group.setAttribute('id', 'lh4'); + + const lh4Path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + lh4Path.setAttribute('d', 'M210 45 Q215 40 220 45 L220 55 Q215 60 210 55 Z'); + lh4Path.setAttribute('fill', 'white'); + lh4Path.setAttribute('stroke', '#333'); + lh4Path.setAttribute('stroke-width', '2'); + lh4Path.setAttribute('id', 'lh4-fill'); + + const lh4Label = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + lh4Label.setAttribute('x', '215'); + lh4Label.setAttribute('y', '35'); + lh4Label.setAttribute('text-anchor', 'middle'); + lh4Label.setAttribute('font-family', 'Arial, sans-serif'); + lh4Label.setAttribute('font-size', '10'); + lh4Label.setAttribute('fill', '#666'); + lh4Label.textContent = 'LH4'; + + lh4Group.appendChild(lh4Path); + lh4Group.appendChild(lh4Label); + svg.appendChild(lh4Group); + + // Connector rod between hands + const connector = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + connector.setAttribute('d', 'M200 70 Q240 65 250 70'); + connector.setAttribute('fill', 'none'); + connector.setAttribute('stroke', '#666'); + connector.setAttribute('stroke-width', '3'); + svg.appendChild(connector); + + // Right hand holes (RH1, RH2, RH3) with uniform size + const rightHoles = [ + { id: 'rh1', x: 280, y: 70, r: 15, label: 'RH1' }, + { id: 'rh2', x: 330, y: 70, r: 15, label: 'RH2' }, + { id: 'rh3', x: 380, y: 70, r: 15, label: 'RH3' } + ]; - g.appendChild(defs); - g.appendChild(tube); + rightHoles.forEach(hole => { + const holeGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + holeGroup.setAttribute('id', hole.id); + + const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); + circle.setAttribute('cx', hole.x); + circle.setAttribute('cy', hole.y); + circle.setAttribute('r', hole.r); + circle.setAttribute('fill', 'white'); + circle.setAttribute('stroke', '#333'); + circle.setAttribute('stroke-width', '2'); + circle.setAttribute('id', hole.id + '-fill'); + + const label = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + label.setAttribute('x', hole.x); + label.setAttribute('y', hole.y + hole.r + 15); + label.setAttribute('text-anchor', 'middle'); + label.setAttribute('font-family', 'Arial, sans-serif'); + label.setAttribute('font-size', '10'); + label.setAttribute('fill', '#666'); + label.textContent = hole.label; + + holeGroup.appendChild(circle); + holeGroup.appendChild(label); + svg.appendChild(holeGroup); + }); - // Add shine/highlight on top of tube - const highlight = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); - highlight.setAttribute('x', '100'); - highlight.setAttribute('y', '87'); - highlight.setAttribute('width', '600'); - highlight.setAttribute('height', '8'); - highlight.setAttribute('rx', '4'); - highlight.setAttribute('fill', 'rgba(255, 255, 255, 0.4)'); + // Trill keys above RH1 and RH2 + const trill1Group = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + trill1Group.setAttribute('id', 'trill1'); + + const trill1Rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + trill1Rect.setAttribute('x', '275'); + trill1Rect.setAttribute('y', '35'); + trill1Rect.setAttribute('width', '10'); + trill1Rect.setAttribute('height', '20'); + trill1Rect.setAttribute('rx', '2'); + trill1Rect.setAttribute('fill', 'white'); + trill1Rect.setAttribute('stroke', '#333'); + trill1Rect.setAttribute('stroke-width', '2'); + trill1Rect.setAttribute('id', 'trill1-fill'); + + const trill1Label = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + trill1Label.setAttribute('x', '280'); + trill1Label.setAttribute('y', '30'); + trill1Label.setAttribute('text-anchor', 'middle'); + trill1Label.setAttribute('font-family', 'Arial, sans-serif'); + trill1Label.setAttribute('font-size', '8'); + trill1Label.setAttribute('fill', '#666'); + trill1Label.textContent = '1st trill'; + + // Connector line from trill to RH1 + const trill1Connector = document.createElementNS('http://www.w3.org/2000/svg', 'line'); + trill1Connector.setAttribute('x1', '280'); + trill1Connector.setAttribute('y1', '55'); + trill1Connector.setAttribute('x2', '280'); + trill1Connector.setAttribute('y2', '55'); + trill1Connector.setAttribute('stroke', '#666'); + trill1Connector.setAttribute('stroke-width', '1'); + + trill1Group.appendChild(trill1Rect); + trill1Group.appendChild(trill1Label); + trill1Group.appendChild(trill1Connector); + svg.appendChild(trill1Group); + + // Second trill key + const trill2Group = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + trill2Group.setAttribute('id', 'trill2'); + + const trill2Rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + trill2Rect.setAttribute('x', '325'); + trill2Rect.setAttribute('y', '35'); + trill2Rect.setAttribute('width', '10'); + trill2Rect.setAttribute('height', '20'); + trill2Rect.setAttribute('rx', '2'); + trill2Rect.setAttribute('fill', 'white'); + trill2Rect.setAttribute('stroke', '#333'); + trill2Rect.setAttribute('stroke-width', '2'); + trill2Rect.setAttribute('id', 'trill2-fill'); + + const trill2Label = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + trill2Label.setAttribute('x', '330'); + trill2Label.setAttribute('y', '30'); + trill2Label.setAttribute('text-anchor', 'middle'); + trill2Label.setAttribute('font-family', 'Arial, sans-serif'); + trill2Label.setAttribute('font-size', '8'); + trill2Label.setAttribute('fill', '#666'); + trill2Label.textContent = '2nd trill'; + + trill2Group.appendChild(trill2Rect); + trill2Group.appendChild(trill2Label); + svg.appendChild(trill2Group); - g.appendChild(highlight); + // RH4 key stack (right side like the diagram) + const rh4Group = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + rh4Group.setAttribute('id', 'rh4'); + + const rh4Rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + rh4Rect.setAttribute('x', '410'); + rh4Rect.setAttribute('y', '60'); + rh4Rect.setAttribute('width', '12'); + rh4Rect.setAttribute('height', '20'); + rh4Rect.setAttribute('rx', '2'); + rh4Rect.setAttribute('fill', 'white'); + rh4Rect.setAttribute('stroke', '#333'); + rh4Rect.setAttribute('stroke-width', '2'); + rh4Rect.setAttribute('id', 'rh4-fill'); + + const rh4Label = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + rh4Label.setAttribute('x', '416'); + rh4Label.setAttribute('y', '95'); + rh4Label.setAttribute('text-anchor', 'middle'); + rh4Label.setAttribute('font-family', 'Arial, sans-serif'); + rh4Label.setAttribute('font-size', '10'); + rh4Label.setAttribute('fill', '#666'); + rh4Label.textContent = 'RH4'; + + rh4Group.appendChild(rh4Rect); + rh4Group.appendChild(rh4Label); + svg.appendChild(rh4Group); - return g; + // Pinky roller keys on far right (like diagram) + const rollerGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + rollerGroup.setAttribute('id', 'roller-keys'); + + // G roller + const gRoller = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + gRoller.setAttribute('x', '450'); + gRoller.setAttribute('y', '45'); + gRoller.setAttribute('width', '15'); + gRoller.setAttribute('height', '8'); + gRoller.setAttribute('rx', '4'); + gRoller.setAttribute('fill', 'white'); + gRoller.setAttribute('stroke', '#333'); + gRoller.setAttribute('stroke-width', '2'); + gRoller.setAttribute('id', 'g-roller-fill'); + + // C roller + const cRoller = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + cRoller.setAttribute('x', '450'); + cRoller.setAttribute('y', '55'); + cRoller.setAttribute('width', '15'); + cRoller.setAttribute('height', '8'); + cRoller.setAttribute('rx', '4'); + cRoller.setAttribute('fill', 'white'); + cRoller.setAttribute('stroke', '#333'); + cRoller.setAttribute('stroke-width', '2'); + cRoller.setAttribute('id', 'c-roller-fill'); + + // Gizmo key + const gizmo = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + gizmo.setAttribute('x', '450'); + gizmo.setAttribute('y', '65'); + gizmo.setAttribute('width', '15'); + gizmo.setAttribute('height', '8'); + gizmo.setAttribute('rx', '4'); + gizmo.setAttribute('fill', 'white'); + gizmo.setAttribute('stroke', '#333'); + gizmo.setAttribute('stroke-width', '2'); + gizmo.setAttribute('id', 'gizmo-fill'); + + // Roller labels + const rollerLabels = [ + { x: 475, y: 50, text: 'G-roller' }, + { x: 475, y: 60, text: 'C-roller' }, + { x: 475, y: 70, text: 'gizmo key' } + ]; + + rollerLabels.forEach(label => { + const text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + text.setAttribute('x', label.x); + text.setAttribute('y', label.y); + text.setAttribute('font-family', 'Arial, sans-serif'); + text.setAttribute('font-size', '8'); + text.setAttribute('fill', '#666'); + text.textContent = label.text; + rollerGroup.appendChild(text); + }); + + rollerGroup.appendChild(gRoller); + rollerGroup.appendChild(cRoller); + rollerGroup.appendChild(gizmo); + svg.appendChild(rollerGroup); + + // Current note display + const noteDisplay = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + noteDisplay.setAttribute('x', '250'); + noteDisplay.setAttribute('y', '20'); + noteDisplay.setAttribute('text-anchor', 'middle'); + noteDisplay.setAttribute('font-family', 'Arial, sans-serif'); + noteDisplay.setAttribute('font-size', '16'); + noteDisplay.setAttribute('font-weight', 'bold'); + noteDisplay.setAttribute('fill', '#333'); + noteDisplay.setAttribute('id', 'current-note-display'); + noteDisplay.textContent = this.currentNote; + svg.appendChild(noteDisplay); } - - createKey(keyPos) { - const g = document.createElementNS('http://www.w3.org/2000/svg', 'g'); - g.setAttribute('class', 'flute-key-svg'); - g.setAttribute('data-key', keyPos.id); - - // Outer ring (key mechanism) - const outerRing = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); - outerRing.setAttribute('cx', keyPos.x); - outerRing.setAttribute('cy', keyPos.y); - outerRing.setAttribute('r', '22'); - outerRing.setAttribute('fill', '#888'); - outerRing.setAttribute('stroke', '#666'); - outerRing.setAttribute('stroke-width', '1.5'); - - // Inner pad (finger hole cover) - const innerPad = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); - innerPad.setAttribute('cx', keyPos.x); - innerPad.setAttribute('cy', keyPos.y); - innerPad.setAttribute('r', '18'); - innerPad.setAttribute('fill', '#b8b8b8'); - innerPad.setAttribute('stroke', '#999'); - innerPad.setAttribute('stroke-width', '1'); - innerPad.setAttribute('class', 'key-pad'); - - // Highlight overlay (shows when key is active) - const highlight = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); - highlight.setAttribute('cx', keyPos.x); - highlight.setAttribute('cy', keyPos.y); - highlight.setAttribute('r', '18'); - highlight.setAttribute('fill', '#667eea'); - highlight.setAttribute('opacity', '0'); - highlight.setAttribute('class', 'key-highlight'); - - // Preview overlay (shows upcoming note) - const preview = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); - preview.setAttribute('cx', keyPos.x); - preview.setAttribute('cy', keyPos.y); - preview.setAttribute('r', '18'); - preview.setAttribute('fill', '#FFD700'); - preview.setAttribute('opacity', '0'); - preview.setAttribute('class', 'key-preview'); - - g.appendChild(outerRing); - g.appendChild(innerPad); - g.appendChild(highlight); - g.appendChild(preview); - - return g; + + // Update fingering display for a specific note + updateFingering(noteName) { + console.log(`SVG updateFingering called with: ${noteName}`); + this.currentNote = noteName; + const fingering = this.fingeringData[noteName]; + + if (!fingering) { + console.warn(`No fingering data found for note: ${noteName}`); + console.log('Available notes:', Object.keys(this.fingeringData)); + return; + } + + console.log(`Updating fingering for ${noteName}:`, fingering); + + // Update note display + const noteDisplay = this.container.querySelector('#current-note-display'); + if (noteDisplay) { + noteDisplay.textContent = noteName; + } + + // Update thumb keys - now handles both! + this.updateKeyState('thumb-fill', fingering.thumb); + this.updateKeyState('thumb-bb-fill', fingering.thumbBb); + + // Update main holes + this.updateKeyState('lh1-fill', fingering.lh1); + this.updateKeyState('lh2-fill', fingering.lh2); + this.updateKeyState('lh3-fill', fingering.lh3); + this.updateKeyState('lh4-fill', fingering.lh4); + this.updateKeyState('rh1-fill', fingering.rh1); + this.updateKeyState('rh2-fill', fingering.rh2); + this.updateKeyState('rh3-fill', fingering.rh3); + this.updateKeyState('rh4-fill', fingering.rh4); + + // Update trill keys + this.updateKeyState('trill1-fill', fingering.trill1); + this.updateKeyState('trill2-fill', fingering.trill2); + + // Update roller keys (usually not used in basic fingerings) + this.updateKeyState('g-roller-fill', false); + this.updateKeyState('c-roller-fill', false); + this.updateKeyState('gizmo-fill', false); } - createEmbouchure() { - const g = document.createElementNS('http://www.w3.org/2000/svg', 'g'); - g.setAttribute('id', 'embouchure'); - - // Head joint (wider section on left) - const headJoint = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); - headJoint.setAttribute('x', '20'); - headJoint.setAttribute('y', '82'); - headJoint.setAttribute('width', '85'); - headJoint.setAttribute('height', '36'); - headJoint.setAttribute('rx', '18'); - headJoint.setAttribute('fill', 'url(#fluteGradient)'); - headJoint.setAttribute('stroke', '#999'); - headJoint.setAttribute('stroke-width', '2'); - - // Embouchure hole (blow hole) - const embouchureHole = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse'); - embouchureHole.setAttribute('cx', '70'); - embouchureHole.setAttribute('cy', '100'); - embouchureHole.setAttribute('rx', '12'); - embouchureHole.setAttribute('ry', '6'); - embouchureHole.setAttribute('fill', '#333'); - embouchureHole.setAttribute('stroke', '#666'); - embouchureHole.setAttribute('stroke-width', '1'); - - g.appendChild(headJoint); - g.appendChild(embouchureHole); - - return g; + updateKeyState(elementId, isPressed) { + const element = this.container.querySelector('#' + elementId); + if (element) { + if (isPressed) { + element.setAttribute('fill', '#333'); // Dark fill for pressed keys + } else { + element.setAttribute('fill', 'white'); // White fill for open keys + } + } } - // Update key states based on fingering - updateFingering(requiredKeys) { - // Reset all keys first - Object.keys(this.keyStates).forEach(key => { - this.keyStates[key] = false; - }); + // Show preview for upcoming note (yellow tint) + showPreview(noteName) { + const fingering = this.fingeringData[noteName]; + if (!fingering) return; + + // Add preview class or style to indicate upcoming note + const previewColor = '#FFF3E0'; // Light orange for preview + + if (fingering.thumb) this.setPreviewState('thumb-fill', previewColor); + if (fingering.thumbBb) this.setPreviewState('thumb-bb-fill', previewColor); + if (fingering.lh1) this.setPreviewState('lh1-fill', previewColor); + if (fingering.lh2) this.setPreviewState('lh2-fill', previewColor); + if (fingering.lh3) this.setPreviewState('lh3-fill', previewColor); + if (fingering.lh4) this.setPreviewState('lh4-fill', previewColor); + if (fingering.rh1) this.setPreviewState('rh1-fill', previewColor); + if (fingering.rh2) this.setPreviewState('rh2-fill', previewColor); + if (fingering.rh3) this.setPreviewState('rh3-fill', previewColor); + if (fingering.rh4) this.setPreviewState('rh4-fill', previewColor); + if (fingering.trill1) this.setPreviewState('trill1-fill', previewColor); + if (fingering.trill2) this.setPreviewState('trill2-fill', previewColor); + } - // Set required keys - requiredKeys.forEach(key => { - this.keyStates[key] = true; - }); + setPreviewState(elementId, color) { + const element = this.container.querySelector('#' + elementId); + if (element) { + element.setAttribute('fill', color); + } + } - this.renderKeyStates(); + // Clear all previews + clearPreview() { + // Reset all keys to their proper states based on current note + this.updateFingering(this.currentNote); } - // Show preview for upcoming note - showPreview(requiredKeys) { - // Clear all previews first using cached elements - Object.values(this.previewElements).forEach(preview => { - if (preview) { - preview.setAttribute('opacity', '0'); - } - }); + // Convert keyboard keys to note names for compatibility + updateFingeringFromKeys(requiredKeys) { + // This method provides compatibility with the existing game system + // Map keyboard keys to fingering positions for display + const keyMap = { + 'A': 'lh1', + 'S': 'lh2', + 'D': 'lh3', + 'F': 'rh1', + 'G': 'rh2', + 'H': 'rh3', + 'J': 'rh4' // Note: rh4 doesn't exist in standard flute + }; - // Show preview for required keys + // Reset all keys + this.updateKeyState('thumb-fill', false); + this.updateKeyState('thumb-bb-fill', false); + this.updateKeyState('lh1-fill', false); + this.updateKeyState('lh2-fill', false); + this.updateKeyState('lh3-fill', false); + this.updateKeyState('lh4-fill', false); + this.updateKeyState('rh1-fill', false); + this.updateKeyState('rh2-fill', false); + this.updateKeyState('rh3-fill', false); + this.updateKeyState('rh4-fill', false); + this.updateKeyState('trill1-fill', false); + this.updateKeyState('trill2-fill', false); + + // Set keys based on required keys requiredKeys.forEach(key => { - const preview = this.previewElements[key]; - if (preview) { - preview.setAttribute('opacity', '0.7'); + const fingerPos = keyMap[key]; + if (fingerPos) { + this.updateKeyState(fingerPos + '-fill', true); } }); } - // Clear all previews - clearPreview() { - Object.values(this.previewElements).forEach(preview => { - if (preview) { - preview.setAttribute('opacity', '0'); - } - }); - } + // Trigger visual feedback animation + triggerHitFeedback(noteName) { + const fingering = this.fingeringData[noteName]; + if (!fingering) return; - // Render current key states - renderKeyStates() { - Object.keys(this.keyStates).forEach(key => { - const highlight = this.highlightElements[key]; - - if (highlight) { - if (this.keyStates[key]) { - // Key is active (pressed) - highlight.setAttribute('opacity', '0.9'); - } else { - // Key is inactive - highlight.setAttribute('opacity', '0'); - } - } - }); + // Brief green flash for successful note hit + const hitColor = '#4CAF50'; + + if (fingering.thumb) this.flashKey('thumb-fill', hitColor); + if (fingering.thumbBb) this.flashKey('thumb-bb-fill', hitColor); + if (fingering.lh1) this.flashKey('lh1-fill', hitColor); + if (fingering.lh2) this.flashKey('lh2-fill', hitColor); + if (fingering.lh3) this.flashKey('lh3-fill', hitColor); + if (fingering.lh4) this.flashKey('lh4-fill', hitColor); + if (fingering.rh1) this.flashKey('rh1-fill', hitColor); + if (fingering.rh2) this.flashKey('rh2-fill', hitColor); + if (fingering.rh3) this.flashKey('rh3-fill', hitColor); + if (fingering.rh4) this.flashKey('rh4-fill', hitColor); + if (fingering.trill1) this.flashKey('trill1-fill', hitColor); + if (fingering.trill2) this.flashKey('trill2-fill', hitColor); } - // Trigger visual feedback animation for a note hit - triggerHitFeedback(requiredKeys) { - requiredKeys.forEach(key => { - const highlight = this.highlightElements[key]; + flashKey(elementId, color) { + const element = this.container.querySelector('#' + elementId); + if (element) { + const originalColor = element.getAttribute('fill'); + element.setAttribute('fill', color); - if (highlight) { - // Use CSS class for animation instead of creating SVG animation elements - highlight.setAttribute('opacity', '1'); - highlight.classList.add('hit-pulse'); - - // Remove class after animation completes - setTimeout(() => { - highlight.classList.remove('hit-pulse'); - highlight.setAttribute('opacity', '0.9'); - }, this.ANIMATION_DURATION_MS); - } - }); + setTimeout(() => { + element.setAttribute('fill', originalColor); + }, 200); + } } } + +// Initialize the flute SVG when the page loads +window.addEventListener('DOMContentLoaded', () => { + if (document.getElementById('flute-diagram')) { + window.fluteSVG = new FluteSVG('flute-diagram'); + } +}); \ No newline at end of file diff --git a/game.js b/game.js index 0d76a2f..1283ecd 100644 --- a/game.js +++ b/game.js @@ -328,7 +328,7 @@ class FluteGame { hit: false, missed: false, played: false, // Track if note has been auto-played - requiredKeys: FLUTE_FINGERINGS[noteData.note] || [], + requiredKeys: BASIC_FINGERINGS[noteData.note] || [], color: NOTE_COLORS[noteData.note] || '#FFFFFF' })); @@ -397,7 +397,7 @@ class FluteGame { // Use SVG flute preview if available if (this.fluteSVG) { if (upcomingNote) { - this.fluteSVG.showPreview(upcomingNote.requiredKeys); + this.fluteSVG.showPreview(upcomingNote.note); } else { this.fluteSVG.clearPreview(); } @@ -407,7 +407,7 @@ class FluteGame { showHitFeedback(note) { // Use SVG flute feedback if available if (this.fluteSVG) { - this.fluteSVG.triggerHitFeedback(note.requiredKeys); + this.fluteSVG.triggerHitFeedback(note.note); } } @@ -423,6 +423,12 @@ class FluteGame { note.played = true; this.playNoteSound(note.note); this.showHitFeedback(note); + + // Update fingering chart to show current note + if (this.fluteSVG) { + this.fluteSVG.updateFingering(note.note); + } + console.log(`Auto-playing note: ${note.note}`); } }