"""
Typed unit wrappers for pitch and frequency quantities.
"""
import numbers
import numpy as np
from fractions import Fraction
from ..topos.types import Unit, _to_fraction_array
from .utils.frequency_conversion import freq_to_midicents, midicents_to_freq
__all__ = ['frequency', 'midi', 'midicent', 'cent', 'ratio', 'partial']
def _to_partial_array(value):
"""
Convert to partial(s): int, Fraction, str -> Fraction; float -> float (inharmonic).
Preserves shape. Returns ndarray with dtype=object.
"""
def _convert(x):
if isinstance(x, (float, np.floating)):
return float(x)
return Fraction(x)
def _map_flat(seq, f):
if isinstance(seq, (list, tuple)):
return [_map_flat(x, f) if isinstance(x, (list, tuple)) else f(x) for x in seq]
return f(seq)
if isinstance(value, (list, tuple)):
mapped = _map_flat(value, _convert)
return np.array(mapped, dtype=object)
if isinstance(value, np.ndarray):
out = np.array([_convert(x) for x in value.flat], dtype=object)
return out.reshape(value.shape)
return np.array(_convert(value), dtype=object)
class Frequency(Unit):
def __init__(self, magnitude):
super().__init__(magnitude, 'frequency', 'Hz')
@property
def midicent(self):
return Midicent(freq_to_midicents(self.magnitude))
@property
def midi(self):
return Midi(self.midicent.magnitude / 100)
class Midi(Unit):
def __init__(self, magnitude):
super().__init__(magnitude, 'midi', 'MIDI')
@property
def frequency(self):
return Frequency(midicents_to_freq(self.magnitude * 100))
@property
def midicent(self):
return Midicent(self.magnitude * 100)
class Midicent(Unit):
def __init__(self, magnitude):
super().__init__(magnitude, 'midicent', 'm¢')
@property
def midi(self):
return Midi(self.magnitude / 100)
@property
def frequency(self):
return Frequency(midicents_to_freq(self.magnitude))
class Cent(Unit):
def __init__(self, magnitude):
super().__init__(magnitude, 'cent', '¢')
@property
def frequency_ratio(self):
return 2.0 ** (self.magnitude / 1200.0)
class Ratio(Unit):
def __init__(self, magnitude):
magnitude = _to_fraction_array(magnitude)
super().__init__(magnitude, 'ratio', '')
@property
def numerator(self):
try:
return self.magnitude.item().numerator
except ValueError:
raise AttributeError("numerator only for scalar")
@property
def denominator(self):
try:
return self.magnitude.item().denominator
except ValueError:
raise AttributeError("denominator only for scalar")
class Partial(Unit):
def __init__(self, magnitude):
magnitude = _to_partial_array(magnitude)
super().__init__(magnitude, 'partial', '')
[docs]
def frequency(value):
return Frequency(value)
[docs]
def midi(value):
return Midi(value)
[docs]
def midicent(value):
return Midicent(value)
[docs]
def cent(value):
return Cent(value)
[docs]
def ratio(value):
return Ratio(value)
[docs]
def partial(value):
return Partial(value)
numbers.Real.register(Frequency)
numbers.Real.register(Midi)
numbers.Real.register(Midicent)
numbers.Real.register(Cent)
numbers.Rational.register(Ratio)