Skip to content

Commit

Permalink
notes and pitchclass; refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
Bernd committed Jan 2, 2024
1 parent cb98f1c commit 64cc1c5
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 139 deletions.
59 changes: 32 additions & 27 deletions pymusictheory/chords.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,55 @@

from .definitions import *
from .scales import ChromaticScale
from .converter import distance_to_note
from .notes import Note, PitchClass

# chord name to integeter notation mapping (semitone distance from root)
chord_integer = {
12 : {
'major' : (0,4,7),
'major7' : (0,4,7,11),
'minor' : (0,3,7)
}
}
12 : {
'major' : (0,4,7),
'major7' : (0,4,7,11),
'minor' : (0,3,7)
}
}

class Chord:

def __init__(self, root, chord, chromaticscale=ChromaticScale()):
""" Creates a Chord object for root and chord. """
def __init__(self, root: str, chord: list, chromaticscale=ChromaticScale()):
""" Creates a Chord object for 'root' and 'chord' (integer list).
'root' must be name of PitchClass (str) """
self._scale = chromaticscale
self._temperament_length = len(self._scale.get_octaves()[0])-1
self._root_index = semitone_distances[self._temperament_length][root]
self._chord = chord_integer[self._temperament_length][chord]

def get_chord(self):
""" Returns the list with the notes of the chord """
td = semitone_distances[self._temperament_length]
rtd = distance_to_note(td)
self._root_index = self._scale.temperament.name_to_distance(root)
self._chord = chord_integer[self._scale.temperament.length][chord]

def get_chord(self, voicing: list =None) -> list:
""" Returns the list with the notes of the chord. Optional: provide
voicing as list of octaves per note in chord """
if voicing is None:
voicing = [4] * len(self._chord)
else:
voicing = [4] * voicing

chord = []
for note in self._chord:
chord.append(rtd[self._root_index + note])
for e in range(len(self._chord)):
n = Note(self._chord[e]) * (voicing[e] + 1)
chord.append(n)
return chord

def get_frequencies(self, voicing=None):
def get_frequencies(self, voicing: list =None) -> list:
""" Returns the list of the frequencies of the chord. List can contain
frequencies of multiple octaves. Optional: An list of octave numbers
can be provided. List must be of equal length to the chord and
transposes the corresponding chord note into the given octave. """
td = semitone_distances[self._temperament_length]
chord = self.get_chord()
if voicing is None:
voicing = [4] * len(chord)
chord = self.get_chord(voicing)

freqs = []
for e in range(len(chord)):
freqs.append(self._scale.get_octaves()[
voicing[e]][td[chord[e][0]]])
for e in chord:
freqs.append(e.frequency)
return freqs

def __str__(self):
""" asdas """
return ", ".join(( self._scale.temperament.distance_to_name(x) for x in self._chord ))

if __name__ == '__main__':
pass
24 changes: 16 additions & 8 deletions pymusictheory/converter.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
#!/usr/bin/env python3

import re
from .definitions import *

##TODO: move to temperament

def distance_to_note(semitone_distance: dict):
reverse_semitone_distance = dict()
for k in semitone_distance:
if semitone_distance[k] not in reverse_semitone_distance:
reverse_semitone_distance[semitone_distance[k]] = [k]
else:
reverse_semitone_distance[semitone_distance[k]].append(k)
return reverse_semitone_distance
#def distance_to_note(semitone_distance: dict):
# reverse_semitone_distance = dict()
# for k in semitone_distance:
# if semitone_distance[k] not in reverse_semitone_distance:
# reverse_semitone_distance[semitone_distance[k]] = [k]
# else:
# reverse_semitone_distance[semitone_distance[k]].append(k)
# return reverse_semitone_distance

#def split_SPN(spn: str):
# match = re.match(r"([abcdefg][b#]?)([0-9])", spn.lower(), re.I)
# if match:
# return match.groups()
# else:
# raise ValueError('Bad note name. Note name in SPN required')

if __name__ == "__main__":
pass
66 changes: 40 additions & 26 deletions pymusictheory/definitions.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,57 @@
#!/usr/bin/env python3

from fractions import Fraction
import re


# definition of temperaments
class TwelveTET():
_len = 12
_semitone_distance = {
'a' : 9,
'a#': 10,
'bb': 10,
'b' : 11,
'c' : 0,
'c#': 1,
'db': 1,
'd' : 2,
'd#': 3,
'eb': 3,
'e' : 4,
'f' : 5,
'f#': 6,
'gb': 6,
'g' : 7,
'g#': 8,
'ab': 8
}

def __init__(self):
pass
reverse_semitone_distance = dict()
for k in self._semitone_distance:
j = self._semitone_distance[k]
try:
reverse_semitone_distance[j].append(k)
except KeyError:
reverse_semitone_distance[j] = [k]
self._reverse_semitone_distance = reverse_semitone_distance

def __len__(self):
return self._len

def get_note(self,root,distance,precision=2):
return round(root * ( (2**Fraction(1,12)) **distance),precision)
return round(root * ( (2**Fraction(1,self._len)) **distance),precision)

def distance_to_name(self, distance):
""" Translates any note distance to the number of the pitch class (int) """
return self._reverse_semitone_distance[distance]

def name_to_distance(self,name):
if name in self._semitone_distance:
return self._semitone_distance[name]
else:
raise ValueError(f'Bad note name "{name}". Note name of pitchclass required')

@property
def length(self):
Expand All @@ -27,29 +65,5 @@ def init_temperament():

init_temperament()

# note to semi tone distance to A
semitone_distances = {
12: {
'a' : 9,
'a#': 10,
'bb': 10,
'b' : 11,
'c' : 0,
'c#': 1,
'db': 1,
'd' : 2,
'd#': 3,
'eb': 3,
'e' : 4,
'f' : 5,
'f#': 6,
'gb': 6,
'g' : 7,
'g#': 8,
'ab': 8
}
}


if __name__ == "__main__":
pass
30 changes: 27 additions & 3 deletions pymusictheory/notes.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#!/usr/bin/env python3

from functools import singledispatchmethod
import re

from .definitions import *
from .scales import ChromaticScale
from .converter import distance_to_note


class Note:
Expand Down Expand Up @@ -164,9 +164,33 @@ def frequency(self):
class PitchClass:

def __init__(self, note, chromaticscale=ChromaticScale()):
""" Creates the PitchClass containing 'note' """
pass
""" Creates the PitchClass containing 'note'; 'note' can be any of SPN,
distance to C0, PitchClass numeric (c=0,...) or frequency """
try:
n = Note(note)
except ValueError:
# if note name and not SPN is provided...
n = Note("".join((note,'0')))

self._chromaticscale = chromaticscale

self._pc_numeric = n.distance % chromaticscale.temperament.length
self._pc_name = chromaticscale.split_SPN(n.name)[0]
pc = list()
octaves = chromaticscale.get_octaves()
for octave in octaves:
pc.append(Note(self._pc_numeric)*(octave+1))
self._pc = pc


def __iter__(self):
return self._pc.__iter__()

def __str__(self):
return str(", ".join((str(x) for x in self._pc)))

def __getitem__(self,n):
return self._pc[n]

if __name__ == '__main__':
pass
Loading

0 comments on commit 64cc1c5

Please sign in to comment.