Source code for Musica.Theory.Temperament

####################################################################################################
#
# Musica - A Music Theory Package for Python
# Copyright (C) 2017 Fabrice Salvaire
#
# This program 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 module defines temperaments.

"""

####################################################################################################

__all__ = [
    'ET12',
    'EqualTemperament',
    'TemperamentAccidentalStep',
    'TemperamentNaturalStep',
    'TemperamentStep',
    'UsualEqualTemperament',
    ]

####################################################################################################

from ..Locale.Note import translate_et12_note
from ..Math.MusicTheory import ET12Tuning
from .NoteTools import flatten_note, sharpen_note
from .PitchStandard import A440
from .Quality import IntervalQualities

####################################################################################################

[docs]class EqualTemperament: """Class to define an Equal Temperament. Step is an alias for semitone. """ ##############################################
[docs] def __init__(self, number_of_steps, pitch_standard): self._number_of_steps = number_of_steps self._pitch_standard = pitch_standard self._fundamental = self._compute_fundamental()
############################################## @property def number_of_steps(self): return self._number_of_steps @property def pitch_standard(self): return self._pitch_standard @property def fundamental(self): """Return the frequency of C0""" return self._fundamental ##############################################
[docs] def is_valid_step_number(self, number): return 0 <= number < self._number_of_steps
##############################################
[docs] def is_last_step_number(self, number): return (self._number_of_steps -1) == number
##############################################
[docs] def fold_step_number(self, number, octave=False): step_number = number % self._number_of_steps if octave: return step_number, number // self._number_of_steps else: return step_number
##############################################
[docs] def _compute_scale(self, octave, step_number): if step_number < 0: raise ValueError("Invalid step number {}".format(step_number)) octave_factor = 2 ** octave interval_factor = 2 ** (step_number / self._number_of_steps) return octave_factor * interval_factor
##############################################
[docs] def _compute_fundamental(self): denominator = self._compute_scale(self._pitch_standard.octave, self._pitch_standard.step_number) return self._pitch_standard.frequency / denominator
##############################################
[docs] def frequency(self, octave, step_number): """Return the frequency for an octave using the scientific pitch notation and an step number ranging from 0 to 11. For A440 (La3), use octave 4 and step number 9. """ return self._fundamental * self._compute_scale(octave, step_number)
####################################################################################################
[docs]class TemperamentStep: ##############################################
[docs] def __init__(self, temperament, step_number, quality=None): self._temperament = temperament self._step_number = step_number self._quality = quality self._prev_natural = None self._next_natural = None self._prev_step = None self._next_step = None
############################################## @property def step_number(self): return self._step_number @property def quality(self): return self._quality @property def prev_natural(self): # Fixme: previous ? return self._prev_natural @property def next_natural(self): return self._next_natural @property def prev_step(self): return self._prev_step @property def next_step(self): return self._next_step ##############################################
[docs] def __int__(self): return self._step_number
[docs] def __lt__(self, other): return self._step_number < int(other)
##############################################
[docs] def translate(self, language): return self._temperament.translator(self._step_number)[language]
####################################################################################################
[docs]class TemperamentNaturalStep(TemperamentStep): ##############################################
[docs] def __init__(self, temperament, step_number, degree, name, quality): super().__init__(temperament, step_number, quality) self._name = name self._degree = degree
############################################## @property def is_natural(self): return True @property def is_accidental(self): return False ############################################## @property def name(self): return self._name @property def degree(self): return self._degree ##############################################
[docs] def __repr__(self): return '{0.__class__.__name__} {0._step_number} {0._name}'.format(self)
####################################################################################################
[docs]class TemperamentAccidentalStep(TemperamentStep): ##############################################
[docs] def __init__(self, temperament, step_number): super().__init__(temperament, step_number, quality=IntervalQualities.minor)
##############################################
[docs] def __repr__(self): return '{0.__class__.__name__} {0.step_number}'.format(self)
############################################## @property def is_natural(self): return False @property def is_accidental(self): return True ############################################## @property def sharpen_name(self): return sharpen_note(self._prev_natural.name) @property def flatten_name(self): return flatten_note(self._next_natural.name)
####################################################################################################
[docs]class UsualEqualTemperament(EqualTemperament): """Base class factory to build for example a twelve-tone equal temperament. """ ##############################################
[docs] def __init__(self, math_implementation, pitch_standard, natural_steps, translator): super().__init__(math_implementation.number_of_steps, pitch_standard) self._math_implementation = math_implementation self._translator = translator perfect_steps = math_implementation.perfect_steps major_scale = math_implementation.major_scale self._natural_steps = [] for degree, ztuple in enumerate(zip(major_scale, natural_steps)): pitch, name = ztuple if pitch in perfect_steps: quality = IntervalQualities.perfect else: quality = IntervalQualities.major step = TemperamentNaturalStep(self, pitch.step_number, degree, name, quality) self._natural_steps.append(step) # complete self._number_of_natural_steps = len(self._natural_steps) for step in self._natural_steps: i_prev = (step.degree - 1) % self._number_of_natural_steps i_next = (step.degree + 1) % self._number_of_natural_steps step._prev_natural = self._natural_steps[i_prev] step._next_natural = self._natural_steps[i_next] # Note names self._natural_step_names = natural_steps # List of natural step number self._natural_step_numbers = major_scale # Map note name -> step self._name_to_step = {step.name:step for step in self._natural_steps} # Map step number -> step self._steps = [None]*self._number_of_steps for step in self._natural_steps: self._steps[step.step_number] = step # Add accidentals and link steps for i in range(self._number_of_steps): step = self._steps[i] if step is None: step = TemperamentAccidentalStep(self, i) self._steps[i] = step # prev and next are natural steps i_prev = self.fold_step_number(i - 1) i_next = self.fold_step_number(i + 1) step._prev_natural = self._steps[i_prev] step._next_natural = self._steps[i_next] for name in (step.flatten_name, step.sharpen_name): self._name_to_step[name] = step # Link steps for i in range(self._number_of_steps): step = self._steps[i] i_prev = self.fold_step_number(i - 1) i_next = self.fold_step_number(i + 1) step._prev_step = self._steps[i_prev] step._next_step = self._steps[i_next]
##############################################
[docs] def __iter__(self): return iter(self._steps)
##############################################
[docs] def iter_on_naturals(self): for step in self._steps: if step.is_natural: yield step
############################################## @property def natural_step_names(self): return self._natural_step_names @property def number_of_natural_steps(self): return self._number_of_natural_steps ##############################################
[docs] def fold_natural_step_number(self, number, octave=False): step_number = number % self._number_of_natural_steps if octave: return step_number, number // self._number_of_natural_steps else: return step_number
##############################################
[docs] def is_natural_step_number(self, number): return number in self._natural_step_numbers
##############################################
[docs] def translator(self, *args, **kwargs): return self._translator(*args, **kwargs)
##############################################
[docs] def is_valid_step_name(self, name): return name in self._natural_step_names
##############################################
[docs] def __getitem__(self, step_number): return self.by_step_number(step_number)
##############################################
[docs] def by_step_number(self, step_number): return self._steps[step_number]
##############################################
[docs] def by_name(self, name): return self._name_to_step[name]
##############################################
[docs] def by_degree(self, degree): return self._natural_steps[degree]
##############################################
[docs] def name_to_number(self, name): return self.by_name(name).step_number
#################################################################################################### #: Twelve-tone equal temperament, also known as 12 equal temperament, 12-TET, or 12-ET ET12 = UsualEqualTemperament( math_implementation=ET12Tuning, pitch_standard=A440, natural_steps=( # sorted by step number 'C', # 1 Do 'D', # 2 Rè 'E', # 3 Mi 'F', # 4 Fa 'G', # 5 Sol 'A', # 6 La 'B', # 7 Si ), translator=translate_et12_note, )