Modified duobinary is the partial-response cousin of duobinary that puts a spectral null at DC. It correlates symbols two baud intervals apart, so the controlled ISI sits on the alternate sample rather than the neighbour.
The Nyquist zero-ISI condition is relaxed to
\[p(nT) = \begin{cases} 1, & n = 0 \\ -1, & n = 2 \\ 0, & \text{otherwise} \end{cases}\]The minus sign at lag \(2T\) is what opens the null at DC. Writing both non-zero samples as \(+1\) would describe a different pulse.
Encoding
A differential precoder of span two, \(c_n = a_n \oplus c_{n-2}\), followed by the polar map \(b_n = 1-2c_n\), feeds the correlative filter
\[y_n = b_n – b_{n-2}\]so the three amplitudes \(+2\), \(0\) and \(-2\) appear. The corresponding continuous-time pulse is
\[h(t) = \operatorname{sinc}\!\left(\frac{t}{T}\right) – \operatorname{sinc}\!\left(\frac{t-2T}{T}\right)\]Decoding
Without the precoder, a recursive subtraction \(\hat{b}_n = y_n + \hat{b}_{n-2}\) again propagates errors. With the precoder, a magnitude test recovers the data directly,
\[|y_n| < d \;\Rightarrow\; \hat{a}_n = 1, \qquad |y_n| > d \;\Rightarrow\; \hat{a}_n = 0\]Python
import numpy as np
def modified_duobinary_encode(bits):
bits = np.asarray(bits, dtype=int)
c = np.zeros(len(bits), dtype=int)
for i, a in enumerate(bits):
c[i] = a if i < 2 else a ^ c[i - 2]
b = 1 - 2 * c
y = np.array(b, dtype=float)
y[2:] = b[2:] - b[:-2]
return b, y
def modified_duobinary_decode(y, d=1.0):
return (np.abs(y) < d).astype(int)
bits = np.array([1, 0, 1, 1, 0, 0, 1, 0, 1, 1])
b, y = modified_duobinary_encode(bits)
hat = modified_duobinary_decode(y)
print("a ", bits.tolist())
print("y ", np.round(y, 1).tolist())
print("hat", hat.tolist(), " match", np.array_equal(hat, bits))
Simulation results
Impulse and frequency responses for this pulse, alongside the other PR classes, are plotted in Partial response signalling schemes.
Rate this post:
See also
[1] Correlative Coding – Duobinary Signaling
[2] Introduction to Inter Symbol Interference

