Source code for pyquist.helper

import numpy as np


[docs] def db_to_amplitude( db: float | np.ndarray, *, reference: float = 1.0 ) -> float | np.ndarray: """Converts a decibel level to a linear amplitude. Decibels are a logarithmic ratio: ``amplitude = reference * 10**(db / 20)``. With the default ``reference=1.0`` and float audio (where ``±1.0`` is digital full scale), this is dBFS: * ``0 dB`` → ``1.0`` (unity / full scale) * ``-6 dB`` → ``≈ 0.501`` (roughly half amplitude) * ``-20 dB`` → ``0.1`` (exactly 1/10th amplitude) * ``+6 dB`` → ``≈ 1.995`` (roughly double amplitude) Pass a different ``reference`` to compare against another baseline (e.g. a measured RMS level, or a non-unity peak). Args: db: Decibel value. Scalar or numpy array. reference: The linear amplitude that corresponds to 0 dB. Defaults to ``1.0`` (full scale). """ return reference * 10.0 ** (db / 20.0)
[docs] def amplitude_to_db( amplitude: float | np.ndarray, *, reference: float = 1.0 ) -> float | np.ndarray: """Converts a linear amplitude to a decibel level. Inverse of :func:`db_to_amplitude`: ``db = 20 * log10(amplitude / reference)``. With the default ``reference=1.0``, this is dBFS: * ``1.0`` → ``0 dB`` (full scale) * ``0.5`` → ``≈ -6.02 dB`` * ``0.1`` → ``-20 dB`` * ``2.0`` → ``≈ +6.02 dB`` An amplitude of ``0.0`` produces ``-inf`` and emits a numpy log warning; callers that may pass exactly zero should clamp first. Args: amplitude: Linear amplitude. Scalar or numpy array. reference: The linear amplitude that corresponds to 0 dB. Defaults to ``1.0`` (full scale). """ return 20.0 * np.log10(amplitude / reference)
[docs] def frequency_to_pitch(frequency: float | np.ndarray) -> float | np.ndarray: """Converts a frequency in Hz to a MIDI pitch number. Uses the standard 12-tone equal-temperament tuning anchored to A4 = 440 Hz (MIDI pitch 69): ``pitch = 69 + 12 * log2(frequency / 440)``. The result is a real number, not just an integer — fractional values represent intermediate frequencies (one semitone = 1.0, one cent = 0.01). For example, ``frequency_to_pitch(466.16) ≈ 70.0`` (A#4). """ return 69 + 12 * np.log2(frequency / 440.0)
[docs] def pitch_to_frequency(pitch: float | np.ndarray) -> float | np.ndarray: """Converts a MIDI pitch number to a frequency in Hz. Inverse of :func:`frequency_to_pitch`: ``frequency = 440 * 2**((pitch - 69) / 12)``, using A4 = 440 Hz (MIDI 69) and 12-tone equal temperament. Fractional pitches are valid: ``pitch_to_frequency(60.5)`` is a quarter-tone above C4. """ return 440.0 * 2 ** ((pitch - 69) / 12)
PITCHES = {"a": 9, "b": 11, "c": 0, "d": 2, "e": 4, "f": 5, "g": 7} ACCIDENTALS = ["#", "b"] def _scientific_pitch_name_to_pitch(pitch_name: str) -> int: # Reference: https://en.wikipedia.org/wiki/Scientific_pitch_notation if len(pitch_name) < 2: raise ValueError(f"Invalid pitch name: {pitch_name}") # Parse pitch pitch_class = pitch_name[0].lower() if pitch_class not in PITCHES: raise ValueError(f"Invalid pitch: {pitch_name}") pitch = PITCHES[pitch_class] # Parse accidentals accidentals = pitch_name[1:-1] while accidentals: if accidentals.startswith("#"): pitch += 1 elif accidentals.startswith("b"): pitch -= 1 else: raise ValueError(f"Invalid accidental: {pitch_name}") accidentals = accidentals[1:] # Parse octave try: octave = int(pitch_name[-1]) except ValueError: raise ValueError(f"Invalid octave: {pitch_name}") pitch += 12 * (octave + 1) return pitch
[docs] def pitch_name_to_pitch(pitch: str | np.ndarray) -> int | np.ndarray: """Converts scientific pitch notation (e.g. ``"C4"``) to a MIDI pitch number. Recognized forms: * Pitch class: one letter ``A``-``G`` (case insensitive). * Accidentals (optional, may repeat): ``#`` for sharp, ``b`` for flat. ``"C##4"`` (double-sharp) and ``"Bbb3"`` (double-flat) both work. * Octave (required): one digit, using scientific octave numbering where C4 = middle C = MIDI 60 and A4 = MIDI 69. Examples: ``"C4"`` → 60, ``"A4"`` → 69, ``"Bb3"`` → 58, ``"C#4"`` → 61. Accepts a single string or a numpy array of strings (vectorized via :func:`numpy.vectorize`). Raises ``ValueError`` on malformed input. """ if isinstance(pitch, str): return _scientific_pitch_name_to_pitch(pitch) return np.vectorize(_scientific_pitch_name_to_pitch)(pitch)