Skip to content

Commit

Permalink
Included transpose methods
Browse files Browse the repository at this point in the history
  • Loading branch information
titus-ong committed Aug 23, 2020
1 parent adac790 commit e31a5d0
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 2 deletions.
115 changes: 114 additions & 1 deletion src/chordparser/music/note.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

from chordparser.music.letter import Letter
from chordparser.music.symbol import Symbol
from chordparser.utils.converters import symbol_to_unicode
from chordparser.utils.note_lists import sharp_scale, flat_scale
from chordparser.utils.regex_patterns import (note_pattern,
sharp_pattern,
flat_pattern)
from chordparser.utils.converters import symbol_to_unicode


class NoteNotationParser:
Expand Down Expand Up @@ -50,6 +51,9 @@ class Note:
_NNP = NoteNotationParser()

def __init__(self, notation):
self._set(notation)

def _set(self, notation):
letter, symbol = self._NNP.parse_notation(notation)
self._letter = Letter(letter)
self._symbol = Symbol(symbol)
Expand Down Expand Up @@ -80,3 +84,112 @@ def as_int(self):
"""
return (self._letter.as_int() + self._symbol.as_int()) % 12

def transpose(self, semitones, letters):
"""Transpose the `Note` by some semitone and letter intervals.
Parameters
----------
semitones
The difference in semitones to the transposed `Note`.
letters
The difference in scale degrees to the transposed `Note`.
Examples
--------
>>> note = Note("C")
>>> note.transpose(6, 3)
>>> note
F\u266f note
>>> note.transpose(0, 1)
>>> note
G\u266d note
"""
original_int = self.as_int()
self._letter.shift_by(letters)
positive_int_diff = (self.as_int() - original_int) % 12
positive_semitones = semitones % 12
semitone_difference = positive_semitones - positive_int_diff
self._symbol.shift_by(semitone_difference)

def transpose_simple(self, semitones, use_flats=False):
"""Transpose the `Note` by some semitone interval.
Parameters
----------
semitones : int
The difference in semitones to the transposed `Note`.
use_flats : boolean, Optional
Selector to use flats or sharps for black keys. Default
False when optional.
Examples
--------
>>> note = Note("C")
>>> note.transpose_simple(6)
F\u266f note
>>> note.transpose_simple(2, use_flats=True)
A\u266d note
"""
if use_flats:
notes = flat_scale
else:
notes = sharp_scale
note = notes[(self.as_int() + semitones) % 12]
self._set(note)

def __repr__(self):
return f"{str(self)} note"

def __str__(self):
return f"{self._letter}{self._symbol}"

def __eq__(self, other):
"""Compare with other `Notes` or strings.
If comparing with a `Note`, checks if the other `Note`'s string notation is the same as this `Note`. If comparing with a
string, checks if the string is equal to this `Note`'s string
notation.'
Parameters
----------
other
The object to be compared with.
Returns
-------
boolean
The outcome of the comparison.
Examples
--------
>>> d = Note("D")
>>> d2 = Note("D")
>>> d_str = "D"
>>> d == d2
True
>>> d == d_str
True
Note that symbols are converted to their unicode characters
when a `Note` is created.
>>> ds = Note("D#")
>>> ds_str = "D#"
>>> ds_str_2 = "D\u266f"
>>> ds == ds_str
False
>>> ds == ds_str_2
True
"""
if isinstance(other, Note):
return (
self._letter == other.letter and
self._symbol == other.symbol
)
if isinstance(other, str):
return str(self) == other
return NotImplemented
15 changes: 15 additions & 0 deletions src/chordparser/utils/note_lists.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from chordparser.utils.unicode_chars import sharp, flat


natural_notes = (
"C", "D", "E", "F", "G", "A", "B",
"C", "D", "E", "F", "G", "A", "B",
Expand All @@ -6,3 +9,15 @@
2, 2, 1, 2, 2, 2, 1,
2, 2, 1, 2, 2, 2, 1,
)
sharp_scale = (
"C", f"C{sharp}", "D", f"D{sharp}", "E", "F", f"F{sharp}", "G",
f"G{sharp}", "A", f"A{sharp}", "B",
"C", f"C{sharp}", "D", f"D{sharp}", "E", "F", f"F{sharp}", "G",
f"G{sharp}", "A", f"A{sharp}", "B",
)
flat_scale = (
"C", f"D{flat}", "D", f"E{flat}", "E", "F", f"G{flat}", "G",
f"A{flat}", "A", f"B{flat}", "B",
"C", f"D{flat}", "D", f"E{flat}", "E", "F", f"G{flat}", "G",
f"A{flat}", "A", f"B{flat}", "B",
)
54 changes: 53 additions & 1 deletion tests/test_music/test_note.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest

from chordparser.utils.unicode_chars import sharp
from chordparser.utils.unicode_chars import (sharp, doublesharp,
flat, doubleflat)
from chordparser.music.note import NoteNotationParser, Note


Expand Down Expand Up @@ -59,3 +60,54 @@ class TestNoteAsInt:
def test_correct_int(self, note, value):
n = Note(note)
assert value == n.as_int()


class TestNoteTranspose:
@pytest.mark.parametrize(
"semitones, letters, old, new", [
(1, 0, "C", f"C{sharp}"),
(-2, -1, "C", f"B{flat}"),
(-5, -4, "C", f"F{doublesharp}"),
(-3, -2, "D", "B"),
]
)
def test_correct_transpose(self, semitones, letters, old, new):
n = Note(old)
n.transpose(semitones, letters)
assert new == str(n)


class TestNoteTransposeSimple:
@pytest.mark.parametrize(
"note, num, new_note", [
("C", 2, "D"),
("C", -13, "B"),
("C", 3, f"D{sharp}")
]
)
def test_correct_transpose_simple(self, note, num, new_note):
n = Note(note)
n.transpose_simple(num)
assert new_note == str(n)


def test_correct_transpose_simple_flats(self):
n = Note("C")
n.transpose_simple(3, use_flats=True)
assert f"E{flat}" == str(n)


class TestNoteEquality:
def test_equality(self):
c = Note("C")
c2 = Note("C")
c_str = "C"
assert c == c2
assert c == c_str

@pytest.mark.parametrize(
"other", [Note("D"), "D", len]
)
def test_inequality(self, other):
c = Note("C")
assert other != c

0 comments on commit e31a5d0

Please sign in to comment.