Skip to content

Frequency Scale Conversion

Convert between Hz and perceptual or musical frequency scales.

Perceptual scales

v_frq2mel

V_FRQ2MEL - Convert Hertz to Mel frequency scale.

v_frq2mel

v_frq2mel(frq) -> tuple[ndarray, ndarray]

Convert frequencies in Hz to the Mel scale.

Parameters:

Name Type Description Default
frq array_like

Frequencies in Hz.

required

Returns:

Name Type Description
mel ndarray

Mel-scale values. mel(1000 Hz) = 1000.

mr ndarray

Gradient in Hz/mel.

Notes

The relationship is: m = ln(1 + f/700) * 1000 / ln(1 + 1000/700) This means that m(1000) = 1000.

References

[1] J. Makhoul and L. Cosell. "Lpcw: An lpc vocoder with linear predictive spectral warping", Proc IEEE ICASSP, 1976.

Source code in pyvoicebox/v_frq2mel.py
def v_frq2mel(frq) -> tuple[np.ndarray, np.ndarray]:
    """Convert frequencies in Hz to the Mel scale.

    Parameters
    ----------
    frq : array_like
        Frequencies in Hz.

    Returns
    -------
    mel : ndarray
        Mel-scale values. mel(1000 Hz) = 1000.
    mr : ndarray
        Gradient in Hz/mel.

    Notes
    -----
    The relationship is: m = ln(1 + f/700) * 1000 / ln(1 + 1000/700)
    This means that m(1000) = 1000.

    References
    ----------
    [1] J. Makhoul and L. Cosell. "Lpcw: An lpc vocoder with linear
        predictive spectral warping", Proc IEEE ICASSP, 1976.
    """
    frq = np.asarray(frq, dtype=float)
    k = 1000.0 / np.log(1.0 + 1000.0 / 700.0)  # 1127.01048
    af = np.abs(frq)
    mel = np.sign(frq) * np.log(1.0 + af / 700.0) * k
    mr = (700.0 + af) / k
    return mel, mr

v_mel2frq

V_MEL2FRQ - Convert Mel frequency scale to Hertz.

v_mel2frq

v_mel2frq(mel) -> tuple[ndarray, ndarray]

Convert Mel-scale values to frequencies in Hz.

Parameters:

Name Type Description Default
mel array_like

Mel-scale values.

required

Returns:

Name Type Description
frq ndarray

Frequencies in Hz.

mr ndarray

Gradient in Hz/mel.

Notes

The relationship is: m = ln(1 + f/700) * 1000 / ln(1 + 1000/700) This means that m(1000) = 1000.

References

[1] J. Makhoul and L. Cosell. "Lpcw: An lpc vocoder with linear predictive spectral warping", Proc IEEE ICASSP, 1976.

Source code in pyvoicebox/v_mel2frq.py
def v_mel2frq(mel) -> tuple[np.ndarray, np.ndarray]:
    """Convert Mel-scale values to frequencies in Hz.

    Parameters
    ----------
    mel : array_like
        Mel-scale values.

    Returns
    -------
    frq : ndarray
        Frequencies in Hz.
    mr : ndarray
        Gradient in Hz/mel.

    Notes
    -----
    The relationship is: m = ln(1 + f/700) * 1000 / ln(1 + 1000/700)
    This means that m(1000) = 1000.

    References
    ----------
    [1] J. Makhoul and L. Cosell. "Lpcw: An lpc vocoder with linear
        predictive spectral warping", Proc IEEE ICASSP, 1976.
    """
    mel = np.asarray(mel, dtype=float)
    k = 1000.0 / np.log(1.0 + 1000.0 / 700.0)  # 1127.01048
    frq = 700.0 * np.sign(mel) * (np.exp(np.abs(mel) / k) - 1.0)
    mr = (700.0 + np.abs(frq)) / k
    return frq, mr

v_frq2bark

V_FRQ2BARK - Convert Hertz to BARK frequency scale.

v_frq2bark

v_frq2bark(f, m='') -> tuple[ndarray, ndarray]

Convert frequencies in Hz to the BARK scale.

Parameters:

Name Type Description Default
f array_like

Frequencies in Hz.

required
m str

