Source code for klotho.tonos.utils.interval_normalization

from klotho.utils.algorithms.factors import to_factors
from typing import Union, List, Tuple, Dict, Set
from collections import namedtuple
from fractions import Fraction
import numpy as np
from sympy import Rational, root
import pandas as pd

A4_Hz   = 440.0
A4_MIDI = 69

from klotho.utils.data_structures.enums import DirectValueEnumMeta, Enum  

__all__ = [
    'equave_reduce',
    'reduce_interval',
    'reduce_interval_relative',
    'reduce_sequence_relative',
    'fold_interval',
    'reduce_freq'
]

[docs] def equave_reduce(interval:Union[int, float, Fraction, str], equave:Union[Fraction, int, str, float] = 2, n_equaves:int = 1) -> Union[int, float, Fraction]: """ Reduce an interval into the range ``[1, equave^n_equaves)``. Repeatedly multiplies or divides by *equave* until the interval falls within the target range. Parameters ---------- interval : int, float, Fraction, or str The interval to reduce. equave : Fraction, int, str, or float, optional Interval of equivalence. Default is 2 (octave). n_equaves : int, optional Number of equaves for the reduction window. Default is 1. Returns ------- Fraction The equave-reduced interval. """ interval = Fraction(interval) equave = Fraction(equave) while interval < 1: interval *= equave while interval >= equave**n_equaves: interval /= equave return interval
[docs] def reduce_interval(interval:Union[Fraction, int, float, str], equave:Union[Fraction, int, float, str] = 2, n_equaves:int = 1) -> Fraction: """ Fold an interval into the bipolar range ``[1/equave^n, equave^n)``. Unlike ``equave_reduce``, the lower bound extends below unison, allowing sub-fundamental intervals to be represented. Parameters ---------- interval : Fraction, int, float, or str The interval to fold. equave : Fraction, int, float, or str, optional Interval of equivalence. Default is 2. n_equaves : int, optional Number of equaves for the range. Default is 1. Returns ------- Fraction The folded interval. """ interval = Fraction(interval) equave = Fraction(equave) while interval < 1/(equave**n_equaves): interval *= equave while interval >= (equave**n_equaves): interval /= equave return interval
[docs] def reduce_interval_relative(target: Union[Fraction, int, float, str], source: Union[Fraction, int, float, str], equave: Union[Fraction, int, float, str] = 2) -> Fraction: """ Fold *target* to the equave transposition closest to *source*. Searches upward and downward by equave until the minimum absolute distance from *source* is found. Parameters ---------- target : Fraction, int, float, or str The interval to fold. source : Fraction, int, float, or str The reference interval. equave : Fraction, int, float, or str, optional Interval of equivalence. Default is 2. Returns ------- Fraction The transposition of *target* that minimizes ``|source - target|``. """ target = Fraction(target) source = Fraction(source) equave = Fraction(equave) while target < 1: target *= equave while source < 1: source *= equave best_target = target min_distance = abs(source - target) test_up = target test_down = target while True: test_up *= equave test_down /= equave up_dist = abs(source - test_up) down_dist = abs(source - test_down) if up_dist < min_distance: min_distance = up_dist best_target = test_up elif down_dist < min_distance: min_distance = down_dist best_target = test_down else: break return best_target
[docs] def reduce_sequence_relative(sequence: List[Union[Fraction, int, float, str]], equave: Union[Fraction, int, float, str] = 2) -> List[Fraction]: """ Fold a sequence of intervals to minimise octave jumps between neighbours. The first and last intervals are kept as anchors. Interior intervals are folded forward then backward to minimise adjacent displacement. Parameters ---------- sequence : list of Fraction, int, float, or str Intervals to fold. equave : Fraction, int, float, or str, optional Interval of equivalence. Default is 2. Returns ------- list of Fraction Folded intervals preserving the original start and end values. """ if len(sequence) <= 2: return [Fraction(x) for x in sequence] result = [Fraction(x) for x in sequence] for i in range(1, len(sequence)-1): result[i] = reduce_interval_relative(result[i], result[i-1], equave) for i in range(len(sequence)-2, 0, -1): result[i] = reduce_interval_relative(result[i], result[i+1], equave) return result
[docs] def fold_interval(interval: Union[Fraction, int, float, str], lower_thresh: Union[Fraction, int, float, str], upper_thresh: Union[Fraction, int, float, str]) -> Fraction: """ Reflect an interval back inside explicit threshold boundaries. If the interval exceeds the upper threshold, it is reflected downward by the overshoot distance; if below the lower threshold, it is reflected upward. Parameters ---------- interval : Fraction, int, float, or str The interval to fold. lower_thresh : Fraction, int, float, or str Lower boundary. upper_thresh : Fraction, int, float, or str Upper boundary. Returns ------- Fraction The folded interval. """ interval = Fraction(interval) lower_thresh = Fraction(lower_thresh) upper_thresh = Fraction(upper_thresh) if interval > upper_thresh: distance = interval / upper_thresh return upper_thresh / distance elif interval < lower_thresh: distance = lower_thresh / interval return lower_thresh * distance return interval
[docs] def reduce_freq(freq: float, lower: float = 27.5, upper: float = 4186, equave: Union[int, float, Fraction, str] = 2) -> float: """ Fold a frequency into a bounded range by equave transposition. Parameters ---------- freq : float The frequency to fold. lower : float, optional Lower bound in Hertz. Default is 27.5 (A0). upper : float, optional Upper bound in Hertz. Default is 4186 (C8). equave : int, float, Fraction, or str, optional Interval of equivalence. Default is 2. Returns ------- float The frequency folded into ``[lower, upper]``. """ equave = Fraction(equave) while freq < lower: freq *= equave while freq > upper: freq /= equave return float(freq)