#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ """ This file handles various pitches and their conversions. """ 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 STEP = 50 MAX = 3140 MIN = 0 #Without a keysignature 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:int)->int: """Return the octave of given note. Lowest 0 is X,,,""" return divmod(pitch, 350)[0] 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: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 other side again.""" #1420 = c', 1520 = e', 1620 = g' #1420 to 1520 is a third so the mirror would be a fifth. #1420 + 2 * (1520 - 1420) #1420 + 2 * 100 #1420 + 200 = 1620 return pitch + 2 * (axis - 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:int)->int: """Like diatonicIndex but works from pitch 20 which gets index 0 middle c is 28 tuning a is 33 these two are indeed 5 steps apart (not traditional interval steps, real step counting from 1)""" return divmod(toWhite[pitch], 50)[0] 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:int, octave:int)->int: """supports indices from - to +. """ while index < 0: index += 7 #plus one octave. index -1 becomes b octave -= 1 while index > 6: index -= 7 #minus one octave. index 7 becomes c again. octave += 1 return toOctave(index * 50 + 20, octave) #0 is cesces, 20 is c 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. Also it will never report anything over an octave since it uses diatonicIndex()""" assert root <= pitch if root == pitch: return 0 else: r = diatonicIndex(root) p = diatonicIndex(pitch) if plain(root) > plain(pitch): #we have an octave break g' to d'' p += 7 return p - r 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: return cache_fromMidi[(midipitch, keysig)] else: midioctave, pitch = divmod(midipitch, 12) table = [ 20, #c 60, #des 70, #d 110, #ees 120, #e 170, #f 180, #fis 220, #g 260, #aas 270, #a 310, #bes 320, #b / h ] #Sample note Gis/Aes in D Major should become Gis, which is the same as Fis in C Maj. midiRoot = toMidi[keysig.root] - 12 #in D Major thats 2 (halftone steps away from C) simpleConverted = toOctave(table[pitch], midioctave -1) #in D Maj it is still Aes fromC = halfToneDistanceFromC(simpleConverted) soundingDistanceToKeysigRoot = fromC - midiRoot #8 half tone steps - 2 from root = 6 (Tritonus) #We now need to know how the 6 steps / tritonus look like in the current keysignature/root enviroment. This is told by the table above. pitchInCMaj = table[soundingDistanceToKeysigRoot] #fis #Interval between keysig root and c. plainInterval, intervalOctaveOffset = interval(20, keysig.root) newInterval = (plainInterval, midioctave -1 ) #tuplets are immutable #Transpose it by this interval pitchInOriginalKey = intervalUp(pitchInCMaj, newInterval, midiIn = True) #Back to the correct Octave. This is what we wanted. #bugfix/workaround. 320 b/h in keysigs <= Bes Major is "ces", but somehow one octave to low. Compensate here. if pillarOfFifth.index(keysig.root) <= 13 and pitch == 11: returnValue = pitchInOriginalKey + 350 else: returnValue = pitchInOriginalKey cache_fromMidi[(midipitch, keysig)] = returnValue return returnValue def halfToneDistanceFromC(pitch:int)->int: """Return the half-tone step distance from C. The "sounding" interval""" return { #00 : 10, # ceses,,, -> bes #10 : 11, # ces,,, -> b 00 : -2, # ceses,,, -> bes 10 : -1, # ces,,, -> b 20 : 0, # c,,, 30 : 1, # cis,,, 40 : 2, # cisis,,, -> d ... 50 : 0, # deses,,, 60 : 1, # des,,, 70 : 2, # d,,, 80 : 3, # dis,,, 90 : 4, # disis,,, 100 : 2, # eeses,,, 110 : 3, # ees,,, 120 : 4, # e,,, 130 : 5, # eis,,, 140 : 6, # eisis,,, 150 : 3, # feses,,, 160 : 4, # fes,,, 170 : 5, # f,,, 180 : 6, # fis,,, 190 : 7, # fisis,,, 200 : 5, # geses,,, 210 : 6, # ges,,, 220 : 7, # g,,, 230 : 8, # gis,,, 240 : 9, # gisis,,, 250 : 7, # aeses,,, 260 : 8, # aes,,, 270 : 9, # a,,, 280 : 10, # ais,,, 290 : 11, # aisis,,, 300 : 9, # beses,,, 310 : 10, # bes,,, 320 : 11, # b,,, 330 : 12, # bis,,, 340 : 13, # bisis,,, #330 : 0, # bis,,, #340 : 1, # bisis,,, }[plain(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? return sharper else: return pitch #too sharp, do nothing. def flatten(pitch:int)->int: """Flatten the pitch until double flat""" flatter = pitch - 10 if toWhite[flatter] == toWhite[pitch]: #still the same base note? return flatter else: return pitch #too flat, do nothing. 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. x[1] = octave in between. Octave is always >= 0 because an interval has no direction. Just a base note and the other note, which is higher per definition""" if pitch1 > pitch2: #bring the notes in right order. We want to calculate from higher to lower return (pillarOfFifth.index(plain(pitch1)) - pillarOfFifth.index(plain(pitch2)), octave(pitch1 - pitch2)) else: return (pillarOfFifth.index(plain(pitch2)) - pillarOfFifth.index(plain(pitch1)), octave(pitch2 - pitch1)) 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)) targetPitch = pillarOfFifth[indexNumber + interval[0]] + octv*350 #return to the old octave if not midiIn and targetPitch < pitch: #the new note is lower than where we started. This is wrong. +1 octave!. Reason: it was the break between two octaves. targetPitch += 350 targetPitch += interval[1]*350 return targetPitch 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,,""" octv = octave(pitch) indexNumber = pillarOfFifth.index(plain(pitch)) targetPitch = pillarOfFifth[indexNumber - interval[0]] + octv*350 #return to the old octave. if targetPitch > pitch: #the new note is higher than where we started. This is wrong. -1 octave!. Reason: it was the break between two octaves. targetPitch -= 350 targetPitch -= interval[1]*350 return 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) else: #rootPitch < targetPitch return intervalUp(originalPitch, iv) #With a Key Signature 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: return cache_toScale[(pitch, keysig)] else: workingcopy = list(keysig.keysigList) workingcopy.sort() # sort first. mod = workingcopy[tonalDistanceFromC[pitch]] # tonalDistanceFromC has the same syntax as the keysig step/list position. mod becomes the (step, value) tuplet value = toWhite[pitch] + mod[1] cache_toScale[(pitch, keysig)] = value return value 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.""" return pitch - toScale(pitch, keysig) #Ordered pitches in fifths. #To calculate real and correct intervals you need the pillar of fifth with 35 steps for each of the 31 realistic notenames (and 4 unrealistic ones)""" pillarOfFifth = [ #pillarOfFifth.index(260) -> 11 150, #feses 0 0, #ceses 1 200, #geses 2 50, #deses 3 250, #aeses 4 100, #eeses 5 300, #beses 6 160, #fes 7 10, #ces 8 210, #ges 9 60, #des 10 260, #aes 11 110, #ees 12 310, #bes 13 170, #f 14 20, #c 15 220, #g 16 70, #d 17 270, #a 18 120, #e 19 320, #b 20 180, #fis 21 30, #cis 22 230, #gis 23 80, #dis 24 280, #ais 25 130, #eis 26 330, #bis 27 190, #fisis 28 40, #cisis 29 240, #gisis 30 90, #disis 31 290, #aisis 32 140, #eisis 33 340, #bisis 34 ] 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 elif pitch + transpose > 127: logger.warning(f"Tranpose lead to a note above midi value 127: {pitch}. Limiting to 127. Please fix manually") return 127 else: return pitch + transpose 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)) return 15 elif value <0: logger.warning("Midi Channel smaller 0 detected: {}. Limiting to 0. Please fix manually".format(value)) return 0 else: return value #The table to convert internal pitches into lilypond and back. #0 - A tone humans cannot hear anymore. #1420 - "Middle" c' #2130 - Soprano-Singers high C #3150 - Goes beyond the range of a modern piano #+inf.0 - A rest #<10 is reserved for microtones, in the future. #+10 One accidental up jumps over to the next note after cisis #+50 One diatonic step, preserve accidentals #+350 One Octave ly2pitch = { "ceses,,," : 00, "ces,,," : 10, "c,,," : 20, "cis,,," : 30, "cisis,,," : 40, "deses,,," : 50, "des,,," : 60, "d,,," : 70, "dis,,," : 80, "disis,,," : 90, "eeses,,," : 100, "ees,,," : 110, "e,,," : 120, "eis,,," : 130, "eisis,,," : 140, "feses,,," : 150, "fes,,," : 160, "f,,," : 170, "fis,,," : 180, "fisis,,," : 190, "geses,,," : 200, "ges,,," : 210, "g,,," : 220, "gis,,," : 230, "gisis,,," : 240, "aeses,,," : 250, "aes,,," : 260, "a,,," : 270, "ais,,," : 280, "aisis,,," : 290, "beses,,," : 300, "bes,,," : 310, "b,,," : 320, "bis,,," : 330, "bisis,,," : 340, "ceses,," : 350, "ces,," : 360, "c,," : 370, "cis,," : 380, "cisis,," : 390, "deses,," : 400, "des,," : 410, "d,," : 420, "dis,," : 430, "disis,," : 440, "eeses,," : 450, "ees,," : 460, "e,," : 470, "eis,," : 480, "eisis,," : 490, "feses,," : 500, "fes,," : 510, "f,," : 520, "fis,," : 530, "fisis,," : 540, "geses,," : 550, "ges,," : 560, "g,," : 570, "gis,," : 580, "gisis,," : 590, "aeses,," : 600, "aes,," : 610, "a,," : 620, "ais,," : 630, "aisis,," : 640, "beses,," : 650, "bes,," : 660, "b,," : 670, "bis,," : 680, "bisis,," : 690, "ceses," : 700, "ces," : 710, "c," : 720, "cis," : 730, "cisis," : 740, "deses," : 750, "des," : 760, "d," : 770, "dis," : 780, "disis," : 790, "eeses," : 800, "ees," : 810, "e," : 820, "eis," : 830, "eisis," : 840, "feses," : 850, "fes," : 860, "f," : 870, "fis," : 880, "fisis," : 890, "geses," : 900, "ges," : 910, "g," : 920, "gis," : 930, "gisis," : 940, "aeses," : 950, "aes," : 960, "a," : 970, "ais," : 980, "aisis," : 990, "beses," : 1000, "bes," : 1010, "b," : 1020, "bis," : 1030, "bisis," : 1040, "ceses" : 1050, "ces" : 1060, "c" : 1070, "cis" : 1080, "cisis" : 1090, "deses" : 1100, "des" : 1110, "d" : 1120, "dis" : 1130, "disis" : 1140, "eeses" : 1150, "ees" : 1160, "e" : 1170, "eis" : 1180, "eisis" : 1190, "feses" : 1200, "fes" : 1210, "f" : 1220, "fis" : 1230, "fisis" : 1240, "geses" : 1250, "ges" : 1260, "g" : 1270, "gis" : 1280, "gisis" : 1290, "aeses" : 1300, "aes" : 1310, "a" : 1320, "ais" : 1330, "aisis" : 1340, "beses" : 1350, "bes" : 1360, "b" : 1370, "bis" : 1380, "bisis" : 1390, "ceses'" : 1400, "ces'" : 1410, "c'" : 1420, "cis'" : 1430, "cisis'" : 1440, "deses'" : 1450, "des'" : 1460, "d'" : 1470, "dis'" : 1480, "disis'" : 1490, "eeses'" : 1500, "ees'" : 1510, "e'" : 1520, "eis'" : 1530, "eisis'" : 1540, "feses'" : 1550, "fes'" : 1560, "f'" : 1570, "fis'" : 1580, "fisis'" : 1590, "geses'" : 1600, "ges'" : 1610, "g'" : 1620, "gis'" : 1630, "gisis'" : 1640, "aeses'" : 1650, "aes'" : 1660, "a'" : 1670, "ais'" : 1680, "aisis'" : 1690, "beses'" : 1700, "bes'" : 1710, "b'" : 1720, "bis'" : 1730, "bisis'" : 1740, "ceses''" : 1750, "ces''" : 1760, "c''" : 1770, "cis''" : 1780, "cisis''" : 1790, "deses''" : 1800, "des''" : 1810, "d''" : 1820, "dis''" : 1830, "disis''" : 1840, "eeses''" : 1850, "ees''" : 1860, "e''" : 1870, "eis''" : 1880, "eisis''" : 1890, "feses''" : 1900, "fes''" : 1910, "f''" : 1920, "fis''" : 1930, "fisis''" : 1940, "geses''" : 1950, "ges''" : 1960, "g''" : 1970, "gis''" : 1980, "gisis''" : 1990, "aeses''" : 2000, "aes''" : 2010, "a''" : 2020, "ais''" : 2030, "aisis''" : 2040, "beses''" : 2050, "bes''" : 2060, "b''" : 2070, "bis''" : 2080, "bisis''" : 2090, "ceses'''" : 2100, "ces'''" : 2110, "c'''" : 2120, "cis'''" : 2130, "cisis'''" : 2140, "deses'''" : 2150, "des'''" : 2160, "d'''" : 2170, "dis'''" : 2180, "disis'''" : 2190, "eeses'''" : 2200, "ees'''" : 2210, "e'''" : 2220, "eis'''" : 2230, "eisis'''" : 2240, "feses'''" : 2250, "fes'''" : 2260, "f'''" : 2270, "fis'''" : 2280, "fisis'''" : 2290, "geses'''" : 2300, "ges'''" : 2310, "g'''" : 2320, "gis'''" : 2330, "gisis'''" : 2340, "aeses'''" : 2350, "aes'''" : 2360, "a'''" : 2370, "ais'''" : 2380, "aisis'''" : 2390, "beses'''" : 2400, "bes'''" : 2410, "b'''" : 2420, "bis'''" : 2430, "bisis'''" : 2440, "ceses''''" : 2450, "ces''''" : 2460, "c''''" : 2470, "cis''''" : 2480, "cisis''''" : 2490, "deses''''" : 2500, "des''''" : 2510, "d''''" : 2520, "dis''''" : 2530, "disis''''" : 2540, "eeses''''" : 2550, "ees''''" : 2560, "e''''" : 2570, "eis''''" : 2580, "eisis''''" : 2590, "feses''''" : 2600, "fes''''" : 2610, "f''''" : 2620, "fis''''" : 2630, "fisis''''" : 2640, "geses''''" : 2650, "ges''''" : 2660, "g''''" : 2670, "gis''''" : 2680, "gisis''''" : 2690, "aeses''''" : 2700, "aes''''" : 2710, "a''''" : 2720, "ais''''" : 2730, "aisis''''" : 2740, "beses''''" : 2750, "bes''''" : 2760, "b''''" : 2770, "bis''''" : 2780, "bisis''''" : 2790, "ceses'''''" : 2800, "ces'''''" : 2810, "c'''''" : 2820, "cis'''''" : 2830, "cisis'''''" : 2840, "deses'''''" : 2850, "des'''''" : 2860, "d'''''" : 2870, "dis'''''" : 2880, "disis'''''" : 2890, "eeses'''''" : 2900, "ees'''''" : 2910, "e'''''" : 2920, "eis'''''" : 2930, "eisis'''''" : 2940, "feses'''''" : 2950, "fes'''''" : 2960, "f'''''" : 2970, "fis'''''" : 2980, "fisis'''''" : 2990, "geses'''''" : 3000, "ges'''''" : 3010, "g'''''" : 3020, "gis'''''" : 3030, "gisis'''''" : 3040, "aeses'''''" : 3050, "aes'''''" : 3060, "a'''''" : 3070, "ais'''''" : 3080, "aisis'''''" : 3090, "beses'''''" : 3100, "bes'''''" : 3110, "b'''''" : 3120, "bis'''''" : 3130, "bisis'''''" : 3140, #"r" : float('inf'), a rest is not a pitch } sortedNoteNameList = [ "ceses,,," , "ces,,," , "c,,," , "cis,,," , "cisis,,," , "deses,,," , "des,,," , "d,,," , "dis,,," , "disis,,," , "eeses,,," , "ees,,," , "e,,," , "eis,,," , "eisis,,," , "feses,,," , "fes,,," , "f,,," , "fis,,," , "fisis,,," , "geses,,," , "ges,,," , "g,,," , "gis,,," , "gisis,,," , "aeses,,," , "aes,,," , "a,,," , "ais,,," , "aisis,,," , "beses,,," , "bes,,," , "b,,," , "bis,,," , "bisis,,," , "ceses,," , "ces,," , "c,," , "cis,," , "cisis,," , "deses,," , "des,," , "d,," , "dis,," , "disis,," , "eeses,," , "ees,," , "e,," , "eis,," , "eisis,," , "feses,," , "fes,," , "f,," , "fis,," , "fisis,," , "geses,," , "ges,," , "g,," , "gis,," , "gisis,," , "aeses,," , "aes,," , "a,," , "ais,," , "aisis,," , "beses,," , "bes,," , "b,," , "bis,," , "bisis,," , "ceses," , "ces," , "c," , "cis," , "cisis," , "deses," , "des," , "d," , "dis," , "disis," , "eeses," , "ees," , "e," , "eis," , "eisis," , "feses," , "fes," , "f," , "fis," , "fisis," , "geses," , "ges," , "g," , "gis," , "gisis," , "aeses," , "aes," , "a," , "ais," , "aisis," , "beses," , "bes," , "b," , "bis," , "bisis," , "ceses" , "ces" , "c" , "cis" , "cisis" , "deses" , "des" , "d" , "dis" , "disis" , "eeses" , "ees" , "e" , "eis" , "eisis" , "feses" , "fes" , "f" , "fis" , "fisis" , "geses" , "ges" , "g" , "gis" , "gisis" , "aeses" , "aes" , "a" , "ais" , "aisis" , "beses" , "bes" , "b" , "bis" , "bisis" , "ceses'" , "ces'" , "c'" , "cis'" , "cisis'" , "deses'" , "des'" , "d'" , "dis'" , "disis'" , "eeses'" , "ees'" , "e'" , "eis'" , "eisis'" , "feses'" , "fes'" , "f'" , "fis'" , "fisis'" , "geses'" , "ges'" , "g'" , "gis'" , "gisis'" , "aeses'" , "aes'" , "a'" , "ais'" , "aisis'" , "beses'" , "bes'" , "b'" , "bis'" , "bisis'" , "ceses''" , "ces''" , "c''" , "cis''" , "cisis''" , "deses''" , "des''" , "d''" , "dis''" , "disis''" , "eeses''" , "ees''" , "e''" , "eis''" , "eisis''" , "feses''" , "fes''" , "f''" , "fis''" , "fisis''" , "geses''" , "ges''" , "g''" , "gis''" , "gisis''" , "aeses''" , "aes''" , "a''" , "ais''" , "aisis''" , "beses''" , "bes''" , "b''" , "bis''" , "bisis''" , "ceses'''" , "ces'''" , "c'''" , "cis'''" , "cisis'''" , "deses'''" , "des'''" , "d'''" , "dis'''" , "disis'''" , "eeses'''" , "ees'''" , "e'''" , "eis'''" , "eisis'''" , "feses'''" , "fes'''" , "f'''" , "fis'''" , "fisis'''" , "geses'''" , "ges'''" , "g'''" , "gis'''" , "gisis'''" , "aeses'''" , "aes'''" , "a'''" , "ais'''" , "aisis'''" , "beses'''" , "bes'''" , "b'''" , "bis'''" , "bisis'''" , "ceses''''" , "ces''''" , "c''''" , "cis''''" , "cisis''''" , "deses''''" , "des''''" , "d''''" , "dis''''" , "disis''''" , "eeses''''" , "ees''''" , "e''''" , "eis''''" , "eisis''''" , "feses''''" , "fes''''" , "f''''" , "fis''''" , "fisis''''" , "geses''''" , "ges''''" , "g''''" , "gis''''" , "gisis''''" , "aeses''''" , "aes''''" , "a''''" , "ais''''" , "aisis''''" , "beses''''" , "bes''''" , "b''''" , "bis''''" , "bisis''''" , "ceses'''''" , "ces'''''" , "c'''''" , "cis'''''" , "cisis'''''" , "deses'''''" , "des'''''" , "d'''''" , "dis'''''" , "disis'''''" , "eeses'''''" , "ees'''''" , "e'''''" , "eis'''''" , "eisis'''''" , "feses'''''" , "fes'''''" , "f'''''" , "fis'''''" , "fisis'''''" , "geses'''''" , "ges'''''" , "g'''''" , "gis'''''" , "gisis'''''" , "aeses'''''" , "aes'''''" , "a'''''" , "ais'''''" , "aisis'''''" , "beses'''''" , "bes'''''" , "b'''''" , "bis'''''" , "bisis'''''" , ] baseNotesToBaseNames = { 20 : "C", 70 : "D", 120 : "E", 170 : "F", 220 : "G", 270 : "A", 320 : "B/H", 20-10 : "Ces", 70-10 : "Des", 120-10 : "Ees", 170-10 : "Fes", 220-10 : "Ges", 270-10 : "Aes", 320-10 : "Bes/Bb", 20+10 : "Cis", 70+10 : "Dis", 120+10 : "Eis", 170+10 : "Fis", 220+10 : "Gis", 270+10 : "Ais", 320+10 : "Bis/His", } orderedBaseNotes = ["C", "D", "E", "F", "G", "A", "H/B"] #Basic notes. For example to use as parameter for key-signatures in the API. #P for Pitch P_C = 20 P_D = 70 P_E = 120 P_F = 170 P_G = 220 P_B = P_H = 270 #This is a funny historical coincidence. The s was used as sharp-sign before. Which lead to people pronouncing Cs "Cis". P_Cs = 20 + 10 P_Ds = 70 + 10 P_Es = 120 + 10 P_Fs = 170 + 10 P_Gs = 220 + 10 P_Bs = P_Hs = 270 + 10 P_Cb = 20 - 10 P_Db = 70 - 10 P_Eb = 120 - 10 P_Fb = 170 - 10 P_Gb = 220 - 10 P_Bb = P_Hb = 270 - 10 #Generate some tables and other cached dicts on startup #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: 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 distanceInDiatonicStepsFrom1720 = {} #filled for all pitches on startup, below. Utilized by item.asDotOnLine for pitch in ly2pitch.values(): toWhite[pitch] = divmod(pitch, 50)[0] * 50 + 20 octOffset = (octave(pitch) +1) * 12 #twelve tones per midi octave toMidi[pitch] = octOffset + halfToneDistanceFromC(pitch) tonalDistanceFromC[pitch] = divmod(plain(toWhite[pitch]), 50)[0] #second round for dependencies on finished pitchmath for pitch in ly2pitch.values(): distanceInDiatonicStepsFrom1720[pitch] = distanceInDiatonicSteps(1720, pitch) #offset from the middle line in treble clef h', which is 0. c'' is -1, a' is +1 #Lists of Notenames. Maps midi to different note systems #These are simple fixed lists without root notes or key signatures. For a real conversion use pitch.fromMidi(root, keysig) midi_notenames_english = [] for _midipitch in range(128): _octave, _pitch = divmod(_midipitch, 12) midi_notenames_english.append("{}{}".format("C Db D Eb E F F# G Ab A Bb B".split()[_pitch] ,_octave-1)) midi_notenames_german = [] for _midipitch in range(128): _octave, _pitch = divmod(_midipitch, 12) midi_notenames_german.append("{}{}".format("C Des D Es E F Fis G As A B H".split()[_pitch] ,_octave-1)) #midi and sfz note names are normal midi notes again, but this time we need a dict because both Eb and D# are in there. c4 is middle. _alt = { "db" : "c#", "eb" : "d#", "f#" : "gb", "ab" : "g#", "bb" : "a#", } midiName2midiPitch = {} for _midipitch in range(128): #0-127 incl. _octave, _pitch = divmod(_midipitch, 12) _octave -= 1 notename = "c db d eb e f f# g ab a bb b".split()[_pitch] sfz_name1 = "{}{}".format(notename ,_octave) midiName2midiPitch[sfz_name1] = _midipitch if notename in ("db", "eb", "f#", "ab", "bb"): #Add the other variant sfz_name2 = "{}{}".format(_alt[notename] ,_octave) midiName2midiPitch[sfz_name2] = _midipitch assert midiName2midiPitch["db4"] == 61, midiName2midiPitch["db4"] assert midiName2midiPitch["db4"] == midiName2midiPitch["c#4"], midiName2midiPitch["c#4"] """midi_notenames_lilypond = [] for _midipitch in range(128): _octave, _pitch = divmod(_midipitch, 12) midi_notenames_lilypond.append("{}{}".format("C Des D Ees E F Fis G Aes A Bes B".split()[_pitch] ,_octave-1)) """ #60 is C' t = """ C,,,, Des,,,, D,,,, Ees,,,, E,,,, F,,,, Fis,,,, G,,,, Aes,,,, A,,,, Bes,,,, B,,,, C,,, Des,,, D,,, Ees,,, E,,, F,,, Fis,,, G,,, Aes,,, A,,, Bes,,, B,,, C,, Des,, D,, Ees,, E,, F,, Fis,, G,, Aes,, A,, Bes,, B,, C, Des, D, Ees, E, F, Fis, G, Aes, A, Bes, B, C Des D Ees E F Fis G Aes A Bes B C' Des' D' Ees' E' F' Fis' G' Aes' A' Bes' B' C'' Des'' D'' Ees'' E'' F'' Fis'' G'' Aes'' A'' Bes'' B'' C''' Des''' D''' Ees''' E''' F''' Fis''' G''' Aes''' A''' Bes''' B''' C'''' Des'''' D'''' Ees'''' E'''' F'''' Fis'''' G'''' Aes'''' A'''' Bes'''' B'''' C''''' Des''''' D''''' Ees''''' E''''' F''''' Fis''''' G''''' Aes''''' A''''' Bes''''' B''''' C'''''' Des'''''' D'''''' Ees'''''' E'''''' F'''''' Fis'''''' G'''''' """ midi_notenames_lilypond = t.split() midi_notenames_gm_drums = [str(i) for i in range(0,35)] + [ "Acoustic BD", "Bass Drum 1", "Side Stick", "Acoustic Snare", "Hand Clap", "Electric Snare", "Low Floor Tom", "Closed Hi Hat", "High Floor Tom", "Pedal Hi-Hat", "Low Tom", "Open Hi-Hat", "Low-Mid Tom", "Hi-Mid Tom", "Crash Cymbal 1", "High Tom", "Ride Cymbal 1", "Chinese Cymbal", "Ride Bell", "Tambourine", "Splash Cymbal", "Cowbell", "Crash Cymbal 2", "Vibraslap", "Ride Cymbal 2", "Hi Bongo", "Low Bongo", "Mute Hi Conga", "Open Hi Conga", "Low Conga", "High Timbale", "Low Timbale", "High Agogo", "Low Agogo", "Cabasa", "Maracas", "Short Whistle", "Long Whistle", "Short Guiro", "Long Guiro", "Claves", "Hi Wood Block", "Low Wood Block", "Mute Cuica", "Open Cuica", "Mute Triangle", "Open Triangle", ] + [str(i) for i in range(87,128)] def _defaultSimpleNoteNames()->list: return midi_notenames_english simpleNoteNames:DefaultDict[str,list] = defaultdict(_defaultSimpleNoteNames) simpleNoteNames["German"] = midi_notenames_german simpleNoteNames["English"] = midi_notenames_english simpleNoteNames["Lilypond"] = midi_notenames_lilypond simpleNoteNames["Drums GM"] = midi_notenames_gm_drums