From 29d3984ffb23c73705414a44fe6c97a828d03b34 Mon Sep 17 00:00:00 2001 From: STEPHANVS Date: Thu, 22 Jul 2021 22:03:55 +0200 Subject: [PATCH] Chord transpose rework, works on verse_edit_all --- openlp/plugins/songs/forms/editverseform.py | 2 +- openlp/plugins/songs/lib/__init__.py | 247 +++++++++++++++----- 2 files changed, 193 insertions(+), 56 deletions(-) diff --git a/openlp/plugins/songs/forms/editverseform.py b/openlp/plugins/songs/forms/editverseform.py index c046c3a03..710ae3785 100644 --- a/openlp/plugins/songs/forms/editverseform.py +++ b/openlp/plugins/songs/forms/editverseform.py @@ -233,7 +233,7 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog): """ if Registry().get('settings').value('songs/enable chords'): try: - transpose_lyrics(self.verse_text_edit.toPlainText(), 1) + transpose_lyrics(self.verse_text_edit.toPlainText(), 0) super(EditVerseForm, self).accept() except ValueError as ve: # Transposing failed diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index 1976d0c8b..4fb4e999c 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -559,15 +559,17 @@ def transpose_lyrics(lyrics, transpose_value): verse_list = re.split(r'(---\[.+?:.+?\]---|\[---\])', lyrics) transposed_lyrics = '' notation = Registry().get('settings').value('songs/chord notation') + key = None for verse in verse_list: if verse.startswith('---[') or verse == '[---]': transposed_lyrics += verse else: - transposed_lyrics += transpose_verse(verse, transpose_value, notation) + transposed_lyric, key = transpose_verse(verse, transpose_value, notation, key) + transposed_lyrics += transposed_lyric return transposed_lyrics -def transpose_verse(verse_text, transpose_value, notation): +def transpose_verse(verse_text, transpose_value, notation, key): """ Transpose Verse @@ -577,10 +579,13 @@ def transpose_verse(verse_text, transpose_value, notation): :return: The transposed lyrics """ if '[' not in verse_text: - return verse_text - # Split the lyrics based on chord tags + return verse_text, key + # Split the lyrics based on chord tags, based on this, chords and bass will be treated equally and separately, + # 6/9 chords should be noted 6-9 or 69 or 6add9 lyric_list = re.split(r'(\[|\]|/)', verse_text) transposed_lyrics = '' + isbass = False + lastchord = None in_tag = False for word in lyric_list: if not in_tag: @@ -591,19 +596,24 @@ def transpose_verse(verse_text, transpose_value, notation): if word == ']': in_tag = False transposed_lyrics += word - elif word == '/' or word == '--}{--': + elif word == '/': + isbass = True + transposed_lyrics += word + elif word == '--}{--': transposed_lyrics += word else: # This MUST be a chord - transposed_lyrics += transpose_chord(word, transpose_value, notation) + transposed_chord, key, lastchord = transpose_chord(word, transpose_value, notation, key, lastchord, isbass) + isbass = False + transposed_lyrics += transposed_chord # If still inside a chord tag something is wrong! if in_tag: - return verse_text + return verse_text, key else: - return transposed_lyrics + return transposed_lyrics, key -def transpose_chord(chord, transpose_value, notation): +def transpose_chord(chord, transpose_value, notation, key, lastchord, isbass): """ Transpose chord according to the notation used. NOTE: This function has a javascript equivalent in chords.js - make sure to update both! @@ -624,59 +634,186 @@ def transpose_chord(chord, transpose_value, notation): 'english': ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'], 'neo-latin': ['Do', 'Reb', 'Re', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si'] } - chord_split = chord.replace('♭', 'b').split('/') + scales = { + 'german': { + 'C' : ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'B', 'H'], + 'Am' : ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'B', 'H'], + 'C#' : ['H#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'H'], + 'A#m' : ['H#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'H'], + 'Db' : ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bb', 'B', 'Cb'], + 'Bm' : ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bb', 'B', 'Cb'], + 'D' : ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'B', 'H'], + 'Hm' : ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'B', 'H'], + 'D#' : ['H#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'H'], + 'H#m' : ['H#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'H'], + 'Eb' : ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bb', 'B', 'Cb'], + 'Cm' : ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bb', 'B', 'Cb'], + 'E' : ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'B', 'H'], + 'C#m' : ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'B', 'H'], + 'F' : ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'Cb'], + 'Dm' : ['C', 'C#', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'H'], + 'F#' : ['C', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'H'], + 'D#m' : ['C', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'H'], + 'Gb' : ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bb', 'B', 'Cb'], + 'Ebm' : ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bb', 'B', 'Cb'], + 'G' : ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'B', 'H'], + 'Em' : ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'B', 'H'], + 'G#' : ['H#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'H'], + 'E#m' : ['H#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'H'], + 'Ab' : ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bb', 'B', 'Cb'], + 'Fm' : ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bb', 'B', 'Cb'], + 'A' : ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'G#', 'A', 'B', 'H'], + 'F#m' : ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'G#', 'A', 'B', 'H'], + 'A#' : ['H#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'Gx', 'A#', 'H'], + 'F##m' : ['H#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'Gx', 'A#', 'H'], + 'Fxm' : ['H#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'Gx', 'A#', 'H'], + 'B' : ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'Cb'], + 'Gm' : ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'Cb'], + 'H' : ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'H'], + 'G#m' : ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'H'], + 'Cb' : ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'Gbb', 'Gb', 'Abb', 'Ab', 'Bb', 'B', 'Cb'], + 'Abm' : ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'Gbb', 'Gb', 'Abb', 'Ab', 'Bb', 'B', 'Cb'] + }, + 'english': { + 'C' : ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'], + 'Am' : ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'], + 'C#' : ['H#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'B'], + 'A#m' : ['H#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'B'], + 'Db' : ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bbb', 'Bb', 'Cb'], + 'Bm' : ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bbb', 'Bb', 'Cb'], + 'D' : ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'], + 'Hm' : ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'], + 'D#' : ['H#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'B'], + 'H#m' : ['H#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'B'], + 'Eb' : ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bbb', 'Bb', 'Cb'], + 'Cm' : ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bbb', 'Bb', 'Cb'], + 'E' : ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'Bb', 'B'], + 'C#m' : ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'Bb', 'B'], + 'F' : ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'Cb'], + 'Dm' : ['C', 'C#', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'], + 'F#' : ['C', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'B'], + 'D#m' : ['C', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'B'], + 'Gb' : ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bbb', 'Bb', 'Cb'], + 'Ebm' : ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bbb', 'Bb', 'Cb'], + 'G' : ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'], + 'Em' : ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'], + 'G#' : ['H#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'B'], + 'E#m' : ['H#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'B'], + 'Ab' : ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bbb', 'Bb', 'Cb'], + 'Fm' : ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bbb', 'Bb', 'Cb'], + 'A' : ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'G#', 'A', 'Bb', 'B'], + 'F#m' : ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'G#', 'A', 'Bb', 'B'], + 'A#' : ['H#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'Gx', 'A#', 'B'], + 'F##m' : ['H#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'Gx', 'A#', 'B'], + 'Fxm' : ['H#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'Gx', 'A#', 'B'], + 'B' : ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'Cb'], + 'Gm' : ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'Cb'], + 'H' : ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'], + 'G#m' : ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'], + 'Cb' : ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'Gbb', 'Gb', 'Abb', 'Ab', 'Bbb', 'Bb', 'Cb'], + 'Abm' : ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'Gbb', 'Gb', 'Abb', 'Ab', 'Bbb', 'Bb', 'Cb'] + } + } + notenumbers = { + 'german': { + 'C' : 0, 'H#' : 0, 'B##' : 0, 'Bx' : 0, 'Dbb' : 0, + 'C#' : 1, 'Db' : 1, + 'D' : 2, 'C##' : 2, 'Cx' : 2, 'Ebb' : 2, + 'D#' : 3, 'Eb' : 3, + 'E' : 4, 'D##' : 4, 'Dx' : 4, 'Fb' : 4, + 'F' : 5, 'E#' : 5, 'Gbb' : 5, + 'F#' : 6, 'Gb' : 6, + 'G' : 7, 'F##' : 7, 'Fx' : 7, 'Abb' : 7, + 'G#' : 8, 'Ab' : 8, + 'A' : 9, 'G##' : 9, 'Gx' : 9, 'Bb' : 9, 'Hbb' : 9, + 'B' : 10, 'A#' : 10, 'Hb' : 10, + 'H' : 11, 'B#' : 11, 'A##' : 11, 'Ax' : 11, 'Cb' : 11 + }, + 'english': { + 'C' : 0, 'B#' : 0, 'Dbb' : 0, + 'C#' : 1, 'Db' : 1, 'B##' : 1, 'Bx' : 1, + 'D' : 2, 'C##' : 2, 'Cx' : 2, 'Ebb' : 2, + 'D#' : 3, 'Eb' : 3, + 'E' : 4, 'D##' : 4, 'Dx' : 4, 'Fb' : 4, + 'F' : 5, 'E#' : 5, 'Gbb' : 5, + 'F#' : 6, 'Gb' : 6, + 'G' : 7, 'F##' : 7, 'Fx' : 7, 'Abb' : 7, + 'G#' : 8, 'Ab' : 8, + 'A' : 9, 'G##' : 9, 'Gx' : 9, 'Bbb' : 9, + 'Bb' : 10, 'A#' : 10, + 'B' : 11, 'A##' : 11, 'Ax' : 11, 'Cb' : 11 + } + } + chord = chord.replace('♭', 'b').replace('♯', '#') transposed_chord = '' - last_chord = '' + minor = '' + thischordchangeskey = False notes_sharp = notes_sharp_notation[notation] notes_flat = notes_flat_notation[notation] - notes_preferred = ['b', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'] - for i in range(0, len(chord_split)): - if i > 0: - transposed_chord += '/' - current_chord = chord_split[i] - if current_chord and current_chord[0] == '(': - transposed_chord += '(' - if len(current_chord) > 1: - current_chord = current_chord[1:] + notes_preferred = ['b', '#', '#', 'b', '#', 'b', '#', '#', 'b', '#', 'b', '#'] + if chord and chord[0] == '(': + transposed_chord += '(' + if len(chord) > 1: + chord = chord[1:] + else: + chord = '' + if chord and chord[0] == '=': + transposed_chord += '=' + if len(chord) > 1: + chord = chord[1:] + thischordchangeskey = True + else: + chord = '' + if chord and chord[0] == '|': + transposed_chord += '|' + if len(chord) > 1: + chord = chord[1:] + else: + chord = '' + if len(chord) > 0: + if notation == 'neo-latin': + if len(chord) > 2 and chord[0:3].lower() == 'sol': + note = chord[0:3] + chord = chord[3:] if len(chord) > 3 else '' + elif len(chord) > 1: + note = chord[0:2] + chord = chord[2:] if len(chord) > 2 else '' + else: + note = chord[0] + chord = chord[1:] if len(chord) > 1 else '' + while len(chord) > 0 and '#bx'.find(chord[0]) > -1: + note += chord[0] + chord = chord[1:] if len(chord) > 0 else '' + if len(chord) > 0: + if 'm-'.find(chord[0]) > -1 or (len(chord) > 1 and chord[0:2].lower() == 'mi'): + minor = chord[0] + chord = chord[1:] if len(chord) > 1 else '' else: - current_chord = '' - if current_chord and current_chord[0] == '=': - transposed_chord += '=' - if len(current_chord) > 1: - current_chord = current_chord[1:] - else: - current_chord = '' - if current_chord and current_chord[0] == '|': - transposed_chord += '|' - if len(current_chord) > 1: - current_chord = current_chord[1:] - else: - current_chord = '' - if len(current_chord) > 0: - if len(current_chord) > 1: - if '#b'.find(current_chord[1]) == -1: - note = current_chord[0:1] - rest = current_chord[1:] - else: - note = current_chord[0:2] - rest = current_chord[2:] - else: - note = current_chord - rest = '' - note_number = notes_flat.index(note) if note not in notes_sharp else notes_sharp.index(note) + minor = '' + if note in notenumbers[notation]: + note_number = notenumbers[notation][note] note_number += transpose_value while note_number > 11: note_number -= 12 while note_number < 0: note_number += 12 - if i == 0: - current_chord = notes_sharp[note_number] if notes_preferred[note_number] == '#' else notes_flat[ - note_number] - last_chord = current_chord + if isbass: + if lastchord: + note = scales[notation][lastchord][note_number] + elif key: + note = scales[notation][key][note_number] + else: + note = notes_sharp[note_number] if notes_preferred[note_number] == '#' else notes_flat[note_number] else: - current_chord = notes_flat[note_number] if last_chord not in notes_sharp else notes_sharp[note_number] - if not (note not in notes_flat and note not in notes_sharp): - transposed_chord += current_chord + rest - else: - transposed_chord += note + rest - return transposed_chord + if not key or thischordchangeskey: + note = notes_sharp[note_number] if notes_preferred[note_number] == '#' else notes_flat[note_number] + else: + note = scales[notation][key][note_number] + transposed_chord += note + minor + chord + else: + transposed_chord += chord + if thischordchangeskey: + key = note + minor + else: + if not isbass: lastchord = note + minor + return transposed_chord, key, lastchord