# -*- coding: utf-8 -*-
# src\scene_rir\rir.py

"""
    SCENE RIR extractor, SCENE project, room impulse response extraction
    Copyright (C) 2025 Christos Sevastiadis

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
"""

import os
import sys
import numpy as np
import scipy as sp

# Vector with sampling rates, in samples per second, or Hz.
_SAMPLING_RATES = np.array([11025, 16000, 22050, 32000, 44100, 48000], dtype=np.uint32)

# Vector with signal sizes in samples, in steps of power of .two, for FFT calculation
# efficiency.
_SIGNAL_SIZES = np.array([2 ** (14 + i) for i in range(7)])

# Default peak level for any produced signal, in deciBel relative to full scale, dB FS.
_PEAK_LEVEL_FS = -3

_DEFAULT_OUTPUT_SUBFOLDER = "output"

_DEFAULT_INPUT_SUBFOLDER = "input"

_DEFAULT_SS_SIGNAL_FILENAME = "ss-signal.wav"

_DEFAULT_IRSSIGNAL_FILENAME = "irs-signal.wav"

_DEFAULT_INPUT_REFERENCE_FILENAME = "ref-signal.wav"

_DEFAULT_INPUT_RECORDING_FILENAME = "rec-signal.wav"

# Default start frequency of swept-sine, in hertz, Hz.
_DEFAULT_START_FREQUENCY = 20

# Default stop frequency of swept-sine, in hertz, Hz.
_DEFAULT_STOP_FREQUENCY = 20000

# Default ante swept-sine silence duration, in seconds, s.
_DEFAULT_ANTE_SILENCE_DURATION = 0

# Default post swept-sine silence duration, in seconds, s.
_DEFAULT_POST_SILENCE_DURATION = 0

_USAGE_STRING = """Usage: python -m scene_rir.rir [command] [path] [rec_path] [ref_path]
or
python3 -m scene_rir.rir [command] [path] [rec_path] [ref_path]
Available commands:
ss-save   Save the default swept-sine signal in the `path` file.
ir-save   Save the impulse response signal in the `path` file, calculated by
          recorder signal read from the `rec_path` file, and reference signal
          read from the `ref_path` file.
"""


