| 1904 | // ── Waterfall row → TCI binary spectrum frames (type=4) ────────────────────── |
| 1905 | |
| 1906 | void TciServer::onWaterfallRowReady(quint32 streamId, const QVector<float>& binsDbm, |
| 1907 | double lowMhz, double highMhz, |
| 1908 | quint32 timecode, qint64 emittedNs) |
| 1909 | { |
| 1910 | Q_UNUSED(timecode); Q_UNUSED(emittedNs); |
| 1911 | |
| 1912 | bool anySpectrum = false; |
| 1913 | for (const auto& cs : m_clients) { |
| 1914 | if (cs.spectrumEnabled) { anySpectrum = true; break; } |
| 1915 | } |
| 1916 | if (!anySpectrum) return; |
| 1917 | |
| 1918 | const int nBins = binsDbm.size(); |
| 1919 | if (nBins == 0) return; |
| 1920 | |
| 1921 | // Resolve waterfall streamId → TRX for multi-pan disambiguation. |
| 1922 | // Waterfall IDs are 0x42xx; each PanadapterModel knows its wfStreamId(). |
| 1923 | int trx = 0; |
| 1924 | if (m_model) { |
| 1925 | for (auto* pan : m_model->panadapters()) { |
| 1926 | if (pan->wfStreamId() == streamId) { |
| 1927 | for (auto* s : m_model->slices()) { |
| 1928 | if (s->panId() == pan->panId()) { |
| 1929 | trx = TciProtocol::tciTrxForSlice(m_model, s); |
| 1930 | break; |
| 1931 | } |
| 1932 | } |
| 1933 | break; |
| 1934 | } |
| 1935 | } |
| 1936 | } |
| 1937 | |
| 1938 | // TciAudioHeader (64 bytes) + float32 dBm bins. |
| 1939 | // type=4 (SPECTRUM, AetherSDR extension — not in TCI spec v2.0). |
| 1940 | // reserved[0] = low edge in Hz, reserved[1] = high edge in Hz. |
| 1941 | QByteArray frame(static_cast<int>(sizeof(TciAudioHeader)) + nBins * static_cast<int>(sizeof(float)), |
| 1942 | Qt::Uninitialized); |
| 1943 | |
| 1944 | TciAudioHeader hdr{}; |
| 1945 | hdr.receiver = static_cast<quint32>(trx); |
| 1946 | hdr.format = 3; // float32 |
| 1947 | hdr.length = static_cast<quint32>(nBins); |
| 1948 | hdr.type = 4; // SPECTRUM (AetherSDR extension) |
| 1949 | hdr.channels = 1; |
| 1950 | hdr.reserved[0] = static_cast<quint32>(lowMhz * 1'000'000.0); |
| 1951 | hdr.reserved[1] = static_cast<quint32>(highMhz * 1'000'000.0); |
| 1952 | std::memcpy(frame.data(), &hdr, sizeof(hdr)); |
| 1953 | |
| 1954 | auto* dst = reinterpret_cast<float*>(frame.data() + sizeof(hdr)); |
| 1955 | std::memcpy(dst, binsDbm.constData(), nBins * sizeof(float)); |
| 1956 | |
| 1957 | for (auto& cs : m_clients) { |
| 1958 | if (cs.spectrumEnabled) |
| 1959 | cs.socket->sendBinaryMessage(frame); |
| 1960 | } |
| 1961 | } |
| 1962 | |
| 1963 | // ── DAX channel management for TCI audio (#1331) ───────────────────────────── |
nothing calls this directly
no test coverage detected