You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

1260 lines
31 KiB

#! /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 <http://www.gnu.org/licenses/>.
"""
"""
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'''''" ,
]
baseNotesToBaseLyNames = {
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",
20-20 : "Ceses",
70-20 : "Deses",
120-20 : "Eeses",
170-20 : "Feses",
220-20 : "Geses",
270-20 : "Aeses",
320-20 : "Beses",
20+10 : "Cis",
70+10 : "Dis",
120+10 : "Eis",
170+10 : "Fis",
220+10 : "Gis",
270+10 : "Ais",
320+10 : "Bis",
20+20 : "Cisis",
70+20 : "Disis",
120+20 : "Eisis",
170+20 : "Fisis",
220+20 : "Gisis",
270+20 : "Aisis",
320+20 : "Bisis",
}
baseNotesToAccidentalNames = {
20 : "C",
70 : "D",
120 : "E",
170 : "F",
220 : "G",
270 : "A",
320 : "B♮/H",
20-10 : "C♭",
70-10 : "D♭",
120-10 : "E♭",
170-10 : "F♭",
220-10 : "G♭",
270-10 : "A♭",
320-10 : "B♭/H♭",
20-20 : "C𝄫",
70-20 : "D𝄫",
120-20 : "E𝄫",
170-20 : "F𝄫",
220-20 : "G𝄫",
270-20 : "A𝄫",
320-20 : "B𝄫/H𝄫",
20+10 : "C♯",
70+10 : "D♯",
120+10 : "E♯",
170+10 : "F♯",
220+10 : "G♯",
270+10 : "A♯",
320+10 : "B♯/H♯",
20+20 : "C𝄪",
70+20 : "D𝄪",
120+20 : "E𝄪",
170+20 : "F𝄪",
220+20 : "G𝄪",
270+20 : "A𝄪",
320+20 : "B𝄪/H𝄪",
}
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