@ -28,11 +28,18 @@ import logging; logger = logging.getLogger(__name__); logger.info("import")
#Standard Library
from collections import defaultdict
from typing import Dict , Tuple , List , DefaultDict
#Third Party Modules
#Template Modules
#Our modules
class KeySignature ( object ) :
""" For tests and typechecking. The real one is in Laborejo/engine/items.py """
def __init__ ( self , root , deviationFromMajorScale ) :
self . root : int = root
self . deviationFromMajorScale : List = deviationFromMajorScale
self . keysigList : tuple = tuple ( )
#Constants
OCTAVE = 350
@ -41,21 +48,21 @@ MAX = 3140
MIN = 0
#Without a keysignature
def plain ( pitch ) :
def plain ( pitch : int ) - > int :
""" Extract the note from a note-number, without any octave but with the tailing zero.
This means we double - use the lowest octave as abstract version . """
#Dividing through the octave, 350, results in the number of the octave and the note as remainder.
return divmod ( pitch , 350 ) [ 1 ]
def octave ( pitch ) :
def octave ( pitch : int ) - > int :
""" Return the octave of given note. Lowest 0 is X,,, """
return divmod ( pitch , 350 ) [ 0 ]
def toOctave ( pitch , octave ) :
def toOctave ( pitch : int , octave : int ) - > int :
""" Take a plain note and give the octave variant. Starts with 0 """
return pitch + octave * 350
def mirror ( pitch , axis ) :
def mirror ( pitch : int , axis : int ) - > int :
""" Calculate the distance between the pitch and the axis-pitch and
set the new pitch twice as far , which creates a mirror effect :
Half the distancen is object - > mirror and then mirror - > object on the
@ -68,12 +75,12 @@ def mirror(pitch, axis):
#1420 + 200 = 1620
return pitch + 2 * ( axis - pitch )
def diatonicIndex ( pitch ) :
def diatonicIndex ( pitch : int ) - > int :
""" Return an int between 0 and 6, resembling the diatonic position
of the given pitch without octave , accidentals . 0 is c """
return divmod ( plain ( toWhite [ pitch ] ) , 50 ) [ 0 ]
def absoluteDiatonicIndex ( pitch ) :
def absoluteDiatonicIndex ( pitch : int ) - > int :
""" Like diatonicIndex but works from pitch 20 which gets index 0
middle c is 28
tuning a is 33
@ -81,13 +88,13 @@ def absoluteDiatonicIndex(pitch):
( not traditional interval steps , real step counting from 1 ) """
return divmod ( toWhite [ pitch ] , 50 ) [ 0 ]
def distanceInDiatonicSteps ( first , second ) :
def distanceInDiatonicSteps ( first : int , second : int ) - > int :
""" root is a pitch like 1720. Pitch as well
Returns not a signed int . If the first is lower than the second
you get a negative return value . """
return absoluteDiatonicIndex ( first ) - absoluteDiatonicIndex ( second )
def diatonicIndexToPitch ( index , octave ) :
def diatonicIndexToPitch ( index : int , octave : int ) - > int :
""" supports indices from - to +. """
while index < 0 :
index + = 7 #plus one octave. index -1 becomes b
@ -97,7 +104,7 @@ def diatonicIndexToPitch(index, octave):
octave + = 1
return toOctave ( index * 50 + 20 , octave ) #0 is cesces, 20 is c
def upStepsFromRoot ( root , pitch ) :
def upStepsFromRoot ( root : int , pitch : int ) - > int :
""" Like diatonicIndex but assumes a different root than C. So it is:
' stepcount upward in white keys from root to pitch ' .
It is always assumed it should go up .
@ -113,7 +120,7 @@ def upStepsFromRoot(root, pitch):
p + = 7
return p - r
def fromMidi ( midipitch , keysig ) :
def fromMidi ( midipitch : int , keysig : KeySignature ) - > int :
""" Convert a midi pitch to internal pitch.
Nearest to pillar of fifth """
if ( midipitch , keysig ) in cache_fromMidi :
@ -162,7 +169,7 @@ def fromMidi(midipitch, keysig):
def halfToneDistanceFromC ( pitch ) :
def halfToneDistanceFromC ( pitch : int ) - > int :
""" Return the half-tone step distance from C. The " sounding " interval """
return {
#00 : 10, # ceses,,, -> bes
@ -208,7 +215,7 @@ def halfToneDistanceFromC(pitch):
} [ plain ( pitch ) ]
def sharpen ( pitch ) :
def sharpen ( pitch : int ) - > int :
""" Sharpen the pitch until double crossed """
sharper = pitch + 10
if toWhite [ sharper ] == toWhite [ pitch ] : #still the same base note?
@ -216,7 +223,7 @@ def sharpen(pitch):
else :
return pitch #too sharp, do nothing.
def flatten ( pitch ) :
def flatten ( pitch : int ) - > int :
""" Flatten the pitch until double flat """
flatter = pitch - 10
if toWhite [ flatter ] == toWhite [ pitch ] : #still the same base note?
@ -224,7 +231,7 @@ def flatten(pitch):
else :
return pitch #too flat, do nothing.
def interval ( pitch1 , pitch2 ) :
def interval ( pitch1 : int , pitch2 : int ) - > Tuple [ int , int ] :
""" Return the distance between two pitches as steps in the pillar of fifths.
Intervals are tuplets with two members x = 1 , 0 #fifth in the same octave
x [ 0 ] = interval . Steps in the pillar of fifths .
@ -235,7 +242,7 @@ def interval(pitch1, pitch2):
else :
return ( pillarOfFifth . index ( plain ( pitch2 ) ) - pillarOfFifth . index ( plain ( pitch1 ) ) , octave ( pitch2 - pitch1 ) )
def intervalUp ( pitch , interval , midiIn = False ) :
def intervalUp ( pitch : int , interval : Tuple [ int , int ] , midiIn : bool = False ) - > int :
""" Return a pitch which is _interval_ higher than the given pitch """
octv = octave ( pitch )
indexNumber = pillarOfFifth . index ( plain ( pitch ) )
@ -245,7 +252,7 @@ def intervalUp(pitch, interval, midiIn = False):
targetPitch + = interval [ 1 ] * 350
return targetPitch
def intervalDown ( pitch , interval ) :
def intervalDown ( pitch : int , interval : Tuple [ int , int ] ) - > int :
""" Return a pitch which is _interval_ lower than the given pitch
intervalUp ( 20 , ( - 12 , 1 ) ) #c,,, to deses,,"""
@ -257,18 +264,18 @@ def intervalDown(pitch, interval):
targetPitch - = interval [ 1 ] * 350
return targetPitch
def intervalAutomatic ( originalPitch , rootPitch , targetPitch ) :
def intervalAutomatic ( originalPitch : int , rootPitch : int , targetPitch : int ) - > int :
""" Return the original pitch transposed by the interval
between rootPitch and targetPitch """
iv = interval ( rootPitch , targetPitch )
if rootPitch > = targetPitch :
return intervalDown ( originalPitch , iv )
elif rootPitch < targetPitch :
else : #rootPitch < targetPitch
return intervalUp ( originalPitch , iv )
#With a Key Signature
def toScale ( pitch , keysig ) :
def toScale ( pitch : int , keysig : KeySignature ) - > int :
""" Return a pitch which is the in-scale variant of the given one.
Needs a Key Signature as second parameter """
if ( pitch , keysig ) in cache_toScale :
@ -281,7 +288,7 @@ def toScale(pitch, keysig):
cache_toScale [ ( pitch , keysig ) ] = value
return value
def diffToKey ( pitch , keysig ) :
def diffToKey ( pitch : int , keysig : KeySignature ) - > int :
""" Return if a note is natural, sharp, flat etc.
Same syntax as Key Signature :
- 20 double flat , 0 natural , + 20 d - sharp . """
@ -329,7 +336,7 @@ pillarOfFifth = [
]
def midiPitchLimiter ( pitch , transpose ) :
def midiPitchLimiter ( pitch : int , transpose : int ) - > int :
if pitch + transpose < 0 :
logger . warning ( f " Tranpose lead to a note below midi value 0: { pitch } . Limiting to 0. Please fix manually " )
return 0
@ -339,7 +346,7 @@ def midiPitchLimiter(pitch, transpose):
else :
return pitch + transpose
def midiChannelLimiter ( value ) :
def midiChannelLimiter ( value : int ) - > int :
""" makes sure that a midi channel is in range 0-15 """
if value > 15 :
logger . warning ( " Midi Channel bigger 15 detected: {} . Limiting to 15. Please fix manually " . format ( value ) )
@ -1057,8 +1064,8 @@ P_Bb = P_Hb = 270 - 10
#These are mostly values that are called multiple thousand times per track for every callback
pitch2ly = dict ( ( ly2pitch [ k ] , k ) for k in ly2pitch )
cache_toScale = { } #filled dynamically
cache_fromMidi = { } #filled dynamically
cache_toScale : Dict [ Tuple [ int , KeySignature ] , int ] = { } #filled dynamically. int is a pitch, object is a keysignature. value is pitch
cache_fromMidi : Dict [ Tuple [ int , KeySignature ] , int ] = { } #filled dynamically midipitch:keysig . type is (midipitch, keysig) : internal pitch
toMidi = { } #filled for all pitches on startup, below
toWhite = { } #filled for all pitches on startup, below
tonalDistanceFromC = { } #filled for all pitches on startup, below
@ -1161,10 +1168,10 @@ midi_notenames_gm_drums = [str(i) for i in range(0,35)] + [
] + [ str ( i ) for i in range ( 87 , 128 ) ]
def _defaultSimpleNoteNames ( ) :
def _defaultSimpleNoteNames ( ) - > list :
return midi_notenames_english
simpleNoteNames = defaultdict ( _defaultSimpleNoteNames )
simpleNoteNames : DefaultDict [ str , list ] = defaultdict ( _defaultSimpleNoteNames )
simpleNoteNames [ " German " ] = midi_notenames_german
simpleNoteNames [ " English " ] = midi_notenames_english
simpleNoteNames [ " Lilypond " ] = midi_notenames_lilypond