forked from openlp/openlp
Merge branch 'advanced-chord-transpose-support' into 'master'
Advanced chord transpose support See merge request openlp/openlp!341
This commit is contained in:
commit
330db2209c
@ -342,6 +342,7 @@ class Settings(QtCore.QSettings):
|
|||||||
'songs/songselect password': '',
|
'songs/songselect password': '',
|
||||||
'songs/songselect searches': '',
|
'songs/songselect searches': '',
|
||||||
'songs/enable chords': True,
|
'songs/enable chords': True,
|
||||||
|
'songs/warn about missing song key': True,
|
||||||
'songs/chord notation': 'english', # Can be english, german or neo-latin
|
'songs/chord notation': 'english', # Can be english, german or neo-latin
|
||||||
'songs/disable chords import': False,
|
'songs/disable chords import': False,
|
||||||
'songs/auto play audio': False,
|
'songs/auto play audio': False,
|
||||||
|
@ -44,13 +44,15 @@ from openlp.core.lib.formattingtags import FormattingTags
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
ENGLISH_NOTES = '[CDEFGAB]'
|
ENGLISH_NOTES = '(C|D|E|F|G|A|B|N\\.C\\.)?'
|
||||||
GERMAN_NOTES = '[CDEFGAH]'
|
GERMAN_NOTES = '(C|D|E|F|G|A|B|H|N\\.C\\.)?'
|
||||||
NEOLATIN_NOTES = '(Do|Re|Mi|Fa|Sol|La|Si)'
|
NEOLATIN_NOTES = '(Do|Re|Mi|Fa|Sol|La|Si|N\\.C\\.)?'
|
||||||
CHORD_SUFFIXES = '(b|bb)?(#)?(m|maj7|maj|min7|min|sus)?(1|2|3|4|5|6|7|8|9)?'
|
CHORD_PREFIXES = '(=|\\(|\\|)*?'
|
||||||
|
CHORD_SUFFIXES = '(b|#|x|\\+|-|M|m|Maj|maj|min|sus|dim|add|aug|dom|0|1|2|3|4|5|6|7|8|9|\\(|\\)|no|omit)*?'
|
||||||
SLIM_CHARS = 'fiíIÍjlĺľrtť.,;/ ()|"\'!:\\'
|
SLIM_CHARS = 'fiíIÍjlĺľrtť.,;/ ()|"\'!:\\'
|
||||||
CHORD_TEMPLATE = '<span class="chordline">{chord}</span>'
|
CHORD_TEMPLATE = '<span class="chordline">{chord}</span>'
|
||||||
FIRST_CHORD_TEMPLATE = '<span class="chordline firstchordline">{chord}</span>'
|
FIRST_CHORD_TEMPLATE = '<span class="chordline">{chord}</span>'
|
||||||
|
NO_CHORD_TEMPLATE = '<span class="nochordline">{chord}</span>'
|
||||||
CHORD_LINE_TEMPLATE = '<span class="chord"><span><strong>{chord}</strong></span></span>{tail}{whitespace}{remainder}'
|
CHORD_LINE_TEMPLATE = '<span class="chord"><span><strong>{chord}</strong></span></span>{tail}{whitespace}{remainder}'
|
||||||
WHITESPACE_TEMPLATE = '<span class="ws">{whitespaces}</span>'
|
WHITESPACE_TEMPLATE = '<span class="ws">{whitespaces}</span>'
|
||||||
VERSE = 'The Lord said to {r}Noah{/r}: \n' \
|
VERSE = 'The Lord said to {r}Noah{/r}: \n' \
|
||||||
@ -78,8 +80,8 @@ def _construct_chord_regex(notes):
|
|||||||
:param notes: The regular expression for a set of valid notes
|
:param notes: The regular expression for a set of valid notes
|
||||||
:return: An expanded regular expression for valid chords
|
:return: An expanded regular expression for valid chords
|
||||||
"""
|
"""
|
||||||
chord = notes + CHORD_SUFFIXES
|
# chord = CHORD_PREFIXES + notes + CHORD_SUFFIXES
|
||||||
return '(' + chord + '(/' + chord + ')?)'
|
return '(' + CHORD_PREFIXES + notes + CHORD_SUFFIXES + '(/' + notes + CHORD_SUFFIXES + ')?)'
|
||||||
|
|
||||||
|
|
||||||
def _construct_chord_match(notes):
|
def _construct_chord_match(notes):
|
||||||
@ -193,11 +195,11 @@ def render_chords_in_line(match):
|
|||||||
# The actual chord, would be "G" in match "[G]sweet the "
|
# The actual chord, would be "G" in match "[G]sweet the "
|
||||||
chord = match.group(1)
|
chord = match.group(1)
|
||||||
# The tailing word of the chord, would be "sweet" in match "[G]sweet the "
|
# The tailing word of the chord, would be "sweet" in match "[G]sweet the "
|
||||||
tail = match.group(11)
|
tail = match.group(8)
|
||||||
# The remainder of the line, until line end or next chord. Would be " the " in match "[G]sweet the "
|
# The remainder of the line, until line end or next chord. Would be " the " in match "[G]sweet the "
|
||||||
remainder = match.group(12)
|
remainder = match.group(9)
|
||||||
# Line end if found, else None
|
# Line end if found, else None
|
||||||
end = match.group(13)
|
end = match.group(10)
|
||||||
# Based on char width calculate width of chord
|
# Based on char width calculate width of chord
|
||||||
for chord_char in chord:
|
for chord_char in chord:
|
||||||
if chord_char not in SLIM_CHARS:
|
if chord_char not in SLIM_CHARS:
|
||||||
@ -275,7 +277,10 @@ def render_chords(text):
|
|||||||
rendered_lines.append(new_line)
|
rendered_lines.append(new_line)
|
||||||
else:
|
else:
|
||||||
chords_on_prev_line = False
|
chords_on_prev_line = False
|
||||||
rendered_lines.append(html.escape(line))
|
# rendered_lines.append(html.escape(line))
|
||||||
|
chord_template = NO_CHORD_TEMPLATE
|
||||||
|
new_line = chord_template.format(chord=line)
|
||||||
|
rendered_lines.append(new_line)
|
||||||
return '{br}'.join(rendered_lines)
|
return '{br}'.join(rendered_lines)
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,15 +28,16 @@ from shutil import copyfile
|
|||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
from openlp.core.state import State
|
|
||||||
from openlp.core.common.applocation import AppLocation
|
from openlp.core.common.applocation import AppLocation
|
||||||
from openlp.core.common.i18n import UiStrings, get_natural_key, translate
|
from openlp.core.common.i18n import UiStrings, get_natural_key, translate
|
||||||
from openlp.core.common.mixins import RegistryProperties
|
from openlp.core.common.mixins import RegistryProperties
|
||||||
from openlp.core.common.path import create_paths
|
from openlp.core.common.path import create_paths
|
||||||
from openlp.core.common.registry import Registry
|
from openlp.core.common.registry import Registry
|
||||||
from openlp.core.lib import MediaType, create_separated_list
|
from openlp.core.lib import MediaType, create_separated_list
|
||||||
|
from openlp.core.lib.formattingtags import FormattingTags
|
||||||
from openlp.core.lib.plugin import PluginStatus
|
from openlp.core.lib.plugin import PluginStatus
|
||||||
from openlp.core.lib.ui import critical_error_message_box, find_and_set_in_combo_box, set_case_insensitive_completer
|
from openlp.core.lib.ui import critical_error_message_box, find_and_set_in_combo_box, set_case_insensitive_completer
|
||||||
|
from openlp.core.state import State
|
||||||
from openlp.core.widgets.dialogs import FileDialog
|
from openlp.core.widgets.dialogs import FileDialog
|
||||||
from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog
|
from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog
|
||||||
from openlp.plugins.songs.forms.editverseform import EditVerseForm
|
from openlp.plugins.songs.forms.editverseform import EditVerseForm
|
||||||
@ -44,8 +45,7 @@ from openlp.plugins.songs.forms.mediafilesform import MediaFilesForm
|
|||||||
from openlp.plugins.songs.lib import VerseType, clean_song
|
from openlp.plugins.songs.lib import VerseType, clean_song
|
||||||
from openlp.plugins.songs.lib.db import Author, AuthorType, Book, MediaFile, Song, SongBookEntry, Topic
|
from openlp.plugins.songs.lib.db import Author, AuthorType, Book, MediaFile, Song, SongBookEntry, Topic
|
||||||
from openlp.plugins.songs.lib.openlyricsxml import SongXML
|
from openlp.plugins.songs.lib.openlyricsxml import SongXML
|
||||||
from openlp.plugins.songs.lib.ui import SongStrings
|
from openlp.plugins.songs.lib.ui import SongStrings, show_key_warning
|
||||||
from openlp.core.lib.formattingtags import FormattingTags
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -245,14 +245,21 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
# Validate tags (lp#1199639)
|
# Validate tags (lp#1199639)
|
||||||
misplaced_tags = []
|
misplaced_tags = []
|
||||||
verse_tags = []
|
verse_tags = []
|
||||||
|
chords = []
|
||||||
for i in range(self.verse_list_widget.rowCount()):
|
for i in range(self.verse_list_widget.rowCount()):
|
||||||
item = self.verse_list_widget.item(i, 0)
|
item = self.verse_list_widget.item(i, 0)
|
||||||
tags = self.find_tags.findall(item.text())
|
tags = self.find_tags.findall(item.text())
|
||||||
|
stripped_text = re.sub(r'\[---\]', "\n", re.sub(r'\[--}{--\]', "\n", item.text()))
|
||||||
|
r = re.compile(r'\[(.*?)\]')
|
||||||
|
for match in r.finditer(stripped_text):
|
||||||
|
chords += match[1]
|
||||||
field = item.data(QtCore.Qt.UserRole)
|
field = item.data(QtCore.Qt.UserRole)
|
||||||
verse_tags.append(field)
|
verse_tags.append(field)
|
||||||
if not self._validate_tags(tags):
|
if not self._validate_tags(tags):
|
||||||
misplaced_tags.append('{field1} {field2}'.format(field1=VerseType.translated_name(field[0]),
|
misplaced_tags.append('{field1} {field2}'.format(field1=VerseType.translated_name(field[0]),
|
||||||
field2=field[1:]))
|
field2=field[1:]))
|
||||||
|
if chords and not chords[0].startswith("="):
|
||||||
|
show_key_warning(self)
|
||||||
if misplaced_tags:
|
if misplaced_tags:
|
||||||
critical_error_message_box(
|
critical_error_message_box(
|
||||||
message=translate('SongsPlugin.EditSongForm',
|
message=translate('SongsPlugin.EditSongForm',
|
||||||
|
@ -69,7 +69,8 @@ class Ui_EditVerseDialog(object):
|
|||||||
self.verse_type_layout.addStretch()
|
self.verse_type_layout.addStretch()
|
||||||
self.dialog_layout.addLayout(self.verse_type_layout)
|
self.dialog_layout.addLayout(self.verse_type_layout)
|
||||||
if Registry().get('settings').value('songs/enable chords'):
|
if Registry().get('settings').value('songs/enable chords'):
|
||||||
self.transpose_layout = QtWidgets.QHBoxLayout()
|
self.transpose_widget = QtWidgets.QWidget()
|
||||||
|
self.transpose_layout = QtWidgets.QHBoxLayout(self.transpose_widget)
|
||||||
self.transpose_layout.setObjectName('transpose_layout')
|
self.transpose_layout.setObjectName('transpose_layout')
|
||||||
self.transpose_label = QtWidgets.QLabel(edit_verse_dialog)
|
self.transpose_label = QtWidgets.QLabel(edit_verse_dialog)
|
||||||
self.transpose_label.setObjectName('transpose_label')
|
self.transpose_label.setObjectName('transpose_label')
|
||||||
@ -82,7 +83,7 @@ class Ui_EditVerseDialog(object):
|
|||||||
self.transpose_down_button.setIcon(UiIcons().arrow_down)
|
self.transpose_down_button.setIcon(UiIcons().arrow_down)
|
||||||
self.transpose_down_button.setObjectName('transpose_down')
|
self.transpose_down_button.setObjectName('transpose_down')
|
||||||
self.transpose_layout.addWidget(self.transpose_down_button)
|
self.transpose_layout.addWidget(self.transpose_down_button)
|
||||||
self.dialog_layout.addLayout(self.transpose_layout)
|
self.dialog_layout.addWidget(self.transpose_widget)
|
||||||
self.button_box = create_button_box(edit_verse_dialog, 'button_box', ['cancel', 'ok'])
|
self.button_box = create_button_box(edit_verse_dialog, 'button_box', ['cancel', 'ok'])
|
||||||
self.dialog_layout.addWidget(self.button_box)
|
self.dialog_layout.addWidget(self.button_box)
|
||||||
self.retranslate_ui(edit_verse_dialog)
|
self.retranslate_ui(edit_verse_dialog)
|
||||||
|
@ -29,6 +29,7 @@ from openlp.core.common.registry import Registry
|
|||||||
from openlp.core.lib.ui import critical_error_message_box
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
from openlp.plugins.songs.forms.editversedialog import Ui_EditVerseDialog
|
from openlp.plugins.songs.forms.editversedialog import Ui_EditVerseDialog
|
||||||
from openlp.plugins.songs.lib import VerseType, transpose_lyrics
|
from openlp.plugins.songs.lib import VerseType, transpose_lyrics
|
||||||
|
from openlp.plugins.songs.lib.ui import show_key_warning
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -122,14 +123,19 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
|
|||||||
The transpose up button clicked
|
The transpose up button clicked
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
lyrics_stripped = re.sub(r'\[---\]', "\n", re.sub(r'---\[.*?\]---', "\n", re.sub(r'\[--}{--\]', "\n",
|
||||||
|
self.verse_text_edit.toPlainText())))
|
||||||
|
chords = re.findall(r'\[(.*?)\]', lyrics_stripped)
|
||||||
|
if chords and not chords[0].startswith("="):
|
||||||
|
show_key_warning(self)
|
||||||
transposed_lyrics = transpose_lyrics(self.verse_text_edit.toPlainText(), 1)
|
transposed_lyrics = transpose_lyrics(self.verse_text_edit.toPlainText(), 1)
|
||||||
self.verse_text_edit.setPlainText(transposed_lyrics)
|
self.verse_text_edit.setPlainText(transposed_lyrics)
|
||||||
except ValueError as ve:
|
except KeyError as ke:
|
||||||
# Transposing failed
|
# Transposing failed
|
||||||
critical_error_message_box(title=translate('SongsPlugin.EditVerseForm', 'Transposing failed'),
|
critical_error_message_box(title=translate('SongsPlugin.EditVerseForm', 'Transposing failed'),
|
||||||
message=translate('SongsPlugin.EditVerseForm',
|
message=translate('SongsPlugin.EditVerseForm',
|
||||||
'Transposing failed because of invalid chord:\n{err_msg}'
|
'Transposing failed because of invalid chord:\n{err_msg}'
|
||||||
.format(err_msg=ve)))
|
.format(err_msg=ke)))
|
||||||
return
|
return
|
||||||
self.verse_text_edit.setFocus()
|
self.verse_text_edit.setFocus()
|
||||||
self.verse_text_edit.moveCursor(QtGui.QTextCursor.End)
|
self.verse_text_edit.moveCursor(QtGui.QTextCursor.End)
|
||||||
@ -139,16 +145,20 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
|
|||||||
The transpose down button clicked
|
The transpose down button clicked
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
lyrics_stripped = re.sub(r'\[---\]', "\n", re.sub(r'---\[.*?\]---', "\n", re.sub(r'\[--}{--\]', "\n",
|
||||||
|
self.verse_text_edit.toPlainText())))
|
||||||
|
chords = re.findall(r'\[(.*?)\]', lyrics_stripped)
|
||||||
|
if chords and not chords[0].startswith("="):
|
||||||
|
show_key_warning(self)
|
||||||
transposed_lyrics = transpose_lyrics(self.verse_text_edit.toPlainText(), -1)
|
transposed_lyrics = transpose_lyrics(self.verse_text_edit.toPlainText(), -1)
|
||||||
self.verse_text_edit.setPlainText(transposed_lyrics)
|
self.verse_text_edit.setPlainText(transposed_lyrics)
|
||||||
except ValueError as ve:
|
except KeyError as ke:
|
||||||
# Transposing failed
|
# Transposing failed
|
||||||
critical_error_message_box(title=translate('SongsPlugin.EditVerseForm', 'Transposing failed'),
|
critical_error_message_box(title=translate('SongsPlugin.EditVerseForm', 'Transposing failed'),
|
||||||
message=translate('SongsPlugin.EditVerseForm',
|
message=translate('SongsPlugin.EditVerseForm',
|
||||||
'Transposing failed because of invalid chord:\n{err_msg}'
|
'Transposing failed because of invalid chord:\n{err_msg}'
|
||||||
.format(err_msg=ve)))
|
.format(err_msg=ke)))
|
||||||
return
|
return
|
||||||
self.verse_text_edit.setPlainText(transposed_lyrics)
|
|
||||||
self.verse_text_edit.setFocus()
|
self.verse_text_edit.setFocus()
|
||||||
self.verse_text_edit.moveCursor(QtGui.QTextCursor.End)
|
self.verse_text_edit.moveCursor(QtGui.QTextCursor.End)
|
||||||
|
|
||||||
@ -197,12 +207,14 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
|
|||||||
self.verse_type_combo_box.setCurrentIndex(verse_type_index)
|
self.verse_type_combo_box.setCurrentIndex(verse_type_index)
|
||||||
self.verse_number_box.setValue(int(verse_number))
|
self.verse_number_box.setValue(int(verse_number))
|
||||||
self.insert_button.setVisible(False)
|
self.insert_button.setVisible(False)
|
||||||
|
self.transpose_widget.setVisible(False)
|
||||||
else:
|
else:
|
||||||
if not text:
|
if not text:
|
||||||
text = '---[{tag}:1]---\n'.format(tag=VerseType.translated_names[VerseType.Verse])
|
text = '---[{tag}:1]---\n'.format(tag=VerseType.translated_names[VerseType.Verse])
|
||||||
self.verse_type_combo_box.setCurrentIndex(0)
|
self.verse_type_combo_box.setCurrentIndex(0)
|
||||||
self.verse_number_box.setValue(1)
|
self.verse_number_box.setValue(1)
|
||||||
self.insert_button.setVisible(True)
|
self.insert_button.setVisible(True)
|
||||||
|
self.transpose_widget.setVisible(True)
|
||||||
self.verse_text_edit.setPlainText(text)
|
self.verse_text_edit.setPlainText(text)
|
||||||
self.verse_text_edit.setFocus()
|
self.verse_text_edit.setFocus()
|
||||||
self.verse_text_edit.moveCursor(QtGui.QTextCursor.End)
|
self.verse_text_edit.moveCursor(QtGui.QTextCursor.End)
|
||||||
@ -233,13 +245,13 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
|
|||||||
"""
|
"""
|
||||||
if Registry().get('settings').value('songs/enable chords'):
|
if Registry().get('settings').value('songs/enable chords'):
|
||||||
try:
|
try:
|
||||||
transpose_lyrics(self.verse_text_edit.toPlainText(), 1)
|
transpose_lyrics(self.verse_text_edit.toPlainText(), 0)
|
||||||
super(EditVerseForm, self).accept()
|
super(EditVerseForm, self).accept()
|
||||||
except ValueError as ve:
|
except KeyError as ke:
|
||||||
# Transposing failed
|
# Transposing failed
|
||||||
critical_error_message_box(title=translate('SongsPlugin.EditVerseForm', 'Invalid Chord'),
|
critical_error_message_box(title=translate('SongsPlugin.EditVerseForm', 'Invalid Chord'),
|
||||||
message=translate('SongsPlugin.EditVerseForm',
|
message=translate('SongsPlugin.EditVerseForm',
|
||||||
'An invalid chord was detected:\n{err_msg}'
|
'An invalid chord was detected:\n{err_msg}'
|
||||||
.format(err_msg=ve)))
|
.format(err_msg=ke)))
|
||||||
else:
|
else:
|
||||||
super(EditVerseForm, self).accept()
|
super(EditVerseForm, self).accept()
|
||||||
|
@ -559,15 +559,17 @@ def transpose_lyrics(lyrics, transpose_value):
|
|||||||
verse_list = re.split(r'(---\[.+?:.+?\]---|\[---\])', lyrics)
|
verse_list = re.split(r'(---\[.+?:.+?\]---|\[---\])', lyrics)
|
||||||
transposed_lyrics = ''
|
transposed_lyrics = ''
|
||||||
notation = Registry().get('settings').value('songs/chord notation')
|
notation = Registry().get('settings').value('songs/chord notation')
|
||||||
|
key = None
|
||||||
for verse in verse_list:
|
for verse in verse_list:
|
||||||
if verse.startswith('---[') or verse == '[---]':
|
if verse.startswith('---[') or verse == '[---]':
|
||||||
transposed_lyrics += verse
|
transposed_lyrics += verse
|
||||||
else:
|
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
|
return transposed_lyrics
|
||||||
|
|
||||||
|
|
||||||
def transpose_verse(verse_text, transpose_value, notation):
|
def transpose_verse(verse_text, transpose_value, notation, key):
|
||||||
"""
|
"""
|
||||||
Transpose Verse
|
Transpose Verse
|
||||||
|
|
||||||
@ -577,10 +579,13 @@ def transpose_verse(verse_text, transpose_value, notation):
|
|||||||
:return: The transposed lyrics
|
:return: The transposed lyrics
|
||||||
"""
|
"""
|
||||||
if '[' not in verse_text:
|
if '[' not in verse_text:
|
||||||
return verse_text
|
return verse_text, key
|
||||||
# Split the lyrics based on chord tags
|
# 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)
|
lyric_list = re.split(r'(\[|\]|/)', verse_text)
|
||||||
transposed_lyrics = ''
|
transposed_lyrics = ''
|
||||||
|
is_bass = False
|
||||||
|
last_chord = None
|
||||||
in_tag = False
|
in_tag = False
|
||||||
for word in lyric_list:
|
for word in lyric_list:
|
||||||
if not in_tag:
|
if not in_tag:
|
||||||
@ -591,19 +596,25 @@ def transpose_verse(verse_text, transpose_value, notation):
|
|||||||
if word == ']':
|
if word == ']':
|
||||||
in_tag = False
|
in_tag = False
|
||||||
transposed_lyrics += word
|
transposed_lyrics += word
|
||||||
elif word == '/' or word == '--}{--':
|
elif word == '/':
|
||||||
|
is_bass = True
|
||||||
|
transposed_lyrics += word
|
||||||
|
elif word == '--}{--':
|
||||||
transposed_lyrics += word
|
transposed_lyrics += word
|
||||||
else:
|
else:
|
||||||
# This MUST be a chord
|
# This MUST be a chord
|
||||||
transposed_lyrics += transpose_chord(word, transpose_value, notation)
|
transposed_chord, key, last_chord = transpose_chord(word, transpose_value, notation, key, last_chord,
|
||||||
|
is_bass)
|
||||||
|
is_bass = False
|
||||||
|
transposed_lyrics += transposed_chord
|
||||||
# If still inside a chord tag something is wrong!
|
# If still inside a chord tag something is wrong!
|
||||||
if in_tag:
|
if in_tag:
|
||||||
return verse_text
|
return verse_text, key
|
||||||
else:
|
else:
|
||||||
return transposed_lyrics
|
return transposed_lyrics, key
|
||||||
|
|
||||||
|
|
||||||
def transpose_chord(chord, transpose_value, notation):
|
def transpose_chord(chord, transpose_value, notation, key, last_chord, is_bass):
|
||||||
"""
|
"""
|
||||||
Transpose chord according to the notation used.
|
Transpose chord according to the notation used.
|
||||||
NOTE: This function has a javascript equivalent in chords.js - make sure to update both!
|
NOTE: This function has a javascript equivalent in chords.js - make sure to update both!
|
||||||
@ -624,47 +635,237 @@ def transpose_chord(chord, transpose_value, notation):
|
|||||||
'english': ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'],
|
'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']
|
'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#': ['B#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'B'],
|
||||||
|
'A#m': ['B#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'B'],
|
||||||
|
'Db': ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bbb', 'Bb', 'Cb'],
|
||||||
|
'Bbm': ['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'],
|
||||||
|
'Bm': ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'],
|
||||||
|
'D#': ['B#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'B'],
|
||||||
|
'B#m': ['B#', '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#': ['B#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'B'],
|
||||||
|
'E#m': ['B#', '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#': ['B#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'Gx', 'A#', 'B'],
|
||||||
|
'F##m': ['B#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'Gx', 'A#', 'B'],
|
||||||
|
'Fxm': ['B#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'Gx', 'A#', 'B'],
|
||||||
|
'Bb': ['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'],
|
||||||
|
'B': ['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']
|
||||||
|
},
|
||||||
|
'neo-latin': {
|
||||||
|
'Do': ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Lab', 'La', 'Sib', 'Si'],
|
||||||
|
'Lam': ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Lab', 'La', 'Sib', 'Si'],
|
||||||
|
'Do#': ['Si#', 'Do#', 'Re', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Sol', 'Sol#', 'La', 'La#', 'Si'],
|
||||||
|
'La#m': ['Si#', 'Do#', 'Re', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Sol', 'Sol#', 'La', 'La#', 'Si'],
|
||||||
|
'Reb': ['Do', 'Reb', 'Mibb', 'Mib', 'Fab', 'Fa', 'Solb', 'Labb', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
||||||
|
'Sibm': ['Do', 'Reb', 'Mibb', 'Mib', 'Fab', 'Fa', 'Solb', 'Labb', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
||||||
|
'Re': ['Do', 'Do#', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Lab', 'La', 'Sib', 'Si'],
|
||||||
|
'Sim': ['Do', 'Do#', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Lab', 'La', 'Sib', 'Si'],
|
||||||
|
'Re#': ['Si#', 'Do#', 'Dox', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Fax', 'Sol#', 'La', 'La#', 'Si'],
|
||||||
|
'Si#m': ['Si#', 'Do#', 'Dox', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Fax', 'Sol#', 'La', 'La#', 'Si'],
|
||||||
|
'Mib': ['Do', 'Reb', 'Re', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
||||||
|
'Dom': ['Do', 'Reb', 'Re', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
||||||
|
'Mi': ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'Sib', 'Si'],
|
||||||
|
'Do#m': ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'Sib', 'Si'],
|
||||||
|
'Fa': ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Dob'],
|
||||||
|
'Rem': ['Do', 'Do#', 'Re', 'Mib', 'Mi', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si'],
|
||||||
|
'Fa#': ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Sol', 'Sol#', 'La', 'La#', 'Si'],
|
||||||
|
'Re#m': ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Sol', 'Sol#', 'La', 'La#', 'Si'],
|
||||||
|
'Solb': ['Rebb', 'Reb', 'Mibb', 'Mib', 'Fab', 'Fa', 'Solb', 'Labb', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
||||||
|
'Mibm': ['Rebb', 'Reb', 'Mibb', 'Mib', 'Fab', 'Fa', 'Solb', 'Labb', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
||||||
|
'Sol': ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Lab', 'La', 'Sib', 'Si'],
|
||||||
|
'Mim': ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Lab', 'La', 'Sib', 'Si'],
|
||||||
|
'Sol#': ['Si#', 'Do#', 'Re', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Fax', 'Sol#', 'La', 'La#', 'Si'],
|
||||||
|
'Mi#m': ['Si#', 'Do#', 'Re', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Fax', 'Sol#', 'La', 'La#', 'Si'],
|
||||||
|
'Lab': ['Do', 'Reb', 'Mibb', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
||||||
|
'Fam': ['Do', 'Reb', 'Mibb', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
||||||
|
'La': ['Do', 'Do#', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'Sib', 'Si'],
|
||||||
|
'Fa#m': ['Do', 'Do#', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'Sib', 'Si'],
|
||||||
|
'La#': ['Si#', 'Do#', 'Dox', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Fax', 'Sol#', 'Solx', 'La#', 'Si'],
|
||||||
|
'Fa##m': ['Si#', 'Do#', 'Dox', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Fax', 'Sol#', 'Solx', 'La#', 'Si'],
|
||||||
|
'Faxm': ['Si#', 'Do#', 'Dox', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Fax', 'Sol#', 'Solx', 'La#', 'Si'],
|
||||||
|
'Sib': ['Do', 'Reb', 'Re', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Dob'],
|
||||||
|
'Solm': ['Do', 'Reb', 'Re', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Dob'],
|
||||||
|
'Si': ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'La#', 'Si'],
|
||||||
|
'Sol#m': ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'La#', 'Si'],
|
||||||
|
'Dob': ['Rebb', 'Reb', 'Mibb', 'Mib', 'Fab', 'Solbb', 'Solb', 'Labb', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
||||||
|
'Labm': ['Rebb', 'Reb', 'Mibb', 'Mib', 'Fab', 'Solbb', 'Solb', 'Labb', 'Lab', 'Sibb', 'Sib', 'Dob']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
note_numbers = {
|
||||||
|
'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, 'Fbb': 3,
|
||||||
|
'E': 4, 'D##': 4, 'Dx': 4, 'Fb': 4,
|
||||||
|
'F': 5, 'E#': 5, 'Gbb': 5,
|
||||||
|
'F#': 6, 'Gb': 6, 'E##': 6, 'Ex': 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, 'Cbb': 10,
|
||||||
|
'B': 11, 'A##': 11, 'Ax': 11, 'Cb': 11
|
||||||
|
},
|
||||||
|
'neo-latin': {
|
||||||
|
'Do': 0, 'Si#': 0, 'Rebb': 0,
|
||||||
|
'Do#': 1, 'Reb': 1, 'Si##': 1, 'Six': 1,
|
||||||
|
'Re': 2, 'Do##': 2, 'Dox': 2, 'Mibb': 2,
|
||||||
|
'Re#': 3, 'Mib': 3, 'Fabb': 3,
|
||||||
|
'Mi': 4, 'Re##': 4, 'Rex': 4, 'Fab': 4,
|
||||||
|
'Fa': 5, 'Mi#': 5, 'Solbb': 5,
|
||||||
|
'Fa#': 6, 'Solb': 6, 'Mi##': 6, 'Mix': 6,
|
||||||
|
'Sol': 7, 'Fa##': 7, 'Fax': 7, 'Labb': 7,
|
||||||
|
'Sol#': 8, 'Lab': 8,
|
||||||
|
'La': 9, 'Sol##': 9, 'Solx': 9, 'Sibb': 9,
|
||||||
|
'Sib': 10, 'La#': 10, 'Dobb': 10,
|
||||||
|
'Si': 11, 'La##': 11, 'Lax': 11, 'Dob': 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chord = chord.replace('♭', 'b').replace('♯', '#')
|
||||||
transposed_chord = ''
|
transposed_chord = ''
|
||||||
last_chord = ''
|
minor = ''
|
||||||
|
is_key_change_chord = False
|
||||||
notes_sharp = notes_sharp_notation[notation]
|
notes_sharp = notes_sharp_notation[notation]
|
||||||
notes_flat = notes_flat_notation[notation]
|
notes_flat = notes_flat_notation[notation]
|
||||||
notes_preferred = ['b', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#']
|
notes_preferred = ['b', '#', '#', 'b', '#', 'b', '#', '#', 'b', '#', 'b', '#']
|
||||||
for i in range(0, len(chord_split)):
|
if chord and chord[0] == '(':
|
||||||
if i > 0:
|
|
||||||
transposed_chord += '/'
|
|
||||||
current_chord = chord_split[i]
|
|
||||||
if current_chord and current_chord[0] == '(':
|
|
||||||
transposed_chord += '('
|
transposed_chord += '('
|
||||||
if len(current_chord) > 1:
|
if len(chord) > 1:
|
||||||
current_chord = current_chord[1:]
|
chord = chord[1:]
|
||||||
else:
|
else:
|
||||||
current_chord = ''
|
chord = ''
|
||||||
if len(current_chord) > 0:
|
if chord and chord[0] == '=':
|
||||||
if len(current_chord) > 1:
|
transposed_chord += '='
|
||||||
if '#b'.find(current_chord[1]) == -1:
|
if len(chord) > 1:
|
||||||
note = current_chord[0:1]
|
chord = chord[1:]
|
||||||
rest = current_chord[1:]
|
is_key_change_chord = True
|
||||||
else:
|
else:
|
||||||
note = current_chord[0:2]
|
chord = ''
|
||||||
rest = current_chord[2:]
|
if chord and chord[0] == '|':
|
||||||
|
transposed_chord += '|'
|
||||||
|
if len(chord) > 1:
|
||||||
|
chord = chord[1:]
|
||||||
else:
|
else:
|
||||||
note = current_chord
|
chord = ''
|
||||||
rest = ''
|
if len(chord) > 0:
|
||||||
note_number = notes_flat.index(note) if note not in notes_sharp else notes_sharp.index(note)
|
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:
|
||||||
|
minor = ''
|
||||||
|
note_number = note_numbers[notation][note]
|
||||||
note_number += transpose_value
|
note_number += transpose_value
|
||||||
while note_number > 11:
|
while note_number > 11:
|
||||||
note_number -= 12
|
note_number -= 12
|
||||||
while note_number < 0:
|
while note_number < 0:
|
||||||
note_number += 12
|
note_number += 12
|
||||||
if i == 0:
|
if is_bass:
|
||||||
current_chord = notes_sharp[note_number] if notes_preferred[note_number] == '#' else notes_flat[
|
if last_chord:
|
||||||
note_number]
|
note = scales[notation][last_chord][note_number]
|
||||||
last_chord = current_chord
|
elif key:
|
||||||
|
note = scales[notation][key][note_number]
|
||||||
else:
|
else:
|
||||||
current_chord = notes_flat[note_number] if last_chord not in notes_sharp else notes_sharp[note_number]
|
note = notes_sharp[note_number] if notes_preferred[note_number] == '#' else notes_flat[note_number]
|
||||||
if not (note not in notes_flat and note not in notes_sharp):
|
|
||||||
transposed_chord += current_chord + rest
|
|
||||||
else:
|
else:
|
||||||
transposed_chord += note + rest
|
if not key or is_key_change_chord:
|
||||||
return transposed_chord
|
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
|
||||||
|
if is_key_change_chord:
|
||||||
|
key = note + minor
|
||||||
|
else:
|
||||||
|
if not is_bass:
|
||||||
|
last_chord = note + minor
|
||||||
|
return transposed_chord, key, last_chord
|
||||||
|
@ -67,7 +67,10 @@ class SongsTab(SettingsTab):
|
|||||||
self.chords_layout.addWidget(self.chords_info_label)
|
self.chords_layout.addWidget(self.chords_info_label)
|
||||||
self.disable_chords_import_check_box = QtWidgets.QCheckBox(self.mode_group_box)
|
self.disable_chords_import_check_box = QtWidgets.QCheckBox(self.mode_group_box)
|
||||||
self.disable_chords_import_check_box.setObjectName('disable_chords_import_check_box')
|
self.disable_chords_import_check_box.setObjectName('disable_chords_import_check_box')
|
||||||
|
self.song_key_warning_check_box = QtWidgets.QCheckBox(self.mode_group_box)
|
||||||
|
self.song_key_warning_check_box.setObjectName('song_key_warning_checkbox')
|
||||||
self.chords_layout.addWidget(self.disable_chords_import_check_box)
|
self.chords_layout.addWidget(self.disable_chords_import_check_box)
|
||||||
|
self.chords_layout.addWidget(self.song_key_warning_check_box)
|
||||||
|
|
||||||
# Chords notation group box
|
# Chords notation group box
|
||||||
self.chord_notation_label = QtWidgets.QLabel(self.chords_group_box)
|
self.chord_notation_label = QtWidgets.QLabel(self.chords_group_box)
|
||||||
@ -128,6 +131,7 @@ class SongsTab(SettingsTab):
|
|||||||
self.songbook_slide_check_box.stateChanged.connect(self.on_songbook_slide_check_box_changed)
|
self.songbook_slide_check_box.stateChanged.connect(self.on_songbook_slide_check_box_changed)
|
||||||
self.auto_play_check_box.stateChanged.connect(self.on_auto_play_check_box_changed)
|
self.auto_play_check_box.stateChanged.connect(self.on_auto_play_check_box_changed)
|
||||||
self.disable_chords_import_check_box.stateChanged.connect(self.on_disable_chords_import_check_box_changed)
|
self.disable_chords_import_check_box.stateChanged.connect(self.on_disable_chords_import_check_box_changed)
|
||||||
|
self.song_key_warning_check_box.stateChanged.connect(self.on_song_key_warning_check_box_changed)
|
||||||
self.english_notation_radio_button.clicked.connect(self.on_english_notation_button_clicked)
|
self.english_notation_radio_button.clicked.connect(self.on_english_notation_button_clicked)
|
||||||
self.german_notation_radio_button.clicked.connect(self.on_german_notation_button_clicked)
|
self.german_notation_radio_button.clicked.connect(self.on_german_notation_button_clicked)
|
||||||
self.neolatin_notation_radio_button.clicked.connect(self.on_neolatin_notation_button_clicked)
|
self.neolatin_notation_radio_button.clicked.connect(self.on_neolatin_notation_button_clicked)
|
||||||
@ -156,6 +160,7 @@ class SongsTab(SettingsTab):
|
|||||||
self.german_notation_radio_button.setText(translate('SongsPlugin.SongsTab', 'German') + ' (C-D-E-F-G-A-H)')
|
self.german_notation_radio_button.setText(translate('SongsPlugin.SongsTab', 'German') + ' (C-D-E-F-G-A-H)')
|
||||||
self.neolatin_notation_radio_button.setText(
|
self.neolatin_notation_radio_button.setText(
|
||||||
translate('SongsPlugin.SongsTab', 'Neo-Latin') + ' (Do-Re-Mi-Fa-Sol-La-Si)')
|
translate('SongsPlugin.SongsTab', 'Neo-Latin') + ' (Do-Re-Mi-Fa-Sol-La-Si)')
|
||||||
|
self.song_key_warning_check_box.setText(translate('SongsPlugin.SongsTab', 'Warn about missing song key'))
|
||||||
self.footer_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Footer'))
|
self.footer_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Footer'))
|
||||||
# Keep this in sync with the list in mediaitem.py
|
# Keep this in sync with the list in mediaitem.py
|
||||||
const = '<code>"{}"</code>'
|
const = '<code>"{}"</code>'
|
||||||
@ -224,6 +229,9 @@ class SongsTab(SettingsTab):
|
|||||||
def on_disable_chords_import_check_box_changed(self, check_state):
|
def on_disable_chords_import_check_box_changed(self, check_state):
|
||||||
self.disable_chords_import = (check_state == QtCore.Qt.Checked)
|
self.disable_chords_import = (check_state == QtCore.Qt.Checked)
|
||||||
|
|
||||||
|
def on_song_key_warning_check_box_changed(self, check_state):
|
||||||
|
self.song_key_warning = (check_state == QtCore.Qt.Checked)
|
||||||
|
|
||||||
def on_english_notation_button_clicked(self):
|
def on_english_notation_button_clicked(self):
|
||||||
self.chord_notation = 'english'
|
self.chord_notation = 'english'
|
||||||
|
|
||||||
@ -245,11 +253,13 @@ class SongsTab(SettingsTab):
|
|||||||
self.enable_chords = self.settings.value('songs/enable chords')
|
self.enable_chords = self.settings.value('songs/enable chords')
|
||||||
self.chord_notation = self.settings.value('songs/chord notation')
|
self.chord_notation = self.settings.value('songs/chord notation')
|
||||||
self.disable_chords_import = self.settings.value('songs/disable chords import')
|
self.disable_chords_import = self.settings.value('songs/disable chords import')
|
||||||
|
self.song_key_warning = self.settings.value('songs/warn about missing song key')
|
||||||
self.tool_bar_active_check_box.setChecked(self.tool_bar)
|
self.tool_bar_active_check_box.setChecked(self.tool_bar)
|
||||||
self.update_on_edit_check_box.setChecked(self.update_edit)
|
self.update_on_edit_check_box.setChecked(self.update_edit)
|
||||||
self.add_from_service_check_box.setChecked(self.update_load)
|
self.add_from_service_check_box.setChecked(self.update_load)
|
||||||
self.chords_group_box.setChecked(self.enable_chords)
|
self.chords_group_box.setChecked(self.enable_chords)
|
||||||
self.disable_chords_import_check_box.setChecked(self.disable_chords_import)
|
self.disable_chords_import_check_box.setChecked(self.disable_chords_import)
|
||||||
|
self.song_key_warning_check_box.setChecked(self.song_key_warning)
|
||||||
if self.chord_notation == 'german':
|
if self.chord_notation == 'german':
|
||||||
self.german_notation_radio_button.setChecked(True)
|
self.german_notation_radio_button.setChecked(True)
|
||||||
elif self.chord_notation == 'neo-latin':
|
elif self.chord_notation == 'neo-latin':
|
||||||
@ -267,6 +277,7 @@ class SongsTab(SettingsTab):
|
|||||||
self.settings.setValue('songs/auto play audio', self.auto_play)
|
self.settings.setValue('songs/auto play audio', self.auto_play)
|
||||||
self.settings.setValue('songs/enable chords', self.chords_group_box.isChecked())
|
self.settings.setValue('songs/enable chords', self.chords_group_box.isChecked())
|
||||||
self.settings.setValue('songs/disable chords import', self.disable_chords_import)
|
self.settings.setValue('songs/disable chords import', self.disable_chords_import)
|
||||||
|
self.settings.setValue('songs/warn about missing song key', self.song_key_warning)
|
||||||
self.settings.setValue('songs/chord notation', self.chord_notation)
|
self.settings.setValue('songs/chord notation', self.chord_notation)
|
||||||
self.settings.setValue('songs/songselect username', self.ccli_username.text())
|
self.settings.setValue('songs/songselect username', self.ccli_username.text())
|
||||||
# Only save password if it's blank or the user acknowleges the warning
|
# Only save password if it's blank or the user acknowleges the warning
|
||||||
|
@ -22,7 +22,10 @@
|
|||||||
The :mod:`openlp.plugins.songs.lib.ui` module provides standard UI components
|
The :mod:`openlp.plugins.songs.lib.ui` module provides standard UI components
|
||||||
for the songs plugin.
|
for the songs plugin.
|
||||||
"""
|
"""
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
from openlp.core.common.i18n import translate
|
from openlp.core.common.i18n import translate
|
||||||
|
from openlp.core.common.registry import Registry
|
||||||
|
|
||||||
|
|
||||||
class SongStrings(object):
|
class SongStrings(object):
|
||||||
@ -41,3 +44,18 @@ class SongStrings(object):
|
|||||||
Topic = translate('OpenLP.Ui', 'Topic', 'Singular')
|
Topic = translate('OpenLP.Ui', 'Topic', 'Singular')
|
||||||
Topics = translate('OpenLP.Ui', 'Topics', 'Plural')
|
Topics = translate('OpenLP.Ui', 'Topics', 'Plural')
|
||||||
XMLSyntaxError = translate('OpenLP.Ui', 'XML syntax error')
|
XMLSyntaxError = translate('OpenLP.Ui', 'XML syntax error')
|
||||||
|
|
||||||
|
|
||||||
|
def show_key_warning(parent):
|
||||||
|
"""
|
||||||
|
Check the settings to see if we need to show the warning message, and then show a warning about the key of the song
|
||||||
|
"""
|
||||||
|
if Registry().get('settings').value('songs/enable chords') and \
|
||||||
|
Registry().get('settings').value('songs/warn about missing song key'):
|
||||||
|
QtWidgets.QMessageBox.warning(
|
||||||
|
parent,
|
||||||
|
translate('SongsPlugin.UI', 'Song key warning'),
|
||||||
|
translate('SongsPlugin.UI', 'No musical key has been detected for this song, it should be placed before '
|
||||||
|
'the first chord.\nFor an optimal chord experience, please include a song key at the beginning '
|
||||||
|
'of the song. For example: [=G]\n\nYou can disable this warning message in songs settings.')
|
||||||
|
)
|
||||||
|
@ -116,7 +116,7 @@ def test_render_chords(settings):
|
|||||||
text_with_rendered_chords = render_chords(text_with_chords)
|
text_with_rendered_chords = render_chords(text_with_chords)
|
||||||
|
|
||||||
# THEN: We should get html that looks like below
|
# THEN: We should get html that looks like below
|
||||||
expected_html = '<span class="chordline firstchordline">H<span class="chord"><span><strong>C</strong></span>' \
|
expected_html = '<span class="chordline">H<span class="chord"><span><strong>C</strong></span>' \
|
||||||
'</span>alleluya.<span class="chord"><span><strong>F</strong></span></span><span class="ws">' \
|
'</span>alleluya.<span class="chord"><span><strong>F</strong></span></span><span class="ws">' \
|
||||||
' </span> <span class="chord"><span><strong>G/B</strong></span></span></span>'
|
' </span> <span class="chord"><span><strong>G/B</strong></span></span></span>'
|
||||||
assert text_with_rendered_chords == expected_html, 'The rendered chords should look as expected'
|
assert text_with_rendered_chords == expected_html, 'The rendered chords should look as expected'
|
||||||
@ -134,7 +134,7 @@ def test_render_chords_with_special_chars(settings):
|
|||||||
text_with_rendered_chords = render_tags(text_with_chords, can_render_chords=True)
|
text_with_rendered_chords = render_tags(text_with_chords, can_render_chords=True)
|
||||||
|
|
||||||
# THEN: We should get html that looks like below
|
# THEN: We should get html that looks like below
|
||||||
expected_html = '<span class="chordline firstchordline">I<span class="chord"><span><strong>D</strong></span>' \
|
expected_html = '<span class="chordline">I<span class="chord"><span><strong>D</strong></span>' \
|
||||||
'</span>'M NOT MOVED BY WHAT I SEE HALLE<span class="chord"><span><strong>F</strong>' \
|
'</span>'M NOT MOVED BY WHAT I SEE HALLE<span class="chord"><span><strong>F</strong>' \
|
||||||
'</span></span>LUJA<span class="chord"><span><strong>C</strong></span></span>H</span>'
|
'</span></span>LUJA<span class="chord"><span><strong>C</strong></span></span>H</span>'
|
||||||
assert text_with_rendered_chords == expected_html, 'The rendered chords should look as expected'
|
assert text_with_rendered_chords == expected_html, 'The rendered chords should look as expected'
|
||||||
|
@ -725,10 +725,11 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
|||||||
'notes': '',
|
'notes': '',
|
||||||
'slides': [
|
'slides': [
|
||||||
{
|
{
|
||||||
'chords': 'Amazing Grace! how sweet the sound\n'
|
'chords': '<span class="nochordline">'
|
||||||
|
'Amazing Grace! how sweet the sound\n'
|
||||||
'That saved a wretch like me;\n'
|
'That saved a wretch like me;\n'
|
||||||
'I once was lost, but now am found,\n'
|
'I once was lost, but now am found,\n'
|
||||||
'Was blind, but now I see.',
|
'Was blind, but now I see.</span>',
|
||||||
'html': 'Amazing Grace! how sweet the sound\n'
|
'html': 'Amazing Grace! how sweet the sound\n'
|
||||||
'That saved a wretch like me;\n'
|
'That saved a wretch like me;\n'
|
||||||
'I once was lost, but now am found,\n'
|
'I once was lost, but now am found,\n'
|
||||||
@ -743,10 +744,11 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
|||||||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'chords': '’Twas grace that taught my heart to fear,\n'
|
'chords': '<span class="nochordline">'
|
||||||
|
'’Twas grace that taught my heart to fear,\n'
|
||||||
'And grace my fears relieved;\n'
|
'And grace my fears relieved;\n'
|
||||||
'How precious did that grace appear,\n'
|
'How precious did that grace appear,\n'
|
||||||
'The hour I first believed!',
|
'The hour I first believed!</span>',
|
||||||
'html': '’Twas grace that taught my heart to fear,\n'
|
'html': '’Twas grace that taught my heart to fear,\n'
|
||||||
'And grace my fears relieved;\n'
|
'And grace my fears relieved;\n'
|
||||||
'How precious did that grace appear,\n'
|
'How precious did that grace appear,\n'
|
||||||
@ -761,10 +763,11 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
|||||||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'chords': 'Through many dangers, toils and snares\n'
|
'chords': '<span class="nochordline">'
|
||||||
|
'Through many dangers, toils and snares\n'
|
||||||
'I have already come;\n'
|
'I have already come;\n'
|
||||||
'’Tis grace that brought me safe thus far,\n'
|
'’Tis grace that brought me safe thus far,\n'
|
||||||
'And grace will lead me home.',
|
'And grace will lead me home.</span>',
|
||||||
'html': 'Through many dangers, toils and snares\n'
|
'html': 'Through many dangers, toils and snares\n'
|
||||||
'I have already come;\n'
|
'I have already come;\n'
|
||||||
'’Tis grace that brought me safe thus far,\n'
|
'’Tis grace that brought me safe thus far,\n'
|
||||||
@ -779,10 +782,11 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
|||||||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'chords': 'The Lord has promised good to me,\n'
|
'chords': '<span class="nochordline">'
|
||||||
|
'The Lord has promised good to me,\n'
|
||||||
'His word my hope secures;\n'
|
'His word my hope secures;\n'
|
||||||
'He will my shield and portion be\n'
|
'He will my shield and portion be\n'
|
||||||
'As long as life endures.',
|
'As long as life endures.</span>',
|
||||||
'html': 'The Lord has promised good to me,\n'
|
'html': 'The Lord has promised good to me,\n'
|
||||||
'His word my hope secures;\n'
|
'His word my hope secures;\n'
|
||||||
'He will my shield and portion be\n'
|
'He will my shield and portion be\n'
|
||||||
@ -797,10 +801,11 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
|||||||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'chords': 'Yes, when this heart and flesh shall fail,\n'
|
'chords': '<span class="nochordline">'
|
||||||
|
'Yes, when this heart and flesh shall fail,\n'
|
||||||
'And mortal life shall cease,\n'
|
'And mortal life shall cease,\n'
|
||||||
'I shall possess within the veil\n'
|
'I shall possess within the veil\n'
|
||||||
'A life of joy and peace.',
|
'A life of joy and peace.</span>',
|
||||||
'html': 'Yes, when this heart and flesh shall fail,\n'
|
'html': 'Yes, when this heart and flesh shall fail,\n'
|
||||||
'And mortal life shall cease,\n'
|
'And mortal life shall cease,\n'
|
||||||
'I shall possess within the veil\n'
|
'I shall possess within the veil\n'
|
||||||
@ -815,10 +820,11 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
|||||||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'chords': 'When we’ve been there a thousand years,\n'
|
'chords': '<span class="nochordline">'
|
||||||
|
'When we’ve been there a thousand years,\n'
|
||||||
'Bright shining as the sun,\n'
|
'Bright shining as the sun,\n'
|
||||||
'We’ve no less days to sing God’s praise\n'
|
'We’ve no less days to sing God’s praise\n'
|
||||||
'Than when we first begun.',
|
'Than when we first begun.</span>',
|
||||||
'html': 'When we’ve been there a thousand years,\n'
|
'html': 'When we’ve been there a thousand years,\n'
|
||||||
'Bright shining as the sun,\n'
|
'Bright shining as the sun,\n'
|
||||||
'We’ve no less days to sing God’s praise\n'
|
'We’ve no less days to sing God’s praise\n'
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
This module contains tests for the editverseform of the Songs plugin.
|
This module contains tests for the editverseform of the Songs plugin.
|
||||||
"""
|
"""
|
||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from openlp.plugins.songs.forms.editverseform import EditVerseForm
|
from openlp.plugins.songs.forms.editverseform import EditVerseForm
|
||||||
|
|
||||||
@ -94,3 +94,29 @@ def test_on_split_button_clicked(edit_verse_form):
|
|||||||
# THEN the verse number must not be changed
|
# THEN the verse number must not be changed
|
||||||
assert '[---]\nText\n' == edit_verse_form.verse_text_edit.toPlainText(), \
|
assert '[---]\nText\n' == edit_verse_form.verse_text_edit.toPlainText(), \
|
||||||
'The verse number should be [---]\nText\n'
|
'The verse number should be [---]\nText\n'
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.plugins.songs.forms.editverseform.show_key_warning')
|
||||||
|
def test_on_transpose_up_button_clicked(mocked_show_key_warning, edit_verse_form):
|
||||||
|
"""
|
||||||
|
Test that transpose button will transpose the chords and warn about missing song key
|
||||||
|
"""
|
||||||
|
# GIVEN some input values
|
||||||
|
edit_verse_form.verse_text_edit.setPlainText('Am[G]azing gr[G/B]ace, how sw[C]eet the s[G]ound')
|
||||||
|
# WHEN the method is called
|
||||||
|
edit_verse_form.on_transpose_up_button_clicked()
|
||||||
|
mocked_show_key_warning.assert_called_once_with(edit_verse_form)
|
||||||
|
# THEN chords should be transposed up
|
||||||
|
assert 'Am[Ab]azing gr[Ab/C]ace, how sw[C#]eet the s[Ab]ound' == edit_verse_form.verse_text_edit.toPlainText()
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_transpose_down_button_clicked(edit_verse_form):
|
||||||
|
"""
|
||||||
|
Test that transpose button will transpose the chords and warn about missing song key
|
||||||
|
"""
|
||||||
|
# GIVEN some input values
|
||||||
|
edit_verse_form.verse_text_edit.setPlainText('[=G]Am[G]azing gr[G/B]ace, how sw[C]eet the s[G]ound')
|
||||||
|
# WHEN the method is called
|
||||||
|
edit_verse_form.on_transpose_down_button_clicked()
|
||||||
|
# THEN chords should be transposed up
|
||||||
|
assert '[=F#]Am[F#]azing gr[F#/A#]ace, how sw[B]eet the s[F#]ound' == edit_verse_form.verse_text_edit.toPlainText()
|
||||||
|
@ -275,12 +275,17 @@ def test_transpose_chord_up():
|
|||||||
"""
|
"""
|
||||||
# GIVEN: A Chord
|
# GIVEN: A Chord
|
||||||
chord = 'C'
|
chord = 'C'
|
||||||
|
key = None
|
||||||
|
last_chord = None
|
||||||
|
is_bass = False
|
||||||
|
|
||||||
# WHEN: Transposing it 1 up
|
# WHEN: Transposing it 1 up
|
||||||
new_chord = transpose_chord(chord, 1, 'english')
|
new_chord, key, last_chord = transpose_chord(chord, 1, 'english', key, last_chord, is_bass)
|
||||||
|
|
||||||
# THEN: The chord should be transposed up one note
|
# THEN: The chord should be transposed up one note
|
||||||
assert new_chord == 'C#', 'The chord should be transposed up.'
|
assert new_chord == 'C#', 'The chord should be transposed up.'
|
||||||
|
assert key is None, 'The key should not be set'
|
||||||
|
assert last_chord == 'C#', 'If not is_bass, then last_chord should be returned'
|
||||||
|
|
||||||
|
|
||||||
def test_transpose_chord_up_adv():
|
def test_transpose_chord_up_adv():
|
||||||
@ -288,13 +293,23 @@ def test_transpose_chord_up_adv():
|
|||||||
Test that the transpose_chord() method works when transposing up an advanced chord
|
Test that the transpose_chord() method works when transposing up an advanced chord
|
||||||
"""
|
"""
|
||||||
# GIVEN: An advanced Chord
|
# GIVEN: An advanced Chord
|
||||||
chord = '(C/D#)'
|
chord = '(D/F#)'
|
||||||
|
key = None
|
||||||
|
last_chord = None
|
||||||
|
is_bass = False
|
||||||
|
chord_split = chord.split("/")
|
||||||
# WHEN: Transposing it 1 up
|
# WHEN: Transposing it 1 up
|
||||||
new_chord = transpose_chord(chord, 1, 'english')
|
new_chord, key, last_chord = transpose_chord(chord_split[0], 1, 'english', key, last_chord, is_bass)
|
||||||
|
|
||||||
|
# AFTER "/" isbass is true, lastchord is set
|
||||||
|
is_bass = True
|
||||||
|
new_bass, key, last_chord = transpose_chord(chord_split[1], 1, 'english', key, last_chord, is_bass)
|
||||||
|
|
||||||
# THEN: The chord should be transposed up one note
|
# THEN: The chord should be transposed up one note
|
||||||
assert new_chord == '(C#/E)', 'The chord should be transposed up.'
|
assert new_chord == '(Eb', 'The chord should be transposed up.'
|
||||||
|
assert new_bass == 'G)', 'Bass should be transposed up.'
|
||||||
|
assert key is None, 'no key should be defined'
|
||||||
|
assert last_chord == 'Eb', 'last_chord is generated'
|
||||||
|
|
||||||
|
|
||||||
def test_transpose_chord_down():
|
def test_transpose_chord_down():
|
||||||
@ -303,12 +318,17 @@ def test_transpose_chord_down():
|
|||||||
"""
|
"""
|
||||||
# GIVEN: A Chord
|
# GIVEN: A Chord
|
||||||
chord = 'C'
|
chord = 'C'
|
||||||
|
key = None
|
||||||
|
last_chord = None
|
||||||
|
is_bass = False
|
||||||
|
|
||||||
# WHEN: Transposing it 1 down
|
# WHEN: Transposing it 1 down
|
||||||
new_chord = transpose_chord(chord, -1, 'english')
|
new_chord, key, last_chord = transpose_chord(chord, -1, 'english', key, last_chord, is_bass)
|
||||||
|
|
||||||
# THEN: The chord should be transposed down one note
|
# THEN: The chord should be transposed down one note
|
||||||
assert new_chord == 'B', 'The chord should be transposed down.'
|
assert new_chord == 'B', 'The chord should be transposed down.'
|
||||||
|
assert key is None, 'The key should not be set'
|
||||||
|
assert last_chord == 'B', 'If not is_bass, then last_chord should be returned'
|
||||||
|
|
||||||
|
|
||||||
def test_transpose_chord_error():
|
def test_transpose_chord_error():
|
||||||
@ -320,10 +340,10 @@ def test_transpose_chord_error():
|
|||||||
|
|
||||||
# WHEN: Transposing it 1 down
|
# WHEN: Transposing it 1 down
|
||||||
# THEN: An exception should be raised
|
# THEN: An exception should be raised
|
||||||
with pytest.raises(ValueError) as err:
|
with pytest.raises(KeyError) as err:
|
||||||
transpose_chord(chord, -1, 'english')
|
transpose_chord(chord, -1, 'english', None, None, False)
|
||||||
assert err.value != ValueError('\'T\' is not in list'), \
|
assert err.value != KeyError('\'T\' is not in list'), \
|
||||||
'ValueError exception should have been thrown for invalid chord'
|
'KeyError exception should have been thrown for invalid chord'
|
||||||
|
|
||||||
|
|
||||||
@patch('openlp.plugins.songs.lib.transpose_verse')
|
@patch('openlp.plugins.songs.lib.transpose_verse')
|
||||||
@ -339,15 +359,15 @@ def test_transpose_lyrics(mocked_transpose_verse, mock_settings):
|
|||||||
'---[Verse:2]---\n'\
|
'---[Verse:2]---\n'\
|
||||||
'I once was lost but now I\'m found.'
|
'I once was lost but now I\'m found.'
|
||||||
mock_settings.value.return_value = 'english'
|
mock_settings.value.return_value = 'english'
|
||||||
|
mocked_transpose_verse.return_value = ['', None]
|
||||||
# WHEN: Transposing the lyrics
|
# WHEN: Transposing the lyrics
|
||||||
transpose_lyrics(lyrics, 1)
|
transpose_lyrics(lyrics, 1)
|
||||||
|
|
||||||
# THEN: transpose_verse should have been called
|
# THEN: transpose_verse should have been called
|
||||||
mocked_transpose_verse.assert_any_call('', 1, 'english')
|
mocked_transpose_verse.assert_any_call('', 1, 'english', None)
|
||||||
mocked_transpose_verse.assert_any_call('\nAmazing grace how sweet the sound\n', 1, 'english')
|
mocked_transpose_verse.assert_any_call('\nAmazing grace how sweet the sound\n', 1, 'english', None)
|
||||||
mocked_transpose_verse.assert_any_call('\nThat saved a wretch like me.\n', 1, 'english')
|
mocked_transpose_verse.assert_any_call('\nThat saved a wretch like me.\n', 1, 'english', None)
|
||||||
mocked_transpose_verse.assert_any_call('\nI once was lost but now I\'m found.', 1, 'english')
|
mocked_transpose_verse.assert_any_call('\nI once was lost but now I\'m found.', 1, 'english', None)
|
||||||
|
|
||||||
|
|
||||||
def test_translated_tag():
|
def test_translated_tag():
|
||||||
|
@ -150,7 +150,7 @@ def test_password_change(mocked_settings_set_val, mocked_question, form):
|
|||||||
form.save()
|
form.save()
|
||||||
# THEN: footer should not have been saved (one less call than the change test below)
|
# THEN: footer should not have been saved (one less call than the change test below)
|
||||||
mocked_question.assert_called_once()
|
mocked_question.assert_called_once()
|
||||||
assert mocked_settings_set_val.call_count == 10
|
assert mocked_settings_set_val.call_count == 11
|
||||||
|
|
||||||
|
|
||||||
@patch('openlp.plugins.songs.lib.songstab.QtWidgets.QMessageBox.question')
|
@patch('openlp.plugins.songs.lib.songstab.QtWidgets.QMessageBox.question')
|
||||||
@ -166,7 +166,7 @@ def test_password_change_cancelled(mocked_settings_set_val, mocked_question, for
|
|||||||
form.save()
|
form.save()
|
||||||
# THEN: footer should not have been saved (one less call than the change test below)
|
# THEN: footer should not have been saved (one less call than the change test below)
|
||||||
mocked_question.assert_called_once()
|
mocked_question.assert_called_once()
|
||||||
assert mocked_settings_set_val.call_count == 9
|
assert mocked_settings_set_val.call_count == 10
|
||||||
|
|
||||||
|
|
||||||
@patch('openlp.core.common.settings.Settings.setValue')
|
@patch('openlp.core.common.settings.Settings.setValue')
|
||||||
@ -178,7 +178,7 @@ def test_footer_nochange(mocked_settings_set_val, form):
|
|||||||
# WHEN: save is invoked
|
# WHEN: save is invoked
|
||||||
form.save()
|
form.save()
|
||||||
# THEN: footer should not have been saved (one less call than the change test below)
|
# THEN: footer should not have been saved (one less call than the change test below)
|
||||||
assert mocked_settings_set_val.call_count == 10
|
assert mocked_settings_set_val.call_count == 11
|
||||||
|
|
||||||
|
|
||||||
@patch('openlp.core.common.settings.Settings.setValue')
|
@patch('openlp.core.common.settings.Settings.setValue')
|
||||||
@ -191,7 +191,7 @@ def test_footer_change(mocked_settings_set_val, form):
|
|||||||
# WHEN: save is invoked
|
# WHEN: save is invoked
|
||||||
form.save()
|
form.save()
|
||||||
# THEN: footer should have been saved (one more call to setValue than the nochange test)
|
# THEN: footer should have been saved (one more call to setValue than the nochange test)
|
||||||
assert mocked_settings_set_val.call_count == 11
|
assert mocked_settings_set_val.call_count == 12
|
||||||
assert form.footer_edit_box.toPlainText() == 'A new footer'
|
assert form.footer_edit_box.toPlainText() == 'A new footer'
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user