Mode string with option characters: 'h' - use high frequency correction from Traunmuller (1990) 'l' - use low frequency correction from Traunmuller (1990) 'H' - do not apply any high frequency correction 'L' - do not apply any low frequency correction 'z' - use the expressions from Zwicker et al. (1980) 's' - use the expression from Schroeder et al. (1979) 'u' - unipolar version: do not force b to be an odd function

''

Returns:

Name Type Description
b ndarray

Bark-scale values.

c ndarray

Critical bandwidth: d(freq)/d(bark).

References

[1] H. Traunmuller, "Analytical Expressions for the Tonotopic Sensory Scale", J. Acoust. Soc. Am. 88, 1990, pp. 97-100. [2] E. Zwicker, "Subdivision of the audible frequency range into critical bands", J Acoust Soc Am 33, 1961, p248. [3] M. R. Schroeder, B. S. Atal, and J. L. Hall, 1979. [4] E. Zwicker and E. Terhardt, 1980.

Source code in pyvoicebox/v_frq2bark.py
def v_frq2bark(f, m='') -> tuple[np.ndarray, np.ndarray]:
    """Convert frequencies in Hz to the BARK scale.

    Parameters
    ----------
    f : array_like
        Frequencies in Hz.
    m : str, optional
        Mode string with option characters:
        'h' - use high frequency correction from Traunmuller (1990)
        'l' - use low frequency correction from Traunmuller (1990)
        'H' - do not apply any high frequency correction
        'L' - do not apply any low frequency correction
        'z' - use the expressions from Zwicker et al. (1980)
        's' - use the expression from Schroeder et al. (1979)
        'u' - unipolar version: do not force b to be an odd function

    Returns
    -------
    b : ndarray
        Bark-scale values.
    c : ndarray
        Critical bandwidth: d(freq)/d(bark).

    References
    ----------
    [1] H. Traunmuller, "Analytical Expressions for the Tonotopic Sensory
        Scale", J. Acoust. Soc. Am. 88, 1990, pp. 97-100.
    [2] E. Zwicker, "Subdivision of the audible frequency range into critical
        bands", J Acoust Soc Am 33, 1961, p248.
    [3] M. R. Schroeder, B. S. Atal, and J. L. Hall, 1979.
    [4] E. Zwicker and E. Terhardt, 1980.
    """
    f = np.asarray(f, dtype=float)

    A = 26.81
    B = 1960.0
    C = -0.53
    D = A * B
    P = 0.53 / (3.53 ** 2)
    Q = 0.25
    R = 20.4
    xy = 2.0
    S = 0.5 * Q / xy
    T = R + 0.5 * xy
    U = T - xy

    if 'u' in m:
        g = f.copy()
    else:
        g = np.abs(f)

    if 'z' in m:
        b = 13.0 * np.arctan(0.00076 * g) + 3.5 * np.arctan((g / 7500.0) ** 2)
        c = 25.0 + 75.0 * (1.0 + 1.4e-6 * g ** 2) ** 0.69
    elif 's' in m:
        b = 7.0 * np.log(g / 650.0 + np.sqrt(1.0 + (g / 650.0) ** 2))
        c = np.cosh(b / 7.0) * 650.0 / 7.0
    else:
        b = A * g / (B + g) + C
        d = D * (B + g) ** (-2)
        if 'l' in m:
            m1 = b < 2
            d = np.where(m1, d * 0.85, d)
            b = np.where(m1, 0.3 + 0.85 * b, b)
        elif 'L' not in m:
            m1 = b < 3
            # MATLAB updates b first, then uses updated b for d
            b_new = b + P * (3 - b) ** 2
            b = np.where(m1, b_new, b)
            d = np.where(m1, d * (1 - 2 * P * (3 - b)), d)
        if 'h' in m:
            m1 = b > 20.1
            d = np.where(m1, d * 1.22, d)
            b = np.where(m1, 1.22 * b - 4.422, b)
        elif 'H' not in m:
            m2 = b > T
            m1 = (b > U) & ~m2
            # MATLAB updates b(m1) first, then b(m2), then d(m2), then d(m1)
            b = np.where(m1, b + S * (b - U) ** 2, b)
            b = np.where(m2, (1 + Q) * b - Q * R, b)
            d = np.where(m2, d * (1 + Q), d)
            d = np.where(m1, d * (1 + 2 * S * (b - U)), d)
        c = 1.0 / d

    if 'u' not in m:
        b = b * np.sign(f)

    return b, c

