Source code for klotho.tonos.utils.frequency_conversion

from klotho.utils.data_structures.enums import DirectValueEnumMeta, Enum
from collections import namedtuple
import numpy as np
from enum import member

A4_Hz   = 440.0
A4_MIDI = 69

__all__ = [
    'PITCH_CLASSES',
    'freq_to_midicents',
    'midicents_to_freq',
    'midicents_to_pitchclass',
    'freq_to_pitchclass',
    'pitchclass_to_freq',
    'A4_Hz',
    'A4_MIDI'
]

[docs] class PITCH_CLASSES(Enum, metaclass=DirectValueEnumMeta): """Enumeration of pitch classes for 12-TET with both sharp and flat naming."""
[docs] @member class N_TET_12(Enum, metaclass=DirectValueEnumMeta): C = 0 Cs = 1 Db = 1 D = 2 Ds = 3 Eb = 3 E = 4 Es = 5 Fb = 4 F = 5 Fs = 6 Gb = 6 G = 7 Gs = 8 Ab = 8 A = 9 As = 10 Bb = 10 B = 11 Bs = 0
[docs] @member class names: as_sharps = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] as_flats = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B']
[docs] def freq_to_midicents(frequency: float) -> float: """ Convert a frequency in Hertz to MIDI cents notation. MIDI cents combine MIDI note numbers with cent offsets. A value of 6900 corresponds to A4 (440 Hz). Each increment of 100 is one 12-TET semitone; non-multiples of 100 indicate microtonal pitches. Parameters ---------- frequency : float The frequency in Hertz. Returns ------- float The MIDI cent value. Examples -------- >>> freq_to_midicents(440.0) 6900.0 """ return 100 * (12 * np.log2(frequency / A4_Hz) + A4_MIDI)
[docs] def midicents_to_freq(midicents: float) -> float: """ Convert MIDI cents back to a frequency in Hertz. Parameters ---------- midicents : float The MIDI cent value (e.g., 6900 for A4). Returns ------- float The corresponding frequency in Hertz. Examples -------- >>> midicents_to_freq(6900) 440.0 """ return A4_Hz * (2 ** ((midicents - A4_MIDI * 100) / 1200.0))
[docs] def midicents_to_pitchclass(midicents: float) -> namedtuple: """ Convert MIDI cents to a pitch class with octave and cents offset. Parameters ---------- midicents : float The MIDI cent value. Returns ------- namedtuple A named tuple with fields ``pitchclass``, ``octave``, and ``cents_offset``. """ result = namedtuple('result', ['pitchclass', 'octave', 'cents_offset']) PITCH_LABELS = PITCH_CLASSES.N_TET_12.names.as_sharps midi = midicents / 100 midi_round = round(midi) note_index = int(midi_round) % len(PITCH_LABELS) octave = int(midi_round // len(PITCH_LABELS)) - 1 # MIDI starts from C-1 pitch_label = PITCH_LABELS[note_index] cents_diff = (midi - midi_round) * 100 return result(pitch_label, octave, round(cents_diff, 4))
# return Pitch(pitch_label, octave, round(cents_diff, 4))
[docs] def freq_to_pitchclass(freq: float, cent_round: int = 4) -> namedtuple: """ Convert a frequency in Hertz to a pitch class with octave and cents offset. Parameters ---------- freq : float The frequency in Hertz. cent_round : int, optional Decimal places for rounding the cents offset. Default is 4. Returns ------- namedtuple A named tuple with fields ``pitchclass``, ``octave``, and ``cents_offset``. """ result = namedtuple('result', ['pitchclass', 'octave', 'cents_offset']) PITCH_LABELS = PITCH_CLASSES.N_TET_12.names.as_sharps n_PITCH_LABELS = len(PITCH_LABELS) midi = A4_MIDI + n_PITCH_LABELS * np.log2(freq / A4_Hz) midi_round = round(midi) note_index = int(midi_round) % n_PITCH_LABELS octave = int(midi_round // n_PITCH_LABELS) - 1 # MIDI starts from C-1 pitch_label = PITCH_LABELS[note_index] cents_diff = (midi - midi_round) * 100 return result(pitch_label, octave, round(cents_diff, cent_round))
[docs] def pitchclass_to_freq(pitchclass: str, octave: int = 4, cent_offset: float = 0.0, hz_round: int = 4, A4_Hz=A4_Hz, A4_MIDI=A4_MIDI): """ Convert a pitch class name to a frequency in Hertz. Parameters ---------- pitchclass : str Pitch class name (e.g., ``"C"``, ``"F#"``, ``"Bb"``). octave : int, optional Octave number. Default is 4. cent_offset : float, optional Microtonal offset in cents. Default is 0.0. hz_round : int, optional Decimal places for rounding the result. Default is 4. A4_Hz : float, optional Reference frequency for A4. Default is 440.0. A4_MIDI : int, optional MIDI note number for A4. Default is 69. Returns ------- float The frequency in Hertz. """ # Try both sharp and flat notations SHARP_LABELS = PITCH_CLASSES.N_TET_12.names.as_sharps FLAT_LABELS = PITCH_CLASSES.N_TET_12.names.as_flats try: note_index = SHARP_LABELS.index(pitchclass) except ValueError: try: note_index = FLAT_LABELS.index(pitchclass) except ValueError: raise ValueError(f"Invalid pitch class: {pitchclass}") midi = note_index + (octave + 1) * 12 midi = midi - A4_MIDI midi = midi + cent_offset / 100 frequency = A4_Hz * (2 ** (midi / 12)) return round(frequency, hz_round)