├── .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 |
129 |
130 |
131 |
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