v_bark2frq

V_BARK2FRQ - Convert the BARK frequency scale to Hertz.

v_bark2frq

v_bark2frq(b, m='') -> tuple[ndarray, ndarray]

Convert BARK-scale values to frequencies in Hz.

Parameters:

Name Type Description Default
b array_like

Bark-scale values.

required
m str

Mode string with option characters: 'h' - use high frequency correction from Traunmuller (1990) 'l' - use low frequency correction from Traunmuller (1990) 'H' - do not apply any high frequency correction 'L' - do not apply any low frequency correction 's' - use the expression from Schroeder et al. (1979) 'u' - unipolar version: do not force result to be an odd function

''

Returns:

Name Type Description
f ndarray

Frequencies in Hz.

c ndarray

Critical bandwidth: d(freq)/d(bark).

References

[1] H. Traunmuller, "Analytical Expressions for the Tonotopic Sensory Scale", J. Acoust. Soc. Am. 88, 1990, pp. 97-100. [2] E. Zwicker, "Subdivision of the audible frequency range into critical bands", J Acoust Soc Am 33, 1961, p248. [3] M. R. Schroeder, B. S. Atal, and J. L. Hall, 1979.

Source code in pyvoicebox/v_bark2frq.py
def v_bark2frq(b, m='') -> tuple[np.ndarray, np.ndarray]:
    """Convert BARK-scale values to frequencies in Hz.

    Parameters
    ----------
    b : array_like
        Bark-scale values.
    m : str, optional
        Mode string with option characters:
        'h' - use high frequency correction from Traunmuller (1990)
        'l' - use low frequency correction from Traunmuller (1990)
        'H' - do not apply any high frequency correction
        'L' - do not apply any low frequency correction
        's' - use the expression from Schroeder et al. (1979)
        'u' - unipolar version: do not force result to be an odd function

    Returns
    -------
    f : ndarray
        Frequencies in Hz.
    c : ndarray
        Critical bandwidth: d(freq)/d(bark).

    References
    ----------
    [1] H. Traunmuller, "Analytical Expressions for the Tonotopic Sensory
        Scale", J. Acoust. Soc. Am. 88, 1990, pp. 97-100.
    [2] E. Zwicker, "Subdivision of the audible frequency range into critical
        bands", J Acoust Soc Am 33, 1961, p248.
    [3] M. R. Schroeder, B. S. Atal, and J. L. Hall, 1979.
    """
    b = np.asarray(b, dtype=float)

    A = 26.81
    B = 1960.0
    C = -0.53
    E = A + C
    D = A * B
    P = 0.53 / (3.53 ** 2)
    V = 3 - 0.5 / P
    W = V ** 2 - 9
    Q = 0.25
    R = 20.4
    xy = 2.0
    S = 0.5 * Q / xy
    T = R + 0.5 * xy
    U = T - xy
    X = T * (1 + Q) - Q * R
    Y = U - 0.5 / S
    Z = Y ** 2 - U ** 2

    if 'u' in m:
        a = b.copy()
    else:
        a = np.abs(b)

    if 's' in m:
        f = 650.0 * np.sinh(a / 7.0)
    else:
        if 'l' in m:
            m1 = a < 2
            a = np.where(m1, (a - 0.3) / 0.85, a)
        elif 'L' not in m:
            m1 = a < 3
            a = np.where(m1, V + np.sqrt(np.maximum(W + a / P, 0)), a)
        if 'h' in m:
            m1 = a > 20.1
            a = np.where(m1, (a + 4.422) / 1.22, a)
        elif 'H' not in m:
            m2 = a > X
            m1 = (a > U) & ~m2
            a = np.where(m2, (a + Q * R) / (1 + Q), a)
            a = np.where(m1, Y + np.sqrt(np.maximum(Z + a / S, 0)), a)
        f = D * (E - a) ** (-1) - B

    if 'u' not in m:
        f = f * np.sign(b)

    # Compute critical bandwidth via v_frq2bark
    _, c = v_frq2bark(f, m)

    return f, c

v_frq2erb

V_FRQ2ERB - Convert Hertz to ERB frequency scale.

