diff --git a/lisainstrument/dependencies/LICENSE_pyplnoise b/lisainstrument/dependencies/LICENSE_pyplnoise
new file mode 100644
index 0000000000000000000000000000000000000000..2765310a334c619e3fb44909800f1d0a745e35ed
--- /dev/null
+++ b/lisainstrument/dependencies/LICENSE_pyplnoise
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2019, Jan Waldmann
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/lisainstrument/dependencies/__init__.py b/lisainstrument/dependencies/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lisainstrument/dependencies/pyplnoise.py b/lisainstrument/dependencies/pyplnoise.py
new file mode 100644
index 0000000000000000000000000000000000000000..4306ea08c73b389c4fe4680cd6689d47de256ba0
--- /dev/null
+++ b/lisainstrument/dependencies/pyplnoise.py
@@ -0,0 +1,295 @@
+"""Noise generators for long streams of 1/f^alpha noise.
+
+The time domain methods used here follow Stephane Plaszczynski, Fluct.
+Noise Lett. 7: R-R13, 2007. DOI: 10.1142/S0219477507003635, see also
+https://arxiv.org/abs/astro-ph/0510081. The code sticks to the notation
+in this article (which is different to the SciPy docs).
+
+Examples and documentation are available on GitHub:
+https://github.com/janwaldmann/pyplnoise
+"""
+
+from typing import List, Optional, Tuple
+
+import numpy as np
+from scipy import signal
+
+
+class WhiteNoise:
+    """White noise generator (constant power spectrum)."""
+
+    @property
+    def fs(self) -> float:
+        """Get the sampling frequency."""
+        return self._fs
+
+    @property
+    def rms(self) -> float:
+        """Get the noise signal RMS value."""
+        return self._rms
+
+    def __init__(self,
+                 f_sample: float,
+                 psd: float = 1.,
+                 seed: Optional[int] = None) -> None:
+        """Create a WhiteNoise instance.
+
+        Args:
+            f_sample: sampling frequency in Hz.
+            psd: constant value of the two-sided power
+                spectral density. The RMS value of the generated noise
+                is sqrt(f_sample x psd).
+            seed: seed for the random number generator. If none is provided,
+                entropy from the system will be used.
+        """
+        self._fs = f_sample
+        self._rms = np.sqrt(f_sample * psd)
+        self._rng = np.random.default_rng(seed)
+
+    def get_sample(self) -> float:
+        """Retrieve a single sample."""
+        return self._rng.normal(loc=0., scale=self.rms)
+
+    def get_series(self, npts: int) -> np.ndarray:
+        """Retrieve an array of npts samples."""
+        if npts > np.iinfo(int).max:
+            raise ValueError("""Argument 'npts' must be an integer <= {}.
+If you want to obtain more samples, run get_series() several times and
+concatenate the results.""".format(np.iinfo(int).max))
+        return self._rng.normal(loc=0., scale=self.rms, size=npts)
+
+
+class RedNoise:
+    """Red (Brownian) noise generator (1/f^2 power spectrum).
+
+    The two-sided power spectral density (PSD) is scaled such that
+    PSD(f = 1 Hz) = 1. Below f_min, the noise is white.
+    """
+
+    @property
+    def fs(self) -> float:
+        """Get the sampling rate."""
+        return self._fs
+
+    @property
+    def fmin(self) -> float:
+        """Get the lower cutoff frequency."""
+        return self._fmin
+
+    def __init__(self,
+                 f_sample: float,
+                 f_min: float,
+                 init_filter: bool = True,
+                 seed: Optional[int] = None) -> None:
+        """Create a RedNoise instance.
+
+        Args:
+            f_sample: sampling frequency in Hz.
+            f_min: frequency cutoff in Hz. Below f_min, the noise
+                will be white.
+            init_filter: settle filter during object initialization. This
+                might take some time depending on the magnitude of the
+                ratio f_sample/f_min. Default and highly recommended: True.
+            seed: seed for the random number generator. If none is
+                provided, entropy from the system will be used.
+
+        """
+        self._fs = f_sample
+        self._fmin = f_min
+        self._whitenoise = WhiteNoise(self.fs, psd=1., seed=seed)
+        self._scaling = 1. / (self.fs * self.fmin)
+        self._a = np.array([2. * np.pi * self.fmin])
+        self._b = np.array(
+            [1., -1. * np.exp(-2. * np.pi * self.fmin / self.fs)])
+        self._zi = signal.lfilter_zi(self._a,
+                                     self._b) * self._whitenoise.get_sample()
+        if init_filter:
+            npts_req = np.ceil(2. * self.fs / self.fmin)
+            # safeguard for machines with small memory
+            npts_per_run_max = 150000000
+            if npts_req > npts_per_run_max:
+                for i in range(
+                        np.ceil(npts_req / npts_per_run_max).astype(int)):
+                    _ = self.get_series(npts_per_run_max)
+            else:
+                _ = self.get_series(int(npts_req))
+
+    def get_sample(self) -> np.float64:
+        """Retrieve a single sample."""
+        sample, self._zi = signal.lfilter(self._a,
+                                          self._b,
+                                          np.array(
+                                              [self._whitenoise.get_sample()]),
+                                          zi=self._zi)
+        return sample[0] * self._scaling
+
+    def get_series(self, npts: int) -> np.ndarray:
+        """Retrieve an array of npts samples."""
+        if npts > np.iinfo(int).max:
+            raise ValueError("""Argument 'npts' must be an integer <= {}.
+If you want to obtain more samples, run get_series() several times and
+concatenate the results.""".format(np.iinfo(int).max))
+        samples, self._zi = signal.lfilter(self._a,
+                                           self._b,
+                                           self._whitenoise.get_series(npts),
+                                           zi=self._zi)
+        return samples * self._scaling
+
+
+class AlphaNoise:
+    """Colored noise noise generator (arbitrary 1/f^alpha power spectrum).
+
+    The two-sided power spectral density (PSD) is scaled such that
+    PSD(f = 1 Hz) = 1. The noise generator has user-specified lower and
+    upper cutoff frequencies. Below and above these frequencies, the
+    generated noise is white.
+    """
+
+    @property
+    def fs(self) -> float:
+        """Get the sampling rate."""
+        return self._fs
+
+    @property
+    def fmin(self) -> float:
+        """Get the lower cutoff frequency."""
+        return self._fmin
+
+    @property
+    def fmax(self) -> float:
+        """Get the upper cutoff frequency."""
+        return self._fmax
+
+    @property
+    def alpha(self) -> float:
+        """Get the exponent of the 1/f^alpha power spectrum."""
+        return self._alpha
+
+    def __init__(self,
+                 f_sample: float,
+                 f_min: float,
+                 f_max: float,
+                 alpha: float,
+                 init_filter: bool = True,
+                 seed: Optional[int] = None) -> None:
+        """Create an AlphaNoise instance.
+
+        Args:
+            f_sample: sampling frequency in Hz.
+            f_min: lower frequency cutoff in Hz. Below f_min, the noise
+                will be white.
+            f_max: upper frequency cutoff in Hz. Above f_max, the noise
+                will be white.
+            alpha: exponent of the 1/f^alpha power spectrum. Must be in the
+                interval [0.01, 2.0].
+            init_filter: settle filter during object initialization. This
+                might take some time depending on the magnitude of the
+                ratio f_sample/f_min. Default and highly recommended: True.
+            seed: seed for the random number generator. If none is
+                provided, entropy from the system will be used.
+        """
+        if alpha > 2. or alpha < 0.01:
+            raise ValueError(
+                "The exponent must be in the range 0.01 <= alpha <= 2.")
+        if f_sample < 2. * f_max:
+            raise ValueError(
+                "The sampling rate must be at least 2 x f_max (= {}).".format(
+                    2. * f_max))
+        self._fs = f_sample
+        self._fmin = f_min
+        self._fmax = f_max
+        self._alpha = alpha
+        self._whitenoise = WhiteNoise(self.fs, psd=1., seed=seed)
+        log_w_min = np.log10(2. * np.pi * self.fmin)
+        log_w_max = np.log10(2. * np.pi * self.fmax)
+        self._num_spectra = np.ceil(4.5 * (log_w_max - log_w_min)).astype(int)
+        dp = (log_w_max - log_w_min) / self._num_spectra
+        self._a: List[np.ndarray] = [None] * self._num_spectra
+        self._b: List[np.ndarray] = [None] * self._num_spectra
+        self._zi: List[np.ndarray] = [None] * self._num_spectra
+        for i in range(0, self._num_spectra):
+            log_p_i = log_w_min + dp * 0.5 * ((2. * i + 1.) - self.alpha / 2.)
+            filter_f_min = np.power(10., log_p_i) / (2. * np.pi)
+            filter_f_max = np.power(10., log_p_i +
+                                    (dp * self.alpha / 2.)) / (2. * np.pi)
+            if i == 0:
+                self._fmin = filter_f_min
+            a0, a1, b1 = self._calc_filter_coeff(filter_f_min, filter_f_max)
+            self._a[i] = np.array([a0, a1])
+            self._b[i] = np.array([1., -1. * b1])
+            self._zi[i] = signal.lfilter_zi(self._a[i], self._b[i])
+        self._fmax = filter_f_max
+        self._scaling = 1. / np.power(self.fmax, self.alpha / 2.)
+        if init_filter:
+            npts_req = np.ceil(2. * self.fs / self.fmin)
+            # safeguard for machines with small memory
+            npts_per_run_max = 150000000
+            if npts_req > npts_per_run_max:
+                for i in range(
+                        np.ceil(npts_req / npts_per_run_max).astype(int)):
+                    _ = self.get_series(npts_per_run_max)
+            else:
+                _ = self.get_series(int(npts_req))
+
+    def get_sample(self) -> np.float64:
+        """Retrieve a single sample."""
+        sample = np.array([self._whitenoise.get_sample()])
+        for i in range(0, self._num_spectra):
+            sample, self._zi[i] = signal.lfilter(self._a[i],
+                                                 self._b[i],
+                                                 sample,
+                                                 zi=self._zi[i])
+        return sample[0] * self._scaling
+
+    def get_series(self, npts: int) -> np.ndarray:
+        """Retrieve an array of npts samples."""
+        if npts > np.iinfo(int).max:
+            raise ValueError("""Argument 'npts' must be an integer <= {}.
+If you want to obtain more samples, run get_series() several times and
+concatenate the results.""".format(np.iinfo(int).max))
+        samples = self._whitenoise.get_series(npts)
+        for i in range(0, self._num_spectra):
+            samples, self._zi[i] = signal.lfilter(self._a[i],
+                                                  self._b[i],
+                                                  samples,
+                                                  zi=self._zi[i])
+        return samples * self._scaling
+
+    def _calc_filter_coeff(self, f_min: float,
+                           f_max: float) -> Tuple[float, float, float]:
+        a0 = (self.fs + f_max * np.pi) / (self.fs + f_min * np.pi)
+        a1 = -1. * (self.fs - f_max * np.pi) / (self.fs + f_min * np.pi)
+        b1 = (self.fs - f_min * np.pi) / (self.fs + f_min * np.pi)
+        return (a0, a1, b1)
+
+
+class PinkNoise(AlphaNoise):
+    """Pink noise generator (1/f power spectrum)."""
+
+    @property
+    def fs(self) -> float:
+        """Get the sampling rate."""
+        return self._fs
+
+    @property
+    def fmin(self) -> float:
+        """Get the lower cutoff frequency."""
+        return self._fmin
+
+    @property
+    def fmax(self) -> float:
+        """Get the upper cutoff frequency."""
+        return self._fmax
+
+    def __init__(self,
+                 f_sample: float,
+                 f_min: float,
+                 f_max: float,
+                 init_filter: bool = True,
+                 seed: Optional[int] = None) -> None:
+        """Create a PinkNoise instance."""
+        self._fs = f_sample
+        self._fmin = f_min
+        self._fmax = f_max
+        AlphaNoise.__init__(self, f_sample, f_min, f_max, 1., init_filter,
+                            seed)
diff --git a/lisainstrument/noises.py b/lisainstrument/noises.py
index 9c715ea2558791d3d2ac40b3f6d5e2770008a4f5..eeb9260f9fd2407b5e72b2d45bffd266ec2af958 100644
--- a/lisainstrument/noises.py
+++ b/lisainstrument/noises.py
@@ -11,7 +11,7 @@ Authors:
 
 import logging
 import numpy as np
-import pyplnoise
+from .dependencies import pyplnoise
 
 from numpy import pi, sqrt
 from lisaconstants import c
diff --git a/requirements.txt b/requirements.txt
index 3a409bdb8d04e4ee78f023c01da0237b26d38fbe..7eec399b403a94fbcbeea1d4a627cd5c0aca9faa 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -78,7 +78,6 @@ pyerfa==2.0.0.1
 Pygments==2.11.2
 pylint==2.14.3
 pyparsing==3.0.7
-pyplnoise==1.3
 pyrsistent==0.18.1
 pytest==7.1.1
 python-dateutil==2.8.2
diff --git a/setup.py b/setup.py
index 9ca88e6ebbec5dc3f0319bcc083f12c1b7dce966..bffce83a0d87ad0e3b62c84b4cc9dbbcd568409f 100644
--- a/setup.py
+++ b/setup.py
@@ -27,7 +27,6 @@ setuptools.setup(
         'h5py',
         'numpy',
         'scipy',
-        'pyplnoise',
         'matplotlib',
         'lisaconstants',
         'packaging',