Example #1
0
    def get_undirected_interval_grade(self, chorale):
        key = chorale.analyze('key')
        chorale_distribution = histogram_to_distribution(
            get_interval_histogram(chorale, directed=False))
        dataset_distribution = self.distributions[
            f'{key.mode}_undirected_interval_distribution']
        u, v, u_weights, v_weights = distributions_to_wasserstein_inputs(
            chorale_distribution, dataset_distribution)

        return wasserstein_distance(u, v, u_weights, v_weights)
Example #2
0
    def get_harmonic_quality_grade(self, chorale):
        key = chorale.analyze('key')
        chorale_distribution = histogram_to_distribution(
            get_harmonic_quality_histogram(chorale))
        dataset_distribution = self.distributions[
            f'{key.mode}_harmonic_quality_distribution']
        u, v, u_weights, v_weights = distributions_to_wasserstein_inputs(
            chorale_distribution, dataset_distribution)

        return wasserstein_distance(u, v, u_weights, v_weights)
Example #3
0
    def get_B_directed_interval_grade(self, chorale):
        key = chorale.analyze('key')
        voice_ih = get_SATB_interval_histogram(chorale, voice=3, directed=True)

        chorale_distribution = histogram_to_distribution(voice_ih)
        dataset_distribution = self.distributions[
            f'{key.mode}_B_directed_interval_distribution']
        u, v, u_weights, v_weights = distributions_to_wasserstein_inputs(
            chorale_distribution, dataset_distribution)

        return wasserstein_distance(u, v, u_weights, v_weights)
Example #4
0
    def get_rhythm_grade(self, chorale):
        """
        Arguments
            chorale: music21.stream.Stream

        Returns Wasserstein distance between normalized chorale rhythm distribution and normalized dataset rhythm distribution
        """
        chorale_distribution = histogram_to_distribution(
            get_rhythm_histogram(chorale))
        dataset_distribution = self.distributions['rhythm_distribution']
        u, v, u_weights, v_weights = distributions_to_wasserstein_inputs(
            chorale_distribution, dataset_distribution)

        return wasserstein_distance(u, v, u_weights, v_weights)
Example #5
0
    def get_note_grade(self, chorale):
        """
        Arguments
            chorale: music21.stream.Stream

        Returns Wasserstein distance between normalized chorale note distribution and normalized dataset note distribution
        """
        key = chorale.analyze('key')
        chorale_distribution = histogram_to_distribution(
            get_note_histogram(chorale, key))
        dataset_distribution = self.distributions[
            f'{key.mode}_note_distribution']
        u, v, u_weights, v_weights = distributions_to_wasserstein_inputs(
            chorale_distribution, dataset_distribution)

        return wasserstein_distance(u, v, u_weights, v_weights)
Example #6
0
    def get_error_grade(self, chorale):
        num_notes = len(chorale.flat.notes)
        chorale_histogram = get_error_histogram(chorale, self.voice_ranges)

        num_errors = sum(chorale_histogram.values())
        # if num_errors == 0, the coefficient will be 0, so we can sidestep the calculation of wasserstein
        if num_errors == 0:
            return 0
        chorale_distribution = histogram_to_distribution(chorale_histogram)
        dataset_distribution = self.distributions['error_distribution']
        error_note_ratio = num_errors / num_notes

        u, v, u_weights, v_weights = distributions_to_wasserstein_inputs(
            chorale_distribution, dataset_distribution)
        return wasserstein_distance(u, v, u_weights, v_weights) * (
            error_note_ratio / self.error_note_ratio)
Example #7
0
    def get_repeated_sequence_grade(self, chorale):
        sh = get_repeated_sequence_histogram(chorale)
        chorale_distribution = histogram_to_distribution(sh)
        dataset_distribution = self.distributions[
            'repeated_sequence_2_distribution']
        max_seq = np.max(
            list(chorale_distribution.keys()) +
            list(dataset_distribution.keys()))  # in ticks
        chorale_list, dataset_list = [0] * (max_seq + 1), [0] * (max_seq + 1)

        # populate chorale_list at the indices corresponding to keys in chorale_distribution
        for seq_len in chorale_distribution:
            chorale_list[seq_len] = chorale_distribution[seq_len]

        for seq_len in dataset_distribution:
            dataset_list[seq_len] = dataset_distribution[seq_len]

        if np.sum(chorale_list) == 0:
            chorale_list[0] = 1

        vals = [i for i in range(len(chorale_list))]
        return wasserstein_distance(vals, vals, chorale_list, dataset_list)
Example #8
0
from Grader.compute_chorale_histograms import get_note_histogram
from Grader.distribution_helpers import histogram_to_distribution
from Grader.voice_leading_helpers import find_parallel_8ve_5th_errors
from transformer_bach.utils import parse_xml

# specify the chorale here (example code assumes Bach dataset has been created)
BACH_DIR = 'chorales/bach_chorales'
chorale = parse_xml(f'{BACH_DIR}/0.xml')

grader = Grader(
    # use default features (see paper)
    features=FEATURES,
    pickle_dir='original',
)