v_frq2erb

v_frq2erb(frq) -> tuple[ndarray, ndarray]

Convert frequencies in Hz to the ERB-rate scale.

Parameters:

Name Type Description Default
frq array_like

Frequencies in Hz.

required

Returns:

Name Type Description
erb ndarray

ERB-rate scale values.

bnd ndarray

ERB bandwidth in Hz.

Notes

The ERB scale is measured using the notched-noise method. The Equivalent Rectangular Bandwidth is: df/de = 6.23f^2 + 93.39f + 28.52 (f in kHz)

References

[1] B.C.J. Moore & B.R. Glasberg, "Suggested formula for calculating auditory-filter bandwidth and excitation patterns", J. Acoust. Soc. Am., V74, pp 750-753, 1983.

Source code in pyvoicebox/v_frq2erb.py
def v_frq2erb(frq) -> tuple[np.ndarray, np.ndarray]:
    """Convert frequencies in Hz to the ERB-rate scale.

    Parameters
    ----------
    frq : array_like
        Frequencies in Hz.

    Returns
    -------
    erb : ndarray
        ERB-rate scale values.
    bnd : ndarray
        ERB bandwidth in Hz.

    Notes
    -----
    The ERB scale is measured using the notched-noise method. The
    Equivalent Rectangular Bandwidth is:
        df/de = 6.23*f^2 + 93.39*f + 28.52 (f in kHz)

    References
    ----------
    [1] B.C.J. Moore & B.R. Glasberg, "Suggested formula for calculating
        auditory-filter bandwidth and excitation patterns", J. Acoust. Soc.
        Am., V74, pp 750-753, 1983.
    """
    frq = np.asarray(frq, dtype=float)

    u = np.array([6.23e-6, 93.39e-3, 28.52])
    p = np.sort(np.roots(u))  # p=[-14678.5, -311.9]
    a = 1e6 / (6.23 * (p[1] - p[0]))  # a=11.17
    c = p[0]  # c=-14678.5
    k = p[0] - p[0] ** 2 / p[1]  # k=676170.42
    h = p[0] / p[1]  # h=47.065

    g = np.abs(frq)
    erb = a * np.sign(frq) * np.log(h - k / (g - c))
    bnd = np.polyval(u, g)

    return erb, bnd

v_erb2frq

V_ERB2FRQ - Convert ERB frequency scale to Hertz.

v_erb2frq

v_erb2frq(erb) -> tuple[ndarray, ndarray]

Convert ERB-rate scale values to frequencies in Hz.

Parameters:

Name Type Description Default
erb array_like

ERB-rate scale values.

required

Returns:

Name Type Description
frq ndarray

Frequencies in Hz.

bnd ndarray

ERB bandwidth in Hz.

Notes

ERB values are clipped to 43.032 which corresponds to infinite frequency.

References

[1] B.C.J. Moore & B.R. Glasberg, "Suggested formula for calculating auditory-filter bandwidth and excitation patterns", J. Acoust. Soc. Am., V74, pp 750-753, 1983.

Source code in pyvoicebox/v_erb2frq.py
def v_erb2frq(erb) -> tuple[np.ndarray, np.ndarray]:
    """Convert ERB-rate scale values to frequencies in Hz.

    Parameters
    ----------
    erb : array_like
        ERB-rate scale values.

    Returns
    -------
    frq : ndarray
        Frequencies in Hz.
    bnd : ndarray
        ERB bandwidth in Hz.

    Notes
    -----
    ERB values are clipped to 43.032 which corresponds to infinite frequency.

    References
    ----------
    [1] B.C.J. Moore & B.R. Glasberg, "Suggested formula for calculating
        auditory-filter bandwidth and excitation patterns", J. Acoust. Soc.
        Am., V74, pp 750-753, 1983.
    """
    erb = np.asarray(erb, dtype=float)

    u = np.array([6.23e-6, 93.39e-3, 28.52])
    p = np.sort(np.roots(u))  # p=[-14678.5, -311.9]
    d = 1e-6 * (6.23 * (p[1] - p[0]))  # d=0.0895
    c = p[0]  # c=-14678.5
    k = p[0] - p[0] ** 2 / p[1]  # k=676170.4
    h = p[0] / p[1]  # h=47.06538

    frq = np.sign(erb) * (k / np.maximum(h - np.exp(d * np.abs(erb)), 0) + c)
    bnd = np.polyval(u, np.abs(frq))

    return frq, bnd

