Implement chord transposing in the lyrics editor.

This commit is contained in:
Tomas Groth 2016-06-07 22:21:21 +02:00
parent 0ee555cd84
commit 3d98d60d59
3 changed files with 137 additions and 17 deletions

View File

@ -25,7 +25,7 @@ import logging
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib import VerseType, transpose_lyrics
from .editversedialog import Ui_EditVerseDialog from .editversedialog import Ui_EditVerseDialog
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -101,13 +101,15 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
""" """
The transpose up button clicked The transpose up button clicked
""" """
print('...') transposed_lyrics = transpose_lyrics(self.verse_text_edit.toPlainText(), 1)
self.verse_text_edit.setPlainText(transposed_lyrics)
def on_transepose_down_button_clicked(self): def on_transepose_down_button_clicked(self):
""" """
The transpose down button clicked The transpose down button clicked
""" """
print('...') transposed_lyrics = transpose_lyrics(self.verse_text_edit.toPlainText(), -1)
self.verse_text_edit.setPlainText(transposed_lyrics)
def update_suggested_verse_number(self): def update_suggested_verse_number(self):
""" """

View File

@ -29,7 +29,7 @@ import re
from PyQt5 import QtWidgets from PyQt5 import QtWidgets
from openlp.core.common import AppLocation from openlp.core.common import AppLocation, Settings
from openlp.core.lib import translate from openlp.core.lib import translate
from openlp.core.utils import CONTROL_CHARS from openlp.core.utils import CONTROL_CHARS
from openlp.plugins.songs.lib.db import MediaFile, Song from openlp.plugins.songs.lib.db import MediaFile, Song
@ -521,18 +521,6 @@ def strip_rtf(text, default_encoding=None):
return text, default_encoding return text, default_encoding
def transpose_lyrics(lyric, transepose_value):
"""
Transepose lyrics
:param lyrcs: The lyrics to be transposed
:param transepose_value: The value to transpose the lyrics with
:return: The transposed lyrics
"""
if '[' not in lyrics:
return lyrics
def delete_song(song_id, song_plugin): def delete_song(song_id, song_plugin):
""" """
Deletes a song from the database. Media files associated to the song are removed prior to the deletion of the song. Deletes a song from the database. Media files associated to the song are removed prior to the deletion of the song.
@ -554,3 +542,107 @@ def delete_song(song_id, song_plugin):
except OSError: except OSError:
log.exception('Could not remove directory: %s', save_path) log.exception('Could not remove directory: %s', save_path)
song_plugin.manager.delete_object(Song, song_id) song_plugin.manager.delete_object(Song, song_id)
def transpose_lyrics(lyrics, transepose_value):
"""
Transepose lyrics
:param lyrcs: The lyrics to be transposed
:param transepose_value: The value to transpose the lyrics with
:return: The transposed lyrics
"""
if '[' not in lyrics:
return lyrics
# Split the lyrics based on chord tags
lyric_list = re.split('(\[|\]|/)', lyrics)
transposed_lyrics = ''
in_tag = False
notation = Settings().value('songs/chord notation')
for word in lyric_list:
if not in_tag:
transposed_lyrics += word
if word == '[':
in_tag = True
else:
if word == ']':
in_tag = False
transposed_lyrics += word
elif word == '/':
transposed_lyrics += word
else:
# This MUST be a chord
transposed_lyrics += transpose_chord(word, transepose_value, notation)
# If still inside a chord tag something is wrong!
if in_tag:
return lyrics
else:
return transposed_lyrics
def transpose_chord(chord, transpose_value, notation):
"""
Transpose chord according to the notation used.
NOTE: This function has a javascript equivalent in chords.js - make sure to update both!
:param chord: The chord to transpose.
:param transpose_value: The value the chord should be transposed.
:param notation: The notation to use when transposing.
:return: The transposed chord.
"""
# See https://en.wikipedia.org/wiki/Musical_note#12-tone_chromatic_scale
notes_sharp_notation = {}
notes_flat_notation = {}
notes_sharp_notation['german'] = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','H']
notes_flat_notation['german'] = ['C','Db','D','Eb','Fb','F','Gb','G','Ab','A','B','H']
notes_sharp_notation['english'] = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B']
notes_flat_notation['english'] = ['C','Db','D','Eb','Fb','F','Gb','G','Ab','A','Bb','B']
notes_sharp_notation['neo-latin'] = ['Do','Do#','Re','Re#','Mi','Fa','Fa#','Sol','Sol#','La','La#','Si']
notes_flat_notation['neo-latin'] = ['Do','Reb','Re','Mib','Fab','Fa','Solb','Sol','Lab','La','Sib','Si']
chord_split = chord.replace('', 'b').split('/[\/\(\)]/')
transposed_chord = ''
note = ''
notenumber = -1
rest = ''
current_chord = ''
last_chord = ''
notes_sharp = notes_sharp_notation[notation]
notes_flat = notes_flat_notation[notation]
notes_preferred = ['b','#','#','#','#','#','#','#','#','#','#','#']
chord_notes = []
for i in range(0, len(chord_split)):
if i > 0:
transposed_chord += '/'
currentchord = chord_split[i]
if currentchord[0] == '(':
transposed_chord += '('
if len(currentchord) > 1:
currentchord = currentchord[1:]
else:
currentchord = ""
if len(currentchord) > 0:
if len(currentchord) > 1:
if '#b'.find(currentchord[1]) == -1:
note = currentchord[0:1]
rest = currentchord[1:]
else:
note = currentchord[0:2]
rest = currentchord[2:]
else:
note = currentchord
rest = ''
notenumber = notes_flat.index(note) if note not in notes_sharp else notes_sharp.index(note)
notenumber += transpose_value
while notenumber > 11:
notenumber -= 12
while notenumber < 0:
notenumber += 12
if i == 0:
current_chord = notes_sharp[notenumber] if notes_preferred[notenumber] == '#' else notes_flat[notenumber]
last_chord = current_chord
else:
current_chord = notes_flat[notenumber] if last_chord not in notes_sharp else notes_sharp[notenumber]
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

