Raised-cosine pulse shaping in Julia
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:
| MATLAB | SignalAnalysis.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# 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)
figVerification 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:
| shape | max |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:
rcosdesignis a recent addition to the Octavesignalpackage (only on its development branch at time of writing), so a stockpkg install -forge signalmay not include it yet — another reason theSignalAnalysis.jlroute is the more convenient one in Julia today.