| 1036 | } |
| 1037 | |
| 1038 | AudioEngine::AudioEngine(QObject* parent) |
| 1039 | : QObject(parent) |
| 1040 | , m_clientEqRx(std::make_unique<ClientEq>()) |
| 1041 | , m_clientEqTx(std::make_unique<ClientEq>()) |
| 1042 | , m_clientCompTx(std::make_unique<ClientComp>()) |
| 1043 | , m_clientCompRx(std::make_unique<ClientComp>()) |
| 1044 | , m_clientGateTx(std::make_unique<ClientGate>()) |
| 1045 | , m_clientGateRx(std::make_unique<ClientGate>()) |
| 1046 | , m_clientDeEssTx(std::make_unique<ClientDeEss>()) |
| 1047 | , m_clientDeEssRx(std::make_unique<ClientDeEss>()) |
| 1048 | , m_clientTubeTx(std::make_unique<ClientTube>()) |
| 1049 | , m_clientTubeRx(std::make_unique<ClientTube>()) |
| 1050 | , m_clientPuduTx(std::make_unique<ClientPudu>()) |
| 1051 | , m_clientPuduRx(std::make_unique<ClientPudu>()) |
| 1052 | , m_clientReverbTx(std::make_unique<ClientReverb>()) |
| 1053 | , m_clientFinalLimiterTx(std::make_unique<ClientFinalLimiter>()) |
| 1054 | , m_clientTxTestTone(std::make_unique<ClientTxTestTone>()) |
| 1055 | , m_cwSidetone(std::make_unique<CwSidetoneGenerator>(48000)) |
| 1056 | , m_cwRecordSidetone(std::make_unique<CwSidetoneGenerator>(DEFAULT_SAMPLE_RATE)) |
| 1057 | , m_clientQuindarTone(std::make_unique<ClientQuindarTone>()) |
| 1058 | { |
| 1059 | // Recorder-sidetone generator: always enabled at a fixed, audible level and |
| 1060 | // centre pan so a Client-Side QSO recording captures the operator's sent |
| 1061 | // CW/CWX regardless of the audible monitor's volume/enable state (#2539). |
| 1062 | // Its pitch is mirrored from the audible generator each TX block. |
| 1063 | m_cwRecordSidetone->setEnabled(true); |
| 1064 | m_cwRecordSidetone->setVolume(0.5f); |
| 1065 | m_cwRecordSidetone->setPan(0.5f); |
| 1066 | // TX-side CW decode mirror (#2417). Plug the sidetone generator's |
| 1067 | // per-block tap into a downsampler + signal emitter; gated on the |
| 1068 | // m_cwDecodeTxTapEnabled atomic so MainWindow can flip TX-decode on |
| 1069 | // and off without rebuilding any audio plumbing. Runs on the |
| 1070 | // sidetone audio thread. |
| 1071 | m_cwSidetone->setSampleTap( |
| 1072 | [this](const float* mono, int frames, int sampleRateHz) { |
| 1073 | if (!m_cwDecodeTxTapEnabled.load(std::memory_order_relaxed)) |
| 1074 | return; |
| 1075 | if (frames <= 0 || sampleRateHz <= 0) return; |
| 1076 | // CwDecoder::feedAudio expects 24 kHz stereo float32 — the |
| 1077 | // same shape PanadapterStream::audioDataReady() emits on |
| 1078 | // the RX side. Decimate 48→24 by averaging consecutive |
| 1079 | // pairs; the sidetone is a single sine well below 12 kHz |
| 1080 | // so the cheap two-tap LPF is sufficient for ggmorse. For |
| 1081 | // sample rates that are not an integer multiple of 24 kHz |
| 1082 | // (rare — only when the device forced a 44.1 kHz negotiation), |
| 1083 | // fall back to nearest-neighbour stepping. |
| 1084 | constexpr int kTargetHz = 24000; |
| 1085 | QByteArray buf; |
| 1086 | if (sampleRateHz == 48000) { |
| 1087 | const int outFrames = frames / 2; |
| 1088 | if (outFrames <= 0) return; |
| 1089 | buf.resize(outFrames * 2 * static_cast<int>(sizeof(float))); |
| 1090 | auto* out = reinterpret_cast<float*>(buf.data()); |
| 1091 | for (int i = 0; i < outFrames; ++i) { |
| 1092 | const float s = 0.5f * (mono[2 * i] + mono[2 * i + 1]); |
| 1093 | out[2 * i] = s; // L |
| 1094 | out[2 * i + 1] = s; // R |
| 1095 | } |
nothing calls this directly
no test coverage detected