├── .gitignore ├── ChangeLog.md ├── images └── frets │ ├── fret.png │ ├── fret.xcf │ ├── fret_s.png │ ├── fret_s1.png │ ├── fret_s2.png │ ├── fret_s3.png │ ├── fret_s4.png │ ├── fret_s5.png │ ├── fret_s6.png │ ├── fret_sd1.png │ ├── fret_sd2.png │ ├── fret_sd3.png │ ├── fret_sd4.png │ └── fret_empty.xcf ├── css ├── scale.css ├── get.css └── base.css ├── mods ├── README.md └── tuner │ ├── tunerWorker.js │ ├── tuner.css │ ├── index.html │ └── tuner.js ├── doc ├── getChordsMd.sh └── getScalesMd.sh ├── src ├── gui │ ├── IRenderer.js │ └── DomUtil.js ├── instruments │ ├── Guitar.js │ ├── Bazooka.js │ └── Piano.js ├── audio │ ├── Audio.js │ └── AudioUtil.js ├── ScaleTypes.js ├── C.js ├── Class.js ├── Scale.js ├── NoteFreq.js ├── ChordTypes.js ├── Note.js ├── Util.js ├── Instrument.js ├── Chord.js └── IStringInstrument.js ├── TODO.md ├── pages ├── info.html ├── index.html ├── scale.html └── get.html ├── Makefile ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | doc/html 3 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | 2 | * dev 3 | 4 | 5 | * v0.1 6 | - Initial release 7 | -------------------------------------------------------------------------------- /images/frets/fret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/urban-1/jsChords/HEAD/images/frets/fret.png -------------------------------------------------------------------------------- /images/frets/fret.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/urban-1/jsChords/HEAD/images/frets/fret.xcf -------------------------------------------------------------------------------- /images/frets/fret_s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/urban-1/jsChords/HEAD/images/frets/fret_s.png -------------------------------------------------------------------------------- /images/frets/fret_s1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/urban-1/jsChords/HEAD/images/frets/fret_s1.png -------------------------------------------------------------------------------- /images/frets/fret_s2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/urban-1/jsChords/HEAD/images/frets/fret_s2.png -------------------------------------------------------------------------------- /images/frets/fret_s3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/urban-1/jsChords/HEAD/images/frets/fret_s3.png -------------------------------------------------------------------------------- /images/frets/fret_s4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/urban-1/jsChords/HEAD/images/frets/fret_s4.png -------------------------------------------------------------------------------- /images/frets/fret_s5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/urban-1/jsChords/HEAD/images/frets/fret_s5.png -------------------------------------------------------------------------------- /images/frets/fret_s6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/urban-1/jsChords/HEAD/images/frets/fret_s6.png -------------------------------------------------------------------------------- /images/frets/fret_sd1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/urban-1/jsChords/HEAD/images/frets/fret_sd1.png -------------------------------------------------------------------------------- /images/frets/fret_sd2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/urban-1/jsChords/HEAD/images/frets/fret_sd2.png -------------------------------------------------------------------------------- /images/frets/fret_sd3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/urban-1/jsChords/HEAD/images/frets/fret_sd3.png -------------------------------------------------------------------------------- /images/frets/fret_sd4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/urban-1/jsChords/HEAD/images/frets/fret_sd4.png -------------------------------------------------------------------------------- /images/frets/fret_empty.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/urban-1/jsChords/HEAD/images/frets/fret_empty.xcf -------------------------------------------------------------------------------- /css/scale.css: -------------------------------------------------------------------------------- 1 | #more select { 2 | height:150px; 3 | padding-right: 4px; 4 | } 5 | 6 | #more { 7 | padding-left: 50px; 8 | } -------------------------------------------------------------------------------- /mods/README.md: -------------------------------------------------------------------------------- 1 | # MODULES 2 | 3 | Create here modules related to chords/scales. These can have GUI and include: 4 | 5 | - A tuner (some fft will be needed?) 6 | - A metronome 7 | - ... maybe a track editor? :) 8 | 9 | Try to use HTML5 API for Audio. -------------------------------------------------------------------------------- /doc/getChordsMd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Get the supported chords in markdown format 5 | # 6 | 7 | echo "| Type | Formula | Name |" 8 | echo "|------|---------|------|" 9 | cat ../src/ChordTypes.js | grep type: | grep formula: | awk -F'"' '{ \ 10 | print "| "$4" | "$6" | "$8" |" 11 | }' 12 | 13 | exit 0 14 | 15 | -------------------------------------------------------------------------------- /src/gui/IRenderer.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Basic Renderer Interface 4 | * @class IRenderer 5 | */ 6 | C.IRenderer = C.Class.extend({ 7 | options: { 8 | type: "abstract" 9 | }, 10 | 11 | initialize: function (options) { 12 | C.setOptions(this, options); 13 | } 14 | }) 15 | 16 | C.IRenderer.byType = function(opts){ 17 | 18 | } 19 | -------------------------------------------------------------------------------- /mods/tuner/tunerWorker.js: -------------------------------------------------------------------------------- 1 | importScripts("../../jschords-src.js") 2 | self.onmessage = function(event) 3 | { 4 | var timeseries = event.data.timeseries; 5 | var test_frequencies = event.data.test_frequencies; 6 | var sample_rate = event.data.sample_rate; 7 | var data = C.AudioUtil.analyze(timeseries, test_frequencies, sample_rate); 8 | self.postMessage(data); 9 | }; 10 | -------------------------------------------------------------------------------- /doc/getScalesMd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Get the supported chords in markdown format 5 | # 6 | 7 | echo "| Name | Interval Structure |" 8 | echo "|------|---------|" 9 | cat ../src/ScaleTypes.js | grep "C.Scale.TYPES\[" | awk -F '[][\"]' '{ \ 10 | intervals = $6 11 | gsub(/ .5/, " H", intervals) 12 | gsub(/ 1\.5/, " W+H", intervals) 13 | gsub(/ 1/, " W", intervals) 14 | print "| "$3" | "intervals"|" 15 | }' 16 | 17 | exit 0 18 | 19 | -------------------------------------------------------------------------------- /css/get.css: -------------------------------------------------------------------------------- 1 | 2 | @-viewport { 3 | width: 240px; 4 | } 5 | .c_controls { 6 | font-size: 15px; 7 | } 8 | 9 | .c_controls .b{ 10 | font-weight: bold; 11 | cursor: pointer; 12 | padding-left: 3px; 13 | padding-right: 3px; 14 | border: 1px solid gray; 15 | border-radius: 3px; 16 | } 17 | 18 | .prev { 19 | margin-left:15px; 20 | } 21 | 22 | #more select { 23 | height:150px; 24 | padding-right: 4px; 25 | } 26 | -------------------------------------------------------------------------------- /src/instruments/Guitar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Guitar class... 3 | * 4 | * @class 5 | * @extends C.IStringInstrument 6 | */ 7 | C.Guitar = C.IStringInstrument.extend({ 8 | options: { 9 | numFrets: 12, 10 | name: "Guitar", 11 | description: "This is A guitar!", 12 | strings: ["E", "A", "D", "G", "B", "e"], 13 | 14 | // Playable? parameters 15 | hasBar: true, 16 | ignoreTone0: true, 17 | maxPlayableTones: 4, 18 | maxFretSpan: 4 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | - Ability to set a ChordRep to an instrument and validate it 4 | - Ability to set positions and get back the chord name 5 | - Refine the API (public/private functions) 6 | - Support TABs at some point? 7 | - Extend with renderers and support SVG (Raphael) and Canvas 8 | - Better chord classification algorithm 9 | - Left-handed diagrams and fretboards 10 | 11 | # Contact the following ppl: 12 | 13 | - Integrate with https://github.com/acspike/ChordJS 14 | - https://github.com/naiquevin/GuitarJs 15 | - Include band.js to play chords and scales... (not that useful tho...) 16 | -------------------------------------------------------------------------------- /pages/info.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # order matters... 3 | JSFILES=\ 4 | src/C.js \ 5 | src/Util.js \ 6 | src/Class.js \ 7 | src/Note.js \ 8 | src/NoteFreq.js \ 9 | src/Chord.js \ 10 | src/ChordTypes.js \ 11 | src/Instrument.js \ 12 | src/IStringInstrument.js \ 13 | src/Scale.js \ 14 | src/ScaleTypes.js \ 15 | 16 | GUIFILES=$(wildcard src/gui/*.js) 17 | AUDIO=$(wildcard src/audio/*.js) 18 | INSTRUMENTS= $(wildcard src/instruments/*.js) 19 | 20 | .PHONY: all jschords auto 21 | 22 | all: jschords 23 | 24 | 25 | jschords: $(JSFILES) $(GUIFILES) $(INSTRUMENTS) $(AUDIO) 26 | cat $^ > jschords-src.js 27 | 28 | # Auto build, echo . to sterr if you need feedback... 29 | auto: 30 | @while [ true ]; \ 31 | do \ 32 | make jschords;\ 33 | sleep 3; \ 34 | echo -n "." >&2 ;\ 35 | done 36 | 37 | doc: ./src/ 38 | -rm -r ./doc/html 39 | -mkdir -p ./doc/html 40 | jsduck -o ./doc/html $^ -------------------------------------------------------------------------------- /src/instruments/Bazooka.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bazooka class... 3 | * 4 | * @class 5 | * @extends C.IStringInstrument 6 | */ 7 | C.Bazooka = C.IStringInstrument.extend({ 8 | options: { 9 | numFrets: 12, 10 | name: "Bazooka", 11 | description: "This is A Bazooka!", 12 | strings: ["D", "A", "D"], 13 | doubleString: true, 14 | 15 | // Playable? parameters 16 | hasBar: true, 17 | ignoreTone0: true, 18 | maxPlayableTones: 4, 19 | maxFretSpan: 6 20 | }, 21 | 22 | 23 | _setDifficulty: function(){ 24 | for (var i=0; i0) diff--; 29 | if (c.isBar()) { 30 | diff+=2; 31 | // uniq-=c.countNum(c.getMinDuplicate()) +1; 32 | } 33 | 34 | diff+=uniq; 35 | diff/=2; 36 | 37 | c.setDiff(diff); 38 | } 39 | } 40 | }) 41 | 42 | -------------------------------------------------------------------------------- /mods/tuner/tuner.css: -------------------------------------------------------------------------------- 1 | .arrow-right { 2 | width: 0; 3 | height: 0; 4 | border-top: 10px solid transparent; 5 | border-bottom: 10px solid transparent; 6 | 7 | border-left: 10px solid #F5A9A9; 8 | display: inline-block; 9 | margin: 4px; 10 | } 11 | 12 | .arrow-left { 13 | width: 0; 14 | height: 0; 15 | border-top: 10px solid transparent; 16 | border-bottom: 10px solid transparent; 17 | 18 | border-right:10px solid #F5A9A9; 19 | display: inline-block; 20 | margin: 4px; 21 | } 22 | 23 | .arrow-left.lightRed { 24 | border-right-color: red; 25 | } 26 | 27 | .arrow-right.lightRed { 28 | border-left-color: red; 29 | } 30 | 31 | .spoton { 32 | width: 18px; 33 | height:18px; 34 | display: inline-block; 35 | border-radius: 18px; 36 | background-color: #A9F5A9; 37 | margin: 4px; 38 | } 39 | 40 | .lightGreen { 41 | background-color: #00FF00; 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | jsCHords -- A project that aims on creating all possible chords and scales, for any string instrument. 3 | 4 | Copyright (c) 2014-2015 Andreas Bontozoglou. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 7 | associated documentation files (the "Software"), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 9 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or substantial 13 | portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 16 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /mods/tuner/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 |

It sounds like you're playing...

19 |

...

20 |

21 |

22 |
23 | 24 |

25 |

26 | Note Frequency: 27 | 28 | Current: 29 | 30 |

31 |

32 | 33 |
34 | 35 |

36 |