class SweptSineSignal:
    """Sweep signal generation class.

    Attributes
    ----------

    smprte : int
        Sampling rate of the signal, in hertz (Hz) or samples per second, with default
        value 44100 Hz.
    sglsze : int
        Signal size, in samples, with default value 128 kibit.
    frqstt : float
        Start frequency of the swept-sine signal, in hertz (Hz), with default value 10
        Hz.
    frqstp : float
        Stop frequency of the swept-sine signal, in hertz (Hz), with default value
        20000 Hz.
    sgllvl : float
        Signal peak level, in decibel relative to full scale (dB FS), with default
        value -3 dB FS.
    antslcdur : float
        Ante swept-sine waveform silence duration, in seconds (s), with default
        valuse 0.01 s.
    pstslcdur : float
        Post swept-sine waveform silence duration, in seconds (s), with default
        value 0.01 s.
    ss_rtetyp : string
        Swept-sine increase rate of frequency in time, "log" for logarithmic, or
        "lin" for linear, with default value "log".
    ss_sglvec : ndarray
        Swept-sine signal vector.
    """

    def __init__(self, params=None):
        """Initialize the class.

        Sampling rate index, smprteidx, signal size index, sglszeidx, combination
        table for sampling rate (Hz), smprte, signal size (samples), sglsze, and
        signal duration (s), sgldur.

                   sglszeidx       0       1       2       3       4       5       6
             sglsze, samples   16384   32768   65536  131072  262144  524288 1048576
        smprteidx smprte, Hz                       sgldur, s
                0      11025   1.486   2.972   5.944  11.889  23.777  47.554  95.109
                1      16000   1.024   2.048   4.096   8.192  16.384  32.768  65.536
                2      22050   0.743   1.486   2.972   5.944  11.889  23.777  47.554
                3      32000   0.512   1.024   2.048   4.096   8.192  16.384  32.768
                4      44100   0.372   0.743   1.486   2.972   5.944  11.889  23.777
                5      48000   0.341   0.683   1.365   2.731   5.461  10.923  21.845

        Parameters
        ----------
        params : dict
            An initialization parameters dictionary where:
            - "smprteidx" (int): Sample rate selection index, from 0 to 5, with default
                value 4, for 44100 Hz. Select a value from _SAMPLING_RATES ndarray,
                [11025, 16000, 22050, 32000, 44100, 48000], in hertz (Hz).
            - "sglszeidx" (int): Signal size selection index, from 0 to 6, with default
                value 3, for 128 kibit of signal samples, or 2.731 seconds of signal
                duration, when 44100 Hz (smprtidx = 4) sampling rate is defined.
                Select a value from _SIGNAL_SIZES ndarray, corresponding to [16, 32, 64,
                128, 255, 512, 1024] kibit signal samples, or corresponding to [0.372,
                0.743, 1.486, 2.972, 5.944, 11.889, 23.777] time duration, in seconds
                (s).
            - "frqstt" (int): Swept-sine start frequency, in hertz (Hz), with default
                value 20 Hz.
            - "frqstp" (int): Swept-sine stop frequency, in hertz (Hz), with default
                value 20000 Hz.
            - "sgllvl" (float): Signal peak level, in decibel relative to full scale
                (dB FS), with default value -3 dB FS."
            - "antslcdur" (float): Ante swept-sine waveform silence duration, in seconds
                (s), with default value 0.01 s.
            - "pstslcdur" (float): Post swept-sine waveform silence duration, in seconds
                (s), with default value 0.01 s.
            - "ss_rtetyp" (string): Swept-sine increase rate of frequency in time, "log"
                for logarithmic, or "lin" for linear, with default value "log".

        Examples
        --------
        Usage example of saving, reading the default swept-sine signal in the default a
        WAV file, and ploting it with matplotlib.

        >>> import scene_rir.rir as rir
        >>> from matplotlib import pyplot as plt
        >>> import numpy as np
        >>> import scipy as sp
        >>>
        >>> signal = rir.SweptSineSignal()
        >>> signal.save()
        >>>
        >>> (smprte, sglvec) = sp.io.wavfile.read("output/ss-signal.wav")
        >>> sgldur = sglvec.size / smprte
        >>> tmevec = np.linspace(0, sgldur, sglvec.size)
        >>>
        >>> fig1, ax1 = plt.subplots()
        >>> ax1.plot(tmevec, sglvec)
        [<matplotlib.lines.Line2D object at 0x000002148814C910>]
        >>> ax1.set_xlim(0, sgldur)
        (0.0, 2.972154195011338)
        >>> ax1.set_ylim(-1, 1)
        (-1.0, 1.0)
        >>> plt.show()

        >>> # Usage example of creating and using a swept-sine signal in a Python script.
        >>>
        >>> import scene_rir.rir as rir
        >>> from matplotlib import pyplot as plt
        >>>
        >>> params2 = {"sgllvl": -3,
        ...            "antslcdur": 0.1,
        ...            "pstslcdur": 0.1,
        ...            "ss_rtetyp": "log",
        ...           }
        >>> signal2 = rir.SweptSineSignal(params2)
        >>> sglvec = signal2.signal_vector()
        >>> tmevec = signal2.time_vector()
        >>> sgldur = signal2.sgldur
        >>>
        >>> fig, ax = plt.subplots()
        >>> ax.plot(tmevec, sglvec)
        [<matplotlib.lines.Line2D object at 0x00000215EFFAACE0>]
        >>> ax.set_xlim(0, sgldur)
        (0.0, 2.9721542)
        >>> ax.set_ylim(-1, 1)
        (-1.0, 1.0)
        >>> plt.show()

        Usage from command line (Windows OS):

        > python -m scene_rir.rir
        Usage: python -m scene_rir.rir [command] [parameter1] [parameter2]
        or
        python3 -m scene_rir.rir [command] [parameter1] [parameter2]
        Available commands:
        save   Save the default swept-sine signal.

        > python -m scene_rir.rir --help
        Usage: python -m scene_rir.rir [command] [parameter1] [parameter2]
        or
        python3 -m scene_rir.rir [command] [parameter1] [parameter2]
        Available commands:
        save   Save the default swept-sine signal.

        > python -m scene_rir.rir save my_folder/my_signal.wav
        """

        if params is None:
            params = {}
        if "smprteidx" in params:
            smprteidx = int(params["smprteidx"])
        else:
            smprteidx = 4
        if smprteidx >= 0 and smprteidx <= 5:
            self.smprte = _SAMPLING_RATES[smprteidx]
        else:
            raise ValueError(
                "Argument for the sampling rate index parameter, smprteidx,"
                " out of bounds, [0, 5]."
            )
        if "sglszeidx" in params:
            sglszeidx = int(params["sglszeidx"])
        else:
            sglszeidx = 3
        if sglszeidx >= 0 and sglszeidx <= 7:
            self.sglsze = _SIGNAL_SIZES[sglszeidx]
        else:
            raise ValueError(
                "Argument for the fft size parameter, sglszeidx,"
                " out of bounds, [0, 7]."
            )
        if "frqstt" in params:
            self.frqstt = params["frqstt"]
        else:
            self.frqstt = _DEFAULT_START_FREQUENCY
        if "frqstp" in params:
            self.frqstp = int(params["frqstp"])
        else:
            self.frqstp = _DEFAULT_STOP_FREQUENCY
        if self.frqstt > self.smprte / 2 or self.frqstt > self.frqstp:
            raise ValueError(
                f"The argument {self.frqstt} for the sweep start frequency parameter, "
                f"frqstt, should not be higher than the half of the sampling frequency, "
                f"i.e. {self.smprte / 2} Hz, and higher than the argument for the sweep "
                f"stop frequency parameter, frqstp, i.e. {self.frqstp} Hz."
            )
        if self.frqstp < self.frqstt or self.frqstp > self.smprte / 2:
            raise ValueError(
                f"The argument {self.frqstp} for the sweep stop frequency parameter, "
                "frqstp, should not be lower than the arguement for the sweep start "
                f"parameter, frqstt, i.e. {self.frqstt} Hz, and higher than the half of "
                f"the sampling frequency, i.e. {self.smprte /2} Hz."
            )
        if "sgllvl" in params:
            self.sgllvl = params["sgllvl"]
        else:
            self.sgllvl = _PEAK_LEVEL_FS
        if self.sgllvl > 0:
            raise ValueError(
                "Argument for the signal level, sgllvl, should be negative"
                " or zero dB FS."
            )
        if "antslcdur" in params:
            self.antslcdur = params["antslcdur"]
        else:
            self.antslcdur = _DEFAULT_ANTE_SILENCE_DURATION
        if "pstslcdur" in params:
            self.pstslcdur = params["pstslcdur"]
        else:
            self.pstslcdur = _DEFAULT_POST_SILENCE_DURATION
        if "ss_rtetyp" in params:
            self.ss_rtetyp = params["ss_rtetyp"]
        else:
            self.ss_rtetyp = "log"
        if self.ss_rtetyp != "log" and self.ss_rtetyp != "lin":
            raise ValueError(
                "Argument for the sweep sine rate type parameter, ss_rtetyp,"
                " should be 'log', for logarithmic, or 'lin' for linear."
            )

        self.calculate_sgldur()

    def _print_help_table(self):
        """
        Print the table of sampling rate index, smprteidx, signal size index, sglszeidx,
        combination table for sampling rate (Hz), smprte, signal size (samples),
        sglsze, and signal duration (s), sgldur.
        """

        print("           sglszeidx", end="")
        for k in range(7):
            print(f" {k:7d}", end="")
        print()

        print("     sglsze, samples", end="")
        for k in range(7):
            tmpparams = {
                "smprteidx": 4,
                "sglszeidx": k,
            }
            tmpsgl = SweptSineSignal(tmpparams)
            print(f" {tmpsgl.sglsze:7d}", end="")
        print()

        print("smprteidx smprte, Hz                       sgldur, s")
        for i in range(6):
            for k in range(7):
                tmpparams = {
                    "smprteidx": i,
                    "sglszeidx": k,
                }
                tmpsgl = SweptSineSignal(tmpparams)
                if k == 0:
                    print(f"{i:9d} {tmpsgl.smprte:10d}", end="")
                print(f" {tmpsgl.sgldur:7.3f}", end="")
            print()

    def calculate_sgldur(self):
        """Calculate the signal duration."""

        self.sgldur = round(self.sglsze / self.smprte, 7)

    def time_vector(self):
        """Generate the time vector of the signal.

        Returns
        -------
        ndarray
            A vector array corresponding to the signal time, in seconds.
        """

        self.calculate_sgldur()

        return np.linspace(0, self.sgldur, self.sglsze)

    def _frqfdelen(self, frqfde, smprte, swplen, fdetyp=""):
        """
        Calculate the fade in/out length for the given frequency.

        Parameters
        ----------
        frqfde : float
            End or begin frequency of fade-in or fade-out [Hz].
        smprte : float
            Sampling rate [Hz] or [samples per second].
        swplen : int
            Sweep length [-].

        Returns
        -------
        fdelen : int
            Fade length.

        """

        if fdetyp == "lin":
            return int(np.ceil(min(swplen / 10, max(smprte / frqfde**2, swplen / 200))))
        else:
            return int(np.ceil(min(swplen / 10, max(smprte / frqfde, swplen / 200))))

    def _iniswp(self, frqstt, frqstp, fftsze, smprte, typ="", antslcdur=0, pstslcdur=1):
        """
        Initialize sweep.

        Parameters
        ----------
            frqstt: float
                Start frequency [Hz].
            frqstp : float
                Stop frequency [Hz].
            fftsze : int
                FFT size [samples].
            smprte : float
                Sampling time frequency [Hz] or [samples/s].
            typ : {'lin', 'log'}
                Sweep rate type, 'lin': Linear, 'log': Logarithmic.
            antslcdur : float
                Ante sweep silence duration [s].
            pstslcdur : float
                Post sweep silence duration [s].

        Returns
        -------
            frqsttcnd : float
                Conditioned start frequency [Hz].
            swplen : int
                Number of sweep samples [-].
            swpdur : float
                Sweep duration [s].
            antslclen : int
                Number of ante sweep silence duration samples [-].
            antfdelen : int
                Start group delay sample number [-].
            pstfdelen : int
                Number of post fade samples [-].

        """

        antslclen = int(np.ceil(antslcdur * smprte))
        pstslclen = int(np.ceil(pstslcdur * smprte))
        swplen = fftsze - antslclen - pstslclen
        swpdur = swplen / smprte
        frqsttcnd = int(np.ceil(max(frqstt, smprte / fftsze)))
        antfdelen = self._frqfdelen(frqsttcnd, smprte, swplen, fdetyp=typ)
        antfdelen = self._frqfdelen(frqsttcnd, smprte, swplen, fdetyp=typ)
        pstfdelen = self._frqfdelen(frqstp, smprte, swplen, fdetyp=typ)
        return frqsttcnd, swplen, swpdur, antslclen, antfdelen, pstfdelen

    def _cndswp(self, swpswf, swplen, antslclen, antfdelen, pstfdelen):
        """
        Condition a swept-sine signal, by fading in and out and adding silence.

        Parameters
        ----------
            swpswf : ndarray
                Swept-sine signal to be conditioned.
            swplen : int
                Swept-sine size, in samples.
            antslclen : int
                Ante silence size, in samples.
            antfdelen : int
                Ante fade size, in samples.
            pstfdelen : int
                Post fade size, in samples.

        Returns
        -------
            ndarray
                The conditioned swept-sine signal vector.

        """

        # Apply Hanning enveloping fade in of the swept-signal.
        wndstt = np.hanning(2 * antfdelen)
        swpswf[antslclen : (antslclen + antfdelen)] = (
            swpswf[antslclen : (antslclen + antfdelen)] * wndstt[:antfdelen]
        )
        # Apply Hanning enveloping fade out of the swept-signal.
        wndstp = np.hanning(2 * pstfdelen)
        swpswf[(antslclen + swplen - pstfdelen) : (antslclen + swplen)] = (
            swpswf[(antslclen + swplen - pstfdelen) : (antslclen + swplen)]
            * wndstp[pstfdelen:]
        )
        swpswf[(antslclen + swplen) :] = 0
        return swpswf

    def _ss_sgltme(
        self,
        frqstt,
        frqstp,
        fftsze,
        smprte,
        antslcdur,
        pstslcdur,
        swpapv=1 / np.sqrt(2),
        typ="log",
    ):
        """
        Return a swept-sine signal in the time domain.

        Parameters
        ----------
            frqstt : float
                Sweep start frequency [Hz].
            frqstp : float
                Sweep stop freqeuncy [Hz].
            fftsze : int
                FFT size [samples].
            smprte : float
                Sampling time frequency [Hz] or [samples/s].
            antslcdur : float
                Ante sweep silence duration [s].
            pstslcdur : float
                Post sweep silence dura [s].
            swpapv : float
                Sweep signal waveform amplitude peak value [-].
            typ : {'lin', 'log'}
                Sweep rate type, 'lin': Linear, 'log': Logarithmic, with default value
                "log".

        Returns
        -------
            swpswf : ndarray
                The swept-sine signal vector.
        """

        self.calculate_sgldur()
        frqsttcnd, swplen, __, antslclen, antfdelen, pstfdelen = self._iniswp(
            frqstt, frqstp, fftsze, smprte, typ, antslcdur, pstslcdur
        )
        swppha = np.zeros(fftsze)
        #    # Swen Muller approach
        #    difphi = 2*pi*frqsttcnd/smprte
        if typ == "lin":
            # Angelo Farina approach
            swpdur = swplen / smprte
            tme = np.linspace(0, swpdur, swplen)
            pha = (
                2 * np.pi * frqsttcnd * tme
                + 2 * np.pi * (frqstp - frqsttcnd) / swpdur * tme**2 / 2
            )
            swppha[antslclen : antslclen + swplen] = pha
        #        # Swen Muller approach
        #        incphi = 2*pi*(frqstp - frqsttcnd) / (smprte*swplen)
        #        for i in range(antslclen, antslclen + swplen):
        #            swppha[i] = swppha[i - 1] + difphi
        #            difphi = difphi + incphi
        elif typ == "log":
            # Angelo Farina approach
            swpdur = swplen / smprte
            tme = np.linspace(0, swpdur, swplen)
            pha = (
                2
                * np.pi
                * frqsttcnd
                * swpdur
                / np.log(frqstp / frqsttcnd)
                * (np.exp(tme / swpdur * np.log(frqstp / frqsttcnd)) - 1)
            )
            swppha[antslclen : (antslclen + swplen)] = pha
        #        # Swen Muller approach
        #        mulphi = exp(log(frqstp/frqsttcnd)/swplen)
        #        for i in range(antslclen, antslclen + swplen):
        #            swppha[i] = swppha[i - 1] + difphi
        #            difphi = difphi*mulphi
        return self._cndswp(
            swpapv * np.sin(swppha), swplen, antslclen, antfdelen, pstfdelen
        )

    def signal_vector(self):
        """Create and return the swept-sine signal vector.

        Returns
        -------
        ndarray
            The swept-sine signal vector.
        """

        self.ss_sglvec = self._ss_sgltme(
            self.frqstt,
            self.frqstp,
            self.sglsze,
            self.smprte,
            self.antslcdur,
            self.pstslcdur,
            10 ** (self.sgllvl / 20),
            self.ss_rtetyp,
        )

        return self.ss_sglvec

    def save(self, /, path=None):
        """Save the swept-sine signal in a WAV file.

        Parameters
        ----------
        path : string
            The filename path, for example "my_signal.wav" or
            "my_output/my_signal.wav". Default value is
            "./output/ss-signal.wav".
        """

        self.signal_vector()
        if path is None:
            path = self.path
        else:
            self.path = path
        dirname = os.path.dirname(path)
        if len(dirname) != 0 and not os.path.exists(dirname):
            os.makedirs(dirname)
        sp.io.wavfile.write(path, self.smprte, self.ss_sglvec)


