Skip to content
Snippets Groups Projects
Commit 9eeae168 authored by Axel Marmoret's avatar Axel Marmoret
Browse files

Clean of the content

parent 75e999c6
No related branches found
No related tags found
No related merge requests found
Showing
with 605 additions and 702 deletions
import random
_VERYLARGENUMBER = 1000000 # effectively infinity
_MODULUS = 12 # size of the octave
_HALFMODULUS = int(0.5 + _MODULUS/2.0)
"""
voiceleading_utilities version 1.0, (c) 2015 by Dmitri Tymoczko
Voiceleading_utilities is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Voiceleading_utilities
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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser
General Public License along with Voiceleading_utilities. If not, see <http://www.gnu.org/licenses/>.
A set of routines that identify the minimal voice leadings between sets of pitches and pitch classes.
1. bijective_vl finds the best bijective voice leading between pitch-class sets assuming a fixed number of voices
- use this if you want to control the number of voices exactly,
e.g., a 3-voice voice leading from [C, E, G] to [F, A, C], or
a 4-voice voice leading from [G, B, D, F] to [C, C, E, G]
- this routine will also rank all the voice-leadings by size, so that, e.g. you can use the second-most efficient voice leading if you want
NB: this routine passes back pairs of the form [startPC, path]
2. voicelead takes an input set of pitches and a target set of PCs, and outputs a set of pitches;
- this is useful if you are generating music, and have a specific C-major chord in register; it will tell you where each voice should go
there is an option here to randomly choose one of the N most efficient voice leadings, so you are not always restricted to the most efficient ones
3. nonbijective_vl allows notes to be doubled; sometimes this produces a more efficient voice leading than a bijective voice leading
- for this reason, you cannot always control the number of voices
NB: this routine passes back pairs of PCs, from which you may need to calculate paths
(For details on the nonbijective_vl algorithm, see Tymozko, D., "The Geometry of Musical Chords", Science, 2006.)
Sometimes, you want something in between, e.g. the best 4-voice voice leading between triads or from a 4-voice seventh to a triad; in this case,
you need to iterate bijective_vl over all possible doublings of the chords. This can be time consuming.
TODO: allow different choices of metric
"""
"""==============================================================================================================================
bijective_vl expects two SORTED equal-length sets of integers representing PCs (in any modulus).
the sort parameter sorts the possible bijective VLs by size; by default it is set to False. Set it to true only if you want to choose from
among the n most efficient VLs"""
def bijective_vl(firstPCs, secondPCs, sort = False):
if len(firstPCs) != len(secondPCs):
return False
bijective_vl.fullList = [] # collects all the bijective VLs along with their size
currentBest = [] # currentBest records the best VL we have found so far
currentBestSize = _VERYLARGENUMBER # currentBestSize is the size of the current best VL (starts at infinity)
for i in range(0, len(firstPCs)): # iterate through every inversion of the second PC
secondPCs = secondPCs[-1:] + secondPCs[:-1]
newSize = 0
newPaths = []
for i in range(0, len(firstPCs)):
path = (secondPCs[i] - firstPCs[i]) % _MODULUS # calculate most efficient path based on the pairs
if path > _HALFMODULUS: # negative numbers for descending paths
path -= _MODULUS
newPaths.append([firstPCs[i], path])
newSize += abs(path)
bijective_vl.fullList.append([newPaths, newSize])
if newSize < currentBestSize: # record the current best size
currentBestSize = newSize
currentBest = newPaths
bijective_vl.size = currentBestSize
if sort:
bijective_vl.fullList = sorted(bijective_vl.fullList, key = lambda x: x[1])
return currentBest
"""==============================================================================================================================
voicelead expects a source list of PITCHES and a target list of PCs, both should be the same length; it outputs one of the topN most efficient voice leadings
from the source pitches to the target PCs.
if topN is 1, it gives you the most efficient voice leading"""
def voicelead(inPitches, targetPCs, topN = 1):
inPCs = sorted([p % _MODULUS for p in inPitches]) # convert input pitches to PCs and sort them
targetPCs = sorted(targetPCs)
paths = bijective_vl(inPCs, targetPCs, topN != 1) # find the possible bijective VLs
if topN != 1: # randomly select on of the N most efficient possibilities
myRange = min(len(bijective_vl.fullList), topN)
paths = bijective_vl.fullList[random.randrange(0, myRange)][0]
output = []
tempPaths = paths[:] # copy the list of paths
for inPitch in inPitches:
for path in tempPaths: # when we find a path remove it from our list (so we don't duplicate paths)
if (inPitch % _MODULUS) == path[0]:
output.append(inPitch + path[1])
tempPaths.remove(path)
break
return output
"""==============================================================================================================================
nonbijective_vl expects a source list of PCs or pitches and a target list of PCs or pitches, of any lengths; it outputs the most efficient voice leading from
source to target. Voices can be arbitrarily doubled.
To see why this is interesting, compare bijective_vl([0, 4, 7, 11], [4, 8, 11, 3]) to nonbijective_vl([0, 4, 7, 11], [4, 8, 11, 3])
for PCs, nonbijective_vl iterates over every inversion of the target chord; for each inversion it builds a matrix showing the most efficient voice leading
such that the first note of source goes to the first note of target (see Tymoczko "The Geometry of Musical Chords" for details)
TODO: choose the smaller of source and target to iterate over??
"""
def nonbijective_vl(source, target, pcs = True):
curVL = []
curSize = _VERYLARGENUMBER
if pcs:
source = [x % _MODULUS for x in source]
target = [x % _MODULUS for x in target]
source = sorted(list(set(source)))
target = sorted(list(set(target)))
if pcs:
for i in range(len(target)): # for PCs, iterate over every inversion of the target
tempTarget = target[i:] + target[:i]
newSize = build_matrix(source, tempTarget) # generate the matrix for this pairing
if newSize < curSize: # save it if it is the most efficient we've found
curSize = newSize
curVL = find_matrix_vl()
curVL = curVL[:-1]
else:
curSize = build_matrix(source, tempTarget) # no need to iterate for pitches
curVL = find_matrix_vl()
return curSize, curVL
def build_matrix(source, target, pcs = True): # requires sorted source and target chords
global theMatrix
global outputMatrix
global globalSource
global globalTarget
if pcs:
source = source + [source[0]]
target = target + [target[0]]
distanceFunction = lambda x, y: min((x - y) % _MODULUS, (y - x) % _MODULUS) # add **2 for Euclidean distance
else:
distanceFunction = lambda x, y: abs(x - y)
globalSource = source
globalTarget = target
theMatrix = []
for targetItem in target:
theMatrix.append([])
for sourceItem in source:
theMatrix[-1].append(distanceFunction(targetItem, sourceItem))
outputMatrix = [x[:] for x in theMatrix]
for i in range(1, len(outputMatrix[0])):
outputMatrix[0][i] += outputMatrix[0][i-1]
for i in range(1, len(outputMatrix)):
outputMatrix[i][0] += outputMatrix[i-1][0]
for i in range(1, len(outputMatrix)):
for j in range(1, len(outputMatrix[i])):
outputMatrix[i][j] += min([outputMatrix[i][j-1], outputMatrix[i-1][j], outputMatrix[i-1][j-1]])
return outputMatrix[i][j] - theMatrix[i][j]
def find_matrix_vl(): # identifies the voice leading for each matrix
theVL = []
i = len(outputMatrix) - 1
j = len(outputMatrix[i-1]) - 1
theVL.append([globalSource[j], globalTarget[i]])
while (i > 0 or j > 0):
newi = i
newj = j
myMin = _VERYLARGENUMBER
if i > 0 and j > 0:
newi = i - 1
newj = j - 1
myMin = outputMatrix[i-1][j-1]
if outputMatrix[i-1][j] < myMin:
myMin = outputMatrix[i-1][j]
newj = j
if outputMatrix[i][j - 1] < myMin:
myMin = outputMatrix[i][j-1]
newi = i
i = newi
j = newj
elif i > 0:
i = i - 1
elif j > 0:
j = j - 1
theVL.append([globalSource[j], globalTarget[i]])
return theVL[::-1]
"""==============================================================================================================================
A simple routine to put voice leadings in 'normal form.' Essentially, we just apply the standard "left-packing" algorithm to the first element
in a list of [startPC, path] pairs.
"""
def vl_normal_form(inList): # list of [PC, path] pairs
myList = sorted([[k[0] % _MODULUS] + k[1:] for k in inList])
currentBest = [[(k[0] - myList[0][0]) % _MODULUS] + k[1:] for k in myList]
vl_normal_form.transposition = myList[0][0] * -1
for i in range(1, len(myList)):
newChallenger = myList[-i:] + myList[:-i]
transp = newChallenger[0][0] * -1
newChallenger = sorted([[(k[0] - newChallenger[0][0]) % _MODULUS] + k[1:] for k in newChallenger])
for j in reversed(range(len(myList))):
if newChallenger[j][0] < currentBest[j][0]:
currentBest = newChallenger
vl_normal_form.transposition = transp
else:
if newChallenger[j][0] > currentBest[j][0]:
break
return currentBest
\ No newline at end of file
This diff is collapsed.
%% Cell type:code id: tags:
``` python
import os
import time
# Self-code imports
from polytopes.model.chord import Chord
import polytopes.data_manipulation as dm
#Generic imports
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import math
```
%% Cell type:markdown id: tags:
# Segment size distribution
%% Cell type:code id: tags:
``` python
annotations_folder_path = "C:\\Users\\amarmore\\Desktop\\Audio samples\\RWC Pop\\annotations\\MIREX10"
persisted_path = "C:\\Users\\amarmore\\Desktop\\data_persisted"
# Files to load: .seq
all_mirex_ten = "C:\\Users\\amarmore\\Desktop\\Projects\\RWC_annotations\\final_bimbot_al\\"
all_manual = []
for file in os.listdir(all_mirex_ten):
bag_of_words = file.split(".")
if bag_of_words[-1] == "seq":
if bag_of_words[-2] == "manual":
all_manual.append(file)
all_res = []
distrib_segments = []
for song in all_manual:
song_number = song.split(".")[0]
annot_name = "{:03d}.manual.seg".format(int(song_number))
annotation_file = open(all_mirex_ten + annot_name,'r')
annotation = annotation_file.read().replace("\n", "").split()
annotation = np.array([int(x) - 1 for x in annotation])
beat_indexed_annotation = np.array(dm.frontiers_to_segments(annotation))
for fst, snd in zip(annotation[:-1], annotation[1:]):
distrib_segments.append(snd - fst)
plt.figure(figsize=(15,5))
plt.hist(distrib_segments, bins = range(48))
plt.xlabel("Segment's size in annotation (in nb of beats)")
plt.title("Distribution of segment sizes in Manual annotation")
plt.plot()
```
%% Output
[]
%% Cell type:markdown id: tags:
# Segmentation with segment every 32 beats
%% Cell type:code id: tags:
``` python
annotations_folder_path = "C:\\Users\\amarmore\\Desktop\\Audio samples\\RWC Pop\\annotations\\MIREX10"
persisted_path = "C:\\Users\\amarmore\\Desktop\\data_persisted"
# Files to load: .seq
all_mirex_ten = "C:\\Users\\amarmore\\Desktop\\Projects\\RWC_annotations\\final_bimbot_al\\"
all_manual = []
for file in os.listdir(all_mirex_ten):
bag_of_words = file.split(".")
if bag_of_words[-1] == "seq":
if bag_of_words[-2] == "manual":
all_manual.append(file)
all_res = []
distrib_segments = []
for song in all_manual:
bag_of_chords = dm.flowify_song(all_mirex_ten + song)
frontiers = [i for i in range(0,len(bag_of_chords), 32)]
#Scores, computed on the beat annotation
beat_indexed_segments = dm.frontiers_to_segments(frontiers)
song_number = song.split(".")[0]
annot_name = "{:03d}.manual.seg".format(int(song_number))
annotation_file = open(all_mirex_ten + annot_name,'r')
annotation = annotation_file.read().replace("\n", "").split()
annotation = np.array([int(x) - 1 for x in annotation])
beat_indexed_annotation = np.array(dm.frontiers_to_segments(annotation))
prec, rec, fmes = dm.compute_score_of_segmentation(beat_indexed_annotation, beat_indexed_segments, window_length = 0.5)
all_res.append([prec, rec, fmes])
results = np.array(all_res)
prec, rap, fmes = round(np.mean(results[:,0]),4), round(np.mean(results[:,1]),4), round(np.mean(results[:,2]),4)
print("Prec: {}, Rec: {} Fmes: {}".format(prec, rap, fmes))
```
%% Output
Prec: 0.3597, Rec: 0.3615 Fmes: 0.3601
This diff is collapsed.
# Polytopes for music segmentation #
Polytopic paradigms to study music, based on [1] and [2].
Polytopic paradigms to study music, defined in [1], based on [2] and [3].
In near future, I should upload my own reference, detailing the specificity of both approaches of [1] and [2], which have been gathered on a same framework and on a same code project (this one).
The goal of the polytopic approach is to define a new compression criteria, then used as cost for music segmentation, based on dynamic programming. See [4] for more details on this approach.
The goal of the polytopic approach is to define a new compression criteria, then used as cost for music segmentation, based on dynamic programming. See [3] or [4] for more details on this approach.
[1] C. Guichaoua, Modèles de compression et critères de complexité pour la description et l’inférence de structure musicale. PhD thesis, 2017.
[2] C. Louboutin, Modélisation multi-échelle et multi-dimensionnelle de la structure musicale par graphes polytopiques. PhD thesis, Rennes 1, 2019.
[3] Sargent, G., Bimbot, F., & Vincent, E. (2016). Estimating the structural segmentation of popular music pieces under regularity constraints. IEEE/ACM Transactions on Audio, Speech, and Language Processing, 25(2), 344-358.
[4] Marmoret, A., Cohen, J., Bertin, N., & Bimbot, F. (2020, October). Uncovering Audio Patterns in Music with Nonnegative Tucker Decomposition for Structural Segmentation. In ISMIR 2020-21st International Society for Music Information Retrieval.
[1] Marmoret, A., Cohen, J. E., & Bibmot, F. (2022). Polytopic Analysis of Music. arXiv preprint arXiv:2212.11054.
[2] Guichaoua, C., Modèles de compression et critères de complexité pour la description et l’inférence de structure musicale. PhD thesis, 2017.
[3] Louboutin, C., Modélisation multi-échelle et multi-dimensionnelle de la structure musicale par graphes polytopiques. PhD thesis, Rennes 1, 2019.
[4] Sargent, G., Bimbot, F., & Vincent, E. (2016). Estimating the structural segmentation of popular music pieces under regularity constraints. IEEE/ACM Transactions on Audio, Speech, and Language Processing, 25(2), 344-358.
Metadata-Version: 2.1
Name: polytopes
Version: 0.1.0
Summary: Package for polytopes applied on musical segmentation
Home-page: TODO
Author: Marmoret Axel
Author-email: axel.marmoret@irisa.fr
License: BSD
Description: # Polytope for music segmentation #
TODO.
Platform: UNKNOWN
Classifier: License :: OSI Approved :: BSD License
Classifier: Programming Language :: Python
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
Classifier: Programming Language :: Python :: 3.7
Requires-Python: >=3.7
Description-Content-Type: text/markdown
README.md
setup.py
polytopes/__init__.py
polytopes/chord_movement.py
polytopes/data_manipulation.py
polytopes/pattern_manipulation.py
polytopes/polytopical_costs.py
polytopes/sequence_segmentation.py
polytopes.egg-info/PKG-INFO
polytopes.egg-info/SOURCES.txt
polytopes.egg-info/dependency_links.txt
polytopes.egg-info/requires.txt
polytopes.egg-info/top_level.txt
polytopes/model/__init__.py
polytopes/model/chord.py
polytopes/model/constants.py
polytopes/model/errors.py
polytopes/model/note.py
\ No newline at end of file
madmom
matplotlib
mir_eval
numpy>=1.18.0
pandas
scipy>=0.13.0
soundfile
polytopes
# -*- coding: utf-8 -*-
"""
Created on Fri Nov 22 13:38:53 2019
@author: amarmore
"""
import unittest
import polytopes.model.triad_manipulation as tm
import polytopes.triad_transformations as tt
import polytopes.model.errors as err
import polytopes.accelerated_polytopical_costs as acc_pc
import numpy as np
class AccelerationFunctionTests(unittest.TestCase):
# def test_check_distance_function(self):
# circle_sharp = ['C','Am','F','Dm','A#','Gm','D#','Cm','G#','Fm','C#','A#m','F#','D#m','B','G#m','E','C#m','A','F#m','D','Bm','G','Em']
# for i in range(24):
# chord_1 = circle_sharp[i]
# for j in range(24):
# chord_2 = circle_sharp[j]
# raw = tt.triadic_tonnetz_distance_symbol(chord_1, chord_2)
# accelerated = acc_pc.accelerated_triadic_tonnetz_distance_symbol(chord_1, chord_2)
# self.assertEqual(raw, accelerated)
# def test_check_relation_function(self):
# circle_sharp = ['C','Am','F','Dm','A#','Gm','D#','Cm','G#','Fm','C#','A#m','F#','D#m','B','G#m','E','C#m','A','F#m','D','Bm','G','Em']
# relation_list = [0,'R','RL','RLR','LRPR','LRP','PR','P','PL','PLR','LPRP','LPRPR','PRPR','PRP','LPLR','LPL','LP','LPR','RP','RPR','LRLR','LRL','LR','L','LRPL','LPLP']
# for i in range(len(circle_sharp)):
# chord_1 = circle_sharp[i]
# for j in range(len(circle_sharp)):
# chord_2 = circle_sharp[j]
# accelerated = acc_pc.accelerated_triadic_tonnetz_relation_symbol(chord_1, chord_2)
# raw = tt.triadic_tonnetz_relation_symbol(chord_1, chord_2)
# if raw == 0:
# self.assertEqual(raw, accelerated)
# self.assertTrue(accelerated in relation_list)
# else:
# iterator_str = ''
# for rel in raw:
# iterator_str += str(rel)
# self.assertEqual(iterator_str, accelerated)
# self.assertTrue(accelerated in relation_list)
# def test_check_apply_relation_function(self):
# circle_sharp = ['C','Am','F','Dm','A#','Gm','D#','Cm','G#','Fm','C#','A#m','F#','D#m','B','G#m','E','C#m','A','F#m','D','Bm','G','Em']
# relation_list = [0,'R','RL','RLR','LRPR','LRP','PR','P','PL','PLR','LPRP','LPRPR','PRPR','PRP','LPLR','LPL','LP','LPR','RP','RPR','LRLR','LRL','LR','L','LRPL','LPLP']
# for i in range(len(circle_sharp)):
# chord_1 = circle_sharp[i]
# for j in range(len(relation_list)):
# rel = relation_list[j]
# accelerated = acc_pc.accelerated_apply_triadic_tonnetz_relation_symbol(chord_1, rel)
# raw = tt.apply_triadic_tonnetz_relation_symbol(chord_1, rel)
# self.assertEqual(raw, accelerated)
def test_check_get_chromatic_mvt_triads(self):
circle_sharp = ['C','Am','F','Dm','A#','Gm','D#','Cm','G#','Fm','C#','A#m','F#','D#m','B','G#m','E','C#m','A','F#m','D','Bm','G','Em']
for i in range(len(circle_sharp)):
chord_1 = circle_sharp[i]
for j in range(len(circle_sharp)):
chord_2 = circle_sharp[j]
accelerated = acc_pc.accelerated_chromatic_mvt_triads(chord_1, chord_2)
raw = tt.chromatic_mvt_triads(chord_1, chord_2)
self.assertEqual(raw, accelerated)
def test_check_get_voice_leading_transformation_symbol(self):
circle_sharp = ['C','Am','F','Dm','A#','Gm','D#','Cm','G#','Fm','C#','A#m','F#','D#m','B','G#m','E','C#m','A','F#m','D','Bm','G','Em']
for i in range(len(circle_sharp)):
chord_1 = circle_sharp[i]
for j in range(len(circle_sharp)):
chord_2 = circle_sharp[j]
accelerated = acc_pc.accelerated_get_voice_leading_transformation_symbol(chord_1, chord_2)
raw = tt.get_voice_leading_transformation_symbol(chord_1, chord_2)
self.assertEqual(raw, accelerated)
if __name__ == '__main__':
unittest.main()
\ No newline at end of file
......@@ -19,18 +19,18 @@ class PatternManipulationTests(unittest.TestCase):
def test_indexing_on_examples(self):
pattern = pf.make_indexed_pattern(4, adding_code = [0,1,0,1], deleting_code = [0,0,1,0])
# pattern = [[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], [[[8, 9], [(10, 11), (12, 13)]], [[14], [(15, 16)]]]]
self.assertEqual(pm.get_index_of_element(2, pattern), [0, 0, 1, 0])
self.assertEqual(pm.get_index_of_element(6, pattern), [0, 1, 1, 0])
self.assertEqual(pm.get_index_of_element(13, pattern), [1,0,1,(1,1)])
self.assertEqual(pm.get_index_of_element(13, pattern), [1, 1, 0, 0])
self.assertEqual(pm.get_index_from_element(2, pattern), [0, 0, 1, 0])
self.assertEqual(pm.get_index_from_element(6, pattern), [0, 1, 1, 0])
self.assertEqual(pm.get_index_from_element(13, pattern), [1,0,1,(1,1)])
self.assertEqual(pm.get_index_from_element(13, pattern), [1, 1, 0, 0])
def test_indexing_on_examples(self):
pattern = pf.make_indexed_pattern(4, adding_code = [0,1,0,1], deleting_code = [0,0,1,0])
# pattern = [[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], [[[8, 9], [(10, 11), (12, 13)]], [[14], [(15, 16)]]]]
self.assertEqual(pm.get_element_with_index([0, 0, 1, 1], pattern), 3)
self.assertEqual(pm.get_element_with_index([0, 1, 0, 0], pattern), 4)
self.assertIsNone(pm.get_element_with_index([0,0,1,(1,1)], pattern))
self.assertEqual(pm.get_element_with_index([1, 1, 1, (0, 0)], pattern), 15)
self.assertEqual(pm.get_element_from_index([0, 0, 1, 1], pattern), 3)
self.assertEqual(pm.get_element_from_index([0, 1, 0, 0], pattern), 4)
self.assertIsNone(pm.get_element_from_index([0,0,1,(1,1)], pattern))
self.assertEqual(pm.get_element_from_index([1, 1, 1, (0, 0)], pattern), 15)
def test_indexing_and_retrieving_from_index(self):
for size in range(2, 70):
......@@ -38,8 +38,8 @@ class PatternManipulationTests(unittest.TestCase):
for add, dele in pf.get_codes(size):
pattern = pf.make_indexed_pattern(dim, adding_code = add, deleting_code = dele, starting_index = 0)
for i in range(0,pf.get_pattern_size(pattern)):
idx_elt = pm.get_index_of_element(i, pattern)
self.assertEqual(i, pm.get_element_with_index(idx_elt, pattern))
idx_elt = pm.get_index_from_element(i, pattern)
self.assertEqual(i, pm.get_element_from_index(idx_elt, pattern))
def test_delete_tuples(self):
self.assertEqual(pm.delete_tuples([0, 1, 0, 0]), [0, 1, 0, 0])
......@@ -61,16 +61,16 @@ class PatternManipulationTests(unittest.TestCase):
def test_some_antecedents_ground_truth(self):
pattern = pf.make_indexed_pattern(4, adding_code = [0,1,0,1], deleting_code = [0,0,1,0])
# pattern = [[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], [[[8, 9], [(10, 11), (12, 13)]], [[14], [(15, 16)]]]]
elt_idx = pm.get_index_of_element(5, pattern)
for ant in pm.get_antecedents_from_idx(elt_idx, pattern):
elt_idx = pm.get_index_from_element(5, pattern)
for ant in pm.get_antecedents_from_index(elt_idx, pattern):
self.assertTrue(ant in [1,4])
elt_idx = pm.get_index_of_element(10, pattern)
for ant in pm.get_antecedents_from_idx(elt_idx, pattern):
elt_idx = pm.get_index_from_element(10, pattern)
for ant in pm.get_antecedents_from_index(elt_idx, pattern):
self.assertTrue(ant in [2,8])
elt_idx = pm.get_index_of_element(13, pattern)
for ant in pm.get_antecedents_from_idx(elt_idx, pattern):
elt_idx = pm.get_index_from_element(13, pattern)
for ant in pm.get_antecedents_from_index(elt_idx, pattern):
self.assertTrue(ant in [11,12])
def test_no_antecedent_is_none(self):
......@@ -79,24 +79,24 @@ class PatternManipulationTests(unittest.TestCase):
for add, dele in pf.get_codes(size):
pattern = pf.make_indexed_pattern(dim, adding_code = add, deleting_code = dele, starting_index = 0)
for i in range(1,pf.get_pattern_size(pattern)):
idx_elt = pm.get_index_of_element(i, pattern)
ant = pm.get_antecedents_from_idx(idx_elt, pattern)
idx_elt = pm.get_index_from_element(i, pattern)
ant = pm.get_antecedents_from_index(idx_elt, pattern)
self.assertNotEqual(ant, None)
def test_some_pivot_ground_truth(self):
pattern = pf.make_indexed_pattern(4, adding_code = [0,1,0,1], deleting_code = [0,0,1,0])
# pattern = [[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], [[[8, 9], [(10, 11), (12, 13)]], [[14], [(15, 16)]]]]
elt_idx = pm.get_index_of_element(5, pattern)
ant_idx = pm.get_index_of_element(1, pattern)
self.assertEqual(pm.get_pivot_idx_from_idx(elt_idx, ant_idx), [0, 1, 0, 0])
elt_idx = pm.get_index_from_element(5, pattern)
ant_idx = pm.get_index_from_element(1, pattern)
self.assertEqual(pm.get_pivot_index_from_index(elt_idx, ant_idx), [0, 1, 0, 0])
elt_idx = pm.get_index_of_element(10, pattern)
ant_idx = pm.get_index_of_element(2, pattern)
self.assertEqual(pm.get_pivot_idx_from_idx(elt_idx, ant_idx), [1, 0, 0, 0])
elt_idx = pm.get_index_from_element(10, pattern)
ant_idx = pm.get_index_from_element(2, pattern)
self.assertEqual(pm.get_pivot_index_from_index(elt_idx, ant_idx), [1, 0, 0, 0])
elt_idx = pm.get_index_of_element(13, pattern)
ant_idx = pm.get_index_of_element(12, pattern)
self.assertEqual(pm.get_pivot_idx_from_idx(elt_idx, ant_idx), [0, 0, 0, 0])
elt_idx = pm.get_index_from_element(13, pattern)
ant_idx = pm.get_index_from_element(12, pattern)
self.assertEqual(pm.get_pivot_index_from_index(elt_idx, ant_idx), [0, 0, 0, 0])
def test_no_pivot_is_none(self):
for size in range(2, 70):
......@@ -104,24 +104,24 @@ class PatternManipulationTests(unittest.TestCase):
for add, dele in pf.get_codes(size):
pattern = pf.make_indexed_pattern(dim, adding_code = add, deleting_code = dele, starting_index = 0)
for i in range(1,pf.get_pattern_size(pattern)):
idx_elt = pm.get_index_of_element(i, pattern)
ants_idx = pm.get_antecedents_idx_from_idx(idx_elt)
idx_elt = pm.get_index_from_element(i, pattern)
ants_idx = pm.get_antecedents_index_from_index(idx_elt)
for ant_idx in ants_idx:
self.assertNotEqual(pm.get_pivot_idx_from_idx(idx_elt, ant_idx), None)
self.assertNotEqual(pm.get_pivot_index_from_index(idx_elt, ant_idx), None)
def test_some_successors_ground_truth(self):
pattern = pf.make_indexed_pattern(4, adding_code = [0,1,0,1], deleting_code = [0,0,1,0])
# pattern = [[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], [[[8, 9], [(10, 11), (12, 13)]], [[14], [(15, 16)]]]]
elt_idx = pm.get_index_of_element(5, pattern)
self.assertEqual(pm.get_successors_from_idx(elt_idx, pattern), [7])
elt_idx = pm.get_index_from_element(5, pattern)
self.assertEqual(pm.get_successors_from_index(elt_idx, pattern), [7])
elt_idx = pm.get_index_of_element(10, pattern)
for suc in pm.get_successors_from_idx(elt_idx, pattern):
elt_idx = pm.get_index_from_element(10, pattern)
for suc in pm.get_successors_from_index(elt_idx, pattern):
self.assertTrue(suc in [11,12,15])
elt_idx = pm.get_index_of_element(13, pattern)
self.assertEqual(pm.get_successors_from_idx(elt_idx, pattern), [])
elt_idx = pm.get_index_from_element(13, pattern)
self.assertEqual(pm.get_successors_from_index(elt_idx, pattern), [])
def test_no_error_when_computing_successors(self):
for size in range(2, 70):
......@@ -129,8 +129,8 @@ class PatternManipulationTests(unittest.TestCase):
for add, dele in pf.get_codes(size):
pattern = pf.make_indexed_pattern(dim, adding_code = add, deleting_code = dele, starting_index = 0)
for i in range(0,pf.get_pattern_size(pattern)):
idx_elt = pm.get_index_of_element(i, pattern)
suc = pm.get_successors_from_idx(idx_elt, pattern)
idx_elt = pm.get_index_from_element(i, pattern)
suc = pm.get_successors_from_index(idx_elt, pattern)
def test_successors_and_antecedents_are_equivalent(self):
......
# -*- coding: utf-8 -*-
"""
Created on Fri Nov 22 13:38:53 2019
@author: amarmore
"""
import unittest
import polytopes.data_manipulation as dm
import polytopes.model.errors as err
import polytopes.model.triad_manipulation as tm
import numpy as np
class TriadManipulationTests(unittest.TestCase):
##################################### Starting from the notes ################################
def test_symbol_from_notes(self):
"""
Tests if the symbol is correctly retrieved from the notes.
"""
chord_maj = [8, 0, 3]
chord_min = [8, 11, 3]
chord_sus4 = [8, 1, 3]
chord_sus2 = [8, 10, 3]
chord_aug = [8, 0, 4]
chord_dim = [8, 0, 2]
self.assertEqual(tm.triad_symbol_from_notes(chord_maj), "G#")
self.assertEqual(tm.triad_symbol_from_notes(chord_min), "G#m")
with self.assertRaises(err.NotAMajMinTriadException):
tm.triad_symbol_from_notes(chord_sus4)
with self.assertRaises(err.NotAMajMinTriadException):
tm.triad_symbol_from_notes(chord_sus2)
with self.assertRaises(err.NotAMajMinTriadException):
tm.triad_symbol_from_notes(chord_aug)
with self.assertRaises(err.NotAMajMinTriadException):
tm.triad_symbol_from_notes(chord_dim)
def test_root_from_notes(self):
"""
Tests if the root is correctly retrieved from the notes.
"""
chord = [8, 11, 3]
self.assertTrue(tm.root_from_notes(chord), "G#")
chord = [3, 11, 8]
self.assertTrue(tm.root_from_notes(chord), "G#")
chord = [8, 1, 3]
with self.assertRaises(err.NotAMajMinTriadException):
tm.root_from_notes(chord)
##################################### Starting from the symbol ################################
def test_notes_from_symbol(self):
"""
Tests if the notes are correctly retrieved from the symbol.
"""
notes = [8, 11, 3]
self.assertEqual(tm.triad_notes_from_symbol("Abm"), notes)
self.assertEqual(tm.triad_notes_from_symbol("Abmin"), notes)
self.assertEqual(tm.triad_notes_from_symbol("G#m"), notes)
self.assertEqual(tm.triad_notes_from_symbol("G#min"), notes)
self.assertEqual(tm.triad_notes_from_symbol("Abd", triadic_reduction = True), notes)
self.assertEqual(tm.triad_notes_from_symbol("Abdim", triadic_reduction = True), notes)
with self.assertRaises(err.NotAMajMinTriadException):
self.assertEqual(tm.triad_notes_from_symbol("Abdim", triadic_reduction = False), notes)
notes = [8, 0, 3]
self.assertEqual(tm.triad_notes_from_symbol("Ab"), notes)
self.assertEqual(tm.triad_notes_from_symbol("Abmaj"), notes)
self.assertEqual(tm.triad_notes_from_symbol("G#"), notes)
self.assertEqual(tm.triad_notes_from_symbol("G#maj"), notes)
self.assertEqual(tm.triad_notes_from_symbol("G#5", triadic_reduction = True), notes)
with self.assertRaises(err.NotAMajMinTriadException):
self.assertEqual(tm.triad_notes_from_symbol("G#5", triadic_reduction = False), notes)
self.assertEqual(tm.triad_notes_from_symbol("Absus4", triadic_reduction = True), notes)
with self.assertRaises(err.NotAMajMinTriadException):
self.assertEqual(tm.triad_notes_from_symbol("Absus4", triadic_reduction = False), notes)
self.assertEqual(tm.triad_notes_from_symbol("G#7s4", triadic_reduction = True), notes)
with self.assertRaises(err.NotAMajMinTriadException):
self.assertEqual(tm.triad_notes_from_symbol("G#7s4", triadic_reduction = False), notes)
def test_root_from_symbol(self):
"""
Tests if the root is correctly retrieved from the symbol.
"""
self.assertTrue(tm.root_from_symbol('Asus2') == 'A')
self.assertTrue(tm.root_from_symbol('D') == 'D')
self.assertTrue(tm.root_from_symbol('Db') == 'Db')
self.assertTrue(tm.root_from_symbol('Dbm') == 'Db')
self.assertTrue(tm.root_from_symbol('Cm') == 'C')
self.assertTrue(tm.root_from_symbol('C#') == 'C#')
self.assertTrue(tm.root_from_symbol('C#m') == 'C#')
self.assertTrue(tm.root_from_symbol('C#dim') == 'C#')
self.assertTrue(tm.root_from_symbol('Abmin') == 'Ab')
def test_little_format_symbol(self):
self.assertTrue(tm.little_format_symbol('C#dim') == 'C#d')
self.assertTrue(tm.little_format_symbol('C#min') == 'C#m')
self.assertTrue(tm.little_format_symbol('C#maj') == 'C#')
def test_reindex_inversed_triad(self):
chord = [8, 11, 3]
self.assertEqual(tm.reindex_inversed_triad([8, 11, 3]), chord)
self.assertEqual(tm.reindex_inversed_triad([8, 3, 11]), chord)
self.assertEqual(tm.reindex_inversed_triad([3, 11, 8]), chord)
self.assertEqual(tm.reindex_inversed_triad([3, 8, 11]), chord)
self.assertEqual(tm.reindex_inversed_triad([11, 8, 3]), chord)
self.assertEqual(tm.reindex_inversed_triad([11, 3, 8]), chord)
chord_maj = [8, 0, 3]
self.assertEqual(tm.reindex_inversed_triad([0, 3, 8]), chord_maj)
self.assertEqual(tm.reindex_inversed_triad([0, 8, 3]), chord_maj)
def test_is_maj_min_triad_from_notes(self):
self.assertTrue(tm.is_maj_min_triad_from_notes([8, 11, 3]))
self.assertTrue(tm.is_maj_min_triad_from_notes([8, 0, 3]))
self.assertFalse(tm.is_maj_min_triad_from_notes([8, 1, 3]))
def test_is_maj_min_triad_from_symbol(self):
self.assertTrue(tm.is_maj_min_triad_from_symbol("Abm"))
self.assertTrue(tm.is_maj_min_triad_from_symbol("G#"))
self.assertFalse(tm.is_maj_min_triad_from_symbol("Abd"))
def test_is_maj_min_triad_from_symbol(self):
self.assertEqual(tm.maj_min_triad_reduction_of_symbol("Abm"), "G#m")
self.assertEqual(tm.maj_min_triad_reduction_of_symbol("G#"), "G#")
self.assertEqual(tm.maj_min_triad_reduction_of_symbol("Abd"), "G#m")
self.assertEqual(tm.maj_min_triad_reduction_of_symbol("G#sus2"), "G#")
if __name__ == '__main__':
unittest.main()
\ No newline at end of file
# -*- coding: utf-8 -*-
"""
Created on Fri Nov 22 13:38:53 2019
@author: amarmore
"""
import unittest
import polytopes.model.triad_manipulation as tm
import polytopes.triad_transformations as tt
import polytopes.model.errors as err
import polytopes.chord_movement as mvt
import numpy as np
class TriadTransformationsTests(unittest.TestCase):
def test_get_voice_leading_distance_symbol(self):
self.assertEqual(tt.get_voice_leading_distance_symbol("F","Am"), 1)
self.assertEqual(tt.get_voice_leading_distance_symbol("Fmaj","Amin"), 1)
self.assertEqual(tt.get_voice_leading_distance_symbol("F","Amin"), 1)
self.assertEqual(tt.get_voice_leading_distance_symbol("Fmaj","Am"), 1)
self.assertEqual(tt.get_voice_leading_distance_symbol("Dm","Am"), 3)
self.assertEqual(tt.get_voice_leading_distance_symbol("Bb","F#m"), 3)
self.assertEqual(tt.get_voice_leading_distance_symbol("C#","F#m"), 2)
self.assertEqual(tt.get_voice_leading_distance_symbol("C#","F#dim", triadic_reduction = True), 2)
with self.assertRaises(err.NotAMajMinTriadException):
tt.get_voice_leading_distance_symbol("Cdim","Asus2", triadic_reduction = False)
def test_get_voice_leading_distance_notes(self):
self.assertEqual(tt.get_voice_leading_distance_notes([8,0,3],[8,11,3]), 1)
self.assertEqual(tt.get_voice_leading_distance_notes([11,3,6],[8,11,3]), 2)
with self.assertRaises(err.NotAMajMinTriadException):
tt.get_voice_leading_distance_notes([11,3,6],[8,1,3])
def test_get_voice_leading_tranformation_notes(self):
self.assertEqual(tt.get_voice_leading_transformation_notes([8,0,3],[8,11,3]), [0,11,0])
self.assertEqual(tt.get_voice_leading_transformation_notes([11,3,6],[8,11,3]), [-3, 8, -3])
with self.assertRaises(err.NotAMajMinTriadException):
tt.get_voice_leading_transformation_notes([11,3,6],[8,1,3])
def test_get_voice_leading_tranformation_symbol(self):
self.assertEqual(tt.get_voice_leading_transformation_symbol("Ab","Abm"), [0,11,0])
self.assertEqual(tt.get_voice_leading_transformation_symbol("B","Abm"), [-3, 8, -3])
self.assertEqual(tt.get_voice_leading_transformation_symbol("B","Absus2"), [-3, -3, -3])
with self.assertRaises(err.NotAMajMinTriadException):
tt.get_voice_leading_transformation_symbol("A","Asus2", triadic_reduction = False)
def test_get_triadic_position_symbol(self):
self.assertEqual(tt.get_triadic_position_symbol("Am"), 1)
self.assertEqual(tt.get_triadic_position_symbol("Gbm"), 19)
self.assertEqual(tt.get_triadic_position_symbol("F#m"), 19)
self.assertEqual(tt.get_triadic_position_symbol("F#dim", triadic_reduction=True), 19)
with self.assertRaises(err.NotAMajMinTriadException):
tt.get_triadic_position_symbol("F#dim", triadic_reduction=False)
def test_get_triadic_position_notes(self):
self.assertEqual(tt.get_triadic_position_notes([9,0,4]), 1)
self.assertEqual(tt.get_triadic_position_notes([8,0,3]), 8)
with self.assertRaises(err.NotAMajMinTriadException):
tt.get_triadic_position_notes([8,1,3])
def test_triadic_mvt_triads(self):
self.assertEqual(tt.triadic_mvt_triads("C","Em"), -1)
self.assertEqual(tt.triadic_mvt_triads("Abm","A"), 3)
self.assertEqual(tt.triadic_mvt_triads("C#","Gbm"), 9)
self.assertEqual(tt.triadic_mvt_triads("C#","Gbdim", triadic_reduction=True), 9)
with self.assertRaises(err.NotAMajMinTriadException):
tt.triadic_mvt_triads("C#","Gbdim", triadic_reduction=False)
def test_triad_and_mvt(self):
circle_sharp = ['C','Am','F','Dm','A#','Gm','D#','Cm','G#','Fm','C#','A#m','F#','D#m','B','G#m','E','C#m','A','F#m','D','Bm','G','Em']
for chord_1 in circle_sharp:
for chord_2 in circle_sharp:
self.assertEqual(tt.triadic_mvt_triads(chord_1, chord_2), mvt.triadic_mvt_chords(chord_1, chord_2))
for rel in range(0,24):
self.assertEqual(tt.apply_triadic_mvt(chord_1, rel), mvt.apply_triadic_mvt(chord_1, rel))
def test_apply_triadic_mvt(self):
self.assertEqual(tt.apply_triadic_mvt("C", -1), "Em")
self.assertEqual(tt.apply_triadic_mvt("Abm", 3), "A")
self.assertEqual(tt.apply_triadic_mvt("D#", 5), "A#m")
self.assertEqual(tt.apply_triadic_mvt("D#sus2", 5, triadic_reduction = True), "A#m")
with self.assertRaises(err.NotAMajMinTriadException):
tt.apply_triadic_mvt("D#sus2", 5, triadic_reduction = False)
def test_triadic_tonnetz_relation_symbol(self):
self.assertEqual(tt.triadic_tonnetz_relation_symbol("D","Gm"), ('P', 'L', 'R'))
self.assertEqual(tt.triadic_tonnetz_relation_symbol("Dm","Db"), ('L', 'P', 'R'))
self.assertEqual(tt.triadic_tonnetz_relation_symbol("C","A"), ('R', 'P'))
self.assertEqual(tt.triadic_tonnetz_relation_symbol("C","Db"), ('L','P','R', 'P'))
def test_triadic_tonnetz_distance_symbol(self):
self.assertEqual(tt.triadic_tonnetz_distance_symbol("D","Gm"), 3)
self.assertEqual(tt.triadic_tonnetz_distance_symbol("Dm","Db"), 3)
self.assertEqual(tt.triadic_tonnetz_distance_symbol("C","A"), 2)
self.assertEqual(tt.triadic_tonnetz_distance_symbol("C","Db"), 4)
def test_triadic_tonnetz_relation_notes(self):
self.assertEqual(tt.triadic_tonnetz_relation_notes([2,6,9],[7,10,2]), ('P', 'L', 'R'))
def test_triadic_tonnetz_distance_notes(self):
self.assertEqual(tt.triadic_tonnetz_distance_notes([2,6,9],[7,10,2]), 3)
def test_apply_triadic_tonnetz_relation_symbol(self):
self.assertEqual(tt.apply_triadic_tonnetz_relation_symbol("D",('P', 'L', 'R')), "Gm")
self.assertEqual(tt.apply_triadic_tonnetz_relation_symbol("D",('L', 'P', 'R')), "D#m")
def test_apply_triadic_tonnetz_relation_notes(self):
self.assertEqual(tt.apply_triadic_tonnetz_relation_notes([2,6,9],('P', 'L', 'R')), [7,10,2])
def test_chromatic_equals_fifth(self):
circle_sharp = ['C','Am','F','Dm','A#','Gm','D#','Cm','G#','Fm','C#','A#m','F#','D#m','B','G#m','E','C#m','A','F#m','D','Bm','G','Em']
for i in range(len(circle_sharp)):
chord_1 = circle_sharp[i]
for j in range(len(circle_sharp)):
chord_2 = circle_sharp[j]
rel_triadic = tt.triadic_mvt_triads(chord_1, chord_2)
triadic = tt.apply_triadic_mvt(chord_1, rel_triadic)
rel_chromatic = tt.chromatic_mvt_triads(chord_1, chord_2)
chromatic = tt.apply_chromatic_mvt(chord_1, rel_chromatic)
self.assertEqual(chromatic, triadic)
if __name__ == '__main__':
unittest.main()
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment