PageSourceSearch

https://tucuatro.com/wp-content/plugins/tucuatro-chords/js/chords.sound.js?ver=0.9.8.1

js tucuatro.com collected 2026-10-02 10:05:24 UTC 7,846 bytes, 233 lines download raw bytes

1/**
2 * TuCuatro Chords — click-to-hear printed diagrams
3 *
4 * Click a .thischord with data-numeric → play fretted pitches for the
5 * active instrument (4-string or 6-string). Inspired by the sync pad
6 * tone engine in tucuatro-songs, but voiced from tuning + fret numbers.
7 *
8 * Skips: muted strings (x), sync chord-pad UI, calculator drawer frets.
9 */
10(function (window, $) {
11  "use strict";
12
13  if (!$ || !$.fn) return;
14
15  var CFG = window.TuCuatroChordHear || {};
16  var TUNINGS = CFG.tunings || {};
17
18  // Fallback when PHP did not localize. Must match object.chords.js string order
19  // (ukulele = GCEA, cavaquinho = DGBD — not the Instruments PHP arrays).
20  var FALLBACK = {
21    cuatro: { notes: ["A", "D", "F#", "B"], octaves: [3, 4, 4, 3] },
22    guitar: { notes: ["E", "A", "D", "G", "B", "E"], octaves: [2, 2, 3, 3, 3, 4] },
23    ukulele: { notes: ["G", "C", "E", "A"], octaves: [4, 4, 4, 4] },
24    cavaquinho: { notes: ["D", "G", "B", "D"], octaves: [4, 4, 4, 5] },
25    "bandola-llanera": { notes: ["A", "D", "A", "E"], octaves: [3, 4, 4, 5] },
26    bandola: { notes: ["A", "D", "A", "E"], octaves: [3, 4, 4, 5] }
27  };
28
29  var NOTE_TO_SEMI_FROM_A = {
30    A: 0, "A#": 1, Bb: 1, B: 2,
31    C: -9, "C#": -8, Db: -8, D: -7, "D#": -6, Eb: -6,
32    E: -5, F: -4, "F#": -3, Gb: -3, G: -2, "G#": -1, Ab: -1
33  };
34
35  var audioCtx = null;
36  var playGen = 0;
37
38  function getCtx() {
39    var AC = window.AudioContext || window.webkitAudioContext;
40    if (!AC) return null;
41    if (!audioCtx) audioCtx = new AC();
42    if (audioCtx.state === "suspended") {
43      try { audioCtx.resume(); } catch (e) {}
44    }
45    return audioCtx;
46  }
47
48  function noteToFreq(note, octave) {
49    var n = String(note || "").trim();
50    if (n.length > 1 && n.charAt(1) === "b") {
51      // already handled via NOTE_TO_SEMI_FROM_A aliases
52    }
53    var semiFromA = NOTE_TO_SEMI_FROM_A[n];
54    if (typeof semiFromA === "undefined") return null;
55    var steps = semiFromA + (octave - 4) * 12;
56    return 440 * Math.pow(2, steps / 12);
57  }
58
59  function trim(s) {
60    return String(s == null ? "" : s).replace(/^\s+|\s+$/g, "");
61  }
62
63  function resolveInstrument($el) {
64    var inst = trim($el.attr("data-instrument") || $el.attr("data-preserveinstrument"));
65    if (!inst) {
66      inst = trim($(".forceinstrument").first().attr("data-instrument"));
67    }
68    if (!inst && window.TuCuatro && window.TuCuatro.Fretboard && typeof window.TuCuatro.Fretboard.detectInstrument === "function") {
69      try { inst = trim(window.TuCuatro.Fretboard.detectInstrument()); } catch (e) { inst = ""; }
70    }
71    if (!inst) {
72      try { inst = trim(localStorage.getItem("selectedInstrument")); } catch (e) { inst = ""; }
73    }
74    if (inst === "bandola") inst = "bandola-llanera";
75    return inst || "guitar";
76  }
77
78  function getTuning(instrument) {
79    var t = TUNINGS[instrument] || FALLBACK[instrument] || FALLBACK.guitar;
80    if (!t || !t.notes || !t.notes.length) t = FALLBACK.guitar;
81    return t;
82  }
83
84  function parseNumeric(raw) {
85    var parts = trim(raw).split(".");
86    if (parts.length !== 4 && parts.length !== 6) return null;
87    var frets = [];
88    for (var i = 0; i < parts.length; i++) {
89      var p = trim(parts[i]).toLowerCase();
90      if (!p || p === "x") {
91        frets.push(null);
92        continue;
93      }
94      var n = parseInt(p, 10);
95      if (isNaN(n) || n < 0) frets.push(null);
96      else frets.push(n);
97    }
98    return frets;
99  }
100
101  function fretsToFreqs(frets, tuning) {
102    var notes = tuning.notes || [];
103    var octaves = tuning.octaves || [];
104    var out = [];
105    var count = Math.min(frets.length, notes.length);
106    for (var i = 0; i < count; i++) {
107      if (frets[i] === null) continue;
108      var open = notes[i];
109      var oct = typeof octaves[i] === "number" ? octaves[i] : 4;
110      var base = noteToFreq(open, oct);
111      if (base == null) continue;
112      out.push(base * Math.pow(2, frets[i] / 12));
113    }
114    return out;
115  }
116
117  function playFreqs(freqs) {
118    var ctx = getCtx();
119    if (!ctx || !freqs.length) return;
120
121    playGen += 1;
122    var myGen = playGen;
123    var now = ctx.currentTime;
124    var stagger = typeof CFG.stagger === "number" ? CFG.stagger : 0.04;
125    var dur = typeof CFG.duration === "number" ? CFG.duration : 0.55;
126    var peak = typeof CFG.peak === "number" ? CFG.peak : 0.22;
127
128    var master = ctx.createGain();
129    master.gain.setValueAtTime(0.0001, now);
130    master.gain.linearRampToValueAtTime(peak, now + 0.02);
131    master.gain.setValueAtTime(peak * 0.55, now + dur);
132    master.gain.linearRampToValueAtTime(0.0001, now + dur + 0.28);
133    master.connect(ctx.destination);
134
135    var filter = ctx.createBiquadFilter();
136    filter.type = "lowpass";
137    filter.frequency.value = typeof CFG.lowpassHz === "number" ? CFG.lowpassHz : 2200;
138    filter.Q.value = 0.8;
139    filter.connect(master);
140
141    var voice = Math.min(0.55, 1.1 / Math.max(2, freqs.length));
142
143    freqs.forEach(function (freq, i) {
144      var t0 = now + i * stagger;
145      var osc = ctx.createOscillator();
146      var g = ctx.createGain();
147      osc.type = i === 0 ? "triangle" : "sine";
148      osc.frequency.value = freq;
149      g.gain.setValueAtTime(0.0001, t0);
150      g.gain.linearRampToValueAtTime(voice, t0 + 0.012);
151      g.gain.linearRampToValueAtTime(voice * 0.7, t0 + 0.12);
152      g.gain.setValueAtTime(voice * 0.55, t0 + dur);
153      g.gain.linearRampToValueAtTime(0.0001, t0 + dur + 0.22);
154      osc.connect(g);
155      g.connect(filter);
156      osc.start(t0);
157      osc.stop(t0 + dur + 0.3);
158    });
159
160    // Generation stamp reserved for future cancel-on-reclick if needed.
161    void myGen;
162  }
163
164  function shouldIgnore($el, e) {
165    if (CFG.enabled === false) return true;
166    if (!$el || !$el.length) return true;
167    // Sync professor pad / overlay — that UI has its own tone engine.
168    if ($el.closest(".tucuatro-chordpad, #tucuatro-chordpad, .add_chords_overlay, .tc-sync-overlay").length) {
169      return true;
170    }
171    // Calculator interactive frets.
172    if ($el.closest(".chordfinder, .tucuatro-calc-drawer").length) {
173      return true;
174    }
175    // Beatbar uses .tc-chord, not .thischord — but be safe.
176    if ($(e.target).closest(".tc-beatbar, .select_instrument, .option.changeinstrument").length) {
177      return true;
178    }
179    return false;
180  }
181
182  function playFromChordEl($el) {
183    var numeric = trim($el.attr("data-numeric"));
184    if (!numeric) return false;
185
186    var frets = parseNumeric(numeric);
187    if (!frets) return false;
188
189    var instrument = resolveInstrument($el);
190    var tuning = getTuning(instrument);
191    // If numeric string count disagrees with tuning, still play what we can.
192    var freqs = fretsToFreqs(frets, tuning);
193    if (!freqs.length) return false;
194
195    playFreqs(freqs);
196    $el.addClass("tc-sound-playing");
197    window.setTimeout(function () {
198      $el.removeClass("tc-sound-playing");
199    }, 350);
200    return true;
201  }
202
203  function bind() {
204    $(document)
205      .off("click.tcChordHear", ".thischord")
206      .on("click.tcChordHear", ".thischord", function (e) {
207        var $el = $(this);
208        if (shouldIgnore($el, e)) return;
209        if (!trim($el.attr("data-numeric"))) return;
210        // Don't steal modifier-clicks / text selection quirks.
211        if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
212        playFromChordEl($el);
213      });
214  }
215
216  // Public API for reuse / console tests.
217  window.TuCuatro = window.TuCuatro || {};
218  window.TuCuatro.ChordHear = {
219    play: playFromChordEl,
220    playNumeric: function (numeric, instrument) {
221      var frets = parseNumeric(numeric);
222      if (!frets) return false;
223      var tuning = getTuning(instrument || "guitar");
224      var freqs = fretsToFreqs(frets, tuning);
225      if (!freqs.length) return false;
226      playFreqs(freqs);
227      return true;
228    },
229    resolveInstrument: resolveInstrument
230  };
231
232  $(bind);
233})(window, window.jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.