grade, feature_vector = grader.grade_chorale(chorale)
print(f'Grade: {grade}')

for f, g in zip(FEATURES, feature_vector):
    print(f'{f}: {g}')

# show the distribution of notes in the given chorale (this can be modified for other features)
key = chorale.analyze('key')
chorale_distribution = histogram_to_distribution(get_note_histogram(chorale, key))
print(f'Chorale distribution: {chorale_distribution}')
dataset_distribution = grader.distributions[f'{key.mode}_note_distribution']
print(f'Bach distribution: {dataset_distribution}')

# example to find and print parallel errors in chorale
error_histogram, errors = find_parallel_8ve_5th_errors(chorale)
print(errors)
Example #9
0
    def calculate_distributions(self):
        print('Calculating ground-truth distributions over Bach chorales')

        # initialize all histograms
        major_nh, minor_nh = Counter(), Counter(
        )  # notes (for chorales in major)
        # notes (for chorales in minor)
        rh = Counter()  # rhythm

        major_hqh, minor_hqh = Counter(), Counter()  # harmonic quality

        major_directed_ih, minor_directed_ih = Counter(), Counter(
        )  # directed intervals for whole chorale
        major_S_directed_ih, minor_S_directed_ih = Counter(), Counter(
        )  # ... for soprano
        major_A_directed_ih, minor_A_directed_ih = Counter(), Counter(
        )  # ... for alto
        major_T_directed_ih, minor_T_directed_ih = Counter(), Counter(
        )  # ... for tenor
        major_B_directed_ih, minor_B_directed_ih = Counter(), Counter(
        )  # ... for bass

        major_undirected_ih, minor_undirected_ih = Counter(), Counter(
        )  # undirected intervals for whole chorale
        major_S_undirected_ih, minor_S_undirected_ih = Counter(), Counter(
        )  # ... for soprano
        major_A_undirected_ih, minor_A_undirected_ih = Counter(), Counter(
        )  # ... for alto
        major_T_undirected_ih, minor_T_undirected_ih = Counter(), Counter(
        )  # ... for tenor
        major_B_undirected_ih, minor_B_undirected_ih = Counter(), Counter(
        )  # ... for bass
        eh, peh = Counter(), Counter()  # errors (not including parallelism)
        # parallel errors (octaves and fifths)
        sh_1, sh_2, S_sh, A_sh, T_sh, B_sh = Counter(), Counter(), Counter(
        ), Counter(), Counter(), Counter()  # repeated sequences
        ssh = Counter()  # self-similarity
        ssh.update({b: 0 for b in BINS[:-1]})
        num_notes = 0  # number of notes

        # calculate histograms for all Bach chorales (for relevant features)
        for chorale in tqdm(self.iterator):
            key = chorale.analyze('key')
            if 'note' in self.features:
                chorale_nh = get_note_histogram(chorale, key)
                if key.mode == 'major':
                    major_nh += chorale_nh
                else:
                    minor_nh += chorale_nh

            if 'harmonic_quality' in self.features:
                if key.mode == 'major':
                    major_hqh += get_harmonic_quality_histogram(chorale)
                else:
                    minor_hqh += get_harmonic_quality_histogram(chorale)

            if 'directed_interval' in self.features:
                if key.mode == 'major':
                    major_directed_ih += get_interval_histogram(chorale,
                                                                directed=True)
                else:
                    minor_directed_ih += get_interval_histogram(chorale,
                                                                directed=True)

            if 'undirected_interval' in self.features:
                if key.mode == 'major':
                    major_undirected_ih += get_interval_histogram(
                        chorale, directed=False)
                else:
                    minor_undirected_ih += get_interval_histogram(
                        chorale, directed=False)

            if 'S_directed_interval' in self.features:
                if key.mode == 'major':
                    major_S_directed_ih += get_SATB_interval_histogram(
                        chorale, voice=0, directed=True)
                else:
                    minor_S_directed_ih += get_SATB_interval_histogram(
                        chorale, voice=0, directed=True)

            if 'A_directed_interval' in self.features:
                if key.mode == 'major':
                    major_A_directed_ih += get_SATB_interval_histogram(
                        chorale, voice=1, directed=True)
                else:
                    minor_A_directed_ih += get_SATB_interval_histogram(
                        chorale, voice=1, directed=True)

            if 'T_directed_interval' in self.features:
                if key.mode == 'major':
                    major_T_directed_ih += get_SATB_interval_histogram(
                        chorale, voice=2, directed=True)
                else:
                    minor_T_directed_ih += get_SATB_interval_histogram(
                        chorale, voice=2, directed=True)

            if 'B_directed_interval' in self.features:
                if key.mode == 'major':
                    major_B_directed_ih += get_SATB_interval_histogram(
                        chorale, voice=3, directed=True)
                else:
                    minor_B_directed_ih += get_SATB_interval_histogram(
                        chorale, voice=3, directed=True)

            if 'S_undirected_interval' in self.features:
                if key.mode == 'major':
                    major_S_undirected_ih += get_SATB_interval_histogram(
                        chorale, voice=0, directed=False)
                else:
                    minor_S_undirected_ih += get_SATB_interval_histogram(
                        chorale, voice=0, directed=False)

            if 'A_undirected_interval' in self.features:
                if key.mode == 'major':
                    major_A_undirected_ih += get_SATB_interval_histogram(
                        chorale, voice=1, directed=False)
                else:
                    minor_A_undirected_ih += get_SATB_interval_histogram(
                        chorale, voice=1, directed=False)

            if 'T_undirected_interval' in self.features:
                if key.mode == 'major':
                    major_T_undirected_ih += get_SATB_interval_histogram(
                        chorale, voice=2, directed=False)
                else:
                    minor_T_undirected_ih += get_SATB_interval_histogram(
                        chorale, voice=2, directed=False)

            if 'B_undirected_interval' in self.features:
                if key.mode == 'major':
                    major_B_undirected_ih += get_SATB_interval_histogram(
                        chorale, voice=3, directed=False)
                else:
                    minor_B_undirected_ih += get_SATB_interval_histogram(
                        chorale, voice=3, directed=False)

            if 'rhythm' in self.features:
                rh += get_rhythm_histogram(chorale)

            if 'error' in self.features:
                eh += get_error_histogram(chorale, self.voice_ranges)

            if 'parallel_error' in self.features:
                peh += get_parallel_error_histogram(chorale)

            if 'repeated_sequence_1' in self.features:
                sh_1 += get_repeated_sequence_histogram_1(chorale)

            if 'repeated_sequence_2' in self.features:
                sh_2 += get_repeated_sequence_histogram_2(chorale)

            if 'S_repeated_sequence' in self.features:
                sh += get_repeated_sequence_histogram(chorale, voice=0)

            if 'A_repeated_sequence' in self.features:
                sh += get_repeated_sequence_histogram(chorale, voice=1)

            if 'T_repeated_sequence' in self.features:
                sh += get_repeated_sequence_histogram(chorale, voice=2)

            if 'B_repeated_sequence' in self.features:
                sh += get_repeated_sequence_histogram(chorale, voice=3)

            if 'self_similarity' in self.features:
                ssh.update(get_self_similarity_histogram(chorale))

            # number of notes
            num_notes += len(chorale.flat.notes)

        print(f'len(ssh.keys()): {len(ssh.keys())}')

        # proportion of errors to notes
        error_note_ratio = sum(eh.values()) / num_notes

        # proportion of parallel errors to notes
        parallel_error_note_ratio = sum(peh.values()) / num_notes

        distributions = {
            'major_note_distribution': major_nh,
            'minor_note_distribution': minor_nh,
            'rhythm_distribution': rh,
            'major_harmonic_quality_distribution': major_hqh,
            'minor_harmonic_quality_distribution': minor_hqh,
            'major_directed_interval_distribution': major_directed_ih,
            'minor_directed_interval_distribution': minor_directed_ih,
            'major_S_directed_interval_distribution': major_S_directed_ih,
            'minor_S_directed_interval_distribution': minor_S_directed_ih,
            'major_A_directed_interval_distribution': major_A_directed_ih,
            'minor_A_directed_interval_distribution': minor_A_directed_ih,
            'major_T_directed_interval_distribution': major_T_directed_ih,
            'minor_T_directed_interval_distribution': minor_T_directed_ih,
            'major_B_directed_interval_distribution': major_B_directed_ih,
            'minor_B_directed_interval_distribution': minor_B_directed_ih,
            'major_undirected_interval_distribution': major_undirected_ih,
            'minor_undirected_interval_distribution': minor_undirected_ih,
            'major_S_undirected_interval_distribution': major_S_undirected_ih,
            'minor_S_undirected_interval_distribution': minor_S_undirected_ih,
            'major_A_undirected_interval_distribution': major_A_undirected_ih,
            'minor_A_undirected_interval_distribution': minor_A_undirected_ih,
            'major_T_undirected_interval_distribution': major_T_undirected_ih,
            'minor_T_undirected_interval_distribution': minor_T_undirected_ih,
            'major_B_undirected_interval_distribution': major_B_undirected_ih,
            'minor_B_undirected_interval_distribution': minor_B_undirected_ih,
            'error_distribution': eh,
            'parallel_error_distribution': peh,
            'repeated_sequence_1_distribution': sh_1,
            'repeated_sequence_2_distribution': sh_2,
            'S_repeated_sequence_distribution': S_sh,
            'A_repeated_sequence_distribution': A_sh,
            'T_repeated_sequence_distribution': T_sh,
            'B_repeated_sequence_distribution': B_sh,
            'self_similarity_distribution': ssh
        }

        # normalize each histogram by the sum of dictionary values, converting to distribution
        for dist in distributions:
            distributions[dist] = histogram_to_distribution(
                distributions[dist])

        self.error_note_ratio = error_note_ratio
        self.parallel_error_note_ratio = parallel_error_note_ratio
        self.distributions = distributions