Dear community, this is so far my module for working with pitch-class sets. If You want to try it, You should save the 2 files in the same folder. I've added a snippet in Frescobaldi, which works fine (at least for me):
-*- python; import sys sys.path.append('/yourfolder/stefanspcs') import stefanspcs text = stefanspcs.get_prime_from_lily(text) text = stefanspcs.get_lilyfrom_prime(text) print(text) You can select some music (pitches only, no durations, and so on) and You will see the prime-form when You run Your the above qwoted In this way You can do some other things with this module, if You're interested. Best, Stefan Am Di., 20. Sept. 2022 um 21:31 Uhr schrieb Jean Abou Samra < j...@abou-samra.fr>: > > > Le 20/09/2022 à 16:27, Lukas-Fabian Moser a écrit : > > > > Hi Stefan, > > > > Am 20.09.22 um 13:00 schrieb Stefan Thomas: > >> Dear community, > >> sorry, it's a bit an off topic: If You are not interested in > >> pitch-class set theory, You don't have to read the following. > >> I've worked on a python module dealing with pitch-class set theory as > >> I've read in "The structure of atonal music" by Allen Forte. > >> I know that there already exists some modules like that but I want to > >> integrate it in lilypond. > >> I'm still working on this module, but at the moment I can do: > >> > >> * Getting the normal form and prime form of a pcs. > >> * Transposing and inverting a pcs > >> * Finding subsets of a pcs given in primeform. > >> > >> I'm working on: > >> > >> * Finding different kinds of similarity of pcs. > >> * Getting subcomplexes k and kh of a pcs. > >> * Finding primeform and so on, when pitches in lilypond-style are > >> given. > >> * Getting pitches in lilypond-style when pitches in midinote-nums > >> or as pcs are given. > >> > >> Let me know it if You are interested. Maybee it's only something of > >> interest for nerdy persons like me, but maybee for others too. > > > > I don't know if this is of use to you, but I created a bunch of > > LilyPond routines for dealing with PC sets (like in Forte, but also > > with a finer equivalence relation not identifying a set with its > > inverse) last year. I attach it unchanged, as I don't have time to > > clean it up at the moment; you'll probably want to uncomment some of > > the routines at the end to see what the functions do. > > > > I'm curious: You write that you are working on a Python module; how > > does this integrate in LilyPond? > > > > > What does the include file dodeka.ily contain? >
toene_notnums = { } toene_notnums["a,,,"]=21 toene_notnums["bes,,,"]=22 toene_notnums["ais,,,"]=22 toene_notnums["b,,"]=23 toene_notnums["bis,,"]=24 toene_notnums["his,,"]=24 toene_notnums["c,,"]=24 toene_notnums["des,,"]=25 toene_notnums["cis,,"]=25 toene_notnums["d,,"]=26 toene_notnums["es,,"]=27 toene_notnums["dis,,"]=27 toene_notnums["e,,"]=28 toene_notnums["eis,,"]=29 toene_notnums["f,,"]=29 toene_notnums["ges,,"]=30 toene_notnums["fis,,"]=30 toene_notnums["g,,"]=31 toene_notnums["as,,"]=32 toene_notnums["gis,,"]=32 toene_notnums["a,,"]=33 toene_notnums["bes,,"]=34 toene_notnums["ais,,"]=34 toene_notnums["b,,"]=35 toene_notnums["bis,,"]=36 toene_notnums["his,,"]=36 toene_notnums["c,"]=36 toene_notnums["des,"]=37 toene_notnums["cis,"]=37 toene_notnums["d,"]=38 toene_notnums["es,"]=39 toene_notnums["dis,"]=39 toene_notnums["e,"]=40 toene_notnums["eis,"]=41 toene_notnums["f,"]=41 toene_notnums["ges,"]=42 toene_notnums["fis,"]=42 toene_notnums["g,"]=43 toene_notnums["as,"]=44 toene_notnums["gis,"]=44 toene_notnums["a,"]=45 toene_notnums["bes,"]=46 toene_notnums["ais,"]=46 toene_notnums["b,"]=47 toene_notnums["bis,"]=48 toene_notnums["his,"]=48 toene_notnums["c"]=48 toene_notnums["des"]=49 toene_notnums["cis"]=49 toene_notnums["d"]=50 toene_notnums["es"]=51 toene_notnums["dis"]=51 toene_notnums["e"]=52 toene_notnums["eis"]=53 toene_notnums["f"]=53 toene_notnums["ges"]=54 toene_notnums["fis"]=54 toene_notnums["g"]=55 toene_notnums["as"]=56 toene_notnums["gis"]=56 toene_notnums["a"]=57 toene_notnums["bes"]=58 toene_notnums["ais"]=58 toene_notnums["b"]=59 toene_notnums["bis"]=60 toene_notnums["his"]=60 toene_notnums["c'"]=60 toene_notnums["des'"]=61 toene_notnums["cis'"]=61 toene_notnums["d'"]=62 toene_notnums["es'"]=63 toene_notnums["dis'"]=63 toene_notnums["e'"]=64 toene_notnums["eis'"]=65 toene_notnums["f'"]=65 toene_notnums["ges'"]=66 toene_notnums["fis'"]=66 toene_notnums["g'"]=67 toene_notnums["as'"]=68 toene_notnums["gis'"]=68 toene_notnums["a'"]=69 toene_notnums["bes'"]=70 toene_notnums["ais'"]=70 toene_notnums["b'"]=71 toene_notnums["bis'"]=72 toene_notnums["his'"]=72 toene_notnums["c''"]=72 toene_notnums["des''"]=73 toene_notnums["cis''"]=73 toene_notnums["d''"]=74 toene_notnums["es''"]=75 toene_notnums["dis''"]=75 toene_notnums["e''"]=76 toene_notnums["eis''"]=77 toene_notnums["f''"]=77 toene_notnums["ges''"]=78 toene_notnums["fis''"]=78 toene_notnums["g''"]=79 toene_notnums["as''"]=80 toene_notnums["gis''"]=80 toene_notnums["a''"]=81 toene_notnums["bes''"]=82 toene_notnums["ais''"]=82 toene_notnums["b''"]=83 toene_notnums["bis''"]=84 toene_notnums["his''"]=84 toene_notnums["c'''"]=84 toene_notnums["des'''"]=85 toene_notnums["cis'''"]=85 toene_notnums["d'''"]=86 toene_notnums["es'''"]=87 toene_notnums["dis'''"]=87 toene_notnums["e'''"]=88 toene_notnums["eis'''"]=89 toene_notnums["f'''"]=89 toene_notnums["ges'''"]=90 toene_notnums["fis'''"]=90 toene_notnums["g'''"]=91 toene_notnums["as'''"]=92 toene_notnums["gis'''"]=92 toene_notnums["a'''"]=93 toene_notnums["bes'''"]=94 toene_notnums["ais'''"]=94 toene_notnums["b'''"]=95 toene_notnums["bis'''"]=96 toene_notnums["his'''"]=96 toene_notnums["c''''"]=96 toene_notnums["des''''"]=97 toene_notnums["cis''''"]=97 toene_notnums["d''''"]=98 toene_notnums["es''''"]=99 toene_notnums["dis''''"]=99 toene_notnums["e''''"]=100 toene_notnums["eis''''"]=101 toene_notnums["f''''"]=101 toene_notnums["ges''''"]=102 toene_notnums["fis''''"]=102 toene_notnums["g''''"]=103 toene_notnums["as''''"]=104 toene_notnums["gis''''"]=104 toene_notnums["a''''"]=105 toene_notnums["bes''''"]=106 toene_notnums["ais''''"]=106 toene_notnums["b''''"]=107 toene_notnums["bis''''"]=108 toene_notnums["his''''"]=108 toene_notnums["c'''''"]=108 notnums_toene = {v: k for k, v in toene_notnums.items()} # umgekehrtes Wörterbuch: hier sind die Tonnamen die Values
#!/usr/bin/env python3 from itertools import combinations import toene_dict as Dict def getmidinum(ton): """liefert die gewünschte Notennummer (Midi) zu einem Ton, eingegeben im Lilypond-Format""" midinotnum = Dict.toene_notnums[ton] return midinotnum def notnames2midinums(tonfolge): """ dasselbe wie getmidinum, aber mit einer folge von Tönen""" midinotnums = [ ] tonfolge = tonfolge.split() for t in tonfolge: midinotnums.append(getmidinum(t)) return midinotnums def getpc(ton): """liefert die pc-Nummer (0-11) zu einem gewünschten Ton,eingegeben im Lilypond-Format""" notnum = getmidinum(ton) pc = notnum%12 return pc def getton(num): # evtl. könnte man ja noch was machen, dass es auch bes gibt """ Liefert den zu einer Midi-Note passenden Ton, allerdings nur als erhöhten Ton.""" ton = Dict.notnums_toene[num] return ton def rotier_pcs(pcs): """ Findet die Rotationen bzw. \"Akkordumkehrungen\" eines pcs heraus. """ rotationen = [pcs] for i in range(1,len(pcs)): new_pcs = rotationen[i-1][1:] new_pcs.append(rotationen[i-1][0]) rotationen.append(new_pcs) return rotationen def find_pcs_intervall(pcs,pos_a,pos_b): """ Bestimmt das Intervall (die Differenz) zwischen den Elementen an den Stellen von pos_a und pos_b eines pcs.""" intervall = pcs[pos_b]-pcs[pos_a] if intervall < 0: intervall+=12 return intervall def find_minima(neliste): """Findet in einer Liste heraus, wo sich in einer Liste der kleinste Wert befindet. Gibt die Indices dieser Liste heraus. """ smallest = min(neliste) indices_smallest = [ ] for n in range(len(neliste)): if neliste[n] == smallest: indices_smallest.append(n) return indices_smallest def find_list_with_smallest_intervall(long_list,pos_a,pos_b): # zuerst eine liste erstellen,in der sich alle intervalle befinden intervall_liste= [ ] for n in long_list: intervall = find_pcs_intervall(n,pos_a,pos_b) intervall_liste.append(intervall) # dann herausfinden, wo sich das kleinste intervall befindet indices_kleinste_intervalle = find_minima(intervall_liste) # aus der langen liste die Listen filtern, in denen sich diese kleinsten intervalle befinden matches_list = [ ] for i in indices_kleinste_intervalle: matches_list.append(long_list[i]) return matches_list def get_normalform(pcs): """ Ermittelt die Normalform (nach Forte) eins Pcs.""" normalformen = rotier_pcs(pcs) for i in range(len(normalformen)): Indx = -1*(i+1) normalformen = find_list_with_smallest_intervall(normalformen,0,Indx) if len(normalformen) ==1: break return normalformen[0] def get_inversion(pitchnums): """Bildet die Umkehrung einer Tonfolge, gegeben in Midi-notnums oder als pcs""" intervalle = [ ] for i in range(1,len(pitchnums)): intervalle.append(pitchnums[i]-pitchnums[0]) intervalle = [i*-1 for i in intervalle ] spiegel = [pitchnums[-1]] for i in intervalle: spiegel.append(pitchnums[-1]+i) return spiegel def zeropc(pcs): """Das pcs wird auf "c"(0) transponiert""" return [(i-pcs[0])%12 for i in pcs ] def get_prime(pcs): """Ermittelt die Prime-Form (nach Forte) eins Pcs.""" normalform = get_normalform(pcs) inversion = sorted(get_inversion(normalform)) inversion = [i%12 for i in inversion ] # normalform auf c transponieren: normalform = zeropc(normalform) inversion = zeropc(get_normalform(inversion)) #inversion = [(i-inversion[0])%12 for i in inversion ] #print(inversion) if inversion <normalform: return(inversion) else: return(normalform) def get_norm_from_lily(lilynotes): "Gets the normalform as pcs from pitches in lilypondformat as a string. Remove double entrys""" lilynotes = lilynotes.split() pcs_unordered = [ ] for i in lilynotes: pcs_unordered.append(getpc(i)) #pcs.sorted() # check if entries are equal and remove them pcs = [pcs_unordered[0]] for i in pcs_unordered: if i not in pcs: pcs.append(i) #return sorted(pcs) return get_normalform(sorted(pcs)) def get_prime_from_lily(lilynotes): "Gets the normalform as pcs from pitches in lilypondformat as a string. Remove double entrys""" norm = get_norm_from_lily(lilynotes) prime = get_prime(norm) return prime def get_lilyfrom_prime(primeform): """Puts out a prime form as a string,readable in lilypond.First pitch is always c', duration's alwyas a whole note.""" lilystring = "" for i in range(len(primeform)): if i ==0: lilystring += getton(primeform[i]+60) + "1 " else: lilystring += getton(primeform[i]+60) + " " return lilystring def get_vector(primeform): """Ermittelt den Intervallvector einer Primeform.""" #Alle Zweiergruppen der primeform ermittels: kombis = list(combinations(primeform,2)) # Die Differenzen jeder kombi berechnen intervalle = [ ] for k in kombis: diff = k[1]-k[0] # diff muss <= 6 sein if diff >6: diff = 12-diff intervalle.append(diff) # jetzt in eine liste eintragen, wie oft die Werte 1-6 erscheinen intervall_vector = [ ] for n in range(1,7): num_matches = 0 for i in intervalle: if i ==n: num_matches +=1 intervall_vector.append(num_matches) return intervall_vector # alle pitch classes allpc_12edo = list(range(0,12)) # alle primeformen ermitteln, der Größe 2 bis 10 primes_12edo = [ ] for i in range(2,11): all_kombis = list(combinations(allpc_12edo,i)) for l in all_kombis: primeform = get_prime(list(l)) if primeform not in primes_12edo: primes_12edo.append(primeform) def find_lists_of_length(longlist,c): """ Find lists of length c (cardinality).""" matches = [ ] for l in longlist: if len(l) == c: matches.append(l) return matches def get_complement(pcs): """Findet die Töne, die nicht in einem pcs vorkommen""" rest = allpc_12edo.copy() for i in pcs: rest.remove(i) return rest # def transpose(pcs,value): """ Transponiert das pcs um den Wert von Value""" return [ (i+value)%12 for i in pcs ] def get_sublists(prime): """ Returns all sublists of prime form, from cardinality 3 up to len(prime)-1""" sublists = [ ] normforms_sublists = [ ] for i in range(3,len(prime)): sublists.extend(list(combinations(prime,i))) # wahrscheinlich muss mann noch mal die normalform dieser sublisten erstellen for l in sublists: normform = get_prime(list(l)) if normform not in normforms_sublists: normforms_sublists.append(normform) return normforms_sublists def findNumMatches(mylistA,mylistB): myNumMatches = 0 for i in mylistB: if i in mylistA: myNumMatches +=1 return myNumMatches def find_superlists(primeform): """ Find all lists that include the primeform or it's inversion, may it be transposed or not""" superlists = [ ] inversion = get_inversion(primeform) for i in range(12): pcs = transpose(primeform,i) pcs_inverted = transpose(inversion,i) for l in primes_12edo: if findNumMatches(l,pcs) == len(primeform) and l not in superlists: superlists.append(l) elif findNumMatches(l,pcs_inverted) == len(primeform) and l not in superlists: superlists.append(l) return superlists # check rp related primeforms def find_rps(prime): """ Searches for other primeforms, which have all but one pc in common with the given prime""" rpsets = [ ] # die primeforms gleicher Größe wie prime finden pcs_of_same_size = find_lists_of_length(primes_12edo,len(prime)) pcs_of_same_size.remove(prime) # aus dieser Liste prime entfernen # von jeder dieser primeform und deren spiegel alle transpositionen bilden und nach der anzahl der treffer len(prime) -1 schauen for pcs in pcs_of_same_size: pcs_inverted = sorted(get_inversion(pcs)) for i in range(12): pcs_transposed = transpose(pcs,i) if findNumMatches(prime,pcs_transposed) == len(prime)-1 and pcs not in rpsets: rpsets.append(pcs) elif findNumMatches(prime,pcs_inverted) == len(prime)-1 and pcs not in rpsets: rpsets.append(pcs) return rpsets def check_eq_trans(prime): """Searches for transpositions, which are identical with it's prime form. Puts out a list with intervall steps, that produce these transpositions.""" intervals = [ ] for i in range(1,12): pcs_transposed = transpose(prime,i) if set(prime) == set(pcs_transposed): intervals.append(i) return intervals def check_eq_invers(prime): # evtl. muss hier noch transponiert werden """ Searches for primeforms, which are identical with it's inversion""" if set(prime) == set(get_inversion(prime)): return True else: return False def check_zrel(prime_a,prime_b): """ Checks if two primes have the same inverval vector or not""" vec_a = get_vector(prime_a) vec_b = get_vector(prime_b) return vec_a == vec_b # Intervallvektoren vergleichen: def comp_vectors(vec_a,vec_b): """Finds out, how many interval-class entries between two vectors are of the same length and how many are not. Shows these interval-classes.""" equals = [ ] unequals = [ ] for i in range(len(vec_a)): interval_class = i+1 num_intervals_a = vec_a[i] num_intervals_b = vec_b[i] if num_intervals_b == num_intervals_a: equals.append((interval_class,num_intervals_a)) else: unequals.append((interval_class,vec_a[i],vec_b[i])) num_equals = len(equals) num_unequals = len(vec_a)-num_equals return num_equals,equals,num_unequals,unequals # list with all primeforms and their vectors primes_and_vectors_12edo = [ ] for p in primes_12edo: primes_and_vectors_12edo.append((p,get_vector(p))) # names for primes and their vectors that have the same length primes_vecs_of_three,primes_vecs_of_four,primes_vecs_of_five,primes_vecs_of_six,primes_vecs_of_seven,primes_vecs_of_eight,primes_vecs_of_nine =[],[],[],[],[],[],[] list_names = [primes_vecs_of_three,primes_vecs_of_four,primes_vecs_of_five,primes_vecs_of_six,primes_vecs_of_seven,primes_vecs_of_eight,primes_vecs_of_nine] for i in range(3,10): for p in primes_and_vectors_12edo: if len(p[0]) == i: list_names[i-3].append(p) def find_zrels(longlist): """ Finds pairs of primeforms with the same intervall-vector. Puts out first the vector followed by it's related primeforms """ zrels = [ ] all_pairs = combinations(longlist,2) for p in all_pairs: vec_a = get_vector(p[0]) vec_b = get_vector(p[1]) print(vec_a,vec_b) if vec_a == vec_b: zrels.append((vec_a,p[0],p[1])) return zrels def find_zrels(c,longlist): """ Finds pairs of primeforms of the length c, that share the same intervall-vector. Puts out the vector followed by it's related primeforms """ list_len_c = [ ] z_list = [ ] for l in longlist: if len(l[0]) == c: list_len_c.append(l) # paare bilden pairs = combinations(list_len_c,2) # die vectoren der Paare vergleichen: for p in pairs: if p[0][1] == p[1][1]: z_list.append((p[0][1],p[0][0],p[1][0])) return z_list print(find_zrels(4,primes_and_vectors_12edo))