Open sequence of light

When a young folk singer sings “Love Must Have” with his guitar, he sweeps the strings at the hole with his right hand and shifts the strings at the neck with his left hand, and a simple strumming and singing takes on a certain look. Without looking at his face or his singing skills, what is it that makes this young man so seductive?

That’s the guitar accompaniment.

But he is just a beginner of guitar, has not been able to compose the accompaniment to the song, had to find the guitar score from the Internet, according to the logo inside to play. He found the following score:

This is what a typical guitar track should look like, and it can be divided into four parts:

  • Chord fingering: Used to mark the name of the chord in the section and the corresponding fingering.
  • Six line-spectrum: It is a score specially belonging to the guitar. Six horizontal lines from top to bottom correspond to the first string to the sixth string of the guitar respectively. Various symbols are added on the horizontal lines to mark the way of playing the right hand.
  • Numbered musical notation: Here is a numerical notation with various symbols to describe the melody and rhythm of the song.
  • The lyrics: Well, the lyrics.

For beginners, the difficulty of guitar entry lies in the fingering of the left hand. At that time, I wrote down the fingering diagram of most chords, and the cocoons of the left finger tips faded and faded. As a bad musician, I finally had doubts one day.

1. Why is this chord called this?

2. Why is this chord fingering?

3. How many different fingerings are there for the same chord on the guitar?

Based on the basic knowledge of music theory, this paper will use code derivation to calculate the answers to the above questions and visualize the results.


First, start with a single note

Statement of guilty conscience: layman interprets music theory forcibly based on his own understanding, expect professionals to lightly comment

Vibrating object, each of the different frequency (i.e., different pitch) sound can be called a single tone, but the ear in distinguishes the sound ability is limited, in the music system will be an interval of the smallest interval of the adjacent single tone known as chromatic (such division already in the ear in distinguishes the sound range, and conform to the interval of the cycle).

The frequency ratio of two notes separated by a semitone is the 12th root of 2.

Why is that? That brings us to the law of duodenal.

Music doyen after lots of listening practice, found that the interval for example do to high do sounds the most harmonious as a cycle, and this high do and do the frequency ratio is 2, just ensure that single note span between harmony, and can more clearly hear a case, the interval according to the frequency of strokes is divided into 12 equal parts, There is a reciprocal relationship between the Chinese pentatonic scale (Gong Shang Jiao Zhi Yu) and the Western seven-tone scale, as shown below (here I temporarily use numbers to mark each note in the equal temperament interval) :

do
A high-pitched voice do
Octave.

That is, the span between a note and its corresponding octave is an interval, and their frequency ratio is 1:2.

There is a whole tone span between 1 (do) and 2 (re), and there is a semitone span between 3 (mi) and 4 (FA), 7 (Si) and 1 (high do). A whole tone span is equivalent to two semitone spans. It can be seen that there is another tone between 1 (do) and 2 (re). Let’s call it #1 (up do) or b2 (down re).

With that in mind, you can implement a monophonic class in code:

1. First of all, determine a monophonic writing form

The numbers 1, 2, 3, 4, 5, 6 and 7 represent the names of DO, RE, MI, FA, SOL, La and Si respectively.

When the note is raised in a half key, a # is added to the front of the number, for example, #1 (up do). When it is lowered in a half key, a B is added to the front of the number, for example, B1 (down do).

When marking the high-pitched sound of a voice, on the right side of the digital plus a “dot”, for example 1 (high do), # 2. (high rise re) (because the string can’t like chords at the top with digital number), when mark a note of the performer voice low 8 degree, plus a “dot” in the top left of the Numbers, for example. 1 (bass do), b2 (bass re);

2. Build monophonic classes

// A common method for detecting data types
function is(data) {
    return function(type) {
        return Object.prototype.toString.call(data) === `[object ${type}] `; }}// Monophonic class, used for sound mapping query and pitch change, at the same time can mark record its position on the guitar