View File

@ -24,7 +24,7 @@ This module contains tests for the lib submodule of the Songs plugin.
""" """
from unittest import TestCase from unittest import TestCase
from openlp.plugins.songs.lib import VerseType, clean_string, clean_title, strip_rtf from openlp.plugins.songs.lib import VerseType, clean_string, clean_title, strip_rtf, transpose_chord
from openlp.plugins.songs.lib.songcompare import songs_probably_equal, _remove_typos, _op_length from openlp.plugins.songs.lib.songcompare import songs_probably_equal, _remove_typos, _op_length
from tests.functional import patch, MagicMock from tests.functional import patch, MagicMock
@ -264,6 +264,32 @@ class TestLib(TestCase):
# THEN: The stripped text matches thed expected result # THEN: The stripped text matches thed expected result
assert result == exp_result, 'The result should be %s' % exp_result assert result == exp_result, 'The result should be %s' % exp_result
def transpose_chord_up_test(self):
"""
Test that the transpose_chord() method works when transposing up
"""
# GIVEN: A Chord
chord = 'C'
# WHEN: Transposing it 1 up
new_chord = transpose_chord(chord, 1, 'english')
# THEN: The chord should be transposed up one note
self.assertEqual(new_chord, 'C#', 'The chord should be transposed up.')
def transpose_chord_down_test(self):
"""
Test that the transpose_chord() method works when transposing down
"""
# GIVEN: A Chord
chord = 'C'
# WHEN: Transposing it 1 down
new_chord = transpose_chord(chord, -1, 'english')
# THEN: The chord should be transposed down one note
self.assertEqual(new_chord, 'B', 'The chord should be transposed down.')
class TestVerseType(TestCase): class TestVerseType(TestCase):
""" """