Waveform

The module waveform.py implements the Waveform class which encapsulates a time-domain signal transmitted from a set of transmitter antennas or received by a set of receiver antennas. A waveform object is usually created by applying OFDM modulation to a resource grid object. See the ofdmModulate() method of the Grid class for more information.

class neoradium.waveform.Waveform(waveform, noiseVar=0)

This class encapsulates a set of sequences of complex values representing the time-domain signals as transmitted by each transmitter antenna or as received by each receiver antenna. A Waveform object is usually created by applying OFDM modulation to a resource grid.

Once you have a Waveform object, you can apply a channel model to it, add AWGN noise to it, or apply other signal processing tasks such as windowing. All of these processes result in new Waveform objects.

At the receiver, the received signals are usually converted back to the frequency domain by applying OFDM demodulation, which results in a Grid object representing the received resource grid.

Parameters:
  • waveform (2D complex NumPy array) – A P x Ns 2D complex NumPy array representing a set of time-domain signals of length Ns for each one of P antenna elements. The value P is equal to Nt, the number of transmitter antennas when this is a transmitted signal, and equal to Nr, the number of receiver antennas when this is a received signal.

  • noiseVar (float) – The variance of the noise applied to the time-domain signals in this Waveform object. This is usually initialized to zero. When an AWGN noise is applied to the waveform using the addNoise() function, the variance of the noise is stored in the Waveform object.

Other Read-Only Properties:

shape:

Returns the shape of the 2-dimensional waveform NumPy array.

numPorts:

The number of transmitter or receiver antennas (P) for this waveform.

length:

The length of the time-domain signal in number of samples (Ns).

print(indent=0, title=None, getStr=False)

Prints the properties of this Waveform object.

Parameters:
  • indent (int) – The number of indentation characters.

  • title (str) – If specified, it is used as the title for the printed information.

  • getStr (bool) – If True, returns a string instead of printing it.

Returns:

If the getStr parameter is True, then this function returns the information in a string. Otherwise, nothing is returned.

Return type:

None or str

addNoise(**kwargs)

Adds Additive White Gaussian Noise (AWGN) to this waveform and returns a new Waveform object. The returned waveform contains the noisy signal, and its noiseVar property stores the variance of the applied noise.

This method is similar to addNoise(), which applies noise in the frequency domain (resource grid). The main difference is that here noise is applied in the time domain, and therefore depends on the FFT size used during OFDM modulation.

You can provide the noise directly, or specify its standard deviation, variance, or a target SNR.

If you already have a noise signal in a NumPy array, use the noise parameter to add it directly:

Example
myNoise = random.awgn(rxWaveform.shape, 0.1)    # Create AWGN with σ = 0.1
rxWaveform.addNoise(noise=myNoise)

If you already know the standard deviation or variance of the noise, use noiseStd or noiseVar respectively:

Example
rxWaveform.addNoise(noiseStd=0.1)       # Same result as above
rxWaveform.addNoise(noiseVar=0.01)      # Same result as above

If you specify snrDb, this function supports two different interpretations of SNR, controlled by the useRxPower parameter.

1) Reference-power SNR (useRxPower=False)

In this mode, the noise power is computed using a fixed reference signal power, independent of the instantaneous waveform. This approach is closer to typical 3GPP-style link-level simulation methodology, where:

  • The AWGN level is fixed for a given SNR point

  • Channel effects (fading, path loss, beamforming, etc.) affect the received signal but do not change the injected noise power

  • The effective SNR varies naturally with the channel realization

In NeoRadium, this corresponds to assuming a normalized received signal power of \(\frac{1}{N_r}\), where \(N_r\) is the number of receive antennas. Since noise is added in the time domain, the FFT size (\(N_{FFT}\)) must also be taken into account:

\[\sigma^2_{AWGN} = \frac{1}{N_r \cdot N_{FFT} \cdot 10^{\frac{SNR_{dB}}{10}}}\]
Example
rxWaveform.addNoise(snrDb=mySnrDb, bwp=bwp, useRxPower=False)

This mode is recommended for link-level performance evaluation and for generating results comparable to standard BLER vs. SNR curves. It is also the convention used in MATLAB 5G Toolbox link-level simulations.

2) Received-power-based SNR (useRxPower=True)

In this mode, the noise power is derived from the actual received waveform. The function first estimates the average received signal power (mapped to per-RE equivalent using \(N_{FFT}\)), and then applies noise to achieve the requested SNR:

\[\sigma^2_{AWGN} = \frac{\sigma^2_{RX}}{N_{FFT} \cdot 10^{\frac{SNR_{dB}}{10}}}\]

where \(\sigma^2_{RX}\) is the estimated received signal power.

Example
rxWaveform.addNoise(snrDb=mySnrDb, bwp=bwp, useRxPower=True)

This mode enforces a post-channel SNR, meaning that the resulting SNR is tied to the instantaneous received waveform. As a result, variations caused by fading or other channel effects are partially normalized out, since both signal and noise scale together.

This approach is useful for controlled algorithm evaluation (e.g., equalization, channel estimation, or decoding at a fixed received SNR), but is generally less suitable for 3GPP-style link-level performance studies, where channel variability should directly impact the effective SNR.

In summary:

  • useRxPower=False: Reference-power SNR (recommended; closer to 3GPP-style simulations)

  • useRxPower=True: Received-power-based SNR (useful for controlled post-channel SNR experiments)

Please refer to the notebook SNR Calculations in NeoRadium for a detailed discussion of SNR definitions and AWGN scaling in NeoRadium.

Parameters:

kwargs (dict) –

The amount of noise must be specified by one of noise, noiseStd, noiseVar, or snrDb.