Musical scales

v_frq2cent

V_FRQ2CENT - Convert Hertz to Cents frequency scale.

v_frq2cent

v_frq2cent(frq) -> tuple[ndarray, ndarray]

Convert frequencies in Hz to the cents scale.

Parameters:

Name Type Description Default
frq array_like

Frequencies in Hz.

required

Returns:

Name Type Description
c ndarray

Cents scale values. 100 cents = one semitone. 440 Hz = 5700 cents.

cr ndarray

Gradient in Hz/cent.

Notes

c = 1200 * log2(f / (440 * 2^((3/12) - 5)))

References

[1] Ellis, A. "On the Musical Scales of Various Nations", Journal of the Society of Arts, 1885.

Source code in pyvoicebox/v_frq2cent.py
def v_frq2cent(frq) -> tuple[np.ndarray, np.ndarray]:
    """Convert frequencies in Hz to the cents scale.

    Parameters
    ----------
    frq : array_like
        Frequencies in Hz.

    Returns
    -------
    c : ndarray
        Cents scale values. 100 cents = one semitone. 440 Hz = 5700 cents.
    cr : ndarray
        Gradient in Hz/cent.

    Notes
    -----
    c = 1200 * log2(f / (440 * 2^((3/12) - 5)))

    References
    ----------
    [1] Ellis, A. "On the Musical Scales of Various Nations", Journal of
        the Society of Arts, 1885.
    """
    frq = np.asarray(frq, dtype=float)
    p = 1200.0 / np.log(2.0)
    q = 5700.0 - p * np.log(440.0)
    af = np.abs(frq)
    c = np.sign(frq) * (p * np.log(af) + q)
    cr = af / p
    return c, cr

v_cent2frq

V_CENT2FRQ - Convert Cents frequency scale to Hertz.

v_cent2frq

v_cent2frq(c) -> tuple[ndarray, ndarray]

Convert cents scale values to frequencies in Hz.

Parameters:

Name Type Description Default
c array_like

Cents scale values. 100 cents = one semitone. 440 Hz = 5700 cents.

required

Returns:

Name Type Description
frq ndarray

Frequencies in Hz.

cr ndarray

Gradient in Hz/cent.

Notes

c = 1200 * log2(f / (440 * 2^((3/12) - 5)))

References

[1] Ellis, A. "On the Musical Scales of Various Nations", Journal of the Society of Arts, 1885.

Source code in pyvoicebox/v_cent2frq.py
def v_cent2frq(c) -> tuple[np.ndarray, np.ndarray]:
    """Convert cents scale values to frequencies in Hz.

    Parameters
    ----------
    c : array_like
        Cents scale values. 100 cents = one semitone. 440 Hz = 5700 cents.

    Returns
    -------
    frq : ndarray
        Frequencies in Hz.
    cr : ndarray
        Gradient in Hz/cent.

    Notes
    -----
    c = 1200 * log2(f / (440 * 2^((3/12) - 5)))

    References
    ----------
    [1] Ellis, A. "On the Musical Scales of Various Nations", Journal of
        the Society of Arts, 1885.
    """
    c = np.asarray(c, dtype=float)
    p = 1200.0 / np.log(2.0)
    q = 5700.0 - p * np.log(440.0)
    af = np.exp((np.abs(c) - q) / p)
    frq = np.sign(c) * af
    cr = af / p
    return frq, cr

v_frq2midi

V_FRQ2MIDI - Convert frequencies to musical note numbers.

v_frq2midi

v_frq2midi(f) -> tuple[ndarray, list]

Convert frequencies in Hz to MIDI note numbers.

Parameters:

Name Type Description Default
f array_like

Frequencies in Hz. Negative frequencies produce flats instead of sharps in the text representation.

required

Returns:

Name Type Description
n ndarray

MIDI note numbers. Middle C is 60. Note 69 (A above middle C) = 440 Hz. Note numbers are not necessarily integers.

t list of str

Text representation of the nearest note. E.g. 'C4 ' for middle C, 'C4#' for C sharp. For negative frequencies, flats are used: 'D4-'.

