| 166 | } |
| 167 | |
| 168 | void CwDecoder::decodeLoop() |
| 169 | { |
| 170 | // ggmorse requests samplesPerFrame * resampleFactor * sampleSize bytes per callback. |
| 171 | // At 24kHz int16, factor=6 (24000/4000), frame=128: 128*6*2 = 1536 bytes. |
| 172 | const int resampleFactor = static_cast<int>(m_ggmorse->getSampleRateInp() / GGMorse::kBaseSampleRate); |
| 173 | const int bytesPerFrame = m_ggmorse->getSamplesPerFrame() * resampleFactor * m_ggmorse->getSampleSizeBytesInp(); |
| 174 | int feedCount = 0; |
| 175 | |
| 176 | qCDebug(lcDsp) << "CwDecoder: decode loop running, bytesPerFrame:" << bytesPerFrame; |
| 177 | |
| 178 | while (m_running) { |
| 179 | // Wait until we have at least one frame of data |
| 180 | { |
| 181 | QMutexLocker lock(&m_bufMutex); |
| 182 | if (m_ringBuf.size() < bytesPerFrame) { |
| 183 | lock.unlock(); |
| 184 | QThread::msleep(20); |
| 185 | continue; |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | int framesThisCall = 0; |
| 190 | |
| 191 | bool gotData = m_ggmorse->decode([this, &framesThisCall](void* data, uint32_t nMaxBytes) -> uint32_t { |
| 192 | if (!m_running) return 0; |
| 193 | |
| 194 | QMutexLocker lock(&m_bufMutex); |
| 195 | // ggmorse requires exactly nMaxBytes — partial returns cause it to abort |
| 196 | if (static_cast<uint32_t>(m_ringBuf.size()) < nMaxBytes) return 0; |
| 197 | |
| 198 | std::memcpy(data, m_ringBuf.constData(), nMaxBytes); |
| 199 | m_ringBuf.remove(0, nMaxBytes); |
| 200 | ++framesThisCall; |
| 201 | return nMaxBytes; |
| 202 | }); |
| 203 | |
| 204 | feedCount += framesThisCall; |
| 205 | |
| 206 | // Log periodically |
| 207 | if (feedCount % 200 == 0 && feedCount > 0) { |
| 208 | const auto& stats = m_ggmorse->getStatistics(); |
| 209 | const auto& rxData = m_ggmorse->getRxData(); |
| 210 | qCDebug(lcDsp) << "CwDecoder:" << feedCount << "frames fed, pitch:" |
| 211 | << stats.estimatedPitch_Hz << "Hz, speed:" |
| 212 | << stats.estimatedSpeed_wpm << "WPM, decode:" << gotData |
| 213 | << "rxLen:" << rxData.size() |
| 214 | << "lastResult:" << m_ggmorse->lastDecodeResult(); |
| 215 | } |
| 216 | |
| 217 | const auto& stats = m_ggmorse->getStatistics(); |
| 218 | |
| 219 | // Accept all decodes — color-coded by confidence in the UI |
| 220 | GGMorse::TxRx rxData; |
| 221 | if (m_ggmorse->takeRxData(rxData) > 0 && stats.costFunction < 1.0f) { |
| 222 | QString text = QString::fromLatin1( |
| 223 | reinterpret_cast<const char*>(rxData.data()), |
| 224 | static_cast<int>(rxData.size())); |
| 225 | emit textDecoded(text, stats.costFunction); |