class ImpulseResponseSignal:
    """Impulse response signal calculation class.

    Attributes
    ----------

    rec_path : string
        Path of the recorded input WAV file, with default value "input/rec-signal.wav".
    ref_path : string)
        Path of the reference input WAV file, with default value "input/ref-signal.wav".
    frqstt : float
        Start frequency of the swept-sine signal, in hertz (Hz), with default value 20
        Hz.
    frqstp : float
        Stop frequency of the swept-sine signal, in hertz (Hz), with default balue
        20000 Hz.
    sgllvl : float
        Signal peak level, in decibel relative to full scale (dB FS).
    ss_rtetyp : string
        Swept-sine increase rate of frequency in time, "log" for logarithmic, or
        "lin" for linear, with default value "log".
    irssgl : ndarray
        Impulse response signal vector.
    """

    def __init__(self, params=None):
        """Initilize the class.

        Parameters
        ----------
        params : dict
            An initialization parameters dictionary where:
            - "rec_path" (string): Path of the recorded input WAV file.
            - "ref_path" (string): Path of the reference input WAV file.
            - "frqstt" (int): Swept-sine start frequency, in hertz (Hz), with default
                value 20 Hz.
            - "frqstp" (int): Swept-sine stop frequency, in hertz (Hz), with default
                value 20000 Hz.
            - "sgllvl" (float): Signal peak level, in decibel relative to full scale
                (dB FS).
            - "ss_rtetyp" (string): Swept-sine increase rate of frequency in time, "log"
                for logarithmic, or "lin" for linear, with default value "log".

        Examples
        --------
        >>> # Usage example of extracting the impulse response in a Python script.
        >>>
        >>> import scene_rir.rir as rir
        >>> import numpy as np
        >>> from matplotlib import pyplot as plt
        >>>
        >>> irs_signal = rir.ImpulseResponseSignal()
        >>> (smprte, irssglvec) = irs_signal.signal_vector()
        >>>
        >>> fig3, ax3 = plt.subplots(3, 1)
        >>>
        >>> refsglvec = irs_signal.refsglvec
        >>> sgldur = refsglvec.size / irs_signal.smprte
        >>> tmevec = np.linspace(0, sgldur, refsglvec.size)
        >>> ax3[0].plot(tmevec, refsglvec)
        [<matplotlib.lines.Line2D object at 0x0000020BDF62E110>]
        >>> ax3[0].set_xlim(0, sgldur)
        (0.0, 2.972154195011338)
        >>>
        >>> recsglvec = irs_signal.recsglvec
        >>> sgldur = recsglvec.size / irs_signal.smprte
        >>> tmevec = np.linspace(0, sgldur, recsglvec.size)
        >>> ax3[1].plot(tmevec, recsglvec)
        [<matplotlib.lines.Line2D object at 0x0000020BDF62E350>]
        >>> ax3[1].set_xlim(0, sgldur)
        (0.0, 4.728503401360544)
        >>>
        >>> sgldur = irssglvec.size / smprte
        >>> tmevec = np.linspace(0, sgldur, irssglvec.size)
        >>> ax3[2].plot(tmevec, irssglvec)
        [<matplotlib.lines.Line2D object at 0x0000020BDF62E800>]
        >>> ax3[2].set_xlim(0, sgldur)
        (0.0, 1.486077097505669)
        >>> ax3[2].set_ylim(-1, 1)
        (-1.0, 1.0)
        >>>
        >>> plt.show()

        >>> # Usage example of extracting the impulse response in a Python script.
        >>>
        >>> import scene_rir.rir as rir
        >>> import numpy as np
        >>> import scipy as sp
        >>> from matplotlib import pyplot as plt
        >>>
        >>> params = {
        ...     "rec_path": "input/rec-signal.wav",
        ...     "ref_path": "output/ref-signal.wav",
        ...     "path": "output/irs-signal.wav",
        ...     "sgllvl": 0,
        ... }
        >>> irs_signal = rir.ImpulseResponseSignal(params)
        >>> irs_signal.save()
        >>>
        >>> fig3, ax3 = plt.subplots(3, 1)
        >>>
        >>> refsglvec = irs_signal.refsglvec
        >>> sgldur = refsglvec.size / irs_signal.smprte
        >>> tmevec = np.linspace(0, sgldur, refsglvec.size)
        >>> ax3[0].plot(tmevec, refsglvec)
        [<matplotlib.lines.Line2D object at 0x000002D23FF9E1D0>]
        >>> ax3[0].set_xlim(0, sgldur)
        (0.0, 2.972154195011338)
        >>>
        >>> recsglvec = irs_signal.recsglvec
        >>> sgldur = recsglvec.size / irs_signal.smprte
        >>> tmevec = np.linspace(0, sgldur, recsglvec.size)
        >>> ax3[1].plot(tmevec, recsglvec)
        [<matplotlib.lines.Line2D object at 0x000002D23FF9E410>]
        >>> ax3[1].set_xlim(0, sgldur)
        (0.0, 4.728503401360544)
        >>>
        >>> (smprte, sglvec) = sp.io.wavfile.read("output/irs-signal.wav")
        >>> sgldur = sglvec.size / smprte
        >>> tmevec = np.linspace(0, sgldur, sglvec.size)
        >>> ax3[2].plot(tmevec, sglvec)
        [<matplotlib.lines.Line2D object at 0x000002D23FF9E980>]
        >>> ax3[2].set_xlim(0, sgldur)
        (0.0, 1.486077097505669)
        >>> ax3[2].set_ylim(-1, 1)
        (-1.0, 1.0)
        >>>
        >>> plt.show()

        """

        if params is None:
            params = {}
        if "ref_path" in params:
            self.ref_path = params["ref_path"]
        else:
            self.ref_path = (
                "./"
                + _DEFAULT_INPUT_SUBFOLDER
                + "/"
                + _DEFAULT_INPUT_REFERENCE_FILENAME
            )
        if "rec_path" in params:
            self.rec_path = params["rec_path"]
        else:
            self.rec_path = (
                "./"
                + _DEFAULT_INPUT_SUBFOLDER
                + "/"
                + _DEFAULT_INPUT_RECORDING_FILENAME
            )
        if "frqstt" in params:
            self.frqstt = params["frqstt"]
        else:
            self.frqstt = _DEFAULT_START_FREQUENCY
        if "frqstp" in params:
            self.frqstp = int(params["frqstp"])
        else:
            self.frqstp = _DEFAULT_STOP_FREQUENCY
        if self.frqstt > self.frqstp:
            raise ValueError(
                f"The argument {self.frqstt} for the sweep start frequency parameter, "
                f"frqstt, should not be higher than the argument for the sweep stop "
                f"frequency parameter, frqstp, i.e. {self.frqstp} Hz."
            )
        if self.frqstp < self.frqstt:
            raise ValueError(
                f"The argument {self.frqstp} for the sweep stop frequency parameter, "
                "frqstp, should not be lower than the arguement for the sweep start "
                f"parameter, frqstt, i.e. {self.frqstt} Hz."
            )
        if "sgllvl" in params:
            self.sgllvl = params["sgllvl"]
        else:
            self.sgllvl = _PEAK_LEVEL_FS
        if self.sgllvl > 0:
            raise ValueError(
                "Argument for the signal level, sgllvl, should be negative"
                " or zero dB FS."
            )
        if "ss_rtetyp" in params:
            self.ss_rtetyp = params["ss_rtetyp"]
        else:
            self.ss_rtetyp = "log"
        if self.ss_rtetyp != "log" and self.ss_rtetyp != "lin":
            raise ValueError(
                "Argument for the sweep sine rate type parameter, ss_rtetyp,"
                " should be 'log', for logarithmic, or 'lin' for linear."
            )

    def signal_vector(self):
        """Create and return the impulse response signal vector.

        Returns
        -------
        int
            The sampling rate of the signal.
        ndarray
            The impulse response signal vector.
        """

        # Read the reference swept-sine signal from a WAV file.
        (self.smprte, self._refsglvec) = sp.io.wavfile.read(self.ref_path)
        # Read the recorded response signal from a WAV file.
        (recsmprte, self._recsglvec) = sp.io.wavfile.read(self.rec_path)
        # Test if the signals have the same sampling rate.
        if self.smprte != recsmprte:
            raise ValueError(
                "Error: Sampling rate should be the same for both reference signal and"
                " recorded signal wave files."
            )
        # Inverse in time the reference swept-sine signal
        invrefvec = np.flip(
            self._refsglvec if np.ndim(self._refsglvec) == 1 else self._refsglvec[:, 0]
        )
        if self.ss_rtetyp == "log":
            # Find where the swept-sine signal starts, after the ante swept-sine signal
            # silence.
            for i in range(self._refsglvec.size):
                if self._refsglvec[i] != 0:
                    sttidx = i - 1
                    if sttidx < 0:
                        sttidx = 0
                    break
            # Find where the post swept-sine signal silence starts, after stopping the
            # swept-sine.
            for i in range(self._refsglvec.size - 1, -1, -1):
                if self._refsglvec[i] != 0:
                    stpidx = i + 2
                    if stpidx > self._refsglvec.size:
                        stpidx = self._refsglvec.size
                    break
            # Calculate the size of the swept-sine signal, without the ante and post
            # silences.
            swplen = stpidx - sttidx
            # Calculate the duration in seconds, of the swept-sine signal, without
            # the silent ante and post parts.
            swpdur = swplen / self.smprte
            # Create the time vector of the swept-sine signal, in seconds.
            swptmevec = np.linspace(0, swpdur, swplen)
            # Calculate the the level envelope for the time-inversed swept-sine, of -3 dB
            # decrease per octave, starting from 0 dB, and endint to -3*lb(frqstp/frqstt) dB.
            swpenvlvlvec = (
                swptmevec * (-6) * np.log2(self.frqstp / self.frqstt) / swpdur
            )
            # Calculate the factor vector of the envelope.
            swpenvvec = 10 ** (swpenvlvlvec / 20)
            # Find where the reversed in time swept-sine signal starts, after the reversed
            # post swept-sine signal silence.
            for i in range(invrefvec.size):
                if invrefvec[i] != 0:
                    sttidx = i - 1
                    if sttidx < 0:
                        sttidx = 0
                    break
            # Find where the reversed in time swept-sine signal stops, before the reversed
            # ante swept-sine signal silence.
            for i in range(invrefvec.size - 1, -1, -1):
                if invrefvec[i] != 0:
                    stpidx = i + 2
                    if stpidx > self._refsglvec.size:
                        stpidx = self._refsglvec.size
                    break
            # Apply the envelope over the swept-sine signal only.
            self._invrefvec = np.concatenate(
                (
                    np.zeros(sttidx),
                    invrefvec[sttidx:stpidx] * swpenvvec,
                    np.zeros(invrefvec.size - stpidx),
                )
            )
        else:
            self._invrefvec = invrefvec
        # A proper (power of 2) FFT size is selected, to accomodate the longest signal.
        # The recorded signal should be longer than the reference (swept-sine) one, but
        # it is tested which one is the longest, for safety.
        fftsze = 2 ** int(
            np.ceil(np.log2(self._refsglvec.size + self._recsglvec.size - 1))
        )  # Next power of 2
        # Get the inversed in time reference signal in the frequency domain.
        self._invrefspc = np.fft.fft(self._invrefvec, fftsze)
        # Get the recorder response signal in the frequency domain.
        self._recspc = np.fft.fft(
            (
                self._recsglvec
                if np.ndim(self._recsglvec) == 1
                else self._recsglvec[:, 0]
            ),  # Left channel by default
            fftsze,
        )
        # Get the reference swept-sine signal in the frequency domain.
        self._refspc = np.fft.fft(
            (
                self._refsglvec
                if np.ndim(self._refsglvec) == 1
                else self._refsglvec[:, 0]
            ),  # Left channel by default
            fftsze,
        )
        # Calculate the impulse response, by multiplying the recorder reponse signal
        # spectrum by the inversed in time reference swept-sine spectrum.
        self._irsspc = self._recspc * self._invrefspc
        # Get the impulse response in real time.
        irssglvec = np.fft.ifft(self._irsspc).real
        # Normalize the impulse response to sgllvl dB bellow its maximum absolut value.
        maxval = np.max(abs(irssglvec))
        irssglvec = irssglvec * 10 ** (self.sgllvl / 20) / maxval
        # Crop the impulse response signal, discarding the half first part and the excess
        # last part.
        self.irssglvec = irssglvec[
            self._refsglvec.size - 1 : 2 * self._refsglvec.size - 1
        ]

        return (self.smprte, self.irssglvec)

    def save(self, /, path=None):
        """Save the impulse response signal in a WAV file.

        Parameters
        ----------
        path : string
            The filename path, for example "my_signal.wav" or
            "my_output/my_signal.wav". Default value is
            "./output/ss-signal.wav".
        """

        self.signal_vector()
        if path is None:
            path = "./" + _DEFAULT_OUTPUT_SUBFOLDER + "/" + _DEFAULT_IRSSIGNAL_FILENAME
        dirname = os.path.dirname(path)
        if len(dirname) != 0 and not os.path.exists(dirname):
            os.makedirs(dirname)
        sp.io.wavfile.write(path, self.smprte, self.irssglvec)


if __name__ == "__main__":
    if len(sys.argv) > 1:
        if sys.argv[1] == "--help":
            print(_USAGE_STRING)
        elif sys.argv[1] == "ss-save":
            signal = SweptSineSignal()
            if len(sys.argv) > 2:
                signal.save(sys.argv[2])
            else:
                print("Error: No filename path provided.")
                print("Run 'python -m scene_rir.rir --help' for usage.")
        elif sys.argv[1] == "ir-save":
            if len(sys.argv) >= 5:
                params = {
                    "rec_path": sys.argv[3],
                    "ref_path": sys.argv[4],
                }
                signal = ImpulseResponseSignal()
                signal.save(sys.argv[2])
            else:
                print("Error: No enough arguments provided.")
                print("Run 'python -m scene_rir.rir --help' for usage.")
        else:
            print(f'Error: unknown command "{sys.argv[1]}" for "scene_rir".')
            print("Run 'python -m scene_rir.rir --help' for usage.")
    else:
        print(_USAGE_STRING)