Source code in pyvoicebox/v_frq2midi.py
def v_frq2midi(f) -> tuple[np.ndarray, list]:
    """Convert frequencies in Hz to MIDI note numbers.

    Parameters
    ----------
    f : array_like
        Frequencies in Hz. Negative frequencies produce flats instead of
        sharps in the text representation.

    Returns
    -------
    n : ndarray
        MIDI note numbers. Middle C is 60. Note 69 (A above middle C) = 440 Hz.
        Note numbers are not necessarily integers.
    t : list of str
        Text representation of the nearest note. E.g. 'C4 ' for middle C,
        'C4#' for C sharp. For negative frequencies, flats are used: 'D4-'.
    """
    f = np.asarray(f, dtype=float)
    n = 69.0 + 12.0 * np.log(np.abs(f) / 440.0) / np.log(2.0)

    # Text representation
    m = np.round(n).astype(int).ravel()
    f_flat = f.ravel()
    o = m // 12 - 1
    # MATLAB 1-based index; subtract 1 for Python 0-based
    m_idx = m - 12 * o + 6 * np.sign(f_flat).astype(int) - 5 - 1

    # First 12: flat names, next 12: sharp names (matching MATLAB layout)
    a_str = 'CDDEEFGGAABBCCDDEFFGGAAB'  # 24 chars
    b_str = ' - -  - - -  # #  # # # '  # 24 chars

    t = []
    for i in range(len(m)):
        idx = int(m_idx[i])
        # Clamp index to valid range (0 to 23)
        idx = max(0, min(23, idx))
        note = a_str[idx]
        octave = str(int(o[i]) % 10)
        accidental = b_str[idx]
        t.append(note + octave + accidental)

    return n, t

v_midi2frq

V_MIDI2FRQ - Convert musical note numbers to frequencies.

v_midi2frq

v_midi2frq(n, s='e') -> ndarray

Convert MIDI note numbers to frequencies in Hz.

Parameters:

Name Type Description Default
n array_like

MIDI note numbers. Middle C is 60. Note 69 = 440 Hz.

required
s str

Scale type: 'e' - equal tempered (default) 'p' - Pythagorean scale 'j' - just intonation

'e'

Returns:

Name Type Description
f ndarray

Frequencies in Hz.

Source code in pyvoicebox/v_midi2frq.py
def v_midi2frq(n, s='e') -> np.ndarray:
    """Convert MIDI note numbers to frequencies in Hz.

    Parameters
    ----------
    n : array_like
        MIDI note numbers. Middle C is 60. Note 69 = 440 Hz.
    s : str, optional
        Scale type:
        'e' - equal tempered (default)
        'p' - Pythagorean scale
        'j' - just intonation

    Returns
    -------
    f : ndarray
        Frequencies in Hz.
    """
    n = np.asarray(n, dtype=float)

    if s and s[0] == 'p':
        r = np.array([256/243, 9/8, 32/27, 81/64, 4/3, 729/512,
                       3/2, 128/81, 27/16, 16/9, 243/128])
    elif s and s[0] == 'j':
        r = np.array([16/15, 9/8, 6/5, 5/4, 4/3, 36/25,
                       3/2, 8/5, 5/3, 9/5, 15/8])
    else:
        r = None

    if r is not None:
        # MATLAB: c=[0 0 12*log(r)/log(2)-(1:11) 0] has 14 elements (1-based indices 1..14)
        # Python: c has 14 elements (0-based indices 0..13)
        c = np.zeros(14)
        c[2:13] = 12.0 * np.log(r) / np.log(2.0) - np.arange(1, 12)
        # c[0]=0, c[1]=0, c[2..12]=corrections, c[13]=0
        nm = np.mod(n, 12.0)
        na = np.floor(nm).astype(int)
        nb = nm - na
        # MATLAB: c(na+2) and c(na+3) with 1-based indexing
        # Python: c[na+1] and c[na+2] with 0-based indexing
        f = 440.0 * np.exp((n + c[na + 1] * (1.0 - nb) + c[na + 2] * nb - 69.0) * np.log(2.0) / 12.0)
    else:
        f = 440.0 * np.exp((n - 69.0) * np.log(2.0) / 12.0)

    return f