class Tone {
    constructor(toneString = '1', string, fret) {
        // All named arrays
        this.syllableMap = ['do'.'re'.'mi'.'fa'.'sol'.'la'.'si']; 
        / / interval
        this.keyMap = ['1'['# 1'.'b2'].'2'['# 2'.'b3'].'3'.'4'['# 4'.'b5'].'5'['# 5'.'b6'].'6'['# 6'.'b7'].'7']; 
        // All the names
        this.intervalMap = ['C'['#C'.'bD'].'D'['#D'.'bE'].'E'.'F'['#F'.'bG'].'G'['#G'.'bA'].'A'['#A'.'bB'].'B']; 
        // A string representation of a single tone
        this.toneString = toneString; 
        // String representation of a single tone (minus the octave mark)
        this.toneNormal = toneString.replace(/\./g.' '); 
        / / digital sound
        this.key = toneString.replace(/\.|b|#/g.' '); 
        / / roll call
        this.syllableName = this.syllableMap[+this.key - 1]; 
        // Drop the half-tone flag
        this.flat = toneString.match('b')?'b' : ' ';
        // Litre halftone mark
        this.sharp = toneString.match(The '#')?The '#' : ' ';
        let octave_arr = toneString.split(this.key);
        let octave_flat = octave_arr[0].toString().match(/\./g);
        let octave_sharp = octave_arr[1].toString().match(/\./g);
        // The number of octaves
        this.octave = (octave_sharp ? octave_sharp.length : 0) - (octave_flat ? octave_flat.length : 0);
        // The string position of the guitar
        this.position = {
            / / how many strings
            string: string,
            // What is the character
            fret: fret
        };
    }
    // Gets the position of a sound on an interval
    findKeyIndex(keyString) {
        return this.keyMap.findIndex((item) = > {
            if (is(item)('Array')) {
                return item.includes(keyString);
            } else if (item === keyString) {
                return true;
            } else {
                return false; }}); }// The pitch increases or decreases. Num is the number of semitones that increase or decrease
    step(num) {
        let keyString = this.flat + this.sharp + this.key;
        let len = this.keyMap.length;
        let index = this.findKeyIndex(keyString);
        if (index > - 1) {
            num = +num;
            // Calculate the position of the note on the interval after changing the pitch
            let nextIndex = parseInt(index + num, 0);
            let octave = this.octave;
            if (nextIndex >= len) {
                let index_gap = nextIndex - len;
                octave += Math.floor(index_gap / len) + 1;
                nextIndex = index_gap % len;
            } else if (nextIndex < 0) {
                let index_gap = nextIndex;
                octave += Math.floor(index_gap / len);
                nextIndex = index_gap % len + len;
            }
            let nextKey = this.keyMap[nextIndex];
            // Count and add octave markers
            let octaveString = new Array(Math.abs(octave)).fill('. ').join(' ');
            let toneString = ' ';
            if(! is(nextKey)('Array')) {
                toneString = (octave < 0 ? octaveString : ' ') + nextKey + (octave > 0 ? octaveString : ' ');
                return new this.constructor(toneString, this.position.string, this.position.fret + num);
            } else {
                // It is possible to get two notes of the same pitch but marked differently
                return nextKey.map((key) = > {
                    return new this.constructor((octave < 0 ? octaveString : ' ') + key + (octave > 0 ? octaveString : ' '), this.position.string, this.position.fret + num); }); }}else {
            return null; }}}Copy the code

Once you have this monophonic class, you can use it later to easily compare the span between two notes, and you can deduce the pitch of any other position of the guitar through the STEP method by building the initial note of each string.

Example:

Create a single tone instance of 1 (do)

Monophonic 1 (DO), 5 semitones up to get monophonic 4 (FA); Go up six semitones, and you get two sounds #4 (fa up) and b5 (sol down), which are on the same pitch, essentially the same, but marked differently.


Second, the derivation of chord naming

1. What is a chord

First baidu entry:

From this vernacular, three elements of chords are distilled:

(1) Consisting of three or more sounds;

(2) there is a span relationship between sounds (third or non-third);

(3) The notes should be arranged from low to high.

So I drew a picture:

12 the sound
clockwise
From low to high
may
chord

If you look at it this way, it’s more of a permutation and combination problem. If you take a combination of three notes, pick any three notes out of 12 (not sorted), there will be 220 situations, but not all of them will be chords; Chords chords, as the name suggests, have to sound harmonious, not ugly, and they start to sound more like subjective judgments, but as the body of music knowledge matures, chords will also have a set of accepted standards, and become as predictable as mathematical formulas.

Think about it carefully, whether a chord sounds good or not, what kind of emotional color, depends on the mutual background relationship of the composition of the sound, that is, the mutual pitch interval between the sound, separated too close will be awkward, separated too far will be awkward, then we have to choose a moderate, this moderate is three degrees;

The three degrees are divided into three degrees and three degrees

The span of two whole tones, i.e., four semitones.

Minor third: span of one whole tone plus one semitone, i.e., span of three semitones.

The C chord in the key of C is formed as follows:

1 (do) and 3 (mi) also sandwiches the 3 sounds #1/ B2, 2, #2/ B3, a total of 4 semitones span;

3 (mi) and 5 (sol) also sandwiched between 4, #4/b5 these two sounds, a total of three semitones span;

So a chord that looks like this is a major triad.

2. Common chord marking rules

Chord types composition tag
Major triads Third degree plus minor third degree
Minor triads Minor third plus major third m
Augmented triads Third degree plus third degree aug
A diminished triad Minor third plus minor third dim
Major and minor seventh chord (dominant seventh chord) Major triads+ small three degrees 7orMm7
Major seventh (major seventh) Major triadsThree c + maj7orM7
Minor seventh (minor seventh) Minor triads+ small three degrees m7ormm7
Minor major seventh chord Minor triadsThree c + mM7
Reduction of seventh chord A diminished triad+ small three degrees dim7
Half minus seventh chord A diminished triadThree c + m7-5
Additional seventh chord Augmented triads+ minus three degrees 7 # 5orM7+5
Augmented seventh chord Augmented triads+ small three degrees aug7orMaj7#5

Added chords and root notes of designated chords are more complex and will not be discussed.

3. Root notes

The first note of a chord is the root note of a chord, also known as the base note. The initial name of a chord can be determined according to the current mode and the root note of a chord. For example, in the key of C, the contrast between the root note and the name of the chord is as follows:

1 2 3 4 5 6 7
C D E F G A B

In layman’s terms, when the root of a chord in a key is 1 (do), it is called a chord (additional markers are added according to the third relation between notes). For example:

C:

The chord with the root 1 (do) is called C;

A chord with a root of 2 (re) is called D;

D:

The chord with the root 1 (do) is called D;

The chord with the root 1 (do) is called E;

B callback:

The chord with the root 1 (do) is called B;

A chord with a root of 2 (do) is called C;

4. Complete chord name calculation

Based on the above rules of music theory, the following classes for deriving chord names can be realized:

// String name derivation
class ChordName {
    constructor(chordTone) {
        // Instantiate a single tone class as a tool to calculate the mapping between sounds and various tags
        this.toneUtil = new Tone();
    }
    // Get the span between two sounds
    getToneSpace(tonePre, toneNext) {
        let toneSpace = this.toneUtil.findKeyIndex(toneNext) - this.toneUtil.findKeyIndex(tonePre);    
        return toneSpace = toneSpace < 0 ? toneSpace + 12 : toneSpace;
    }
    / / three degrees
    isMajorThird(tonePre, toneNext) {
        return this.getToneSpace(tonePre, toneNext) === 4;
    }
    / / small three degrees
    isMinorThird(tonePre, toneNext) {
        return this.getToneSpace(tonePre, toneNext) === 3;
    }
    / / add three degrees
    isMajorMajorThird(tonePre, toneNext) {
        return this.getToneSpace(tonePre, toneNext) === 5;
    }
    / / minus three degrees
    isMinorMinorThird(tonePre, toneNext) {
        return this.getToneSpace(tonePre, toneNext) === 2;
    }
    // Major triad
    isMajorChord(chordTone) {
        return this.isMajorThird(chordTone[0], chordTone[1&&])this.isMinorThird(chordTone[1], chordTone[2]);
    }
    // Minor triad m
    isMinorChord(chordTone) {
        return this.isMinorThird(chordTone[0], chordTone[1&&])this.isMajorThird(chordTone[1], chordTone[2]);
    }
    // Add three chord Aug
    isAugmentedChord(chordTone) {
        return this.isMajorThird(chordTone[0], chordTone[1&&])this.isMajorThird(chordTone[1], chordTone[2]);
    }
    // Dim
    isDiminishedChord(chordTone) {
        return this.isMinorThird(chordTone[0], chordTone[1&&])this.isMinorThird(chordTone[1], chordTone[2]);
    }
    // Hang four chords
    isSus4(chordTone) {
        return this.isMajorMajorThird(chordTone[0], chordTone[1&&])this.isMinorMinorThird(chordTone[1], chordTone[2]);
    }
    // Major seventh chord/dominant seventh chord 7 / Mm7
    isMajorMinorSeventhChord(chordTone) {
        if (chordTone.length < 4) return false;
        return this.isMajorChord(chordTone) && this.isMinorThird(chordTone[2], chordTone[3]);
    }
    // The minor major seventh chord mM7
    isMinorMajorSeventhChord(chordTone) {
        if (chordTone.length < 4) return false;
        return this.isMinorChord(chordTone) && this.isMajorThird(chordTone[2], chordTone[3]);
    }
    // Maj7 / M7
    isMajorMajorSeventhChord(chordTone) {
        if (chordTone.length < 4) return false;
        return this.isMajorChord(chordTone) && this.isMajorThird(chordTone[2], chordTone[3]);
    }
    // Minor seventh chord m7 / mm7
    isMinorMinorSeventhChord(chordTone) {
        if (chordTone.length < 4) return false;
        return this.isMinorChord(chordTone) && this.isMinorThird(chordTone[2], chordTone[3]);
    }
    // Subtract the seventh chord dim7
    isDiminishedSeventhChord(chordTone) {
        if (chordTone.length < 4) return false;
        return this.isDiminishedChord(chordTone) && this.isMinorThird(chordTone[2], chordTone[3]);
    }
    // Half minus seventh chords m7-5
    isHalfDiminishedSeventhChord(chordTone) {
        if (chordTone.length < 4) return false;
        return this.isDiminishedChord(chordTone) && this.isMajorThird(chordTone[2], chordTone[3]);
    }
    // Add 7#5 / M7+5
    isHalfAugmentedSeventhChord(chordTone) {
        if (chordTone.length < 4) return false;
        return this.isAugmentedChord(chordTone) && this.isMinorMinorThird(chordTone[2], chordTone[3]);
    }
    // aug7 / Maj7#5
    isAugmentedSeventhChord(chordTone) {
        if (chordTone.length < 4) return false;
        return this.isAugmentedChord(chordTone) && this.isMinorThird(chordTone[2], chordTone[3]);
    }
    // Get the root chord name corresponding to the note
    getKeyName(key) {
        let keyName = this.toneUtil.intervalMap[this.toneUtil.findKeyIndex(key)];
        if (is(keyName)('Array')) {
            keyName = /b/.test(key) ? keyName[1] : keyName[0];
        };
        return keyName;
    }
    // Compute chord names
    getChordName(chordTone) {
        let rootKey = chordTone[0];
        // The letter name of the chord
        let chordRootName = this.getKeyName(rootKey);
        // The specific modifier name after the chord letter
        let suffix = '... ';
        let suffixArr = [];
        // Three tone chord traversal method and corresponding modifier name
        let chord3SuffixMap = [{
            fn: this.isMajorChord,
            suffix: ' '
        }, {
            fn: this.isMinorChord,
            suffix: 'm'
        }, {
            fn: this.isAugmentedChord,
            suffix: 'aug'
        }, {
            fn: this.isDiminishedChord,
            suffix: 'dim'
        }, {
            fn: this.isSus4,
            suffix: 'sus4'
        }];
        // Four tone chord traversal method and corresponding modifier name
        let chord4SuffixMap = [{
            fn: this.isMajorMinorSeventhChord,
            suffix: '7'
        }, {
            fn: this.isMinorMajorSeventhChord,
            suffix: 'mM7'
        }, {
            fn: this.isMajorMajorSeventhChord,
            suffix: 'maj7'
        }, {
            fn: this.isMinorMinorSeventhChord,
            suffix: 'm7'
        }, {
            fn: this.isDiminishedSeventhChord,
            suffix: 'dim7'
        }, {
            fn: this.isHalfDiminishedSeventhChord,
            suffix: 'm7-5'
        }, {
            fn: this.isHalfAugmentedSeventhChord,
            suffix: '7 # 5'
        }, {
            fn: this.isAugmentedSeventhChord,
            suffix: 'aug7'
        }];
        // Three tone chords
        if (chordTone.length === 3) {
            suffixArr = chord3SuffixMap.filter((item) = > {
                return item.fn.bind(this, chordTone)();
            });
            suffix = suffixArr.length > 0 ? suffixArr[0].suffix : suffix;
        } else {
        // Four tone chords
            suffixArr = chord4SuffixMap.filter((item) = > {
                return item.fn.bind(this, chordTone)();
            });
            suffix = suffixArr.length > 0 ? suffixArr[0].suffix : suffix;
        }
        // Get the complete chord name
        returnchordRootName + suffix; }}Copy the code

Run example:


3. Derivation of chord fingering

1. The fingering chart

An example of a complete guitar chord fingering diagram is shown below, with a real guitar on the right:

Taste: the true had been divided into a lot of grid on the guitar neck, when the finger on the different grid, changed corresponding to the chord length of the string vibration then its pronunciation will also change accordingly, these hold the can change the pitch of the lattice is known as a grade, or character (it’s good to have to say this word “goods”);

Grade mark: a number is marked on the left side of the finger-map to indicate how much grade of the line on the map is actually located on the guitar (the so-called relative and absolute coordinate systems);

Empty string sound: the 0 th product, the left hand does not need to mark the corresponding string, the right hand directly dial it;

Chord overtones: When the empty chord notes of a string and the notes it can produce within the range of the fingering diagram are not part of the chord’s constituent notes, then the string should not be played, so a “cross” is marked on the string;

Finger by string: Black dots are used to mark the position of the finger on each string. The most complete fingery diagrams also include the numbers 1, 2, 3 and 4, representing the index, middle, ring and little fingers, so it is clear which finger should be placed where.

2. Distribution of notes on guitar strings

I pulled this historical color map off the Internet:

For example, you can use no more than 5 fingers on your left hand and 6 strings, but the index finger can use the horizontal press to press multiple strings, but the horizontal press can only press on the lowest grade of the fingering; Also consider the fingering of the string to include all the notes in the chord, at the same time two adjacent strings can not be the same note…

And so on, want to work out all possible results in my head at once, afraid it is difficult for me fat tiger.

But this is a good recursive algorithm to solve.

3. Fingering derivation

Build a class specifically for this purpose, and use the monophonic class I wrote earlier to calculate all the notes on the guitar string during initialization. The tone in that position can then be obtained directly by fret this.toneMap[tring][fret], as in this.toneMap[1][3].

// Guitar chord finger-derivation class
class GuitarChord {
    constructor() {
        // The maximum character number of the tentative guitar
        this.fretLength = 15;
        // Construct the initial notes for strings 1 through 6
        this.initialTone = [
            new Tone('3.'.1.0),
            new Tone('7'.2.0),
            new Tone('5'.3.0),
            new Tone('2'.4.0),
            new Tone('6'.5.0),
            new Tone('3'.6.0)];// For all positions on the guitar
        this.toneMap = [];
        Count the notes at each position from string 1 to string 6, from the lowest grade to the highest grade
        for (let string = 1; string <= this.initialTone.length; string++) {
            this.toneMap[string] = [];
            for (let fret = 0; fret <= this.fretLength; fret++) {
                this.toneMap[string].push(this.initialTone[string - 1].step(fret)); }}}}Copy the code

Add a common monophonic location search method to it:

// Find all character positions of a note in the range of a string within the specified character number range
/* * @param key search tone (string) * @param toneArray array, The sequence of all the monophonic classes on a string * @param fretStart minimum character searched * @param fretEnd maximum character searched */
findFret(key, toneArray, fretStart, fretEnd) {
    key = key.replace(/\./g.' ');
    let fretArray = [];
    fretStart = fretStart ? fretStart : 0;
    fretEnd = fretEnd ? (fretEnd + 1) : toneArray.length;
    for (let i = fretStart; i < fretEnd; i++) {
        if (is(toneArray[i])('Array')) {
            let toneStringArray = toneArray[i].map((item) = > {
                return item.toneNormal;
            });
            if(toneStringArray.includes(key)) { fretArray.push(i); }}else {
            if (toneArray[i].toneString.replace(/\./g.' ') === key) { fretArray.push(i); }}}return fretArray;
}
Copy the code

Next is the core recursive loop algorithm, first think about the general recursive process:

(1) Specifies that starting from string 1, recursion is initiated. (Recursive entry)

(2) After a string is specified, iterate over the constituent notes of the chord and calculate whether any notes fall within the specified grade range of the string. If not, return false; If yes, go to Step 3.

(3) First save the sound and its string position, the current position is ultimately valid depends on, if and only if all strings behind it can also find a valid solution to the string position, if the string is the 6th string, return true, end of recursion (recursive exit), otherwise go to step (4);

(4) The final validity of the current result = the validity of the current temporary result (true) && Whether there is a valid solution for the next string (at this point, it has gone to Step (3)). Returns true if the current result is ultimately valid; If not, roll back the result saved on the string before popping out.

In the final implementation, it is also necessary to consider that the two adjacent strings cannot be the same. In addition, in order to facilitate the backtracking of the overall result, pre, a pointer pointing to the last result, is added when the single result is saved.

// Recursively iterate over all positions of the specified chord in range
/* * @param stringIndex Number of strings traversed * @param toneIndex Number of notes used on a string (used when two adjacent strings do not repeat) * @param fretStart Minimum number of characters traversed * @param The highest number of characters for fretEnd traversal * @param preResult the result of a certain note on a string * @param positionSave saves the result of that round recursion */
calc(stringIndex, toneIndex, fretStart, fretEnd, preResult, positionSave) {
    let toneArray = this.toneMap[stringIndex];
    let result = false;
    (this.chordTone is assigned in the function described below)
    for (let i = 0; i < this.chordTone.length; i++) {
        // The tone already used by the previous adjacent string is not counted
        if(i ! == toneIndex) {let resultNext = false;
            let toneKey = this.chordTone[i];
            // Find the position of the current tone in the character range
            let fret = this.findFret(toneKey, toneArray, fretStart, fretEnd);
            // The sound exists in the range of character
            if (fret.length > 0) {
                // Record the position of the sound, the number of strings and tones
                let resultNow = {
                    string: stringIndex,
                    fret: fret[0].key: toneKey
                }
                // Save the result of the last string on this record for easy backtracking
                resultNow.pre = preResult ? preResult : null;
                // Save the result
                positionSave.push(resultNow);
                // Sets the result flag on the string
                resultNext = true;
                // If you have not traversed all 6 strings, proceed to the next string with the result recorded
                if (stringIndex < this.initialTone.length) {
                    let nextStringIndex = stringIndex + 1;
                    // The valid notation of the result on the string depends on the validity of the result on the string after it
                    resultNext = resultNext && this.calc(nextStringIndex, i, fretStart, fretEnd, resultNow, positionSave);
                } else {
                    // If all strings are traversed successfully, the recursive result is valid
                    resultNext = true;
                }
                // Discard the string result saved before the string result is invalid
                if (!resultNext) {
                    positionSave.pop();
                }
            } else {
                // There is no such sound in the range
                resultNext = false;
            }
            // If a note in any chord has a valid result on that string, the result on that string is validresult = result || resultNext; }};return result;
}
Copy the code

Using this recursive method, with inputs of 1, 3, and 5 for chord components, results in something like the following:

filter

// String fingering filter
filter(positionSave) {
    // Recall the chord fingering results recorded from string 6 and disassemble all fingering combinations
    let allResult = positionSave.filter((item) = > {
        return item.string === this.initialTone.length
    }).map((item) = > {
        let resultItem = [{
            string: item.string,
            fret: item.fret,
                key: item.key
        }];
        while (item.pre) {
            item = item.pre;
            resultItem.unshift({
                string: item.string,
                fret: item.fret,
                key: item.key
            });
        }
        return resultItem;
    });
    if (allResult.length > 0) {
        // Call each filter in turn
        return this.integrityFilter(this.fingerFilter(this.rootToneFilter(allResult)));
    } else {
        return[]; }}Copy the code

As you can see, after backtracking to calculate the desired result form, multiple filters are also called at the end, because the code calculated all the fingering combinations that fit the constituent tones may not fit the real string-by-string situation, requiring multiple filters.

4. Fingering filter

  • Root tone conditional filtering

For example, if 1, 3 and 5 are used as chord sounds and the root sound is 1, the preliminary results may be as follows:

6 string 0
3
6 string 3 items
5
5 string 3 items
Disable the 6th string
fret
null

// Filter the root tone condition
rootToneFilter(preResult) {
    let nextResult = new Set(a); preResult.forEach((item) = > {
        // The total number of strings allowed to sound, starting with 6
        let realStringLength = 6;
        // From bass string to treble string traversal, does not meet the root tone condition prohibit its sound
        for (var i = item.length - 1; i >= 0; i--) {
            if(item[i].key ! = =this.rootTone) {
                item[i].fret = null;
                item[i].key = null;
                realStringLength--;
            } else {
                break; }}if (realStringLength >= 4) {
            / / to repeat
            nextResult.add(JSON.stringify(item)); }});// The Set is parsed into an array
    return [...nextResult].map(item= > JSON.parse(item));
}
Copy the code
  • Filter by number of string fingers

The left hand usually uses only four fingers at most (the thumb is rarely used), and the result of the recursive method can involve all sorts of weird manipulations, such as this one:

non-zero
5

// Filter by string number of fingers
fingerFilter(preResult) {
    return preResult.filter((chordItem) = > {
        // Press the minimum grade of the string
        let minFret = Math.min.apply(null, chordItem.map(item= > item.fret).filter(fret= >(fret ! =null)));
        // Record the number of fingers required
        let fingerNum = minFret > 0 ? 1 : 0;
        chordItem.forEach((item) = > {
        if(item.fret ! =null&& item.fret > minFret) { fingerNum++; }});return fingerNum <= 4;
    });
}
Copy the code
  • Chords form sound integrity filtering

Recursive calculation of fingering combination of all possible, to ensure the adjacent two notes don’t repeat, but does not guarantee that all the chords were used, and in the first round of the root filter, may disable the voice part of the string, which may lead to a component, lost the only integrity filtering, so finally still need to round out of incomplete:

// Chords form sound integrity filter
integrityFilter(preResult) {
    return preResult.filter((chordItem) = > {
        let keyCount = [...new Set(chordItem.map(item= > item.key).filter(key= >key ! =null))].length;
        return keyCount === this.chordTone.length;
    });
}
Copy the code

5. Fingering calculation entrance

Input the constituent tones of chords here, calculate all possible character positions of these tones, and then calculate chord fingerings within 4 or 5 character ranges from low to high, and get the correct fingerings of all positions of the chords after integration and filtering.

Note that the input notes here are based on the key of C, so the corresponding chord names and fingering diagrams calculated are also in the key of C.

// string fingerprinting entry
chord() {
    let chordTone;
    if (is(arguments[0]) ('Array')) {
        chordTone = arguments[0];
    } else {
        chordTone = Array.prototype.slice.apply(arguments).map((item) = > {
            let tone = new Tone(item.toString());
                return tone.flat + tone.sharp + tone.key;
            });
    }
    // Chords form notes
    this.chordTone = chordTone;
    / / roots
    this.rootTone = chordTone[0];
    this.chordResult = [];
    let fretArray = [];
    // Find the possible positions of the notes in the chord and save to fretArray
    chordTone.forEach((item) = > {
        for (let i = 1; i < this.toneMap.length; i++) {
            fretArray = fretArray.concat(this.findFret(item, this.toneMap[i])); }}); fretArray = [...newSet(fretArray)];
    // The character position is sorted from smallest to largest
    fretArray.sort((a, b) = > {
        return a - b;
    });
    // Count all the fingings of the chord in the range from low to high
    for (let i = 0; i < fretArray.length; i++) {
        let fretStart = fretArray[i];
        // In the case of no need to use the horizontal, i.e. in the lowest bit of the calculation, the character range can be expanded by one space
        let fretEnd = fretStart > 0 ? (fretStart + 4) : (fretStart + 5);
        // The maximum range cannot exceed the maximum character number of the guitar
        if (fretEnd <= this.fretLength) {
            let positionSave = [];
            // Start the recursive calculation from string 1
            if (this.calc(1.null, fretStart, fretEnd, null, positionSave)) {
                // The single result is filtered and saved
                this.chordResult.push(... this.filter(positionSave)); }}}// The result is deduplicated
    let result = [...new Set(this.chordResult.map(item= > JSON.stringify(item)))].map(item= > JSON.parse(item));
    return result;
}
Copy the code

Run example:


3. Visualization of chord fingering results

I deliberately chose SVG for drawing, because I didn’t know it before, so I took this opportunity to learn about it.

A more complete chord fingling diagram, SVG code example is as follows (throw this into your OWN HTML to open can also see the results directly) :

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="svg" width="200" height="220" viewBox="0 0 150 150" preserveAspectRatio="xMidYMin meet">
    <defs>
        <g id="forbidden">
            <path d="M-5 -5 L5 5 M-5 5 L5 -5" stroke="# 666" stroke-width="1" fill="none"/>
        </g>
        <g id="blank_circle">
            <circle cx="0" cy="0" r="6" stroke="# 666" stroke-width="1" fill="none"/>
        </g>
        <g id="block_circle">
            <circle cx="0" cy="0" r="8" fill="# 333"/>
        </g>
    </defs>
    <rect x="25" y=45 rx="5" ry="5" width="100" height="100" style="fill:none; stroke:#666; stroke-width:2"/>
    <path d="M25 65 L125 65 M25 85 L125 85 M25 105 L125 105 M25 125 L125 125 M45 45 L45 145 M65 45 L65 145 M85 45 L85 145 M105 45 L105 145 M25 40 L125 40" stroke="# 666" stroke-width="2" fill="none"/>
    <use xlink:href="#forbidden" x="25" y="30" />
    <use xlink:href="#blank_circle" x="125" y="30" />
    <use xlink:href="#blank_circle" x="85" y="30" />
    <use xlink:href="#block_circle" x="105" y="55" />
    <use xlink:href="#block_circle" x="65" y="75" />
    <use xlink:href="#block_circle" x="45" y="95" />
    <text x="67" y="20" fill="# 333" font-size="20" font-weight="700">C</text>
    <text x="41.5" y="160" fill="# 333" font-size="10" font-weight="700">C</text>
    <text x="61.5" y="160" fill="# 333" font-size="10" font-weight="700">E</text>
    <text x="81.5" y="160" fill="# 333" font-size="10" font-weight="700">G</text>
    <text x="101.5" y="160" fill="# 333" font-size="10" font-weight="700">C</text>
    <text x="121.5" y="160" fill="# 333" font-size="10" font-weight="700">E</text>
    <text x="8" y="60" font-size="14" font-weight="700" fill="# 333">1</text>
</svg>
Copy the code

The following information is displayed:

In simple terms, you can divide the fingering diagram into multiple sub-elements, some drawing grids, some drawing string positions, some drawing empty string symbols, etc., and then dynamically create these sub-elements in SVG based on the fingering results passed in. However, special consideration should be given to the position of each element that may change dynamically, as well as the drawing handling of large horizontal presses.

Here is a stack of code I put out, with more detailed notes, I will not go into detail (typing tired…)

// Chord SVG drawing
class ChordSvg {
    constructor() {
        this.SVG_NS = "http://www.w3.org/2000/svg";
        this.XLINK_NS = "http://www.w3.org/1999/xlink";
        this.ATTR_MAP = {
            "className": "class"."svgHref": "href"
        };
        this.NS_MAP = {
            "svgHref": this.XLINK_NS
        };
        this.initChordSvg();
        this.minFret = 0;
    }
    // Create svG-related elements
    createSVG(tag, attributes) {
        let elem = document.createElementNS(this.SVG_NS, tag);
        for (let attribute in attributes) {
            let name = (attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute);
            let value = attributes[attribute];
            if (attribute in this.NS_MAP) {
                elem.setAttributeNS(this.NS_MAP[attribute], name, value);
            } else{ elem.setAttribute(name, value); }}return elem;
    }
    // Create the use tag
    createUse(href, x, y) {
        return this.createSVG('use', {
            svgHref: href,
            x: x,
            y: y
        });
    }
    // Set the position of the forbidded cross on which string
    setForbidden(svg, string = 6) {
        svg.appendChild(this.createUse('#forbidden'.25 + 20 * (6 - string), 30));
    }
    // Set the position of the hollow circle for the empty string
    setOpen(svg, string = 6) {
        svg.appendChild(this.createUse('#blank_circle'.25 + 20 * (6 - string), 30));
    }
    // Set fingering according to string position
    setFinger(svg, string = 6, fret = 0) {
        if (+fret > 0 && +fret <= 5) {
            svg.appendChild(this.createUse('#block_circle'.25 + 20 * (6 - string), 35 + 20* fret)); }}// Set the horizontal press position
    setBarre(svg, stringTo, fret, barreFret) {
        if (fret > 0 && fret <= 5) {
            svg.appendChild(this.createSVG('rect', {
                className: 'chord-barre'.width: stringTo * 20.x: 15 + 20 * (6 - stringTo),
                y: 27 + 20 * fret,
                rx: 8.ry: 8})); }}// Set the digit prompt to offset the bit
    setFretOffset(svg, fret, fretOffset, isBarreCover) {
        if (fret > 0) {
            let text = this.createSVG('text', {
                className: 'chord-barre-fret'.x: isBarreCover ? 1 : 8.y: 40 + fret * 20}); text.innerHTML = fretOffset; svg.appendChild(text); }}// Set the sound name of each string after holding down the chord
    setStringKey(svg, string, keyName) {
        let xFixed = keyName.length === 2 ? 4 - : 0;
        let text = this.createSVG('text', {
            className: 'chord-string-key'.x: 21.5 + 20 * (6 - string) + xFixed,
            y: 160
        });
        text.innerHTML = keyName;
        svg.appendChild(text);
    }
    // Set the chord name
    setChordName(svg, name = ' ') {
        let xFixed = / / \ \. \..test(name) ? 10 : 0;
        let text = this.createSVG('text', {
            className: 'chord-name'.x: 75 - name.toString().length * 7 + xFixed,
            y: 20
        });
        text.innerHTML = name;
        svg.appendChild(text);
    }
    // Initialize SVG
    initChordSvg() {
        / / SVG element
        this.svg = this.createSVG('svg', {
            className: 'chord-svg'.viewBox: '0 0, 150, 150'.preserveAspectRatio: 'xMidYMin meet'
        });
        // String graph block
        this.chordRect = this.createSVG('rect', {
            className: 'chord-rect'.x: 25.y: 45.rx: 5.ry: 5
        });
        // Chord grid, representing strings and strings
        this.chordGird = this.createSVG('path', {
            className: 'chord-gird'.d: 'M25 65 L125 65 M25 85 L125 85 M25 105 L125 105 M25 125 L125 125 M45 45 L45 145 M65 45 L65 145 M85 45 L85 145 M105 45 L105 145 M25 40 L125 40'
        });
        // Used to place reusable SVG elements
        this.defs = this.createSVG('defs');
        // Do not press the string cross mark
        this.g_forbidden = this.createSVG('g', {
            id: 'forbidden'
        });
        this.g_forbidden.appendChild(this.createSVG('path', {
            className: 'chord-forbidden'.d: 'M-5 -5 L5 5 M-5 5 L5 -5'
        }));
        // Empty string playing hollow circle symbol
        this.g_blank_circle = this.createSVG('g', {
            id: 'blank_circle'});this.g_blank_circle.appendChild(this.createSVG('circle', {
            className: 'chord-blank-circle'.cx: 0.cy: 0.r: 6
        }));
        // Indicate the position of the string by the solid circle flag
        this.g_block_circle = this.createSVG('g', {
            id: 'block_circle'
        });
        this.g_block_circle.appendChild(this.createSVG('circle', {
            className: 'chord-block-circle'.cx: 0.cy: 0.r: 8
        }));
        // Add reusable elements
        this.defs.appendChild(this.g_forbidden);
        this.defs.appendChild(this.g_blank_circle);
        this.defs.appendChild(this.g_block_circle);
        // add SVG child element
        this.svg.appendChild(this.chordRect);
        this.svg.appendChild(this.chordGird);
        this.svg.appendChild(this.defs);
    }
    // Draw a chord SVG pattern
    /* * @param chordTone chords form an array of chords * @param chord Chord result * @param target SVG pointer dom container */
    drawChord(chordTone, chord, target) {
        let svg = this.svg.cloneNode(true);
        let fretArr = chord.map(item= > item.fret).filter(fret= >(fret ! =null));
        // The highest character position in chord fingering
        let maxFret = Math.max.apply(null, fretArr);
        // The lowest grade position in chord fingering
        let minFret = Math.min.apply(null, fretArr);
        // The offset of the starting character position of the SVG fingerprinting pattern relative to the position of 0 on the guitar
        let fretOffset = maxFret <= 5 ? 0 : minFret;
        // Record the lowest fingering grade may need to press the string number
        let barreCount = 0;
        // The first string is only across the first string.
        let barreStringTo = 1;
        // instantiate the class used to compute chord names
        let chordName = new ChordName();
        // Iterate over the array of chord fingering
        chord.forEach((item) = > {
            if (item.fret == null) {
                // If a string does not mark its character position, it is forbidden to play the string
                this.setForbidden(svg, item.string);
            } else if (item.fret === 0) {
                // mark the empty string when the unmarked character position is 0
                this.setOpen(svg, item.string);
            } else {
                // The rest of the fingering draws its corresponding press position
                this.setFinger(svg, item.string, fretOffset > 0 ? item.fret - fretOffset + 1 : item.fret);
            }
            // when the fingering at the lowest character position of the chord is repeated
            if (item.fret === minFret) {
                // Calculate the span of the horizontal press
                barreStringTo = item.string > barreStringTo ? item.string : barreStringTo;
                // Count the actual number of strings
                barreCount++;
            }
            // Mark the corresponding sound name below the string that is allowed to be played
            if(item.fret ! =null) {
                this.setStringKey(svg, item.string, chordName.getKeyName(item.key)); }});// Convert the real string character position to the character position relative to the SVG pattern
        let relativeFret = fretOffset > 0 ? minFret - fretOffset + 1 : minFret;
        if (barreCount > 1) {
            // If the number of horizontal presses is greater than 1, use the horizontal press
            this.setBarre(svg, barreStringTo, relativeFret, minFret);
        }
        // Draw the character position offset mark on the left side of the pattern
        this.setFretOffset(svg, relativeFret, minFret, barreStringTo === 6);
        // Draw the chord name on the side of the pattern
        this.setChordName(svg, chordName.getChordName(chordTone));
        // Inserts the SVG pattern of the generated number into the specified structure
        target ? target.appendChild(svg) : document.body.appendChild(svg); }}Copy the code

SVG can also use CSS to modify some attributes. The common style should add:

.chord-svg{
  width: 200px;
  height: 220px;
}
.chord-rect{
  width: 100px;
  height: 100px;
  fill:none;
  stroke:# 666;
  stroke-width:2;
}
.chord-gird{
  stroke: # 666;
  stroke-width: 2;
  fill: none;
}
.chord-forbidden..chord-blank-circle{
  stroke: # 666;
  stroke-width: 1;
  fill: none;
}
.chord-block-circle{
  fill: # 333;
}
.chord-barre{
  height: 16px;
  fill:# 333;
}
.chord-barre-fret{
  fill:# 333;
  font-size: 14px;
  font-weight: 700;
}
.chord-string-key{
  fill:# 333;
  font-size: 10px;
  font-weight: 700;
}
.chord-name{
  fill:# 333;
  font-size: 20px;
  font-weight: 700;
}
Copy the code

Clang when running an example:

Of course I didn’t stop there.

Based on the code I’ve already implemented, I’ve created a web tool that computes chord fingering diagrams from time to time by dragging the numbers left and right to change chord components:

Online trial address

If you go against the script and give odd intervals of constituent notes, it might look something like this (ellipsis is used instead because you can’t figure out the full chord name) :

Of course, if you procrastinate, this is what happens most of the time:


Scented tail

While searching the basic music theory, fill the blank of the open-ended knowledge and can be get this stuff out, involved is just the tip of the iceberg of the music foundation, such as there are many more sound of the chord, more advanced and more strange chords in name, ability is limited, there is not into account first.

I have to say, I’m here to write code, but I’ve unwittingly given myself a little music lesson.

Some of the dynamics are just amazing.

If the officer still feel interesting, they won the world countless.

Project address: github.com/youngdro/gu…

Try the secondary portal online

——– The following advertisements can be ignored ——–

Previous articles:

Three. Js particle effects, shader rendering

Console Awakening path, how about printing an animation?

Node fund crawler, self-directed self-play understand?