Repository: librosa/librosa

Issue:
Overflow in `feature.rms` when dtype='int16'
Edit - I guess this is because you're supposed to only use float with librosa?

```
In [1]: import numpy as np
In [2]: from librosa import feature
In [3]: y = np.random.randint(-32768, 32768, 10000, dtype='int16')
In [4]: feature.rms(y=y, frame_length=256, hop_length=32)
venv/lib/python3.8/site-packages/librosa/feature/spectral.py:981: RuntimeWarning: invalid value encountered in sqrt
  return np.sqrt(power)
```

The `RuntimeWarning` is because it's trying to compute the sqrt of a negative (overflowed) number.





Discussion:
That's a good question.  Nothing in the docs or definition suggests that small int inputs are *not* supported, but I think it's sane to expect a floating point conversion at some point in the computation.  That's not happening because `util.abs2` preserves dtype implicitly for real-valued inputs (including int):

https://github.com/librosa/librosa/blob/bd4cd2009c126da744780d4f4b04a7efc32fd618/librosa/util/utils.py#L2509-L2514

Probably the easiest fix here is to extend the `abs2` function to support an output dtype, and replacing `x**2` by `np.power(x, 2, dtype=dtype)`.  It might take a little finagling to make this square with the numba-accelerated implementation for complex inputs, but I think it's doable.

Shorter term: you could obviously resolve this from the user side by casting to float first.

Resolving patch:
diff --git a/librosa/feature/spectral.py b/librosa/feature/spectral.py
--- a/librosa/feature/spectral.py
+++ b/librosa/feature/spectral.py
@@ -17,6 +17,7 @@
 from ..core.constantq import cqt, hybrid_cqt, vqt
 from ..core.pitch import estimate_tuning
 from typing import Any, Optional, Union, Collection
+from numpy.typing import DTypeLike
 from .._typing import _FloatLike_co, _WindowSpec, _PadMode, _PadModeSTFT
 
 
@@ -807,6 +808,7 @@ def rms(
     hop_length: int = 512,
     center: bool = True,
     pad_mode: _PadMode = "constant",
+    dtype: DTypeLike = np.float32,
 ) -> np.ndarray:
     """Compute root-mean-square (RMS) value for each frame, either from the
     audio samples ``y`` or from a spectrogram ``S``.
@@ -834,6 +836,8 @@ def rms(
     pad_mode : str
         Padding mode for centered analysis.  See `numpy.pad` for valid
         values.
+    dtype : np.dtype, optional
+        Data type of the output array.  Defaults to float32.
 
     Returns
     -------
@@ -880,7 +884,7 @@ def rms(
         x = util.frame(y, frame_length=frame_length, hop_length=hop_length)
 
         # Calculate power
-        power = np.mean(util.abs2(x), axis=-2, keepdims=True)
+        power = np.mean(util.abs2(x, dtype=dtype), axis=-2, keepdims=True)
     elif S is not None:
         # Check the frame length
         if S.shape[-2] != frame_length // 2 + 1:
@@ -893,7 +897,7 @@ def rms(
             )
 
         # power spectrogram
-        x = np.abs(S) ** 2
+        x = util.abs2(S, dtype=dtype)
 
         # Adjust the DC and sr/2 component
         x[..., 0, :] *= 0.5
diff --git a/librosa/util/utils.py b/librosa/util/utils.py
--- a/librosa/util/utils.py
+++ b/librosa/util/utils.py
@@ -2481,7 +2481,7 @@ def _cabs2(x: _ComplexLike_co) -> _FloatLike_co:  # pragma: no cover
 _NumberOrArray = TypeVar("_NumberOrArray", bound=Union[_Number, np.ndarray])
 
 
-def abs2(x: _NumberOrArray) -> _NumberOrArray:
+def abs2(x: _NumberOrArray, dtype: Optional[DTypeLike] = None) -> _NumberOrArray:
     """Compute the squared magnitude of a real or complex array.
 
     This function is equivalent to calling `np.abs(x)**2` but it
@@ -2491,6 +2491,9 @@ def abs2(x: _NumberOrArray) -> _NumberOrArray:
     ----------
     x : np.ndarray or scalar, real or complex typed
         The input data, either real (float32, float64) or complex (complex64, complex128) typed
+    dtype : np.dtype, optional
+        The data type of the output array.
+        If not provided, it will be inferred from `x`
 
     Returns
     -------
@@ -2508,10 +2511,14 @@ def abs2(x: _NumberOrArray) -> _NumberOrArray:
     """
     if np.iscomplexobj(x):
         # suppress type check, mypy doesn't like vectorization
-        return _cabs2(x)  # type: ignore
+        y = _cabs2(x)
+        if dtype is None:
+            return y  # type: ignore
+        else:
+            return y.astype(dtype)  # type: ignore
     else:
         # suppress type check, mypy doesn't know this is real
-        return x**2  # type: ignore
+        return np.power(x, 2, dtype=dtype)  # type: ignore
 
 
 @numba.vectorize(


Test patch:
diff --git a/tests/test_util.py b/tests/test_util.py
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -1367,6 +1367,26 @@ def test_abs2_complex(x, dtype):
     assert p.dtype == librosa.util.dtype_c2r(x_cast.dtype)
 
 
+def test_abs2_int_dtype():
+    x = np.arange(5, dtype=np.int16)
+    y = librosa.util.abs2(x, dtype=None)
+    assert x.dtype == y.dtype
+
+    z = librosa.util.abs2(x, dtype=np.float32)
+    assert z.dtype == np.float32
+
+
+def test_abs2_complex_dtype():
+    x = np.arange(5, dtype=np.complex64)
+    y = librosa.util.abs2(x, dtype=None)
+    assert np.isrealobj(y)
+    # complex64 -> float32 by default
+    assert y.dtype == np.float32
+
+    z = librosa.util.abs2(x, dtype=np.float64)
+    # force it to float64
+    assert z.dtype == np.float64
+
 
 @pytest.mark.parametrize('dtype', [np.float32, np.float64])
 @pytest.mark.parametrize('angles', [np.pi/2, [np.pi/2, -np.pi/3]])