| 157 | // ── Decode loop (worker thread) ─────────────────────────────────────────────── |
| 158 | |
| 159 | void RttyDecoder::decodeLoop() |
| 160 | { |
| 161 | constexpr int kChunkSamples = 240; // 10 ms at 24 kHz |
| 162 | constexpr int kChunkBytes = kChunkSamples * static_cast<int>(sizeof(float)); |
| 163 | |
| 164 | // Envelope time constant: 0.3/baud gives crossover in ~0.2 bit periods, |
| 165 | // fast enough for start-bit detection while still smoothing carrier ripple. |
| 166 | auto calcEnvAlpha = [](double baud) { |
| 167 | return 1.0 - std::exp(-baud / (kSampleRate * 0.3)); |
| 168 | }; |
| 169 | |
| 170 | // Schmitt trigger hysteresis: a 10% band prevents chattering at the |
| 171 | // mark/space boundary when the signal is near equal amplitude. |
| 172 | constexpr double kHyst = 0.10; |
| 173 | |
| 174 | double baud = m_baudRate.load(); |
| 175 | double envAlpha = calcEnvAlpha(baud); |
| 176 | int statsTick = 0; |
| 177 | |
| 178 | // Decoder state machine |
| 179 | int prevBit = 1; // idle = mark |
| 180 | int curBit = 1; |
| 181 | bool inChar = false; |
| 182 | int bitCount = 0; |
| 183 | int shiftReg = 0; |
| 184 | bool figsMode = false; |
| 185 | double bitClock = 0.0; |
| 186 | double samplesPerBit = kSampleRate / baud; |
| 187 | |
| 188 | while (m_running) { |
| 189 | if (m_paramsChanged.exchange(false)) { |
| 190 | recalcFilterCoeffs(); |
| 191 | baud = m_baudRate.load(); |
| 192 | envAlpha = calcEnvAlpha(baud); |
| 193 | samplesPerBit = kSampleRate / baud; |
| 194 | // Reset bit-level state but preserve figsMode: the LTRS/FIGS |
| 195 | // shift is session state set by received codes, not a filter |
| 196 | // parameter, so a baud/mark change mid-stream shouldn't flip |
| 197 | // the character set back to LTRS unexpectedly. |
| 198 | inChar = false; |
| 199 | bitCount = 0; |
| 200 | shiftReg = 0; |
| 201 | prevBit = 1; |
| 202 | curBit = 1; |
| 203 | bitClock = 0.0; |
| 204 | } |
| 205 | |
| 206 | QByteArray chunk; |
| 207 | { |
| 208 | QMutexLocker lock(&m_bufMutex); |
| 209 | if (m_ringBuf.size() < kChunkBytes) { |
| 210 | lock.unlock(); |
| 211 | QThread::msleep(10); |
| 212 | continue; |
| 213 | } |
| 214 | chunk = m_ringBuf.left(kChunkBytes); |
| 215 | m_ringBuf.remove(0, kChunkBytes); |
| 216 | } |
nothing calls this directly
no test coverage detected