Skip to content

Commit

Permalink
Improve chord parsing and transposing (kmille#20)
Browse files Browse the repository at this point in the history
* Parse chord symbols into their parts. Fix parsing for chord with parens

* Parses chords into Root, Quality, and Base (e.g. `Fm6/Ab` becomes `Fm`, `6`, `Ab`)
* Fix regex for parsing out chords with parentheses like `Cm(maj7)`

* Update transposing to ignore chord qualities
  • Loading branch information
smoogan authored Feb 25, 2024
1 parent 5f82c69 commit 091ba5d
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 30 deletions.
70 changes: 41 additions & 29 deletions freetar/static/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,47 +30,59 @@ function initialise_transpose() {
transpose()
});

$('.tab').find('strong').each(function () {
$('.tab').find('.chord-root, .chord-bass').each(function () {
const text = $(this).text()
$(this).attr('data-original', text)
})

function transpose() {
$('.tab').find('strong').each(function () {
const text = $(this).attr('data-original')
const new_text = transpose_chord(text.trim(), transpose_value)
$(this).text(new_text)
$('.tab').find('.chord-root, .chord-bass').each(function () {
const originalText = $(this).attr('data-original')
if (transpose_value === 0) {
$(this).text(originalText)
} else {
const new_text = transpose_note(originalText.trim(), transpose_value)
$(this).text(new_text)
}
});
}

const variations = ['', 'dim', 'm', 'm7', 'maj7', '7', 'sus4', 'sus2', 'sus', 'dim7', 'min7b5', '7sus4', '6', 'm6', '9', 'm9', 'maj9', '11', 'add2', 'add9']
const notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', "A", 'A#', 'B']
const notesFlat = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', "A", 'Bb', 'B']

const chords = notes.map(note => variations.map(variation => note + variation))
const chordsFlat = notesFlat.map(note => variations.map(variation => note + variation))


function transpose_chord(chord, transpose_value) {
if (chord.indexOf('/') !== -1) {
const transposed = chord.split('/').map(cho => transpose_chord(cho, transpose_value))
return transposed.join('/')
// Defines a list of notes, grouped with any alternate names (like D# and Eb)
const noteNames = [
['A'],
['A#', 'Bb'],
['B','Cb'],
['C', 'B#'],
['C#', 'Db'],
['D'],
['D#', 'Eb'],
['E', 'Fb'],
['F', 'E#'],
['F#', 'Gb'],
['G'],
['G#', 'Ab'],
];

// Find the given note in noteNames, then step through the list to find the
// next note up or down. Currently just selects the first note name that
// matches. It doesn't preserve sharp, flat, or any try to determine what
// key we're in.
function transpose_note(note, transpose_value) {

let noteIndex = noteNames.findIndex(tone => tone.includes(note));
if (noteIndex === -1)
{
console.debug("Note ["+note+"] not found. Can't transpose");
return note;
}
let chordGroup = chords;
let chord_index = chords.findIndex(chordGroup => chordGroup.includes(chord))
if (chord_index === -1) {
chordGroup = chordsFlat;
chord_index = chordsFlat.findIndex(chordGroup => chordGroup.includes(chord))
if (chord_index === -1) {
return chord
}
}
let new_index = (chord_index + transpose_value) % 12

let new_index = (noteIndex + transpose_value) % 12;
if (new_index < 0) {
new_index += 12
new_index += 12;
}

return chordGroup[new_index][chordGroup[chord_index].indexOf(chord)]
// TODO: Decide on sharp, flat, or natural
return noteNames[new_index][0];
}
}

Expand Down
17 changes: 16 additions & 1 deletion freetar/ug.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,24 @@ def fix_tab(self):
tab = tab.replace(" ", "&nbsp;")
tab = tab.replace("[tab]", "")
tab = tab.replace("[/tab]", "")
tab = re.sub(r'\[ch\]([/#\w()+-]+)\[\/ch\]', r'<strong>\1</strong>', tab)

# (?P<root>[A-Ga-g](#|b)?) : Chord root is any letter A - G with an optional sharp or flat at the end
# (?P<quality>[^[/]+)? : Chord quality is anything after the root, but before the `/` for the base note
# (?P<bass>/[A-Ga-g](#|b)?)? : Chord quality is anything after the root, including parens in the case of 'm(maj7)'
# tab = re.sub(r'\[ch\](?P<root>[A-Ga-g](#|b)?)(?P<quality>[#\w()]+)?(?P<bass>/[A-Ga-g](#|b)?)?\[\/ch\]', self.parse_chord, tab)
tab = re.sub(r'\[ch\](?P<root>[A-Ga-g](#|b)?)(?P<quality>[^[/]+)?(?P<bass>/[A-Ga-g](#|b)?)?\[\/ch\]', self.parse_chord, tab)
self.tab = tab

def parse_chord(self, chord):
root = '<span class="chord-root">%s</span>' % chord.group('root')
quality = ''
bass = ''
if chord.group('quality') is not None:
quality = '<span class="chord-quality">%s</span>' % chord.group('quality')
if chord.group('bass') is not None:
bass = '/<span class="chord-bass">%s</span>' % chord.group('bass')[1:]
return '<span class="chord fw-bold">%s</span>' % (root + quality + bass)


def ug_search(value: str):
resp = requests.get(f"https://www.ultimate-guitar.com/search.php?search_type=title&value={quote(value)}")
Expand Down

0 comments on commit 091ba5d

Please sign in to comment.