diff --git a/tools/ld-chroma-decoder/encoder/main.cpp b/tools/ld-chroma-decoder/encoder/main.cpp index 926614844..a0ff6cad6 100644 --- a/tools/ld-chroma-decoder/encoder/main.cpp +++ b/tools/ld-chroma-decoder/encoder/main.cpp @@ -77,7 +77,7 @@ int main(int argc, char *argv[]) // Option to select chroma mode (--chroma-mode) QCommandLineOption chromaOption(QStringList() << "chroma-mode", - QCoreApplication::translate("main", "NTSC only. Chroma encoder mode to use (wideband-yuv, wideband-yiq; default: wideband-yuv)"), + QCoreApplication::translate("main", "NTSC only. Chroma encoder mode to use (wideband-yuv, wideband-yiq, narrowband-q; default: wideband-yuv)"), QCoreApplication::translate("main", "chroma-mode")); parser.addOption(chromaOption); @@ -142,6 +142,8 @@ int main(int argc, char *argv[]) chromaName = parser.value(chromaOption); if (chromaName == "wideband-yiq") { chromaMode = WIDEBAND_YIQ; + } else if (chromaName == "narrowband-q") { + chromaMode = NARROWBAND_Q; } else if (chromaName == "wideband-yuv") { chromaMode = WIDEBAND_YUV; } else { diff --git a/tools/ld-chroma-decoder/encoder/ntscencoder.cpp b/tools/ld-chroma-decoder/encoder/ntscencoder.cpp index a6c1f6437..6dfa9cd64 100644 --- a/tools/ld-chroma-decoder/encoder/ntscencoder.cpp +++ b/tools/ld-chroma-decoder/encoder/ntscencoder.cpp @@ -268,18 +268,26 @@ static double syncPulseGate(double t, double startTime, SyncPulseType type) return raisedCosineGate(t, startTime, startTime + length, 200.0e-9 / 2.0); } -// 1.3 MHz low-pass Gaussian filter, as used in pyctools-pal's coder. -// Generated by: c = scipy.signal.gaussian(13, 1.49); c / sum(c) +// 1.3 MHz low-pass filter // -// The UV filter should be 0 dB at 0 Hz, >= -3 dB at 1.3 MHz, <= -20 dB at -// 4.0 MHz. [Clarke p8] -static constexpr std::array uvFilterCoeffs { - 8.06454142158873e-05, 0.0009604748783110286, 0.007290763490157312, 0.035272860169480155, 0.10876496139131472, - 0.21375585039760908, 0.2677488885178237, 0.21375585039760908, 0.10876496139131472, 0.035272860169480155, - 0.007290763490157312, 0.0009604748783110286, 8.06454142158873e-05 +// The filter should be 0 dB at 0 Hz, >= -2 dB at 1.3 MHz, < -20 dB at +// 3.6 MHz. [Clarke p15] +static constexpr std::array uvFilterCoeffs { + 0.0021, 0.0191, 0.0903, 0.2308, 0.3153, + 0.2308, 0.0903, 0.0191, 0.0021 }; static constexpr auto uvFilter = makeFIRFilter(uvFilterCoeffs); +// 0.6 MHz low-pass filter +// +// The filter should be 0 dB at 0 Hz, >= -2 dB at 0.4 MHz, >= -6 dB at +// 0.5 MHz, <= -6 dB at 0.6 MHz. [Clarke p15] +static constexpr std::array qFilterCoeffs { + 0.0002, 0.0027, 0.0085, 0.0171, 0.0278, 0.0398, 0.0522, 0.0639, 0.0742, 0.0821, 0.0872, 0.0889, + 0.0872, 0.0821, 0.0742, 0.0639, 0.0522, 0.0398, 0.0278, 0.0171, 0.0085, 0.0027, 0.0002 +}; +static constexpr auto qFilter = makeFIRFilter(qFilterCoeffs); + void NTSCEncoder::encodeLine(qint32 fieldNo, qint32 frameLine, const quint16 *rgbData, QVector &outputLine) { // Resize the output line and fill with blanking @@ -394,7 +402,11 @@ void NTSCEncoder::encodeLine(qint32 fieldNo, qint32 frameLine, const quint16 *rg // Low-pass filter chroma components to 1.3 MHz [Poynton p342] uvFilter.apply(C1); - uvFilter.apply(C2); + if (chromaMode == NARROWBAND_Q) { + qFilter.apply(C2); + } else { + uvFilter.apply(C2); + } } for (qint32 x = 0; x < outputLine.size(); x++) { diff --git a/tools/ld-chroma-decoder/encoder/ntscencoder.h b/tools/ld-chroma-decoder/encoder/ntscencoder.h index bf38c1327..c55ecd834 100644 --- a/tools/ld-chroma-decoder/encoder/ntscencoder.h +++ b/tools/ld-chroma-decoder/encoder/ntscencoder.h @@ -35,6 +35,7 @@ enum ChromaMode { WIDEBAND_YUV = 0, // Y'UV WIDEBAND_YIQ, // Y'IQ + NARROWBAND_Q // Y'IQ with Q low-passed }; class NTSCEncoder