Raised-cosine pulse shaping in Julia

Abstract

MATLAB's rcosdesign(beta, span, sps, shape) designs a (root-)raised-cosine FIR pulse-shaping filter. Julia's DSP.jl has no direct equivalent, but SignalAnalysis.jl provides both shapes as thin wrappers over DSP.digitalfilter:

MATLABSignalAnalysis.jl
rcosdesign(β, span, sps, 'normal')rcosfir(β, sps, span)
rcosdesign(β, span, sps, 'sqrt')rrcosfir(β, sps, span)

Arguments map cleanly: β rolloff, sps samples per symbol, span filter span in symbols. This notebook designs both filters, plots their impulse and frequency responses, and demonstrates the zero-ISI (Nyquist) property with a matched transmit/receive pair.

"loaded SignalAnalysis 0.11.0, CairoMakie 0.15.11"
# Design parameters (same meaning as MATLAB's rcosdesign)
β    = 0.25      # rolloff factor
sps  = 8         # samples per symbol
span = 10        # filter span, in symbols

rc  = rcosfir(β, sps, span)    # normal raised cosine  ('normal')
rrc = rrcosfir(β, sps, span)   # root raised cosine    ('sqrt')

(taps_rc = length(rc), taps_rrc = length(rrc), type = eltype(rc))
(taps_rc = 81, taps_rrc = 81, type = Float64)
# Time axis in symbol periods: taps span ±span/2 symbols, sps samples per symbol
t = range(-span/2, span/2; length = length(rc))

fig = Figure()
ax = Axis(fig[1, 1]; xlabel = "time  (symbol periods)", ylabel = "amplitude",
          title = "Impulse response — raised cosine vs root-raised cosine (β = $β)")

lines!(ax, t, rc;  label = "RC  (rcosfir)",  linewidth = 2)
lines!(ax, t, rrc; label = "RRC (rrcosfir)", linewidth = 2)

# The normal RC has zero crossings at every nonzero integer symbol offset (zero-ISI).
symt = -span÷2 : span÷2
scatter!(ax, symt, rc[1:sps:end]; color = :orangered, markersize = 9,
         label = "RC at symbol instants")
hlines!(ax, [0]; color = (:gray, 0.5), linestyle = :dash)
axislegend(ax; position = :rt)
fig
output
# Magnitude response by direct DTFT; frequency in units of the SYMBOL RATE (cycles/symbol).
# Digital frequency = f/sps cycles/sample, so f = sps/2 is Nyquist of the sampled filter.
H(h, f) = sum(h[m+1] * cispi(-2 * (f / sps) * m) for m in 0:length(h)-1)
freqs   = range(0, 1.5; length = 800)
mag_db(h) = (m = abs.(H.(Ref(h), freqs)); 20 .* log10.(m ./ maximum(m)))

fig = Figure()
ax = Axis(fig[1, 1]; xlabel = "frequency  (× symbol rate)", ylabel = "magnitude (dB)",
          title = "Frequency response (β = $β)")

lines!(ax, freqs, mag_db(rc);  label = "RC",  linewidth = 2)
lines!(ax, freqs, mag_db(rrc); label = "RRC", linewidth = 2)

# Ideal RC band edges: passband to (1-β)/2, stopband from (1+β)/2, -6 dB at 1/2.
vlines!(ax, [(1 - β)/2, 0.5, (1 + β)/2]; color = (:gray, 0.5), linestyle = :dash)
ylims!(ax, -80, 5)
axislegend(ax; position = :lb)
fig
output

Verification against a reference implementation

To confirm that rcosfir/rrcosfir really are a drop-in for MATLAB's rcosdesign, the coefficients were checked against an independent, openly-licensed reference: the GNU Octave signal package's rcosdesign.m (GPL-3). Octave's function is written for MATLAB compatibility and uses the same standard closed-form (root-)raised-cosine impulse responses, the same tap grid t = (-span·sps/2 : span·sps/2)/sps, and the same unit-energy normalization (b / norm(b), i.e. Σbᵢ² = 1).

Test performed. The reference rcosdesign.m was run in GNU Octave 11.3.0 for the parameters used here — β = 0.25, span = 10, sps = 8, for both "normal" and "sqrt" shapes — and its 81-tap output was differenced against rcosfir/rrcosfir:

shapemax |Octave − SignalAnalysis|center tap agreement
"normal" (RC)≈ 2.8 × 10⁻¹⁷identical to 17 significant figures
"sqrt" (RRC)≈ 5.6 × 10⁻¹⁷identical to 17 significant figures

The worst-case per-tap difference sits at the floating-point rounding floor (eps ≈ 2.2 × 10⁻¹⁶), so the two are numerically identical. The only non-numerical differences are in the wrappers, not the math: rcosdesign requires span and errors unless span·sps is even, whereas SignalAnalysis supplies a default span heuristic and floors an odd order; and Octave detects the removable singularities with a sqrt(eps) tolerance while SignalAnalysis tests for them by exact equality (equivalent when a singular point lands on the sample grid, as it does for β = 0.25).

Note: rcosdesign is a recent addition to the Octave signal package (only on its development branch at time of writing), so a stock pkg install -forge signal may not include it yet — another reason the SignalAnalysis.jl route is the more convenient one in Julia today.