#include "BackEnd.h" #ifdef MockBackEnd #include "./MainWidget.h" #include "../component/FrequentShower.h" #include "../component/ProgressBar.h" #include "IntermediateFrequencyWidget.h" #include "RadioWidget.h" #include "SweepFrequencyWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include "export.h" namespace { enum class Trace_Mode { Random, Sine }; struct Mock_Config { bool performance{}; int interval_ms = 16; int batch_count = 1; Trace_Mode trace_mode = Trace_Mode::Random; }; int read_env_int(const char* name, int fallback) { bool ok = false; int value = qEnvironmentVariableIntValue(name, &ok); return ok ? value : fallback; } Trace_Mode read_trace_mode() { QByteArray value = qgetenv("RADIO_MOCK_TRACE_MODE").trimmed().toLower(); return value == "sine" ? Trace_Mode::Sine : Trace_Mode::Random; } const char* trace_mode_text(Trace_Mode mode) { return mode == Trace_Mode::Sine ? "sine" : "random"; } Mock_Config read_mock_config() { Mock_Config config; config.performance = read_env_int("RADIO_MOCK_PERF_MODE", 0) != 0; config.interval_ms = std::max(1, read_env_int("RADIO_MOCK_INTERVAL_MS", config.performance ? 1 : 16)); config.batch_count = std::max(1, read_env_int("RADIO_MOCK_BATCH_COUNT", config.performance ? 4 : 1)); config.trace_mode = read_trace_mode(); return config; } std::pmr::vector make_random_data(renderive::Range range, int size, renderive::Memory_Domain domain) { if (range.origin > range.target) std::swap(range.origin, range.target); std::pmr::vector data(renderive::memory_resource(domain)); data.resize(static_cast(std::max(0, size))); renderive::fill_data(range, data); return data; } double make_random_value(renderive::Range range) { if (range.origin > range.target) std::swap(range.origin, range.target); return renderive::get_data(range); } double make_trace_value(renderive::Range range, Trace_Mode mode) { if (mode == Trace_Mode::Random) return make_random_value(range); if (range.origin > range.target) std::swap(range.origin, range.target); static std::uint64_t sine_index = 0; const double phase = static_cast(sine_index++) * 0.08; const double amplitude = range.length() * 0.40; return range.center() + std::sin(phase) * amplitude; } renderive::Time_Of_Day current_render_time() { const QTime now = QTime::currentTime(); return {now.isValid() ? now.msecsSinceStartOfDay() : -1}; } void push_radio_data(Radio_Widget* rw, Trace_Mode trace_mode) { if (!rw || !rw->spectrogram_line || !rw->water_fall || !rw->time_power || !rw->audio_spectrogram_plot || !rw->progress_bar1 || !rw->progress_bar2) return; { auto r = rw->spectrogram_line->power_axis()->coord_range(); renderive::Range nr = {r.center() - r.length() / 4, r.center() + r.length() / 4}; auto data = make_random_data(nr, rw->spectrogram_line->frequency_point_size(), renderive::Memory_Domain::Spectrum); double middle_power = data.empty() ? nr.center() : data[data.size() / 2]; rw->spectrogram_line->update_samples(std::move(data)); rw->progress_bar1->setValue(middle_power); } { rw->water_fall->append_row(current_render_time(), make_random_data(rw->water_fall->power_range(), rw->water_fall->frequency_bin_count(), renderive::Memory_Domain::Waterfall)); } { renderive::Range r = rw->time_power->value_axis()->coord_range(); renderive::Range nr = {r.center() - r.length() / 4, r.center() + r.length() / 4}; rw->time_power->append_sample(current_render_time(), make_trace_value(nr, trace_mode)); } { auto r = rw->audio_spectrogram_plot->power_axis()->coord_range(); renderive::Range nr = {r.center() - r.length() / 4, r.center() + r.length() / 4}; rw->audio_spectrogram_plot->update_samples(make_random_data(nr, rw->audio_spectrogram_plot->frequency_point_size(), renderive::Memory_Domain::Spectrum)); } rw->progress_bar2->setValue(make_random_value({1, 5})); } struct Mock_Performance_Frame { std::pmr::vector spectrum; double spectrum_middle_power{}; renderive::Time_Of_Day waterfall_time; std::pmr::vector waterfall; renderive::Time_Of_Day trace_time; double trace_value{}; std::pmr::vector audio; double progress_value{}; }; struct Mock_Performance_Batch { std::vector frames; }; struct Mock_Delivery_State { std::mutex mutex; std::shared_ptr latest; bool delivery_posted{}; }; std::shared_ptr make_performance_batch(const Mock_Config& config) { auto batch = std::make_shared(); batch->frames.reserve(static_cast(config.batch_count)); for (int i = 0; i < config.batch_count; ++i) { auto spectrum = make_random_data({40, 120}, 1024, renderive::Memory_Domain::Spectrum); double middle_power = spectrum.empty() ? 80.0 : spectrum[spectrum.size() / 2]; auto waterfall_time = current_render_time(); auto waterfall = make_random_data({0, 160}, 1024, renderive::Memory_Domain::Waterfall); auto trace_time = current_render_time(); double trace_value = make_trace_value({-6000, 6000}, config.trace_mode); auto audio = make_random_data({-6000, 6000}, 512, renderive::Memory_Domain::Spectrum); batch->frames.push_back(Mock_Performance_Frame{ std::move(spectrum), middle_power, waterfall_time, std::move(waterfall), trace_time, trace_value, std::move(audio), make_random_value({1, 5}) }); } return batch; } void apply_performance_batch(Radio_Widget* rw, Mock_Performance_Batch& batch) { if (!rw || !rw->spectrogram_line || !rw->water_fall || !rw->time_power || !rw->audio_spectrogram_plot || !rw->progress_bar1 || !rw->progress_bar2) return; for (auto& frame : batch.frames) { rw->spectrogram_line->update_samples(std::move(frame.spectrum)); rw->progress_bar1->setValue(frame.spectrum_middle_power); rw->water_fall->append_row(frame.waterfall_time, std::move(frame.waterfall)); rw->time_power->append_sample(frame.trace_time, frame.trace_value); rw->audio_spectrogram_plot->update_samples(std::move(frame.audio)); rw->progress_bar2->setValue(frame.progress_value); } } } void Back_End::play(const QString& fileName) { qDebug() << "播放音频文件:" << fileName; } QString Back_End::get_screenshot_save_dir_path() { return "D:/work/电台保存文件"; } QString Back_End::get_screenshot_save_file_name() { QDateTime currentDateTime = QDateTime::currentDateTime(); return "截屏_" + currentDateTime.toString() .replace(":", "_") .replace("/", "_") + ".png"; } QString Back_End::get_audio_file_location() { return "D:/work/save/"; } Back_End* Back_End::instance() { static Back_End temp; return &temp; } bool Back_End::match(QKeyEvent* e, const QString& function) { bool ok = function_map.contains(function); if (!ok) { qDebug() << "error Global::match 失败! 功能" << function << "不存在!" << e; } return e->key() == function_map[function]; } void Back_End::YsSetFreq(quint64 value) { qDebug() << "后端设置频率为: " << value; } // 获取到频率 void Back_End::YsReceiveFreq(quint64 freq) { qDebug() << "接收到频率:" << freq; } // 音量设置 void Back_End::YsSetAfGain(int value) {} int Back_End::YsGetAfGain() { return 60; } int Back_End::YsGetRfGain() { return 60; } qint64 Back_End::YsGetFreq() { return 70; } // 增益设置 void Back_End::YsSetRfGain(int value) {} Back_End::Back_End() { //qDebug() << "Back_End::Back_End() " << QThread::current_thread_id(); function_map["主菜单"] = Qt::Key_F1; function_map["属性菜单"] = Qt::Key_F2; function_map["顶部菜单"] = Qt::Key_F3; function_map["退出"] = Qt::Key_Escape; function_map["确定"] = Qt::Key_Return; function_map["左移"] = Qt::Key_Left; function_map["右移"] = Qt::Key_Right; function_map["增加"] = Qt::Key_Up; function_map["减小"] = Qt::Key_Down; function_map["切换音量/RF Gain"] = Qt::Key_Return; function_map["特别增加"] = Qt::Key_PageUp; function_map["特别减小"] = Qt::Key_PageDown; Mock_Config config = read_mock_config(); qInfo() << "Radio mock perf:" << config.performance << "interval_ms:" << config.interval_ms << "batch_count:" << config.batch_count << "trace_mode:" << trace_mode_text(config.trace_mode); std::uint64_t start_generation = mock_generation; QTimer::singleShot(0, this, [this, config, start_generation]() { if (start_generation != mock_generation) return; QPointer rw = Main_Widget::instance()->radio_widget; if (!rw) return; if (!config.performance) { mock_timer = new QTimer(this); mock_timer->setTimerType(Qt::PreciseTimer); QObject::connect(mock_timer, &QTimer::timeout, this, [config, rw]() { for (int i = 0; i < config.batch_count; ++i) { if (!rw) return; push_radio_data(rw.data(), config.trace_mode); } }); mock_timer->start(config.interval_ms); return; } auto delivery_state = std::make_shared(); mock_thread = new QThread(this); mock_timer = new QTimer; mock_timer->setTimerType(Qt::PreciseTimer); mock_timer->setInterval(config.interval_ms); mock_timer->moveToThread(mock_thread); QTimer* timer = mock_timer; QObject::connect(mock_thread, &QThread::started, timer, [timer]() { timer->start(); }); QObject::connect(timer, &QTimer::timeout, timer, [this, config, rw, delivery_state, start_generation]() { auto batch = make_performance_batch(config); bool post_delivery = false; { std::lock_guard lock(delivery_state->mutex); delivery_state->latest = std::move(batch); if (!delivery_state->delivery_posted) { delivery_state->delivery_posted = true; post_delivery = true; } } if (!post_delivery) return; QMetaObject::invokeMethod(this, [this, rw, delivery_state, start_generation]() { std::shared_ptr latest; { std::lock_guard lock(delivery_state->mutex); latest = std::move(delivery_state->latest); delivery_state->delivery_posted = false; } if (start_generation != mock_generation || !rw || !latest) return; apply_performance_batch(rw.data(), *latest); }, Qt::QueuedConnection); }); mock_thread->start(); }); } #endif