noise:

NumPy array with the same shape as this Waveform object containing the noise values. If provided, it is added directly and all other parameters are ignored.

noiseStd:

Standard deviation of the AWGN. Complex zero-mean AWGN is generated using the specified standard deviation. If provided, noiseVar and snrDb are ignored.

noiseVar:

Variance of the AWGN. Complex zero-mean AWGN is generated using the specified variance. If provided, snrDb is ignored.

snrDb:

Signal-to-noise ratio in decibels (dB). When provided, the noise standard deviation is calculated from the given SNR and the parameters bwp and useRxPower.

bwp:

BandwidthPart Bandwidth part used to obtain the FFT size (nFFT). Required when snrDb is specified.

useRxPower:

Controls how snrDb is interpreted.

  • False: Use the reference-power SNR convention. A normalized received power of \(\frac{1}{N_r}\) is assumed. This keeps noise independent of the instantaneous channel realization and is the default.

  • True: Use the actual received waveform power to compute the noise level.

Note

The default is False. This keeps the behavior closer to common 3GPP-style link-level simulations and MATLAB 5G Toolbox conventions. For reproducibility and clarity, explicitly set this parameter.

ranGen:

Random-number generator used for AWGN generation. If not provided, NeoRadium’s global random generator is used.

Returns:

A new waveform containing the noisy version of this waveform.

Return type:

Waveform

pad(numPad)

Appends a sequence of numPad zeros to the end of time-domain signals in this Waveform object.

To make sure a signal is received in its entirety when it goes through a channel model, we usually need to pad zeros to the end of the time-domain signal. The number of these zeros usually depends on the maximum channel delay. The function getMaxDelay() of the channel model can be used to get the number of padded zeros.

Parameters:

numPad (int) – The number of time-domain zero samples to be appended to the end of this waveform.

Returns:

A new Waveform object which is numPad samples longer than the original waveform.

Return type:

Waveform

sync(timingOffset)

Removes timingOffset values from the beginning of the time-domain signals in this Waveform object. This effectively shifts the signal in time domain by timingOffset samples.

When a time-domain signal goes through a channel model, it is delayed in time because of the propagation delay. Different transmission paths may be affected by different propagation delays. The channel’s chanOffset member can be used to obtain the timingOffset. In practice, this value is calculated by finding the time-domain sample index where the correlation between the received signal and a set of reference signals is at its maximum. See for example, the function estimateTimingOffset() of the Grid class.

Parameters:

timingOffset (int) – The number of time-domain samples that are removed from the beginning of the time-domain signals in this Waveform object.

Returns:

A new Waveform object which is timingOffset samples shorter than the original waveform.

Return type:

Waveform

applyChannel(channel)

Applies the channel model channel to this Waveform object and returns a new Waveform object representing the received signal. This function internally calls the applyToSignal() method of the channel model passing in this waveform object as the inputSignal.

Parameters:

channel (ChannelModel) – The channel model that is applied to this time-domain waveform.

Returns:

A new Waveform object which represents the received time-domain waveform.

Return type:

Waveform

applyWindowing(cpLens, windowing, bwp)

This is a helper function that is used to apply windowing to the OFDM waveform obtained from OFDM modulation of a resource grid.

This method supports several different windowing approaches including the ones specified in 3GPP TS 38.101-1 section F.5, table F.5.4-1.

You usually do not need to call this function directly. It is called internally at the end of the OFDM modulation process when the function ofdmModulate() of the Grid class is called.

Parameters:
  • cpLens (list) – A list of integer values each representing the length of the cyclic prefix part at the beginning of each OFDM symbol in number of time-domain samples. This list can be obtained from the BandwidthPart object.

  • windowing (str) –

    A string specifying how the window length is obtained. It can be one of the following:

    ”STD”:

    The windowing size is determined based on 3GPP TS 38.101-1 section F.5, table F.5.4-1.

    Ratio as percentage:

    A windowing ratio can be specified as a percentage value. For example, the text string “%25” represents a windowing ratio of 0.25. The window length is calculated as the minimum value of cpLens multiplied by the windowing ratio, and rounded to the nearest integer value.

    Ratio:

    A windowing ratio (between 0 and 1) can be specified as a number. For example, the string “0.125” represents a windowing ratio of 0.125. The window length is calculated as the minimum value of cpLens multiplied by the windowing ratio and rounded to the nearest integer value.

    Window Length:

    The actual window length can also be specified as an integer value. For example, the text string “164” represents a window length equal to 164.

  • bwp (BandwidthPart) – The bandwidth part used for the communication.

Returns:

A new Waveform object which represents the waveform after applying the windowing.

Return type:

Waveform

ofdmDemodulate(bwp, f0=0, cpOffsetRatio=0.5)

Applies OFDM demodulation to the waveform which results in a frequency-domain resource grid returned as a Grid object.

If an AWGN noise was applied to the waveform using the addNoise() method, then the amount of noise is transferred to the Grid object that is created. The noise variance of the returned resource grid is equal to the waveform’s noise variance times nFFT.

Parameters:
  • bwp (BandwidthPart) – The bandwidth part used for the communication.

  • f0 (float) – The carrier frequency of the waveform. If it is 0 (default), then a baseband waveform is assumed. This should match the value originally used when applying OFDM modulation at the transmitter side. See the ofdmModulate() method of the Grid class.

  • cpOffsetRatio (float) – This value determines where, in the cyclic prefix (as a ratio from the beginning of the CP), the FFT should be applied. The default value of 0.5 means that the FFT is applied at the midpoint of the cyclic prefix.

Returns:

A Grid object representing the received resource grid.

Return type:

Grid