Repository: librosa/librosa
Pull request #1960 (2025-07-17 20:02:19)

SPEC07 compliant rng parameters
#### Reference Issue

Partially implements #1935 

#### What does this implement/fix? Explain your changes.

This PR deprecates the `random_state=` parameters of griffinlim and griffinlim_cqt to a standardized `rng=` parameter as described by SPEC07.

These are the only functions in the library that depend on randomness, so having implemented this, we can endorse SPEC07.

#### Any other comments?

The deprecation is set to expire in librosa 1.2.0.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/librosa/librosa/pull/1960?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=librosa) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 97.88%. Comparing base [(`c02de65`)](https://app.codecov.io/gh/librosa/librosa/commit/c02de657028d82dc5198210f675b6d273b55ca83?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=librosa) to head [(`39cb732`)](https://app.codecov.io/gh/librosa/librosa/commit/39cb73255b583ee0e3699b4cac7ff7a477625af7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=librosa).

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##           1.0.0dev    #1960      +/-   ##
============================================
- Coverage     97.90%   97.88%   -0.03%     
============================================
  Files            35       35              
  Lines          4776     4775       -1     
============================================
- Hits           4676     4674       -2     
- Misses          100      101       +1     
```

| [Flag](https://app.codecov.io/gh/librosa/librosa/pull/1960/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=librosa) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/librosa/librosa/pull/1960/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=librosa) | `97.88% <100.00%> (-0.03%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=librosa#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/librosa/librosa/pull/1960?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=librosa).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=librosa).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/README.md b/README.md
index 10562c9e..6ed35d8b 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ A python package for music and audio analysis.
 [![CI](https://github.com/librosa/librosa/actions/workflows/ci.yml/badge.svg)](https://github.com/librosa/librosa/actions/workflows/ci.yml)
 [![codecov](https://codecov.io/gh/librosa/librosa/branch/main/graph/badge.svg?token=ULWnUHaIJC)](https://codecov.io/gh/librosa/librosa)
 [![Docs](https://github.com/librosa/librosa/actions/workflows/docs.yml/badge.svg)](https://librosa.org/doc/latest/index.html)
-
+[![Scientific Python Ecosystem Coordination](https://img.shields.io/badge/SPEC-1,7-green?labelColor=%23004811&color=%235CA038)](https://scientific-python.org/specs/)
 #  Table of Contents
 
 - [Documentation](#Documentation)
diff --git a/librosa/_typing.py b/librosa/_typing.py
index 79e31d61..a0bc036b 100644
--- a/librosa/_typing.py
+++ b/librosa/_typing.py
@@ -1,12 +1,18 @@
 from __future__ import annotations
 
-from typing import Callable, Generator, List, TypeVar, Union, Tuple, Any, Sequence
+from collections.abc import Sequence
+from typing import Callable, Generator, List, TypeVar, Union, Tuple, Any
 from typing_extensions import Literal, Never
 import numpy as np
 from numpy.typing import ArrayLike
 import scipy.sparse as sp
 
 
+# RNG types
+SeedLike = Union[int, np.integer, Sequence[int], np.random.SeedSequence]
+RNGLike = Union[np.random.Generator, np.random.BitGenerator]
+
+
 _WindowSpec = Union[str, Tuple[Any, ...], float, Callable[[int], np.ndarray], ArrayLike]
 _T = TypeVar("_T")
 _IterableLike = Union[List[_T], Tuple[_T, ...], Generator[_T, None, None]]
diff --git a/librosa/core/constantq.py b/librosa/core/constantq.py
index 860beaba..cfa2a5cf 100644
--- a/librosa/core/constantq.py
+++ b/librosa/core/constantq.py
@@ -15,9 +15,17 @@ from .._cache import cache
 from .. import filters
 from .. import util
 from ..util.exceptions import ParameterError
+from ..util.deprecation import rename_kw, Deprecated
 from numpy.typing import DTypeLike
 from typing import Optional, Union, Collection, List
-from .._typing import _WindowSpec, _PadMode, _FloatLike_co, _ensure_not_reachable
+from .._typing import (
+    _WindowSpec,
+    _PadMode,
+    _FloatLike_co,
+    _ensure_not_reachable,
+    RNGLike,
+    SeedLike,
+)
 
 __all__ = ["cqt", "hybrid_cqt", "pseudo_cqt", "icqt", "griffinlim_cqt", "vqt"]
 
@@ -1226,9 +1234,10 @@ def griffinlim_cqt(
     length: Optional[int] = None,
     momentum: float = 0.99,
     init: Optional[str] = "random",
+    rng: Optional[Union[RNGLike, SeedLike]] = None,
     random_state: Optional[
-        Union[int, np.random.RandomState, np.random.Generator]
-    ] = None,
+        Union[int, np.random.RandomState, np.random.Generator, Deprecated]
+    ] = Deprecated(),
 ) -> np.ndarray:
     """Approximate constant-Q magnitude spectrogram inversion using the "fast" Griffin-Lim
     algorithm.
@@ -1331,7 +1340,15 @@ def griffinlim_cqt(
         an initial guess for phase can be provided, or when you want to resume
         Griffin-Lim from a previous output.
 
+    rng : None, int, sequence of int, np.random.Generator, or np.random.RandomState
+        Pseudorandom number generator state. When `rng` is None, a new
+        `numpy.random.Generator` is created using entropy from the
+        operating system. Types other than `numpy.random.Generator` are
+        passed to `numpy.random.default_rng` to instantiate a ``Generator``.
+
     random_state : None, int, np.random.RandomState, or np.random.Generator
+        .. warning:: This parameter is deprecated in 1.0.0 and will be removed in 1.2.0.
+
         If int, random_state is the seed used by the random number generator
         for phase initialization.
 
@@ -1339,6 +1356,8 @@ def griffinlim_cqt(
 
         If ``None``, defaults to the `np.random.default_rng()` object.
 
+        An exception is raised if both `rng` and `random_state` are provided.
+
     Returns
     -------
     y : np.ndarray [shape=(..., n)]
@@ -1368,27 +1387,39 @@ def griffinlim_cqt(
 
     >>> import matplotlib.pyplot as plt
     >>> fig, ax = plt.subplots(nrows=3, sharex=True, sharey=True)
-    >>> librosa.display.waveshow(y, sr=sr, color='b', ax=ax[0])
+    >>> librosa.display.waveshow(y, sr=sr, color='C0', ax=ax[0])
     >>> ax[0].set(title='Original', xlabel=None)
     >>> ax[0].label_outer()
-    >>> librosa.display.waveshow(y_inv, sr=sr, color='g', ax=ax[1])
+    >>> librosa.display.waveshow(y_inv, sr=sr, color='C1', ax=ax[1])
     >>> ax[1].set(title='Griffin-Lim reconstruction', xlabel=None)
     >>> ax[1].label_outer()
-    >>> librosa.display.waveshow(y_icqt, sr=sr, color='r', ax=ax[2])
+    >>> librosa.display.waveshow(y_icqt, sr=sr, color='C2', ax=ax[2])
     >>> ax[2].set(title='Magnitude-only icqt reconstruction')
     """
     if fmin is None:
         fmin = note_to_hz("C1")
 
-    if random_state is None:
-        rng = np.random.default_rng()
-    elif isinstance(random_state, int):
-        rng = np.random.RandomState(seed=random_state)  # type: ignore
-    elif isinstance(random_state, (np.random.RandomState, np.random.Generator)):
-        rng = random_state  # type: ignore
-    else:
-        _ensure_not_reachable(random_state)
-        raise ParameterError(f"Unsupported random_state={random_state!r}")
+    if not isinstance(random_state, Deprecated):
+        if rng is not None:
+            raise ParameterError(
+                f"Both random_state={random_state!r} and rng={rng!r} were provided. "
+                "Please use only the rng parameter."
+            )
+
+        # Otherwise transfer the state object and throw a deprecation warning
+        rng = rename_kw(
+            old_name="random_state",
+            old_value=random_state,
+            new_name="rng",
+            new_value=rng,
+            version_deprecated="1.0.0",
+            version_removed="1.2.0",
+        )
+
+    # Coerce the various input types to a proper Generator
+    # This branch is necessary until we bump to numpy 2.2
+    if not isinstance(rng, np.random.RandomState):
+        rng = np.random.default_rng(rng)
 
     if momentum > 1:
         warnings.warn(
diff --git a/librosa/core/spectrum.py b/librosa/core/spectrum.py
index 647336b7..b8b8248f 100644
--- a/librosa/core/spectrum.py
+++ b/librosa/core/spectrum.py
@@ -18,7 +18,7 @@ from .audio import resample
 from .._cache import cache
 from .. import util
 from ..util.exceptions import ParameterError
-from ..util.deprecation import Deprecated
+from ..util.deprecation import Deprecated, rename_kw
 from ..filters import get_window, semitone_filterbank
 from ..filters import window_sumsquare
 from numpy.typing import DTypeLike
@@ -32,7 +32,9 @@ from .._typing import (
     _SequenceLike,
     _ScalarOrSequence,
     _ComplexLike_co,
-    _FloatLike_co
+    _FloatLike_co,
+    SeedLike,
+    RNGLike,
 )
 
 __all__ = [
@@ -1481,7 +1483,9 @@ def phase_vocoder(
         raise ParameterError("t_out values must be in the range [0, D.shape[-1])")
 
     if np.any(np.diff(t_out) < 0):
-        warnings.warn("t_out is not monotonic; phase estimation may be unstable", stacklevel=2)
+        warnings.warn(
+            "t_out is not monotonic; phase estimation may be unstable", stacklevel=2
+        )
 
     i0 = np.floor(t_out).astype(int)
     i1 = np.minimum(i0 + 1, n_frames - 1)
@@ -1715,8 +1719,8 @@ def power_to_db(
     ref: Union[float, Callable] = ...,
     amin: float = ...,
     top_db: Optional[float] = ...,
-) -> np.floating[Any]:
-    ...
+) -> np.floating[Any]: ...
+
 
 @overload
 def power_to_db(
@@ -1725,8 +1729,8 @@ def power_to_db(
     ref: Union[float, Callable] = ...,
     amin: float = ...,
     top_db: Optional[float] = ...,
-) -> np.ndarray:
-    ...
+) -> np.ndarray: ...
+
 
 @overload
 def power_to_db(
@@ -1735,8 +1739,8 @@ def power_to_db(
     ref: Union[float, Callable] = ...,
     amin: float = ...,
     top_db: Optional[float] = ...,
-) -> Union[np.floating[Any], np.ndarray]:
-    ...
+) -> Union[np.floating[Any], np.ndarray]: ...
+
 
 @cache(level=30)
 def power_to_db(
@@ -1871,27 +1875,29 @@ def db_to_power(
     S_db: _FloatLike_co,
     *,
     ref: float = ...,
-) -> np.floating[Any]:
-    ...
+) -> np.floating[Any]: ...
+
 
 @overload
 def db_to_power(
-        S_db: np.ndarray,
+    S_db: np.ndarray,
     *,
     ref: float = ...,
-) -> np.ndarray:
-    ...
+) -> np.ndarray: ...
+
 
 @overload
 def db_to_power(
     S_db: Union[_FloatLike_co, np.ndarray],
     *,
     ref: float = ...,
-) -> Union[np.floating[Any], np.ndarray]:
-    ...
+) -> Union[np.floating[Any], np.ndarray]: ...
+
 
 @cache(level=30)
-def db_to_power(S_db: Union[_FloatLike_co, np.ndarray], *, ref: float = 1.0) -> Union[np.floating[Any], np.ndarray]:
+def db_to_power(
+    S_db: Union[_FloatLike_co, np.ndarray], *, ref: float = 1.0
+) -> Union[np.floating[Any], np.ndarray]:
     """Convert dB-scale values to a power values.
 
     This effectively inverts ``power_to_db``::
@@ -1924,8 +1930,8 @@ def amplitude_to_db(
     ref: Union[float, Callable] = ...,
     amin: float = ...,
     top_db: Optional[float] = ...,
-) -> np.floating[Any]:
-    ...
+) -> np.floating[Any]: ...
+
 
 @overload
 def amplitude_to_db(
@@ -1934,8 +1940,8 @@ def amplitude_to_db(
     ref: Union[float, Callable] = ...,
     amin: float = ...,
     top_db: Optional[float] = ...,
-) -> np.ndarray:
-    ...
+) -> np.ndarray: ...
+
 
 @overload
 def amplitude_to_db(
@@ -1944,8 +1950,8 @@ def amplitude_to_db(
     ref: Union[float, Callable] = ...,
     amin: float = ...,
     top_db: Optional[float] = ...,
-) -> Union[np.floating[Any], np.ndarray]:
-    ...
+) -> Union[np.floating[Any], np.ndarray]: ...
+
 
 @cache(level=30)
 def amplitude_to_db(
@@ -2022,27 +2028,29 @@ def db_to_amplitude(
     S_db: _FloatLike_co,
     *,
     ref: float = ...,
-) -> np.floating[Any]:
-    ...
+) -> np.floating[Any]: ...
+
 
 @overload
 def db_to_amplitude(
     S_db: np.ndarray,
     *,
     ref: float = ...,
-) -> np.ndarray:
-    ...
+) -> np.ndarray: ...
+
 
 @overload
 def db_to_amplitude(
     S_db: Union[_FloatLike_co, np.ndarray],
     *,
     ref: float = ...,
-) -> Union[np.floating[Any], np.ndarray]:
-    ...
+) -> Union[np.floating[Any], np.ndarray]: ...
+
 
 @cache(level=30)
-def db_to_amplitude(S_db: Union[_FloatLike_co, np.ndarray], *, ref: float = 1.0) -> Union[np.floating[Any], np.ndarray]:
+def db_to_amplitude(
+    S_db: Union[_FloatLike_co, np.ndarray], *, ref: float = 1.0
+) -> Union[np.floating[Any], np.ndarray]:
     """Convert a dB-scaled spectrogram to an amplitude spectrogram.
 
     This effectively inverts `amplitude_to_db`::
@@ -2358,8 +2366,7 @@ def pcen(
     max_axis: Optional[int] = ...,
     zi: Optional[np.ndarray] = ...,
     return_zf: Literal[False] = ...,
-) -> np.ndarray:
-    ...
+) -> np.ndarray: ...
 
 
 @overload
@@ -2380,8 +2387,7 @@ def pcen(
     max_axis: Optional[int] = ...,
     zi: Optional[np.ndarray] = ...,
     return_zf: Literal[True],
-) -> Tuple[np.ndarray, np.ndarray]:
-    ...
+) -> Tuple[np.ndarray, np.ndarray]: ...
 
 
 @overload
@@ -2402,8 +2408,7 @@ def pcen(
     max_axis: Optional[int] = ...,
     zi: Optional[np.ndarray] = ...,
     return_zf: bool = ...,
-) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]:
-    ...
+) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]: ...
 
 
 @cache(level=30)
@@ -2689,9 +2694,10 @@ def griffinlim(
     pad_mode: _PadModeSTFT = "constant",
     momentum: float = 0.99,
     init: Optional[str] = "random",
+    rng: Optional[Union[RNGLike, SeedLike]] = None,
     random_state: Optional[
-        Union[int, np.random.RandomState, np.random.Generator]
-    ] = None,
+        Union[int, np.random.RandomState, np.random.Generator, Deprecated]
+    ] = Deprecated(),
 ) -> np.ndarray:
     """Approximate magnitude spectrogram inversion using the "fast" Griffin-Lim algorithm.
 
@@ -2767,7 +2773,15 @@ def griffinlim(
         an initial guess for phase can be provided, or when you want to resume
         Griffin-Lim from a previous output.
 
+    rng : None, int, sequence of int, np.random.Generator, or np.random.RandomState
+        Pseudorandom number generator state. When `rng` is None, a new
+        `numpy.random.Generator` is created using entropy from the
+        operating system. Types other than `numpy.random.Generator` are
+        passed to `numpy.random.default_rng` to instantiate a ``Generator``.
+
     random_state : None, int, np.random.RandomState, or np.random.Generator
+        .. warning:: This parameter is deprecated in 1.0.0 and will be removed in 1.2.0.
+
         If int, random_state is the seed used by the random number generator
         for phase initialization.
 
@@ -2776,6 +2790,8 @@ def griffinlim(
 
         If `None`, defaults to the `np.random.default_rng()` object.
 
+        An exception is raised if both `rng` and `random_state` are provided.
+
     Returns
     -------
     y : np.ndarray [shape=(..., n)]
@@ -2804,23 +2820,36 @@ def griffinlim(
 
     >>> import matplotlib.pyplot as plt
     >>> fig, ax = plt.subplots(nrows=3, sharex=True, sharey=True)
-    >>> librosa.display.waveshow(y, sr=sr, color='b', ax=ax[0])
+    >>> librosa.display.waveshow(y, sr=sr, color='C0', ax=ax[0])
     >>> ax[0].set(title='Original', xlabel=None)
     >>> ax[0].label_outer()
-    >>> librosa.display.waveshow(y_inv, sr=sr, color='g', ax=ax[1])
+    >>> librosa.display.waveshow(y_inv, sr=sr, color='C1', ax=ax[1])
     >>> ax[1].set(title='Griffin-Lim reconstruction', xlabel=None)
     >>> ax[1].label_outer()
-    >>> librosa.display.waveshow(y_istft, sr=sr, color='r', ax=ax[2])
+    >>> librosa.display.waveshow(y_istft, sr=sr, color='C2', ax=ax[2])
     >>> ax[2].set_title('Magnitude-only istft reconstruction')
     """
-    if random_state is None:
-        rng = np.random.default_rng()
-    elif isinstance(random_state, int):
-        rng = np.random.RandomState(seed=random_state)  # type: ignore
-    elif isinstance(random_state, (np.random.RandomState, np.random.Generator)):
-        rng = random_state  # type: ignore
-    else:
-        raise ParameterError(f"Unsupported random_state={random_state!r}")
+    if not isinstance(random_state, Deprecated):
+        if rng is not None:
+            raise ParameterError(
+                f"Both random_state={random_state!r} and rng={rng!r} were provided. "
+                "Please use only the rng parameter."
+            )
+
+        # Otherwise transfer the state object and throw a deprecation warning
+        rng = rename_kw(
+            old_name="random_state",
+            old_value=random_state,
+            new_name="rng",
+            new_value=rng,
+            version_deprecated="1.0.0",
+            version_removed="1.2.0",
+        )
+
+    # Coerce the various input types to a proper Generator
+    # This branch is necessary until we bump to numpy 2.2
+    if not isinstance(rng, np.random.RandomState):
+        rng = np.random.default_rng(rng)
 
     if momentum > 1:
         warnings.warn(


Test patch:
diff --git a/tests/test_constantq.py b/tests/test_constantq.py
index 0b132fbe..22c11dcd 100644
--- a/tests/test_constantq.py
+++ b/tests/test_constantq.py
@@ -640,7 +640,7 @@ def y_chirp():
 @pytest.mark.parametrize("pad_mode", ["reflect"])
 @pytest.mark.parametrize("scale", [False, True])
 @pytest.mark.parametrize("momentum", [0.99])
-@pytest.mark.parametrize("random_state", [0])
+@pytest.mark.parametrize("rng", [0])
 @pytest.mark.parametrize("fmin", [40.0])
 @pytest.mark.parametrize("dtype", [np.float32])
 @pytest.mark.parametrize("init", [None])
@@ -657,7 +657,7 @@ def test_griffinlim_cqt(
     scale,
     momentum,
     init,
-    random_state,
+    rng,
     dtype,
 ):
 
@@ -695,7 +695,7 @@ def test_griffinlim_cqt(
         pad_mode=pad_mode,
         n_iter=1,
         momentum=momentum,
-        random_state=random_state,
+        rng=rng,
         length=length,
         res_type=res_type,
         init=init,
@@ -744,20 +744,20 @@ def test_griffinlim_cqt_momentum(y_chirp, momentum):
     assert np.all(np.isfinite(y_rec))
 
 
-@pytest.mark.parametrize("random_state", [None, 0, np.random.RandomState()])
+@pytest.mark.parametrize("rng", [None, 0, np.random.RandomState()])
 @pytest.mark.filterwarnings("ignore:n_fft=.*is too large")
-def test_griffinlim_cqt_rng(y_chirp, random_state):
+def test_griffinlim_cqt_rng(y_chirp, rng):
 
     C = librosa.cqt(y=y_chirp, sr=22050, res_type="polyphase")
     y_rec = librosa.griffinlim_cqt(
-        np.abs(C), sr=22050, n_iter=2, random_state=random_state, res_type="polyphase"
+        np.abs(C), sr=22050, n_iter=2, rng=rng, res_type="polyphase"
     )
 
     y_rec2 = librosa.griffinlim_cqt(
-        np.abs(C), sr=22050, n_iter=2, random_state=random_state, res_type="polyphase"
+        np.abs(C), sr=22050, n_iter=2, rng=rng, res_type="polyphase"
     )
 
-    if not isinstance(random_state, np.random.RandomState) and random_state is not None:
+    if not isinstance(rng, np.random.RandomState) and rng is not None:
         assert np.allclose(y_rec, y_rec2)
 
 
@@ -779,11 +779,11 @@ def test_griffinlim_cqt_badinit():
     librosa.griffinlim_cqt(x, init="garbage")
 
 
-@pytest.mark.xfail(raises=librosa.ParameterError)
+@pytest.mark.xfail(raises=TypeError)
 @pytest.mark.filterwarnings("ignore:n_fft=.*is too large")
 def test_griffinlim_cqt_badrng():
     x = np.zeros((33, 3))
-    librosa.griffinlim_cqt(x, random_state="garbage")  # type: ignore
+    librosa.griffinlim_cqt(x, rng="garbage")  # type: ignore
 
 
 @pytest.mark.xfail(raises=librosa.ParameterError)
@@ -798,6 +798,35 @@ def test_griffinlim_cqt_momentum_warn():
         librosa.griffinlim_cqt(x, momentum=2)
 
 
+def test_griffinlim_cqt_deprecated_randomstate(y_chirp):
+    # Test that the deprecated random_state argument raises a warning
+    C = librosa.cqt(y=y_chirp, sr=22050, res_type="polyphase")
+    with pytest.warns(FutureWarning, match="renamed to 'rng'"):
+        y1 = librosa.griffinlim_cqt(
+            np.abs(C),
+            sr=22050,
+            n_iter=2,
+            random_state=5,
+            res_type="polyphase",
+        )
+    # And verify that it produces the same output as the new rng argument
+    y2 = librosa.griffinlim_cqt(
+        np.abs(C),
+        sr=22050,
+        n_iter=2,
+        rng=5,
+        res_type="polyphase",
+    )
+    assert np.allclose(y1, y2)
+
+
+@pytest.mark.xfail(raises=librosa.ParameterError)
+def test_griffinlim_cqt_oldrng():
+    x = np.zeros((33, 3))
+    # We can't have both parameters set
+    librosa.griffinlim_cqt(x, rng=0, random_state=0)
+
+
 @pytest.mark.parametrize("dtype", [np.complex64, np.complex128])
 def test_cqt_precision(y_cqt, sr_cqt, dtype):
     C = librosa.cqt(y=y_cqt, sr=sr_cqt, dtype=dtype)
diff --git a/tests/test_core.py b/tests/test_core.py
index 0467263a..08945144 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -2311,15 +2311,15 @@ def test_griffinlim_momentum(y_chirp, momentum):
     # No value tests here, just ensuring that it passes cleanly
 
 
-@pytest.mark.parametrize("random_state", [None, 0, np.random.RandomState()])
-def test_griffinlim_state(y_chirp, random_state):
+@pytest.mark.parametrize("rng", [None, 0, np.random.RandomState()])
+def test_griffinlim_state(y_chirp, rng):
     D = librosa.stft(y_chirp)
-    y_rec = librosa.griffinlim(np.abs(D), random_state=random_state, n_iter=1)
+    y_rec = librosa.griffinlim(np.abs(D), rng=rng, n_iter=1)
 
-    y_rec2 = librosa.griffinlim(np.abs(D), random_state=random_state, n_iter=1)
+    y_rec2 = librosa.griffinlim(np.abs(D), rng=rng, n_iter=1)
 
     # Ensure that seeding with a constant gives us consistent values
-    if not isinstance(random_state, np.random.RandomState) and random_state is not None:
+    if not isinstance(rng, np.random.RandomState) and rng is not None:
         assert np.allclose(y_rec, y_rec2)
 
 
@@ -2329,10 +2329,29 @@ def test_griffinlim_badinit():
     librosa.griffinlim(x, init="garbage")
 
 
-@pytest.mark.xfail(raises=librosa.ParameterError)
+@pytest.mark.xfail(raises=TypeError)
 def test_griffinlim_badrng():
     x = np.zeros((33, 3))
-    librosa.griffinlim(x, random_state="garbage")  # type: ignore
+    librosa.griffinlim(x, rng="garbage")  # type: ignore
+
+
+@pytest.mark.xfail(raises=librosa.ParameterError)
+def test_griffinlim_oldrng():
+    x = np.zeros((33, 3))
+    # We can't have both parameters set
+    librosa.griffinlim(x, rng=0, random_state=0)
+
+
+def test_griffinlim_deprecated_randomstate(y_chirp):
+    # Test that the deprecated random_state argument raises a warning
+    D = librosa.stft(y=y_chirp)
+    with pytest.warns(FutureWarning, match="renamed to 'rng'"):
+        y1 = librosa.griffinlim(
+            np.abs(D), n_iter=2, random_state=5,
+        )
+    # And verify that it produces the same output as the new rng argument
+    y2 = librosa.griffinlim(np.abs(D), n_iter=2, rng=5)
+    assert np.allclose(y1, y2)
 
 
 @pytest.mark.xfail(raises=librosa.ParameterError)
@@ -2737,7 +2756,7 @@ def test_phase_vocoder_fixed_rate(rate, rng):
     if rate >= 1:
         assert np.allclose(np.abs(D[:, ::rate]), np.abs(D_stretch))
     else:
-        assert np.allclose(np.abs(D), np.abs(D_stretch[:, ::int(1 / rate)]))
+        assert np.allclose(np.abs(D), np.abs(D_stretch[:, :: int(1 / rate)]))
 
 
 def test_phase_vocoder_variable_rate(rng):
@@ -2753,7 +2772,7 @@ def test_phase_vocoder_variable_rate(rng):
     D_stretch = librosa.phase_vocoder(D, t_out=t_out)
 
     assert D_stretch.shape == (D.shape[0], len(t_out))
-    assert np.allclose(np.abs(D[:, :-2]), np.abs(D_stretch[:, ::int(1 / rate)]))
+    assert np.allclose(np.abs(D[:, :-2]), np.abs(D_stretch[:, :: int(1 / rate)]))
 
 
 @pytest.mark.xfail(raises=librosa.ParameterError)
@@ -2794,6 +2813,7 @@ def test_phase_vocoder_nonmonotone():
     with pytest.warns(UserWarning, match="t_out is not monotonic"):
         D_stretch = librosa.phase_vocoder(D, t_out=t_out)
 
+
 @pytest.mark.parametrize("rate", [0, -1])
 @pytest.mark.xfail(raises=librosa.ParameterError)
 def test_phase_vocoder_negative_rate(rate):