37 |
38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/audio/Audio.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base class for audio operations 3 | */ 4 | C.Audio = C.Class.extend({ 5 | options:{ 6 | success: function(){}, // Success callback 7 | error: function(err){ // Error callback 8 | lg("C.Audio Error: "+err) 9 | } 10 | }, 11 | 12 | // Static 13 | audioCtx: {}, 14 | 15 | /** 16 | * Base init function 17 | * @param {Object} options 18 | */ 19 | initialize: function (options) { 20 | C.setOptions(this, options); 21 | 22 | navigator.getUserMedia = (navigator.getUserMedia || 23 | navigator.webkitGetUserMedia || 24 | navigator.mozGetUserMedia || 25 | navigator.msGetUserMedia); 26 | 27 | this.audioCtx = new (window.AudioContext || window.webkitAudioContext)(); 28 | 29 | var tmpThis = this; 30 | 31 | if (navigator.getUserMedia) { 32 | navigator.getUserMedia ( 33 | {audio:true}, 34 | function(stream){ 35 | tmpThis._success(stream); 36 | tmpThis.options.success(stream); 37 | }, 38 | this.options.error); 39 | } 40 | else { 41 | lg ("NOT SUPPORTED BROWSER"); 42 | } 43 | }, 44 | 45 | /** 46 | * Base callback to set the stream on success and call user-level 47 | * callback 48 | */ 49 | _success: function(stream) { 50 | this.stream = stream; 51 | }, 52 | 53 | getSampleRate: function(){ 54 | return this.audioCtx.sampleRate; 55 | }, 56 | 57 | getCtx: function(){ 58 | return this.audioCtx; 59 | } 60 | 61 | }); 62 | 63 | /** 64 | * distortion curve for the waveshaper, thanks to Kevin Ennis 65 | * http://stackoverflow.com/questions/22312841/waveshaper-node-in-webaudio-how-to-emulate-distortion 66 | * 67 | * @param {Number} amount [0-100] 68 | * @return {Array} curve 69 | */ 70 | C.Audio.makeDistortionCurve = function(amount) { 71 | var k = typeof amount === 'number' ? amount : 50, 72 | n_samples = 44100, 73 | curve = new Float32Array(n_samples), 74 | deg = Math.PI / 180, 75 | i = 0, 76 | x; 77 | for ( ; i < n_samples; ++i ) { 78 | x = i * 2 / n_samples - 1; 79 | curve[i] = ( 3 + k ) * x * 20 * deg / ( Math.PI + k * Math.abs(x) ); 80 | } 81 | return curve; 82 | }; 83 | -------------------------------------------------------------------------------- /src/ScaleTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Full credit to: :( ? 3 | * 4 | * Total: 5 | * 6 | * @static 7 | * @member C.Scale 8 | * @var 9 | */ 10 | C.Scale.TYPES = new Array(); 11 | 12 | C.Scale.TYPES["Major"] = [ 1, 1, .5, 1, 1, 1, .5 ]; 13 | C.Scale.TYPES["Natural Minor"] = [ 1, .5, 1, 1, .5, 1, 1 ]; 14 | 15 | C.Scale.TYPES["Major Pentatonic"] = [ 1, 1, 1.5, 1, 1.5 ]; 16 | C.Scale.TYPES["Minor Pentatonic"] = [ 1.5, 1, 1, 1.5, 1 ]; 17 | C.Scale.TYPES["Blues"] = [ 1.5, 1, .5, .5, 1.5, 1 ]; 18 | C.Scale.TYPES["Major Blues"] = [ 1, .5, .5, .5, .5, .5, 1, .5, 1 ]; 19 | C.Scale.TYPES["Minor Blues"] = [ 1, .5, 1, .5, .5, .5, 1, 1 ]; 20 | 21 | // Shifting! 22 | C.Scale.TYPES["Ionian Mode"] = [ 1, 1, .5, 1, 1, 1, .5 ]; 23 | C.Scale.TYPES["Dorian Mode"] = [ 1, .5, 1, 1, 1, .5, 1 ]; 24 | C.Scale.TYPES["Phrygian Mode"] = [ .5, 1, 1, 1, .5, 1, 1 ]; 25 | C.Scale.TYPES["Lydian Mode"] = [ 1, 1, 1, .5, 1, 1, .5 ]; 26 | C.Scale.TYPES["Mixolydian Mode"] = [ 1, 1, .5, 1, 1, .5, 1 ]; 27 | C.Scale.TYPES["Aeolian Mode"] = [ 1, .5, 1, 1, .5, 1, 1 ]; 28 | C.Scale.TYPES["Locrian Mode"] = [ .5, 1, 1, .5, 1, 1, 1 ]; 29 | 30 | 31 | C.Scale.TYPES["Harmonic Minor"] = [ 1, .5, 1, 1, .5, 1.5, .5 ]; 32 | C.Scale.TYPES["Phrygian Dominant"] = [ .5, 1.5, .5, 1, .5, 1, 1 ]; 33 | 34 | C.Scale.TYPES["Jazz Melodic Minor"] = [ 1, .5, 1, 1, 1, 1, .5 ]; 35 | C.Scale.TYPES["Dorian b2"] = [ .5, 1, 1, 1, 1, .5, 1 ]; 36 | C.Scale.TYPES["Lydian Augmented"] = [ 1, 1, 1, 1, .5, 1, .5 ]; 37 | C.Scale.TYPES["Lydian b7"] = [ 1, 1, 1, .5, 1, .5, 1 ]; 38 | C.Scale.TYPES["Mixoydian b13"] = [ 1, 1, .5, 1, .5, 1, 1 ]; 39 | C.Scale.TYPES["Locrian #2"] = [ 1, .5, 1, .5, 1, 1, 1 ]; 40 | C.Scale.TYPES["Super Locrian"] = [ .5, 1, .5, 1, 1, 1, 1 ]; 41 | 42 | 43 | 44 | C.Scale.TYPES["Chromatic"] = [ .5, .5, .5, .5, .5, .5, .5, .5, .5, .5, .5, .5 ]; 45 | C.Scale.TYPES["Whole Tone"] = [ 1, 1, 1, 1, 1, 1, 1 ]; 46 | C.Scale.TYPES["Dimished Whole Half"] = [ 1, .5, 1, .5, 1, .5, 1, .5 ]; 47 | C.Scale.TYPES["Dimished Half Whole"] = [ .5, 1, .5, 1, .5, 1, .5, 1 ]; 48 | 49 | 50 | C.Scale.TYPES["Hungarian Minor"] = [ 1, .5, 1.5, .5, 1.5, .5 ]; 51 | C.Scale.TYPES["Double Harmonic"] = [ .5, 1.5, .5, 1, .5, 1.5, .5 ]; 52 | C.Scale.TYPES["Enigmatic"] = [ 1, 1.5, 1, 1, 1, .5, .5 ]; 53 | C.Scale.TYPES["Japanese"] = [ .5, 1.5, 1, .5, 1, .5 ]; -------------------------------------------------------------------------------- /src/C.js: -------------------------------------------------------------------------------- 1 | ; // Prevent error when combining scripts! 2 | 3 | 4 | if (typeof console === 'undefined'){ 5 | var console = { 6 | log:function(){}, 7 | warn:function(){}, 8 | assert:function(){}, 9 | error:function(){}, 10 | info:function(){} 11 | } 12 | }; 13 | 14 | /** 15 | * Chords Lib 16 | */ 17 | var C = { 18 | version: 'v0.1' 19 | }; 20 | 21 | /** 22 | * Generic error handler for all AJAX errors. Logs the error message and 23 | * status 24 | * 25 | * @method 26 | * @param {Event} e 27 | * @static 28 | */ 29 | C.ajaxErr = function(e){ 30 | if (e.statusText=="OK") return; 31 | console.log("AJAX ERROR:"+e.responseText+" status="+e.statusText); 32 | console.log(e); 33 | }; 34 | 35 | /** 36 | * All base notes (no flats - major scale from C) 37 | * @member C 38 | * @var 39 | */ 40 | C.NOTES2 = ["C", "D", "E", "F", "G", "A", "B"]; 41 | 42 | /** 43 | * All notes (in semi-tones) 44 | * @member C 45 | * @var 46 | */ 47 | C.NOTES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; 48 | 49 | /** 50 | * Notes with different naming... 51 | * @member C 52 | * @var 53 | */ 54 | C.NOTESIoanna = ["Do", "Do+", "Re", "Re+", "Mi", "Fa", "Fa+", "Sol", "Sol+", "La", "La+", "Si"]; 55 | 56 | 57 | // function lg(a){console.log(a)} 58 | // Shortcut for console.log but may not work with IE! 59 | lg = /*console.log =*/ Function.prototype.bind.call(console.log,console); 60 | 61 | 62 | /** 63 | * Get a notes frequency 64 | * http://jonathan.bergknoff.com/journal/making-a-guitar-tuner-html5 65 | * 66 | * F = Fo * 2^(n/12) 67 | * 68 | * @param {Number} referenceFreq 69 | * @param {Number} halfToneOffset 70 | * @return {Number} frequency 71 | * @method 72 | * @static 73 | */ 74 | C.getFreq = function(referenceFreq, halfToneOffset){ 75 | return (referenceFreq * Math.pow(2, (halfToneOffset/12))) 76 | }; 77 | 78 | /** 79 | * Calculate the distance between a note frequency and 80 | * a given one. The result is in half-tone steps and can 81 | * be less than 1 82 | * 83 | * @param {Number} noteFreq Note frequency 84 | * @param {Number} currentFreq Testing frequency 85 | * @return {Number} 86 | */ 87 | C.getFreqOffset = function(noteFreq, currentFreq){ 88 | return 12*Math.log2(currentFreq/noteFreq) 89 | }; 90 | 91 | /** 92 | * Get all unique frequencies from C.Note.F along with 93 | * their note names. Returns array of objects each one 94 | * being {frequency: Number, name: String}. 95 | * 96 | * @return {Array} 97 | */ 98 | C.getAllFreq = function(){ 99 | var fs = []; 100 | 101 | for (i in C.Note.F) { 102 | if (i[1]=="b") continue; 103 | fs.push( {frequency: C.Note.F[i], name: i}); 104 | } 105 | 106 | return fs; 107 | }; 108 | -------------------------------------------------------------------------------- /src/Class.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | //------------------------------- 4 | // Class OOP base 5 | //------------------------------- 6 | 7 | /* 8 | * @file 9 | * C.Class powers the OOP facilities of the library. 10 | * Thanks to John Resig and Dean Edwards for inspiration! 11 | * Urban: THANKS TO LEAFLET FOR THIS CLASS! 12 | */ 13 | 14 | 15 | /** 16 | * @class C.Class 17 | */ 18 | C.Class = function () {}; 19 | 20 | /** 21 | * Base method to extend an object 22 | * @static 23 | */ 24 | C.Class.extend = function (props) { 25 | 26 | // extended class with the new prototype 27 | var NewClass = function () { 28 | 29 | // call the constructor 30 | if (this.initialize) { 31 | this.initialize.apply(this, arguments); 32 | } 33 | 34 | // call all constructor hooks 35 | this.callInitHooks(); 36 | }; 37 | 38 | // jshint camelcase: false 39 | var parentProto = NewClass.__super__ = this.prototype; 40 | var proto = C.Util.create(parentProto); 41 | proto.constructor = NewClass; 42 | 43 | NewClass.prototype = proto; 44 | 45 | 46 | //inherit parent's statics 47 | for (var i in this) { 48 | if (this.hasOwnProperty(i) && i !== 'prototype') { 49 | NewClass[i] = this[i]; 50 | } 51 | } 52 | 53 | // mix static properties into the class 54 | if (props.statics) { 55 | C.extend(NewClass, props.statics); 56 | delete props.statics; 57 | } 58 | 59 | // mix includes into the prototype 60 | if (props.includes) { 61 | C.Util.extend.apply(null, [proto].concat(props.includes)); 62 | delete props.includes; 63 | } 64 | 65 | // merge options 66 | if (proto.options) { 67 | props.options = C.Util.extend(C.Util.create(proto.options), props.options); 68 | } 69 | 70 | // mix given properties into the prototype 71 | C.extend(proto, props); 72 | 73 | proto._initHooks = []; 74 | 75 | // add method for calling all hooks 76 | proto.callInitHooks = function () { 77 | 78 | if (this._initHooksCalled) { return; } 79 | 80 | if (parentProto.callInitHooks) { 81 | parentProto.callInitHooks.call(this); 82 | } 83 | 84 | this._initHooksCalled = true; 85 | 86 | for (var i = 0, len = proto._initHooks.length; i < len; i++) { 87 | proto._initHooks[i].call(this); 88 | } 89 | }; 90 | 91 | return NewClass; 92 | }; 93 | 94 | 95 | /** 96 | * Method for adding properties to prototype 97 | * @static 98 | */ 99 | C.Class.include = function (props) { 100 | C.extend(this.prototype, props); 101 | }; 102 | 103 | /** 104 | * Merge new default options to the Class 105 | * @static 106 | */ 107 | C.Class.mergeOptions = function (options) { 108 | C.extend(this.prototype.options, options); 109 | }; 110 | 111 | /** 112 | * Add a constructor hook 113 | * @static 114 | */ 115 | C.Class.addInitHook = function (fn) { // (Function) || (String, args...) 116 | var args = Array.prototype.slice.call(arguments, 1); 117 | 118 | var init = typeof fn === 'function' ? fn : function () { 119 | this[fn].apply(this, args); 120 | }; 121 | 122 | this.prototype._initHooks = this.prototype._initHooks || []; 123 | this.prototype._initHooks.push(init); 124 | }; 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /src/Scale.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Basic Scale class 4 | * 5 | * @class C.Scale 6 | * @extends C.Class 7 | */ 8 | C.Scale = C.Class.extend({ 9 | options: { 10 | root: "C", 11 | name: "Major" 12 | }, 13 | 14 | /** 15 | * Base init function. It will load the scale 16 | * distances and cache it 17 | * 18 | * @param {Object} options 19 | */ 20 | initialize: function (options) { 21 | C.setOptions(this, options); 22 | // Cache distances 23 | this.dist = C.Scale.TYPES[this.options.name]; 24 | }, 25 | 26 | /** 27 | * Get string representation of this scale 28 | */ 29 | toString: function(){ 30 | return this.options.root+" "+this.options.name; 31 | }, 32 | 33 | /** 34 | * Set the root note of the scale 35 | * @param {String} root 36 | */ 37 | setRoot: function(root){ 38 | this.options.root=root; 39 | return this; 40 | }, 41 | 42 | /** 43 | * Set/Change the scale name/type 44 | * 45 | * @param {String} name 46 | */ 47 | setName: function(name){ 48 | this.options.name = name; 49 | this.dist = C.Scale.TYPES[this.options.name]; 50 | return this; 51 | }, 52 | 53 | /** 54 | * Get a note offset by f on the current scale and 55 | * from the current root note. 'f' is in the formula 56 | * format (ie. "1" or "b5") 57 | * 58 | * @param {String} f 59 | * @return {C.Note} 60 | */ 61 | offset: function(f){ 62 | // Check for optional 1st 63 | if (f[0]=="(") { 64 | f = f.substring(1,f.length-1); 65 | } 66 | 67 | var off = 0; 68 | if (f[0]=="b") { 69 | f = parseInt(f.substring(1)); 70 | off=-1; 71 | } 72 | else if (f[0]=="#"){ 73 | f = parseInt(f.substring(1)); 74 | off=1; 75 | } 76 | 77 | 78 | // Loop offset! 9ths/11ths 79 | var oo = 0; 80 | if (f>this.dist.length) { 81 | f=f%this.dist.length; 82 | oo=1; // FIXME:!Not exactly, assuming only 1 octave offset 83 | } 84 | 85 | 86 | for (var i=0; (i 3 | 4 | 5 | 6 | 7 | 8 | Chords tester 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 | 23 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /src/ChordTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Full credit to: http://guitarchordsworld.com/chord theory 3 | * and http://www.smithfowler.org/music/Chord_Formulas.htm 4 | * 5 | * Total: 47 6 | * 7 | * @static 8 | * @member C.Chord 9 | * @var 10 | */ 11 | C.Chord.TYPES = new Array(47); 12 | 13 | C.Chord.TYPES["M"] = { type: "M", formula: "1 3 5", name: "Major" }; 14 | C.Chord.TYPES["m"] = { type: "m", formula: "1 b3 5", name: "Minor" }; 15 | C.Chord.TYPES["7"] = { type: "7", formula: "1 3 5 b7", name: "7th" }; 16 | C.Chord.TYPES["m7"] = { type: "m7", formula: "1 b3 5 b7", name: "Minor 7th" }; 17 | C.Chord.TYPES["maj7"] = { type: "maj7", formula: "1 3 5 7", name: "Major 7th" }; 18 | C.Chord.TYPES["sus4"] = { type: "sus4", formula: "1 4 5", name: "Suspended 4th" }; 19 | C.Chord.TYPES["dim"] = { type: "dim", formula: "1 b3 b5", name: "Diminished" }; 20 | C.Chord.TYPES["aug"] = { type: "aug", formula: "1 3 #5", name: "Augmented" }; 21 | C.Chord.TYPES["6"] = { type: "6", formula: "1 3 5 6", name: "6th" }; 22 | C.Chord.TYPES["m6"] = { type: "m6", formula: "1 b3 5 6", name: "Minor 6th" }; 23 | C.Chord.TYPES["6add9"] = { type: "6add9", formula: "1 3 5 6 9", name: "6th Add 9th" }; 24 | C.Chord.TYPES["9"] = { type: "9", formula: "1 3 5 b7 9", name: "9th" }; 25 | C.Chord.TYPES["m9"] = { type: "m9", formula: "1 b3 5 b7 9", name: "Minor 9th" }; 26 | C.Chord.TYPES["maj9"] = { type: "maj9", formula: "1 3 5 7 9", name: "Major 9th" }; 27 | 28 | C.Chord.TYPES["11"] = { type: "11", formula: "1 (3) 5 b7 (9) 11", name: "11th" }; 29 | C.Chord.TYPES["m11"] = { type: "m11", formula: "1 b3 5 b7 (9) 11", name: "Minor 11th" }; 30 | C.Chord.TYPES["maj11"] = { type: "maj11", formula: "1 3 5 7 (9) 11", name: "Major 11th" }; 31 | 32 | C.Chord.TYPES["13"] = { type: "13", formula: "1 3 5 b7 (9) (11) 13", name: "13th" }; 33 | C.Chord.TYPES["m13"] = { type: "m13", formula: "1 b3 5 b7 (9) (11) 13", name: "Minor 13th" }; 34 | C.Chord.TYPES["maj13"] = { type: "maj13", formula: "1 3 5 7 (9) (11) 13", name: "Major 13th" }; 35 | 36 | // Weird 37 | C.Chord.TYPES["maj7#11"]= { type: "maj7#11", formula: "1 3 5 7 #11", name: "Major seven sharp 7th" }; 38 | C.Chord.TYPES["maj-5"] = { type: "maj-5", formula: "1 3 b5", name: "Major Flat Five" }; 39 | C.Chord.TYPES["mmaj7"] = { type: "m/maj7", formula: "1 b3 5 7", name: "Minor/Major 9th" }; 40 | C.Chord.TYPES["mmaj9"] = { type: "m/maj9", formula: "1 b3 5 7 9", name: "Minor/Major 9th" }; 41 | C.Chord.TYPES["mmaj11"]= { type: "m/maj11", formula: "1 b3 5 7 (9) 11", name: "Minor/Major 11th" }; 42 | C.Chord.TYPES["mmaj13"]= { type: "m/maj13", formula: "1 b3 5 7 (9) (11) 13", name: "Minor/Major 13th" }; 43 | C.Chord.TYPES["m7-5"] = { type: "m7-5", formula: "1 b3 b5 b7", name: "Minor seven flat fifth" }; 44 | 45 | // Sharps? 46 | C.Chord.TYPES["7#5"] = { type: "7#5", formula: "1 3 #5 b7", name: "Seven sharp five" }; 47 | C.Chord.TYPES["7b5"] = { type: "7b5", formula: "1 3 b5 b7", name: "Seven flat five" }; 48 | C.Chord.TYPES["7b9"] = { type: "7b9", formula: "1 3 5 b7 b9", name: "Seven flat ninth" }; 49 | C.Chord.TYPES["7#9"] = { type: "7#9", formula: "1 3 5 b7 #9", name: "Seven sharp ninth" }; 50 | C.Chord.TYPES["9#5"] = { type: "9#5", formula: "1 3 #5 b7 9", name: "Nine sharp five" }; 51 | C.Chord.TYPES["9b5"] = { type: "9b5", formula: "1 3 b5 b7 9", name: "Nine flat five" }; 52 | C.Chord.TYPES["7#5#9"] = { type: "7#5#9", formula: "1 3 #5 b7 #9", name: "Seven sharp five sharp nine" }; 53 | C.Chord.TYPES["7#5b9"] = { type: "7#5b9", formula: "1 3 #5 b7 b9", name: "Seven sharp five flat nine" }; 54 | C.Chord.TYPES["7b5#9"] = { type: "7b5#9", formula: "1 3 b5 b7 #9", name: "Seven flat five sharp nine" }; 55 | C.Chord.TYPES["7b5b9"] = { type: "7b5b9", formula: "1 3 b5 b7 b9", name: "Seven flat five flat nine" }; 56 | C.Chord.TYPES["7#11"] = { type: "7#11", formula: "1 3 5 b7 #11", name: "Seven sharp eleven" }; 57 | 58 | // Symatrical 59 | C.Chord.TYPES["dim7"] = { type: "dim7", formula: "1 b3 b5 bb7", name: "Diminished 7th" }; 60 | 61 | // 2 finger! 62 | C.Chord.TYPES["5"] = { type: "5", formula: "1 5", name: "5th" }; 63 | C.Chord.TYPES["-5"] = { type: "-5", formula: "1 b5", name: "Flat 5th" }; 64 | C.Chord.TYPES["sus2"] = { type: "sus2", formula: "1 2 5", name: "Suspended 2nd" }; 65 | C.Chord.TYPES["#11"] = { type: "#11", formula: "1 5 #11", name: "Sharp Eleven" }; 66 | 67 | 68 | // Aliases 69 | C.Chord.TYPES["°"] = C.Chord.TYPES["dim"]; 70 | C.Chord.TYPES["°7"] = C.Chord.TYPES["dim7"]; 71 | C.Chord.TYPES["+"] = C.Chord.TYPES["aug"]; 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/audio/AudioUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | C.AudioUtil = { 5 | 6 | /** 7 | * Calculate the average frequency given the data and the 8 | * halftone range to focus in. This can be used to avoid 9 | * overtones and harmonics when large spectrum is considered. 10 | * 11 | * The result is appended in the dataset in: 12 | * data.frequency.stats.avgFinOctave 13 | * 14 | * @param {Number} halfTones 15 | * @param {Object} data as returned by C.Audio.analyze 16 | * @param {Array} test_frequencies as returned by C.getAllFreq 17 | */ 18 | calcFinOctave: function (halfTones, data, test_frequencies){ 19 | var fstats = data.frequency.stats; 20 | 21 | var mLen = data.frequency.magnitudes.length; 22 | var mag = data.frequency.magnitudes; 23 | 24 | var avgFinOctave = 0; 25 | var sumMinOctave = 0; 26 | var notesLen = C.NOTES.length; 27 | var octStart = notesLen * (parseInt(fstats.maxIdx/notesLen)) 28 | 29 | // Loop only for this octave! Do not calc HARMONICS 30 | // or noise in other bandwidth ranges 31 | // for (var f=octStart; ftstats.max) tstats.max=timeseries[t]; 99 | if (timeseries[t]fstats.max) { 120 | fstats.max = magnitudes[f]; 121 | fstats.maxIdx = f; 122 | } 123 | 124 | 125 | sum_mag+=magnitudes[f]; 126 | fstats.avgF+=test_frequencies[f].frequency * magnitudes[f]; 127 | } 128 | 129 | // Final stats 130 | fstats.avgF = fstats.avgF/sum_mag; 131 | 132 | // Compute the average magnitude. We'll only pay attention to frequencies 133 | // with magnitudes significantly above average. 134 | fstats.avgM = sum_mag/magnitudes.length; 135 | 136 | // Analog amplitude 137 | tstats.amp = tstats.max-tstats.min; 138 | 139 | return { 140 | timeseries: timeseries, 141 | frequency: { 142 | amplitudes: amplitudes, 143 | magnitudes: magnitudes, 144 | stats: fstats 145 | }, 146 | time: { 147 | stats: tstats 148 | } 149 | }; 150 | } 151 | } -------------------------------------------------------------------------------- /pages/scale.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | Chords tester 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 118 | 119 | 120 |
121 |
122 |
123 | 124 | 125 |
126 | 127 | 128 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /src/instruments/Piano.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Piano class... 3 | * @class 4 | * @extends C.Instrument 5 | */ 6 | C.Piano = C.Instrument.extend({ 7 | options: { 8 | numFrets: 0, 9 | name: "Piano", 10 | description: "This is A piano!", 11 | strings: [ 12 | "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", 13 | "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", 14 | "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" 15 | ], 16 | 17 | // Playable? parameters 18 | hasBar: false, 19 | ignoreTone0: false, 20 | maxPlayableTones: 5, 21 | maxFretSpan: 2 // TODO: [ -1 3 5 5 1 0 ] ISNT PLAYABLE 22 | }, 23 | 24 | _splitFormulaDbgStr: function(){ 25 | var str = "For each formula part on a per string basis."; 26 | for (var fp = 0; fp0) 128 | off+=this.options.octaveOffset*C.NOTES.length; 129 | return off; 130 | }, 131 | 132 | /** 133 | * Clone a Note. Avoid using deep copy C.Util.clone... 134 | * 135 | * @return {C.Note} 136 | */ 137 | clone: function(){ 138 | var c = new C.Note({note: this.options.note}); 139 | 140 | for (var o in this.options){ 141 | if (o=="note") continue; 142 | c.options[o] = this.options[o]; 143 | } 144 | 145 | return c; 146 | }, 147 | 148 | /** 149 | * Return this note's F in a given octave 150 | */ 151 | getFreq: function(octave) { 152 | 153 | if (octave===undefined) 154 | octave=0; 155 | 156 | var note=this.options.note+""+octave; 157 | 158 | return C.Note.F[note] 159 | } 160 | 161 | }); 162 | 163 | /** 164 | * Get index for a given note name 165 | * 166 | * @param {String} note 167 | * @return {Number} Index 168 | * @static 169 | */ 170 | C.Note.noteIndex = function(note){ 171 | // lg(note) 172 | if (note.length>1 && note[1]=="b") { 173 | note = note[0]; 174 | var idx = C.NOTES.indexOf(note)-1; 175 | if (idx<0) idx = C.NOTES.length -idx; 176 | return idx; 177 | } 178 | 179 | return C.NOTES.indexOf(note); 180 | }; 181 | 182 | /** 183 | * Get note name given an index 184 | * 185 | * @param {Number} index 186 | * @return {String} Note name 187 | * @static 188 | */ 189 | C.Note.indexNote = function(idx){ 190 | return C.NOTES[idx]; 191 | }; 192 | 193 | /** 194 | * Create a C.Note instance by a given index 195 | * 196 | * @param {Number} idx 197 | * @return {C.Note} 198 | * @static 199 | */ 200 | C.Note.byIdx = function(idx){ 201 | return new C.Note({note: C.NOTES[idx]}); 202 | }; 203 | 204 | 205 | 206 | /** 207 | * Constant "Concert A" frequency in Hz 208 | * 209 | * @var 210 | * @static 211 | */ 212 | C.Note.CONCERTA = 440; 213 | 214 | 215 | -------------------------------------------------------------------------------- /src/gui/DomUtil.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * C.DomUtil contains various utility functions for working with DOM. 4 | * FULL CREDIT TO LEAFLET 5 | */ 6 | C.DomUtil = { 7 | get: function (id) { 8 | return typeof id === 'string' ? document.getElementById(id) : id; 9 | }, 10 | 11 | getStyle: function (el, style) { 12 | 13 | var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); 14 | 15 | if ((!value || value === 'auto') && document.defaultView) { 16 | var css = document.defaultView.getComputedStyle(el, null); 17 | value = css ? css[style] : null; 18 | } 19 | 20 | return value === 'auto' ? null : value; 21 | }, 22 | 23 | create: function (tagName, className, container) { 24 | 25 | var el = document.createElement(tagName); 26 | el.className = className; 27 | 28 | if (container) { 29 | container.appendChild(el); 30 | } 31 | 32 | return el; 33 | }, 34 | 35 | remove: function (el) { 36 | var parent = el.parentNode; 37 | if (parent) { 38 | parent.removeChild(el); 39 | } 40 | }, 41 | 42 | empty: function (el) { 43 | while (el.firstChild) { 44 | el.removeChild(el.firstChild); 45 | } 46 | }, 47 | 48 | toFront: function (el) { 49 | el.parentNode.appendChild(el); 50 | }, 51 | 52 | toBack: function (el) { 53 | var parent = el.parentNode; 54 | parent.insertBefore(el, parent.firstChild); 55 | }, 56 | 57 | hasClass: function (el, name) { 58 | if (el.classList !== undefined) { 59 | return el.classList.contains(name); 60 | } 61 | var className = C.DomUtil.getClass(el); 62 | return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); 63 | }, 64 | 65 | addClass: function (el, name) { 66 | if (el.classList !== undefined) { 67 | var classes = C.Util.splitWords(name); 68 | for (var i = 0, len = classes.length; i < len; i++) { 69 | el.classList.add(classes[i]); 70 | } 71 | } else if (!C.DomUtil.hasClass(el, name)) { 72 | var className = C.DomUtil.getClass(el); 73 | C.DomUtil.setClass(el, (className ? className + ' ' : '') + name); 74 | } 75 | }, 76 | 77 | removeClass: function (el, name) { 78 | if (el.classList !== undefined) { 79 | el.classList.remove(name); 80 | } else { 81 | C.DomUtil.setClass(el, C.Util.trim((' ' + C.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' '))); 82 | } 83 | }, 84 | 85 | setClass: function (el, name) { 86 | if (el.className.baseVal === undefined) { 87 | el.className = name; 88 | } else { 89 | // in case of SVG element 90 | el.className.baseVal = name; 91 | } 92 | }, 93 | 94 | getClass: function (el) { 95 | return el.className.baseVal === undefined ? el.className : el.className.baseVal; 96 | }, 97 | 98 | setOpacity: function (el, value) { 99 | 100 | if ('opacity' in el.style) { 101 | el.style.opacity = value; 102 | 103 | } else if ('filter' in el.style) { 104 | 105 | var filter = false, 106 | filterName = 'DXImageTransform.Microsoft.Alpha'; 107 | 108 | // filters collection throws an error if we try to retrieve a filter that doesn't exist 109 | try { 110 | filter = el.filters.item(filterName); 111 | } catch (e) { 112 | // don't set opacity to 1 if we haven't already set an opacity, 113 | // it isn't needed and breaks transparent pngs. 114 | if (value === 1) { return; } 115 | } 116 | 117 | value = Math.round(value * 100); 118 | 119 | if (filter) { 120 | filter.Enabled = (value !== 100); 121 | filter.Opacity = value; 122 | } else { 123 | el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; 124 | } 125 | } 126 | }, 127 | 128 | testProp: function (props) { 129 | 130 | var style = document.documentElement.style; 131 | 132 | for (var i = 0; i < props.length; i++) { 133 | if (props[i] in style) { 134 | return props[i]; 135 | } 136 | } 137 | return false; 138 | }, 139 | 140 | setTransform: function (el, offset, scale) { 141 | var pos = offset || new C.Point(0, 0); 142 | 143 | el.style[C.DomUtil.TRANSFORM] = 144 | 'translate3d(' + pos.x + 'px,' + pos.y + 'px' + ',0)' + (scale ? ' scale(' + scale + ')' : ''); 145 | }, 146 | 147 | setPosition: function (el, point, no3d) { // (HTMLElement, Point[, Boolean]) 148 | 149 | // jshint camelcase: false 150 | el._leaflet_pos = point; 151 | 152 | if (C.Browser.any3d && !no3d) { 153 | C.DomUtil.setTransform(el, point); 154 | } else { 155 | el.style.left = point.x + 'px'; 156 | el.style.top = point.y + 'px'; 157 | } 158 | }, 159 | 160 | getPosition: function (el) { 161 | // this method is only used for elements previously positioned using setPosition, 162 | // so it's safe to cache the position for performance 163 | 164 | // jshint camelcase: false 165 | return el._leaflet_pos; 166 | }, 167 | 168 | setSelectedByValue: function(sel_id, value){ 169 | var sel = document.getElementById(sel_id); 170 | 171 | for (i=0; i 4 | 5 | 6 | 7 | 8 | 9 | Chords tester 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 123 | 124 | 125 |
126 | 127 | 128 | 132 | 133 |
134 |
135 | < 136 | 137 | > 138 | ? 139 | ! 140 |
141 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsChords::README (Re-A-D-Mi) 2 | 3 | jsChords aims to be a complete and independent javascript-based music library. 4 | It can be used for educational purposes or integrated in larger music sites 5 | (chord databases, song/tab listings, etc). However, it is mainly written by 6 | programmers at the moment so any feedback/help would be very much appreciated. 7 | 8 | 9 | Visit [Am demo page!](http://urban-1.github.io/jsChords/demo.html?chord=Am&inst=Guitar) to see 10 | what is possible atm. 11 | 12 | The idea is to keep this library as simple and extensible as possible. Javascript 13 | was chosen due to good integration with other web technologies and frameworks. 14 | Additionally, requires no server setup and can run in "offline" mode on any device 15 | with a browser (in theory at least). Server-side implementation may be provided in 16 | the future (TeaJS, nodejs?). We will try to avoid jQuery dependency, however it is 17 | in the future plans to provided SVG and Canvas rendering for which we will need 18 | external libs. 19 | 20 | 21 | ## Usage 22 | 23 | For use in another website just download jschords-src.js and include it. Optionally, 24 | it may be a good idea to use a minifier/compiler of your choice to compress the library. 25 | Code examples [here](http://urban-1.github.io/jsChords/#code) (the demo code) 26 | 27 | ## Contributing 28 | 29 | If you want to edit and extend the project, clone it (or preferably fork on github) and 30 | use the provided makefile. This simply combines all the files into jschords-src.js. 31 | 32 | Documentation is build with jsduck which is assumed to be in your path. To rebuild: 33 | 34 | cd /doc 35 | make doc 36 | 37 | The output will be generated in /doc/html. Online documentation will be updated 38 | regularly and can be found at [the project homepage](http://urban-1.github.io/jsChords/doc). 39 | 40 | 41 | ## Supported Chords 42 | 43 | jsChords is easy to extend with new chords (any request would be implemented given 44 | the formulas). At the moment the following are supported: 45 | 46 | | Type | Formula | Name | 47 | |------|---------|------| 48 | | M | 1 3 5 | Major | 49 | | m | 1 b3 5 | Minor | 50 | | 7 | 1 3 5 b7 | 7th | 51 | | m7 | 1 b3 5 b7 | Minor 7th | 52 | | maj7 | 1 3 5 7 | Major 7th | 53 | | sus4 | 1 4 5 | Suspended 4th | 54 | | dim | 1 b3 b5 | Diminished | 55 | | aug | 1 3 #5 | Augmented | 56 | | 6 | 1 3 5 6 | 6th | 57 | | m6 | 1 b3 5 6 | Minor 6th | 58 | | 6add9 | 1 3 5 6 9 | 6th Add 9th | 59 | | 9 | 1 3 5 b7 9 | 9th | 60 | | m9 | 1 b3 5 b7 9 | Minor 9th | 61 | | maj9 | 1 3 5 7 9 | Major 9th | 62 | | 11 | 1 (3) 5 b7 (9) 11 | 11th | 63 | | m11 | 1 b3 5 b7 (9) 11 | Minor 11th | 64 | | maj11 | 1 3 5 7 (9) 11 | Major 11th | 65 | | 13 | 1 3 5 b7 (9) (11) 13 | 13th | 66 | | m13 | 1 b3 5 b7 (9) (11) 13 | Minor 13th | 67 | | maj13 | 1 3 5 7 (9) (11) 13 | Major 13th | 68 | | maj7#11 | 1 3 5 7 #11 | Major seven sharp 7th | 69 | | maj-5 | 1 3 b5 | Major Flat Five | 70 | | m/maj7 | 1 b3 5 7 | Minor/Major 9th | 71 | | m/maj9 | 1 b3 5 7 9 | Minor/Major 9th | 72 | | m/maj11 | 1 b3 5 7 (9) 11 | Minor/Major 11th | 73 | | m/maj13 | 1 b3 5 7 (9) (11) 13 | Minor/Major 13th | 74 | | m7-5 | 1 b3 b5 b7 | Minor seven flat fifth | 75 | | 7#5 | 1 3 #5 b7 | Seven sharp five | 76 | | 7b5 | 1 3 b5 b7 | Seven flat five | 77 | | 7b9 | 1 3 5 b7 b9 | Seven flat ninth | 78 | | 7#9 | 1 3 5 b7 #9 | Seven sharp ninth | 79 | | 9#5 | 1 3 #5 b7 9 | Nine sharp five | 80 | | 9b5 | 1 3 b5 b7 9 | Nine flat five | 81 | | 7#5#9 | 1 3 #5 b7 #9 | Seven sharp five sharp nine | 82 | | 7#5b9 | 1 3 #5 b7 b9 | Seven sharp five flat nine | 83 | | 7b5#9 | 1 3 b5 b7 #9 | Seven flat five sharp nine | 84 | | 7b5b9 | 1 3 b5 b7 b9 | Seven flat five flat nine | 85 | | 7#11 | 1 3 5 b7 #11 | Seven sharp eleven | 86 | | dim7 | 1 b3 b5 bb7 | Diminished 7th | 87 | | 5 | 1 5 | 5th | 88 | | -5 | 1 b5 | Flat 5th | 89 | | sus2 | 1 2 5 | Suspended 2nd | 90 | | #11 | 1 5 #11 | Sharp Eleven | 91 | 92 | 93 | ## Supported Scales 94 | 95 | Note: Formulas need to be confirmed... 96 | 97 | | Name | Interval Structure | 98 | |------|---------| 99 | | Major | W, W, H, W, W, W, H | 100 | | Natural Minor | W, H, W, W, H, W, W | 101 | | Major Pentatonic | W, W, W+H, W, W+H | 102 | | Minor Pentatonic | W+H, W, W, W+H, W | 103 | | Blues | W+H, W, H, H, W+H, W | 104 | | Major Blues | W, H, H, H, H, H, W, H, W | 105 | | Minor Blues | W, H, W, H, H, H, W, W | 106 | | Ionian Mode | W, W, H, W, W, W, H | 107 | | Dorian Mode | W, H, W, W, W, H, W | 108 | | Phrygian Mode | H, W, W, W, H, W, W | 109 | | Lydian Mode | W, W, W, H, W, W, H | 110 | | Mixolydian Mode | W, W, H, W, W, H, W | 111 | | Aeolian Mode | W, H, W, W, H, W, W | 112 | | Locrian Mode | H, W, W, H, W, W, W | 113 | | Harmonic Minor | W, H, W, W, H, W+H, H | 114 | | Phrygian Dominant | H, W+H, H, W, H, W, W | 115 | | Jazz Melodic Minor | W, H, W, W, W, W, H | 116 | | Dorian b2 | H, W, W, W, W, H, W | 117 | | Lydian Augmented | W, W, W, W, H, W, H | 118 | | Lydian b7 | W, W, W, H, W, H, W | 119 | | Mixoydian b13 | W, W, H, W, H, W, W | 120 | | Locrian #2 | W, H, W, H, W, W, W | 121 | | Super Locrian | H, W, H, W, W, W, W | 122 | | Chromatic | H, H, H, H, H, H, H, H, H, H, H, H | 123 | | Whole Tone | W, W, W, W, W, W, W | 124 | | Dimished Whole Half | W, H, W, H, W, H, W, H | 125 | | Dimished Half Whole | H, W, H, W, H, W, H, W | 126 | | Hungarian Minor | W, H, W+H, H, W+H, H | 127 | | Double Harmonic | H, W+H, H, W, H, W+H, H | 128 | | Enigmatic | W, W+H, W, W, W, H, H | 129 | | Japanese | H, W+H, W, H, W, H | 130 | 131 | -------------------------------------------------------------------------------- /css/base.css: -------------------------------------------------------------------------------- 1 | /**, *:before, *:after { 2 | -webkit-box-sizing: border-box; 3 | -moz-box-sizing: border-box; 4 | box-sizing: border-box; 5 | }*/ 6 | 7 | 8 | 9 | table.c_chord_info, table.c_chord_info *, 10 | table.c_guitar_diagram, table.c_guitar_diagram *, 11 | table.c_piano_diagram, table.c_piano_diagram *, 12 | table.c_guitar_fretboard, table.c_guitar_fretboard *{ 13 | font-size:14px; 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | outline: none; 18 | border-collapse: collapse; 19 | border-spacing: 0; 20 | vertical-align:baseline; 21 | font-family: Arial; 22 | } 23 | 24 | .c_chord_info .c { 25 | font-weight:bold; 26 | } 27 | 28 | .noselect { 29 | -webkit-touch-callout: none; 30 | -webkit-user-select: none; 31 | -khtml-user-select: none; 32 | -moz-user-select: none; 33 | -ms-user-select: none; 34 | user-select: none; 35 | } 36 | 37 | 38 | /* 39 | * Guitar/Strings ========================================== 40 | */ 41 | 42 | 43 | table.c_guitar_diagram, table.c_guitar_fretboard{ 44 | /* border: 1px solid black; */ 45 | table-layout:fixed; 46 | border-spacing: 0px; 47 | } 48 | 49 | .c_guitar_diagram td { 50 | border:0px; 51 | padding: 0; 52 | outline: none; 53 | margin: 0; 54 | width: 18px; 55 | max-width: 18px; 56 | height: 28px; 57 | overflow: visible; 58 | } 59 | 60 | .c_guitar_diagram .c_openclose { 61 | height: 12px; 62 | font-size: 14px; 63 | } 64 | 65 | .c_guitar_diagram .c_openclose span{ 66 | padding-left: 16px; 67 | margin-top: -10px; 68 | position:absolute; 69 | } 70 | 71 | .c_guitar_diagram .c_openclose span.root{ 72 | color: darkred; 73 | font-weight:bold; 74 | } 75 | 76 | .c_guitar_diagram .c_fretstart { 77 | border-bottom: 3px solid black; 78 | } 79 | 80 | 81 | .c_guitar_diagram .c_fret_cell { 82 | border-right: 1px solid black; 83 | border-bottom: 1px solid #666; 84 | height: 1.4em; 85 | } 86 | 87 | .c_guitar_diagram .c_fret_cell_1st { 88 | border-right: 1px solid black; 89 | border-left: 1px solid black; 90 | height: 1.4em; 91 | } 92 | 93 | .c_guitar_diagram .c_fret_cell_bottom { 94 | border-bottom: 1px solid black; 95 | } 96 | 97 | .c_guitar_diagram .c_fret_cell_top { 98 | border-top: 1px solid black; 99 | } 100 | 101 | table.c_guitar_diagram .guitardot { 102 | border-radius:50%; 103 | background-color: darkblue; 104 | width: 15px; 105 | height: 15px; 106 | margin-left: -8px; 107 | margin-top: 6px; 108 | position:absolute; 109 | 110 | color: white; 111 | font-size:10px; 112 | font-weight:bold; 113 | vertical-align: bottom; 114 | text-align: center; 115 | line-height: 15px; 116 | } 117 | 118 | table.c_guitar_diagram .guitardot.root{ 119 | background-color: darkred; 120 | } 121 | 122 | td.c_chord_title { 123 | font-weight: bold; 124 | text-align: center; 125 | width: 5.5em; 126 | } 127 | 128 | 129 | /* Fretboard.... */ 130 | .c_guitar_fretboard td { 131 | border:0px; 132 | padding: 0; 133 | outline: none; 134 | margin: 0; 135 | width: 50px; 136 | max-width: 50px; 137 | min-width: 50px; 138 | height: 20px; 139 | overflow: visible; 140 | background-position: 50% 50%; 141 | } 142 | 143 | .c_guitar_fretboard td.g_fret_open { 144 | border-right: 5px solid black; 145 | } 146 | 147 | table.c_guitar_fretboard .guitardot { 148 | border-radius:50%; 149 | width: 15px; 150 | height: 15px; 151 | margin-left: 18px; 152 | margin-top: 1px; 153 | position:absolute; 154 | background-color: yellow; 155 | border: 1px solid black; 156 | 157 | color: black; 158 | font-size:10px; 159 | font-weight:bold; 160 | vertical-align: bottom; 161 | text-align: center; 162 | line-height: 15px; 163 | } 164 | 165 | table.c_guitar_fretboard .g_fret_open .guitardot{ 166 | margin-left: 33px; 167 | } 168 | 169 | 170 | table.c_guitar_fretboard .guitardot.root{ 171 | background-color: orange; 172 | } 173 | 174 | table.c_guitar_fretboard .g_fret_num { 175 | text-align:center; 176 | padding-top:2px; 177 | font-weight: bold; 178 | } 179 | 180 | .g_fret { 181 | background-image: url("../images/frets/fret.png") 182 | } 183 | 184 | .g_fret_s { 185 | background-image: url("../images/frets/fret_s.png") 186 | } 187 | 188 | .g_fret_s.s1 {background-image: url("../images/frets/fret_s1.png")} 189 | .g_fret_s.s2 {background-image: url("../images/frets/fret_s2.png")} 190 | .g_fret_s.s3 {background-image: url("../images/frets/fret_s3.png")} 191 | .g_fret_s.s4 {background-image: url("../images/frets/fret_s4.png")} 192 | .g_fret_s.s5 {background-image: url("../images/frets/fret_s5.png")} 193 | .g_fret_s.s6 {background-image: url("../images/frets/fret_s6.png")} 194 | 195 | /* Double Strings */ 196 | .g_fret_sd.s1 {background-image: url("../images/frets/fret_sd1.png")} 197 | .g_fret_sd.s2 {background-image: url("../images/frets/fret_sd2.png")} 198 | .g_fret_sd.s3 {background-image: url("../images/frets/fret_sd3.png")} 199 | .g_fret_sd.s4 {background-image: url("../images/frets/fret_sd4.png")} 200 | 201 | 202 | /* 203 | * PIANO ========================================== 204 | */ 205 | table.c_piano_diagram { 206 | table-layout:fixed; 207 | border-spacing: 0px; 208 | } 209 | 210 | .c_piano_diagram .c_piano_cell { 211 | width: 8px; 212 | max-width: 10px; 213 | height: 20px; 214 | overflow: visible; 215 | text-align:center; 216 | border-bottom: 1px solid black; 217 | } 218 | 219 | .c_piano_diagram .c_piano_cell_top { 220 | height: 30px; 221 | border-top: 1px solid black; 222 | } 223 | 224 | 225 | .c_piano_diagram .c_piano_cell_sharp { 226 | background-color:black; 227 | } 228 | 229 | .c_piano_diagram .c_piano_cell_border { 230 | border-right: 1px solid black; 231 | } 232 | 233 | .c_piano_diagram .c_piano_cell_first { 234 | border-left: 1px solid black; 235 | } 236 | 237 | div.vertical-line{ 238 | width: 1px; /* Line width */ 239 | background-color: black; /* Line color */ 240 | height: 100%; /* Override in-line if you want specific height. */ 241 | margin-left: 4px; 242 | } 243 | 244 | table.c_piano_diagram .pianodot { 245 | border-radius:50%; 246 | background-color: darkblue; 247 | width: 8px; 248 | height: 8px; 249 | margin-top: 5px; 250 | position:absolute; 251 | } 252 | 253 | table.c_piano_diagram .pianodot_up { 254 | border-radius:50%; 255 | background-color: yellow; 256 | width: 8px; 257 | height: 8px; 258 | margin-top: 15px; 259 | position:absolute; 260 | } 261 | 262 | table.c_piano_diagram .pianodot.root { 263 | background-color: darkred; 264 | } 265 | 266 | table.c_piano_diagram .pianodot_up.root { 267 | background-color: red; 268 | } 269 | 270 | -------------------------------------------------------------------------------- /src/Util.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | //------------------------------- 4 | // General Utilities 5 | //------------------------------- 6 | 7 | /** 8 | * Utilities class for chords. This is for general utils and should not depend on 9 | * any other class 10 | * 11 | * @class C.Util 12 | */ 13 | C.Util = { 14 | /** 15 | * Extend an object with properties of one or more other objects 16 | * 17 | * @param {Object} dest Object to be extended 18 | * @param {Object} classes/instances One or more other objects to extend from 19 | * @return {Object} Altered dest 20 | * @static 21 | */ 22 | extend: function (dest) { 23 | var i, j, len, src; 24 | 25 | for (j = 1, len = arguments.length; j < len; j++) { 26 | src = arguments[j]; 27 | for (i in src) { 28 | dest[i] = src[i]; 29 | } 30 | } 31 | return dest; 32 | }, 33 | 34 | /** 35 | * Create an object from a given prototype 36 | * 37 | * @return {Object} 38 | * @static 39 | */ 40 | create: Object.create || (function () { 41 | function F() {} 42 | return function (proto) { 43 | F.prototype = proto; 44 | return new F(); 45 | }; 46 | })(), 47 | 48 | /** 49 | * Set options to an object, inheriting parent's options as well 50 | * 51 | * @param {Object} obj 52 | * @param {Object} options 53 | * @return {Object} obj.options 54 | * @static 55 | */ 56 | setOptions: function (obj, options) { 57 | if (!obj.hasOwnProperty('options')) { 58 | obj.options = obj.options ? C.Util.create(obj.options) : {}; 59 | } 60 | for (var i in options) { 61 | obj.options[i] = options[i]; 62 | } 63 | return obj.options; 64 | }, 65 | 66 | 67 | /** 68 | * Check (and return true) if a propery of an object 69 | * is null. Key is given in string format "xxx.yyy.zzz" 70 | * 71 | * @param {Object} obj 72 | * @param {String} key 73 | * @return {Boolean} 74 | * @static 75 | */ 76 | objNull: function(obj, key){ 77 | return (C.Util.objValue(obj,key,undefined)===undefined) 78 | }, 79 | 80 | /** 81 | * Return the value of a propery of an object. If it is null, the 82 | * defVal will be returned. Key is given in string format "xxx.yyy.zzz" 83 | * 84 | * @param {Object} obj 85 | * @param {String} key 86 | * @param {Object} defVal 87 | * @return Property value or defVal 88 | * @static 89 | */ 90 | objValue: function(obj, key, defVal){ 91 | if (!obj) return defVal; 92 | var keys = key.split("."), value; 93 | for(var i = 0; i < keys.length; i++){ 94 | if(typeof obj[keys[i]] !== "undefined"){ 95 | value = obj = obj[keys[i]]; 96 | }else{ 97 | return defVal; 98 | } 99 | } 100 | return value; 101 | }, 102 | 103 | /** 104 | * Return true if an object is empty 105 | * 106 | * @param {Object} obj 107 | * @return {Boolean} 108 | * @static 109 | */ 110 | objIsEmpty: function(obj){ 111 | for(var key in obj) { 112 | if(obj.hasOwnProperty(key)) return false; 113 | } 114 | 115 | return true; 116 | }, 117 | 118 | /** 119 | * Trim whitespace from both sides of a string 120 | * @param {String} str 121 | * @return {String} 122 | * @static 123 | */ 124 | trim: function (str) { 125 | return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); 126 | }, 127 | 128 | /** 129 | * Split a string into words 130 | * 131 | * @param {String} str 132 | * @return {Array} of Strings... 133 | * @static 134 | */ 135 | splitWords: function (str) { 136 | return C.Util.trim(str).split(/\s+/); 137 | }, 138 | 139 | /** 140 | * Clone an object instead of pointing to it! 141 | * 142 | * @param {Object/Array} o 143 | * @return {Object/Array} 144 | * @static 145 | */ 146 | clone: function(o) { 147 | if(typeof(o) != 'object' || o == null) return o; 148 | 149 | var newO = new Object(); 150 | if (Object.prototype.toString.call( o ) === '[object Array]' ) 151 | newO = new Array(); 152 | 153 | for(var i in o) { 154 | // Detect dom ones 155 | 156 | if (o["map"] == o[i]){ 157 | newO[i] = o[i]; 158 | }else if (o[i].parentNode == undefined){ 159 | newO[i] = C.Util.clone(o[i]); 160 | }else{ 161 | newO[i] = o[i].cloneNode(false); 162 | } 163 | } 164 | return newO; 165 | }, 166 | 167 | /** 168 | * Parse the URL query string and return it as object 169 | * @return {Object} 170 | * @static 171 | */ 172 | getQueryString: function() { 173 | var query_string = {}; 174 | var query = window.location.search.substring(1); 175 | var vars = query.split("&"); 176 | 177 | for (var i=0;i gt) 243 | min=this[i]; 244 | } 245 | return min; 246 | }; 247 | 248 | /** 249 | * Sort numerically 250 | * @return {Array} 251 | */ 252 | Array.prototype.sortn = function(){ 253 | return this.sort(function(a,b){return a-b}); 254 | }; 255 | 256 | /** 257 | * Return true if item is found in the array 258 | * @return {Boolean} 259 | */ 260 | Array.prototype.hasItem = function(item){ 261 | return (this.indexOf(item)!==-1); 262 | }; 263 | 264 | /** 265 | * Return new array with duplicate values removed 266 | * @return {Array} 267 | */ 268 | Array.prototype.unique = function() { 269 | var a = []; 270 | var l = this.length; 271 | for(var i=0; i minBufLen) 70 | { 71 | recording = false; 72 | correlation_worker.postMessage 73 | ( 74 | { 75 | "timeseries": buffer, 76 | "test_frequencies": test_frequencies, 77 | "sample_rate": audio_context.sampleRate 78 | } 79 | ); 80 | 81 | buffer = []; 82 | // Urban: sliding window ?? Keep middle values...?! 83 | // buffer=buffer.splice(minBufLen/1.5); 84 | 85 | setTimeout(function() { recording = true; }, 250); 86 | } 87 | }; 88 | script_processor.onaudioprocess = window.capture_audio; 89 | } 90 | 91 | 92 | 93 | 94 | function interpret_correlation_result(event) 95 | { 96 | data = event.data; 97 | var timeseries = data.timeseries; 98 | var frequency_amplitudes = data.frequency.amplitudes; 99 | var magnitudes = data.frequency.magnitudes; 100 | 101 | 102 | // var average = magnitudes.reduce(function(a, b) { return a + b; }, 0) / magnitudes.length; 103 | var fstats = data.frequency.stats; 104 | 105 | C.AudioUtil.calcFinOctave(2, data, test_frequencies); 106 | 107 | 108 | var confidence = fstats.max / fstats.avgM; 109 | var avgF = fstats.avgFinOctave; 110 | 111 | 112 | // Clear lights 113 | C.DomUtil.removeClass(spot, "lightGreen"); 114 | C.DomUtil.removeClass(left, "lightRed"); 115 | C.DomUtil.removeClass(right, "lightRed"); 116 | if (confidence > CONFTHRES && data.time.stats.amp > SIGNALAMP) 117 | { 118 | var dominant_frequency = test_frequencies[fstats.maxIdx]; 119 | document.getElementById("note-name").textContent = dominant_frequency.name; 120 | document.getElementById("frequency").textContent = dominant_frequency.frequency; 121 | document.getElementById("avgf").textContent = avgF; 122 | 123 | var halfToneDist = C.getFreqOffset(dominant_frequency.frequency, avgF); 124 | halfToneDist=Math.abs(halfToneDist); 125 | 126 | // FIXME: calc note spacing! 127 | if (halfToneDist<=HALFTONEGOOD) 128 | C.DomUtil.addClass(spot, "lightGreen"); 129 | else if (avgF"+JSON.stringify(data.time.stats); 140 | } 141 | 142 | 143 | function vis(el){ 144 | var WIDTH = el.width; 145 | var HEIGHT = el.height; 146 | 147 | var canvasCtx = el.getContext("2d"); 148 | canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); 149 | 150 | 151 | function draw() { 152 | drawVisual = requestAnimationFrame(draw); 153 | var magnitudes = ( data ) ? data.frequency.magnitudes : false; 154 | if (!magnitudes) return; 155 | 156 | 157 | canvasCtx.fillStyle = 'rgb(0, 0, 0)'; 158 | canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); 159 | drawFs(el); 160 | 161 | var barWidth = (WIDTH / magnitudes.length)-1; 162 | var barHeight; 163 | var x = 0; 164 | for(var i = 0; i < magnitudes.length; i++) { 165 | if (magnitudes[i]==0) continue; 166 | barHeight = magnitudes[i]/allTimesMax*HEIGHT; 167 | 168 | canvasCtx.fillStyle = 'red'; 169 | canvasCtx.fillRect(x,HEIGHT-barHeight,barWidth,barHeight); 170 | 171 | x += barWidth + 1; 172 | } 173 | }; 174 | 175 | draw(); 176 | } 177 | 178 | function drawFs(el){ 179 | var WIDTH = el.width; 180 | var HEIGHT = el.height; 181 | 182 | var canvasCtx = el.getContext("2d"); 183 | 184 | 185 | var barWidth = 1; 186 | var barWidth2 = (WIDTH / test_frequencies.length)-1; 187 | 188 | var min = test_frequencies[0].frequency 189 | var max = test_frequencies[test_frequencies.length-1].frequency 190 | // Bandwidth 191 | var bw = max-min; 192 | 193 | for(var i = 0; i < test_frequencies.length; i++) { 194 | var f = test_frequencies[i].frequency; 195 | var n = test_frequencies[i].name; 196 | if (n[1]=="#") { 197 | continue; 198 | } 199 | var color = "blue"; 200 | if ((i%12)==0) color = "yellow"; 201 | 202 | 203 | 204 | canvasCtx.fillStyle = color; 205 | // We dont plot in scale (lower Fs are too dense) 206 | // canvasCtx.fillRect((f-min)*WIDTH/bw,0,barWidth,HEIGHT); 207 | canvasCtx.fillRect(i*(barWidth2+1),0,barWidth2,HEIGHT); 208 | 209 | } 210 | } 211 | 212 | 213 | function visAnal(el){ 214 | var WIDTH = el.width; 215 | var HEIGHT = el.height; 216 | 217 | var canvasCtx = el.getContext("2d"); 218 | canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); 219 | 220 | 221 | 222 | function draw2() { 223 | requestAnimationFrame(draw2); 224 | var dataAnalog = ( data ) ? data.timeseries : false; 225 | 226 | if (!dataAnalog) return; 227 | 228 | canvasCtx.fillStyle = 'rgb(200, 200, 200)'; 229 | canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); 230 | 231 | canvasCtx.lineWidth = 1; 232 | canvasCtx.strokeStyle = 'rgb(0, 0, 0)'; 233 | 234 | canvasCtx.beginPath(); 235 | 236 | var sliceWidth = WIDTH * 1.0 / dataAnalog.length; 237 | var x = 0; 238 | 239 | for(var i = 0; i < dataAnalog.length; i++) { 240 | 241 | var v = dataAnalog[i]; 242 | var y = v * HEIGHT/2 + HEIGHT/2; 243 | 244 | if(i === 0) { 245 | canvasCtx.moveTo(x, y); 246 | } else { 247 | canvasCtx.lineTo(x, y); 248 | } 249 | 250 | x += sliceWidth; 251 | } 252 | 253 | canvasCtx.lineTo(WIDTH, HEIGHT/2); 254 | canvasCtx.stroke(); 255 | }; 256 | 257 | draw2(); 258 | } 259 | 260 | // Unnecessary addition of button to play an E note. 261 | var note_context = new AudioContext(); 262 | var note_node = note_context.createOscillator(); 263 | var gain_node = note_context.createGain(); 264 | note_node.frequency.value = C.Note.F["E4"]; // E, ~82.41 Hz. 265 | gain_node.gain.value = 0; 266 | note_node.connect(gain_node); 267 | gain_node.connect(note_context.destination); 268 | note_node.start(); 269 | var playing = false; 270 | 271 | function toggle_playing_note() 272 | { 273 | lg(note_node.frequency.value) 274 | playing = !playing; 275 | if (playing) 276 | gain_node.gain.value = 0.5; 277 | else 278 | gain_node.gain.value = 0; 279 | } -------------------------------------------------------------------------------- /src/Instrument.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Basic Instrument class 3 | * 4 | * @class 5 | * @extends C.Class 6 | */ 7 | C.Instrument = C.Class.extend({ 8 | options: { 9 | numFrets: -1, 10 | description: "", 11 | maxFretSpan: 4, // Inclusive distance 12 | renderer: null 13 | }, 14 | 15 | /** 16 | * Base init function. Creates the chord cache structure 17 | * 18 | * @param {Object} options 19 | */ 20 | initialize: function (options) { 21 | C.setOptions(this, options); 22 | 23 | // Chord Data: TODO: Make Object to have functions? 24 | this.c = { 25 | // Chord set to the instrument 26 | chord: "", 27 | // Chord notes 28 | notes: [], 29 | // Chord formula 30 | form: "", 31 | // Formula split 32 | fsplit: [], 33 | // Chord sub-notes on each string 34 | fpos: [], 35 | // Final chord positions 36 | pos: [], 37 | 38 | // Diagram data 39 | diag: { 40 | el: null, 41 | idx: -1 42 | }, 43 | // 9ths,11ths,13ths 44 | th9: false, 45 | th11: false, 46 | th13: false 47 | }; 48 | 49 | 50 | 51 | }, 52 | 53 | /** 54 | * Return the number of strings 55 | * 56 | * @return {Number} 57 | */ 58 | getNumStrings: function(){ 59 | return this.options.strings.length; 60 | }, 61 | 62 | 63 | /** 64 | * Return the instruments name 65 | * 66 | * @return {String} 67 | */ 68 | getName: function(){ 69 | return this.options.name; 70 | }, 71 | 72 | /** 73 | * Return the instruments description 74 | * 75 | * @return {String} 76 | */ 77 | getDescription: function(){ 78 | return this.options.description; 79 | }, 80 | 81 | /** 82 | * Map a chord on this instrument. This function creates 83 | * the posible chord positions. Internaly it will call: 84 | * - _initChordData 85 | * - _procChord 86 | * - _slideWindow 87 | * - _setDifficulty 88 | * - _sortChordPos 89 | * 90 | * @param {C.Chord} c 91 | */ 92 | mapChord: function(c){ 93 | this.c.chord=c; 94 | this._initChordData(); 95 | this._procChord(); 96 | lg(this._splitFormulaDbgStr()); 97 | 98 | this._slideWindow(); 99 | this._setDifficulty(); 100 | this._sortChordPos(); 101 | // C.ChordRep.dbgPrintArray(this.c.pos,true) 102 | 103 | return this; 104 | 105 | }, 106 | 107 | /** 108 | * Init chord data structure and reset any previous data. 109 | * 110 | * @private 111 | */ 112 | _initChordData: function(){ 113 | 114 | // Init data 115 | this.c.form = this.c.chord.getFormula(); 116 | this.c.fsplit = this.c.form.split(" "); 117 | 118 | this.c.notes = new Array(); 119 | this.c.fpos = new Array(); 120 | this.c.pos = new Array(); 121 | 122 | return this 123 | }, 124 | 125 | /** 126 | * Process chord and find the positions for each formula 127 | * part on each string. 128 | * 129 | * @private 130 | */ 131 | _procChord: function(){ 132 | 133 | // Find all positions for all sub-notes in chord 134 | for (var fp = 0; fp= rIdx) 194 | first = parseInt(nIdx-rIdx); 195 | else 196 | first = parseInt((C.NOTES.length - rIdx)+nIdx); 197 | 198 | // Expand to the number of frets 199 | while (first<=till){ 200 | if (frets.hasItem(first)) continue; 201 | frets.push(first); 202 | first+=C.NOTES.length; 203 | } 204 | 205 | return frets; 206 | }, 207 | 208 | 209 | /** 210 | * Debug print the result of _procChord function 211 | * @private 212 | */ 213 | _splitFormulaDbgStr: function(){ 214 | var str = "For each formula part on a per string basis.\n"; 215 | for (var i=0; i=0; i--) { 326 | if (chord.getPos(i)==-1) 327 | continue; 328 | 329 | var n = chord.getNote(i).toString(); 330 | 331 | // Detect ROOT 332 | var po = chord.getNote(i).options.playPos; 333 | if (root==-1 && n==this.c.chord.getRoot()) { 334 | root=chord.getNote(i).options.playPos; 335 | } 336 | 337 | // 9ths,etc 338 | if (root && (n==this.c.th9 || n==this.c.th11 || n==this.c.th13) 339 | && (po-root=this.c.pos.length) idx=this.c.pos.length-1; 403 | 404 | return this.c.pos[idx]; 405 | }, 406 | 407 | /** 408 | * Sort chord positions based on their difficulty 409 | * @private 410 | */ 411 | _sortChordPos: function(){ 412 | this.c.pos.sort(function(a,b){ 413 | return (a.getDiff()-b.getDiff()) 414 | }); 415 | 416 | return this; 417 | }, 418 | 419 | 420 | //-------------------------------- 421 | // Plotting/GUI 422 | //-------------------------------- 423 | 424 | /** 425 | * Base of diagram, ensure that what is a chord representation 426 | * and store info. This function is responsible for calling the 427 | * correct plotter based on the options object. 428 | * 429 | * @param {C.ChordRep/Number} Index of chord or ChordRep to plot 430 | * @param {HTMLElement} el 431 | * @param {Object} opts Options 432 | */ 433 | diagram: function(what,el,opts){ 434 | 435 | el = (el) ? el : this.c.diag.el; 436 | 437 | this.c.diag.el = el; 438 | 439 | // Accept eiter chord representation or 440 | // position 441 | if (!(what instanceof C.ChordRep)) { 442 | if (this.c.pos.length==0) return false; 443 | if (what<0) what=0; 444 | if (what>=this.c.pos.length) what=this.c.pos.length-1; 445 | 446 | // Store 447 | this.c.diag.idx = what; 448 | what = this.c.pos[what]; 449 | } 450 | 451 | var t = C.Util.objValue(opts, "type", "html"); 452 | 453 | switch (t) { 454 | case "html": 455 | return this.diagramHTML(what,el,opts); 456 | default: 457 | throw new Error({'C.Instrument':'Diagram type "'+t+'" is not known...'}) 458 | } 459 | 460 | return this; 461 | }, 462 | 463 | /** 464 | * Show next chord from the available positions 465 | * 466 | * @return {Boolean} True if next exists 467 | */ 468 | diagramNext: function(){ 469 | if (this.c.diag.idx==-1 || !this.c.diag.el) return false; 470 | this.diagram(this.c.diag.idx+1); 471 | return true; 472 | }, 473 | 474 | /** 475 | * Show prev chord 476 | * @return {Boolean} True if previous exists 477 | */ 478 | diagramPrev: function(){ 479 | if (this.c.diag.idx==-1 || !this.c.diag.el) return false; 480 | this.diagram(this.c.diag.idx-1); 481 | return true; 482 | } 483 | }); 484 | 485 | 486 | 487 | 488 | 489 | 490 | -------------------------------------------------------------------------------- /src/Chord.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * Basic Chord class 5 | * 6 | * @class 7 | * @extends C.Class 8 | */ 9 | C.Chord = C.Class.extend({ 10 | options: { 11 | root: "-", 12 | type: "" // Empty for major, m, dim, dim7, 7, m7, etc 13 | }, 14 | 15 | /** 16 | * Base init function 17 | * @param {Object} options 18 | */ 19 | initialize: function (options) { 20 | C.setOptions(this, options); 21 | }, 22 | 23 | /** 24 | * Get string representation of the chord name 25 | * concatenating the root and the type (Am7). 26 | * 27 | * @return {String} 28 | */ 29 | toString: function(){ 30 | return this.options.root+ this.getChordType().type 31 | }, 32 | 33 | /** 34 | * Offset the root of the chord. If step is given 35 | * then it will off*step ie: step=2 (by tone) 36 | * 37 | * @param {Number} off Offset steps 38 | * @param {Number} step The step size (default 1 - half-tone) 39 | * @return this 40 | */ 41 | offset: function(off, step){ 42 | var idx = C.Note.noteIndex(this.options.root); 43 | if (step!==undefined) off*=step; 44 | var newIdx = (idx+off); 45 | this.options.root = C.NOTES[newIdx]; 46 | 47 | return this; 48 | }, 49 | 50 | /** 51 | * Set the chord type 52 | * 53 | * @param {String} type 54 | * @return this 55 | */ 56 | setType: function(type){ 57 | this.options.type = type; 58 | return this; 59 | }, 60 | 61 | /** 62 | * Set the chord root 63 | * 64 | * @param {String} root 65 | * @return this 66 | */ 67 | setRoot: function(root){ 68 | this.options.root = root; 69 | return this; 70 | }, 71 | 72 | /** 73 | * Return a note index by offset-ing the root note of 74 | * this chord on the major scale 75 | * 76 | * @param {String} f 77 | * @return {Number} 78 | * @private 79 | */ 80 | _formulaToIdx: function(f){ 81 | return this._formulaToNote(f).getIdx(); 82 | 83 | }, 84 | 85 | /** 86 | * Return a note by offset-ing the root note of 87 | * this chord on the major scale 88 | * 89 | * @param {String} f 90 | * @return {C.Note} 91 | * @private 92 | */ 93 | _formulaToNote: function(f){ 94 | var scale = new C.Scale({root: this.options.root,name:"Major"}) 95 | return scale.offset(f); 96 | }, 97 | 98 | /** 99 | * Get the chord's formula as string 100 | * 101 | * @return {String} 102 | */ 103 | getFormula: function(){ 104 | return this.getChordType().formula; 105 | }, 106 | 107 | /** 108 | * Get chord type information (type/name/formula) 109 | * 110 | * @return {Object} 111 | */ 112 | getChordType: function(){ 113 | return C.Chord.TYPES[this.getType()]; 114 | }, 115 | 116 | /** 117 | * Get chord's root 118 | * @return {String} 119 | */ 120 | getRoot: function(){ 121 | return this.options.root; 122 | }, 123 | 124 | /** 125 | * Get chord's type 126 | * @return {String} 127 | */ 128 | getType: function(){ 129 | return (this.options.type=="") ? "M" : this.options.type; 130 | }, 131 | 132 | /** 133 | * Get full chord's name from C.Chord.TYPES 134 | * @return {String} 135 | */ 136 | getFullName: function(){ 137 | return this.getChordType().name; 138 | }, 139 | 140 | /** 141 | * Get the notes included in this chord 142 | * @return {Array} of C.Note 143 | */ 144 | getNotes: function(){ 145 | var f = this.getFormula().split(" "); 146 | var notes = []; 147 | for (var i=0; ithis.pos[i]) lowerDup=true; // Lower note than the min duplicate 514 | } 515 | 516 | return (dup>0 && lowerDup!=true); 517 | } 518 | 519 | 520 | }); 521 | 522 | /** 523 | * Debug print of a ChordRep array 524 | * @param {Array} arr of C.ChordRep 525 | * @param {Boolean} debug 526 | * @static 527 | */ 528 | C.ChordRep.dbgPrintArray = function(arr,debug){ 529 | for (var i=0; i=this.options.maxFretSpan) return false; 36 | 37 | var dup = c.getMinDuplicate(); 38 | 39 | // Do the basic checking and see if this can be played 40 | // as bare 41 | var higherDup = false; 42 | var lowerDup = false; 43 | var count=0; 44 | for (var i=0; i0) { 57 | if (dupc.getPos(i)) lowerDup=true; // Lower note than the min duplicate 59 | } 60 | } 61 | 62 | // Now do the Math... 63 | var bar = (dup>0 && lowerDup!=true); 64 | var finalCount = count; 65 | if (bar && this.options.hasBar) 66 | finalCount=count - c.countNum(dup) +1; 67 | // lg(c+" "+finalCount) 68 | var playable = (finalCount <= this.options.maxPlayableTones); 69 | 70 | if (!playable) return false; 71 | return true; 72 | 73 | }, 74 | 75 | /** 76 | * Make all the posible string/fret combinations that include notes 77 | * from the chords' formula. This is actually done recursively calling 78 | * __doRecursion 79 | * 80 | * @private 81 | */ 82 | _slideWindow: function(){ 83 | 84 | 85 | // For each string! set the the first postion and do all 86 | // combos for the next string! 87 | this.__doRecursion( 88 | 0, 89 | new C.ChordRep.getEmpty(this.getNumStrings(), this) 90 | ); 91 | 92 | return this; 93 | }, 94 | 95 | /** 96 | * Make all the posible string/fret combinations (recursively) that 97 | * include notes from the chords' formula 98 | * 99 | * @param {Number} s Index of string 100 | * @param {C.ChordRep} chord Chord built at the current stage 101 | * @private 102 | */ 103 | __doRecursion: function(s,chord){ 104 | // var tab = ""; 105 | // for (var t=0; ttmp_pos) min=tmp_pos; 127 | } 128 | if (max-min > this.options.maxFretSpan) { 129 | // lg(tab+"Skipping pos="+tmp_pos+" max="+max+" min="+min) 130 | continue; 131 | } 132 | } 133 | 134 | // Clone: Do NOT change the base 135 | var newChord = chord.clone(); 136 | newChord.setPos(s,tmp_pos); 137 | newChord.setNote(s, this.getNoteForString(s, tmp_pos)); 138 | 139 | // Current string was the last 140 | if (s==this.getNumStrings()-1) { 141 | newChord=this._checkBase(newChord, this.c.chord.getRoot()); 142 | 143 | if (newChord.isEmpty() || 144 | this._chordPosExists(newChord) || 145 | !this.isChordPlayable(newChord) || 146 | !this._checkChord(newChord) ) { 147 | // lg (tab+"Failing: "+newChord +this._chordPosExists(newChord)) 148 | continue; 149 | } 150 | 151 | 152 | // Assuming we got everything now... 153 | // lg (tab+"Adding: "+newChord) 154 | this.c.pos.push(newChord); 155 | } 156 | else { 157 | this.__doRecursion(s+1, newChord.clone()); 158 | } 159 | // lg(tab+"-"+p) 160 | } 161 | return this; 162 | }, 163 | 164 | /** 165 | * Return all partial positions for this chord given 166 | * the number of tones/piches in it 167 | * 168 | * @return {Array/Boolean} false if not partials exist 169 | * @deprecated 170 | * @private 171 | */ 172 | _getVariations: function(data, chord){ 173 | var variations = []; 174 | var foundNotes = []; 175 | var complete = false; 176 | 177 | 178 | for (var i=this.getNumStrings()-1; i>=0; i--) { 179 | if (chord.getPos(i)==-1) { 180 | continue; 181 | } 182 | 183 | var n = chord.getNote(i).toString(); 184 | if (!foundNotes.hasItem(n)) 185 | foundNotes.push(n); 186 | 187 | // Check that all formula parts are there 188 | if (!complete && foundNotes.length === data.notes.length){ 189 | var complete = true; 190 | } 191 | 192 | // Now if all there and base is correct add as variation 193 | var tmpChord = chord.getPartialHigher(i); 194 | 195 | if (complete && // complete 196 | data.chord.getRoot()==n && // correct base 197 | !tmpChord.isEmpty() && // not the last string 198 | !chord.isEmptyTill(i) && 199 | i!=0 200 | ) 201 | { 202 | variations.push(tmpChord); 203 | } 204 | } 205 | 206 | if (!complete) return false; 207 | return variations; 208 | }, 209 | 210 | /** 211 | * Check that the chord start with the base note... 212 | * MAYBE: Optional? 213 | * 214 | * @param {C.ChordRep} c 215 | * @param {C.Note} root 216 | * @private 217 | */ 218 | _checkBase: function(c,root){ 219 | 220 | for (var s=c.getBasePos(); smaxDiff) maxDiff=diff; 272 | c.setDiff(diff); 273 | } 274 | 275 | // Normalize scores... optional 276 | 277 | return this; 278 | }, 279 | 280 | /** 281 | * 282 | * Get chords difficulty 283 | * 284 | * Change and loop: 285 | * - Higher priority chords on top 286 | * - UnInterapted patters (x ONLY) 287 | * - Make sure the distance between fret and o is small 288 | * - Make sure a base string is played! 289 | * - Span, the sorter the better 290 | * - Number of position < the better 291 | * 292 | * TODO:FIXME: This does not work very well... find another way 293 | * 294 | * @param {C.ChordRep} c 295 | * @return {Number} 296 | */ 297 | getChordDiff: function(c){ 298 | var min = 100000; 299 | var max = -1; 300 | var countXinBetween = 0; 301 | var countX = 0; 302 | var countXTotal = 0; 303 | var countOinBetween = 0; 304 | var countO = 0; 305 | var countOTotal = 0; 306 | var foundPress = false; 307 | 308 | for (var s=0; s0 && curmax) max=cur; 314 | 315 | // Handle 0/-1 316 | if (cur>-1) { 317 | // If 1st playable, discart OX 318 | if (!foundPress) { 319 | countO=0; 320 | countX=0; 321 | } 322 | foundPress=true; 323 | // Add all previous... 324 | countXinBetween+=countX; 325 | countX=0; 326 | if (cur!=0) { 327 | countOinBetween+=countO; 328 | countO=0; 329 | } 330 | else { 331 | countO++; // this resets 332 | countOTotal++; // this doesn't 333 | } 334 | } 335 | else if (cur==-1) { 336 | countX++; // this resets 337 | countXTotal++; // this doesn't 338 | } 339 | } 340 | 341 | // We should be OK here! 342 | // lg (c+":"+countXTotal+" "+ countXinBetween+", "+countOTotal+" "+ countOinBetween+", "+min+" "+max) 343 | 344 | var score=50; // out of 100 345 | // An Open is good 346 | score -= (countOTotal)*5; 347 | // ... however an open in between is worse 348 | score += countOinBetween*10; 349 | // and all open is also Bad boy... 350 | if (max==0) score+=60; 351 | 352 | // X in the middle is very bad 353 | score += countXinBetween*20; 354 | // Small Penalty for not playing strings... 355 | score += (countXTotal-countXinBetween)*4; 356 | 357 | // Bar is bad! 358 | if (c.isBar()) score += 20; 359 | // However (full bar) 360 | if (c.isBar() && !countXinBetween && !countOinBetween) score -= 20; 361 | 362 | 363 | // Use less a bit long open 364 | if (countOTotal>0 && max>5) score+= (max -5)*3; 365 | 366 | // The longer it spans the more diff 367 | if (max!=0) score+=(max-min)*5; 368 | 369 | 370 | 371 | 372 | // The Higher the better! (If no shit in the between) 373 | // just fractions for Fs... 374 | // if (!countXinBetween && !countOinBetween) 375 | // score+=(min); 376 | 377 | // Playing a base is good... 378 | // Penalty chords that do not have one... 379 | if (c.getBasePos()>=this.getNumStrings()/2) score+=c.getBasePos()*5; 380 | 381 | 382 | 383 | return score; 384 | 385 | }, 386 | 387 | /** 388 | * Make current set chord diagram in an HTML element 389 | * 390 | * @param {C.ChordRep} what 391 | * @param {HTMLElement} el 392 | * @param {Object} opts 393 | */ 394 | diagramHTML: function(what,el,opts){ 395 | 396 | el = (el) ? el : this.c.diag.el; 397 | if (what===false) return false; 398 | 399 | C.DomUtil.empty(el); 400 | var base = this._getBaseTable(); 401 | var idx = base[1]; 402 | 403 | // Title 404 | idx[0][1].textContent = this.c.chord.toString(); 405 | 406 | // First fret && Fretboard start! 407 | var numStrings = this.getNumStrings(); 408 | var f = what.minPos(); 409 | 410 | // We start from top 411 | if (f<=1 || what.maxPos()<=this.options.maxFretSpan+1){ 412 | f="" 413 | for (var i=1; ithis.options.maxFretSpan+1) 440 | idxpos = what.getPos(i) - what.minPos() +1; // start from 0 fret + headers 441 | 442 | var dot = C.DomUtil.create("div","guitardot"+cls,idx[idxpos+1][i+1]); 443 | dot.innerText = tmpNote; 444 | } 445 | 446 | el.appendChild(base[0]); 447 | 448 | return this; 449 | }, 450 | 451 | 452 | /** 453 | * Helper function to get the basic HTML table to be used for plotting 454 | * the chord on... 455 | * 456 | * @return {Array} of DOMElement (table) and index of all it's cells 457 | * @@private 458 | */ 459 | _getBaseTable: function(){ 460 | var t = C.DomUtil.create('table','c_guitar_diagram noselect'); 461 | var idx = []; 462 | 463 | // 1st line - title 464 | idx[0] = []; 465 | var cell = C.DomUtil.create('tr','',t); 466 | idx[0][0] = C.DomUtil.create('td','',cell); // wasted column 467 | idx[0][1] = C.DomUtil.create('td','c_chord_title',cell); // title 468 | idx[0][1].colSpan = this.getNumStrings()-1; 469 | 470 | var numStrings = this.getNumStrings(); 471 | 472 | // The rest! 473 | for (var row=1; row<2+this.options.maxFretSpan+1; row++) { 474 | idx[row] = []; 475 | var cell = C.DomUtil.create('tr','',t); 476 | for (var i=0; i1 && i>0 && i0 && i0 && i1 && i==1) cls+=" c_fret_cell_1st" 485 | 486 | idx[row][i] = C.DomUtil.create('td',cls,cell); 487 | } 488 | } 489 | return [t,idx]; 490 | }, 491 | 492 | 493 | /** 494 | * Create full the fretboard, if applicable. Store 495 | * the HTML elements on this.fretboard to be able to 496 | * clear without redrawing 497 | * 498 | * @param {HTMLElement} el 499 | * @param {Object} opts 500 | * @param {String} opts.cssClass Base css class 501 | * @param {Boolean} opts.changeSize Change string size 502 | * @param {Function} opts.fretClick Callback for on click on fret 503 | * @return {Array} index 504 | */ 505 | drawInstrument: function(el,opts){ 506 | 507 | if (this.fretboard!==undefined) return; 508 | 509 | C.DomUtil.empty (el); 510 | var numStrings = this.getNumStrings(); 511 | var numFrets = this.options.numFrets; 512 | 513 | // Base css class 514 | var cls = C.Util.objValue(opts, "cssClass", "g_fret_s"); 515 | 516 | if (this.options.doubleString) cls+="d"; 517 | 518 | // Create base table 519 | var t = C.DomUtil.create('table','c_guitar_fretboard noselect'); 520 | var idx = []; 521 | 522 | var cb = C.Util.objValue(opts, "fretClick", false); 523 | 524 | for (var s=0; s