三维频谱marker
This commit is contained in:
@@ -189,16 +189,39 @@ std::pair<double, double> generator_range(const Json& input, std::string_view mi
|
||||
if (minimum >= maximum) throw std::invalid_argument(std::string(maximum_key) + " must be greater than " + std::string(minimum_key));
|
||||
return {minimum, maximum};
|
||||
}
|
||||
void generate_spectral_row(std::vector<Plot_Value>& values, std::size_t row,
|
||||
std::size_t signal_count, double minimum, double maximum,
|
||||
double noise_standard_deviation, std::mt19937_64& engine) {
|
||||
if (noise_standard_deviation < 0.0)
|
||||
throw std::invalid_argument("noise_stddev must not be negative");
|
||||
std::normal_distribution<double> noise(0.0, noise_standard_deviation);
|
||||
const double span = maximum - minimum;
|
||||
const double denominator = static_cast<double>(std::max<std::size_t>(1, values.size() - 1));
|
||||
for (std::size_t index = 0; index < values.size(); ++index) {
|
||||
const double x = static_cast<double>(index) / denominator;
|
||||
double value = minimum + span * 0.10 + noise(engine);
|
||||
for (std::size_t signal = 0; signal < signal_count; ++signal) {
|
||||
const double phase = static_cast<double>(signal + 1) / static_cast<double>(signal_count + 1);
|
||||
const double center = std::clamp(phase + 0.035 * std::sin(row * 0.09 + signal * 1.73), 0.01, 0.99);
|
||||
const double width = 0.003 + 0.018 * static_cast<double>((signal % 5) + 1) / 5.0;
|
||||
const double distance = (x - center) / width;
|
||||
value += span * (0.45 + 0.45 * std::sin(signal * 2.17 + row * 0.037)) * std::exp(-0.5 * distance * distance);
|
||||
}
|
||||
values[index] = std::clamp(value, minimum, maximum);
|
||||
}
|
||||
}
|
||||
template <typename Definition>
|
||||
Json generator_2d_schema() {
|
||||
Json fields = Json::array();
|
||||
std::string label;
|
||||
std::string description;
|
||||
if constexpr (std::same_as<Definition, Spectrum>) {
|
||||
label = "生成频谱采样"; description = "生成一条完整功率频谱,样本沿当前频率范围均匀分布。";
|
||||
label = "生成频谱采样"; description = "生成一条含可控噪声底和多个窄带谱峰的完整功率频谱。";
|
||||
fields.push_back(generator_integer_field("sample_count", "频谱采样点数", "一次频谱更新包含的功率采样点数。", 4096));
|
||||
fields.push_back(generator_number_field("power_min", "功率下界", "随机功率值下界。", -110.0, -1'000'000.0, 1'000'000.0));
|
||||
fields.push_back(generator_number_field("power_max", "功率上界", "随机功率值上界,必须大于下界。", -20.0, -1'000'000.0, 1'000'000.0));
|
||||
fields.push_back(generator_number_field("power_min", "功率下界", "噪声底和谱峰最终裁剪的功率下界。", -110.0, -1'000'000.0, 1'000'000.0));
|
||||
fields.push_back(generator_number_field("power_max", "功率上界", "谱峰最终裁剪的功率上界,必须大于下界。", -20.0, -1'000'000.0, 1'000'000.0));
|
||||
fields.push_back(generator_integer_field("signal_count", "窄带信号数", "叠加在噪声底上的漂移高斯谱峰数量。", 8, 256));
|
||||
fields.push_back(generator_number_field("noise_stddev", "噪声标准差", "功率噪声的标准差,单位与功率值一致。", 2.0, 0.0, 1'000'000.0));
|
||||
} else if constexpr (std::same_as<Definition, Frequency_Trace>) {
|
||||
label = "生成频率轨迹"; description = "按时间顺序生成一组轨迹采样。";
|
||||
fields.push_back(generator_integer_field("sample_count", "轨迹采样点数", "时间有序的轨迹点数量。", 4096));
|
||||
@@ -211,18 +234,24 @@ Json generator_2d_schema() {
|
||||
fields.push_back(generator_integer_field("bins_per_block", "每块频点数", "每个扫频块保存的连续频点数量。", 8, 65'536));
|
||||
fields.push_back(generator_number_field("power_min", "功率下界", "随机扫频功率下界。", -110.0, -1'000'000.0, 1'000'000.0));
|
||||
fields.push_back(generator_number_field("power_max", "功率上界", "随机扫频功率上界,必须大于下界。", -20.0, -1'000'000.0, 1'000'000.0));
|
||||
fields.push_back(generator_integer_field("signal_count", "窄带信号数", "跨扫频块连续分布的谱峰数量。", 8, 256));
|
||||
fields.push_back(generator_number_field("noise_stddev", "噪声标准差", "扫频噪声底标准差。", 2.0, 0.0, 1'000'000.0));
|
||||
} else if constexpr (std::same_as<Definition, Afterglow>) {
|
||||
label = "生成余辉历史"; description = "生成多帧频谱历史,用于测试余辉累积和衰减。";
|
||||
fields.push_back(generator_integer_field("history_count", "历史频谱帧数", "余辉中保留的历史频谱数量。", 32, 4096));
|
||||
fields.push_back(generator_integer_field("samples_per_spectrum", "每帧采样点数", "每条历史频谱包含的功率采样点数。", 512, 65'536));
|
||||
fields.push_back(generator_number_field("power_min", "功率下界", "随机功率值下界。", -110.0, -1'000'000.0, 1'000'000.0));
|
||||
fields.push_back(generator_number_field("power_max", "功率上界", "随机功率值上界,必须大于下界。", -20.0, -1'000'000.0, 1'000'000.0));
|
||||
fields.push_back(generator_integer_field("signal_count", "漂移信号数", "在历史帧之间连续漂移的谱峰数量。", 8, 256));
|
||||
fields.push_back(generator_number_field("noise_stddev", "噪声标准差", "历史频谱噪声底标准差。", 2.0, 0.0, 1'000'000.0));
|
||||
} else if constexpr (std::same_as<Definition, Waterfall>) {
|
||||
label = "生成瀑布图历史"; description = "生成带时间刻度的多行频谱数据。";
|
||||
fields.push_back(generator_integer_field("row_count", "瀑布行数", "瀑布图中保存的时间行数量。", 256, 4096));
|
||||
fields.push_back(generator_integer_field("bins_per_row", "每行频点数", "每一时间行包含的频率采样点数。", 512, 65'536));
|
||||
fields.push_back(generator_number_field("power_min", "功率下界", "随机功率值下界。", -110.0, -1'000'000.0, 1'000'000.0));
|
||||
fields.push_back(generator_number_field("power_max", "功率上界", "随机功率值上界,必须大于下界。", -20.0, -1'000'000.0, 1'000'000.0));
|
||||
fields.push_back(generator_integer_field("signal_count", "漂移信号数", "沿时间行移动的窄带谱峰数量。", 8, 256));
|
||||
fields.push_back(generator_number_field("noise_stddev", "噪声标准差", "瀑布噪声底标准差。", 2.0, 0.0, 1'000'000.0));
|
||||
} else if constexpr (std::same_as<Definition, Constellation_Diagram>) {
|
||||
label = "生成星座采样"; description = "分别按 I/Q 坐标范围生成随机星座点。";
|
||||
fields.push_back(generator_integer_field("point_count", "星座点数", "本次写入的 I/Q 采样数量。", 10'000));
|
||||
@@ -238,20 +267,21 @@ Json generator_2d_schema() {
|
||||
fields.push_back(generator_number_field("y_min", "Y 坐标下界", "矩形起点 Y 随机范围下界。", 0.0, -1'000'000.0, 1'000'000.0));
|
||||
fields.push_back(generator_number_field("y_max", "Y 坐标上界", "矩形终点 Y 随机范围上界。", 100.0, -1'000'000.0, 1'000'000.0));
|
||||
}
|
||||
return {{"label", std::move(label)}, {"description", std::move(description)}, {"fields", std::move(fields)}};
|
||||
if (!fields.empty())
|
||||
fields.push_back(generator_integer_field("seed", "随机种子", "固定种子可重现同一压力数据集,便于对比不同帧策略和像素传输模式。", 42, 4'294'967'295ULL));
|
||||
return {{"label", std::move(label)}, {"description", std::move(description) + " 可通过数据规模与坐标/数值范围构造可重复的压力负载。"}, {"fields", std::move(fields)}};
|
||||
}
|
||||
template <typename Object>
|
||||
nlohmann::json generate_2d_data(Object& object, const Json& input) {
|
||||
using Definition = typename Object::Attached_Object;
|
||||
try {
|
||||
std::mt19937_64 engine{std::random_device{}()};
|
||||
std::mt19937_64 engine{generator_count(input, "seed", 4'294'967'295ULL)};
|
||||
std::size_t generated_count{};
|
||||
if constexpr (std::same_as<Definition, Spectrum>) {
|
||||
const auto count = generator_count(input, "sample_count");
|
||||
const auto [minimum, maximum] = generator_range(input, "power_min", "power_max");
|
||||
std::uniform_real_distribution<double> distribution(minimum, maximum);
|
||||
std::vector<Plot_Value> values(count);
|
||||
std::ranges::generate(values, [&] { return distribution(engine); });
|
||||
generate_spectral_row(values, 0, generator_count(input, "signal_count", 256), minimum, maximum, generator_number(input, "noise_stddev"), engine);
|
||||
object.update_samples(values);
|
||||
generated_count = count;
|
||||
} else if constexpr (std::same_as<Definition, Frequency_Trace>) {
|
||||
@@ -269,9 +299,11 @@ nlohmann::json generate_2d_data(Object& object, const Json& input) {
|
||||
const auto width = generator_count(input, "bins_per_block", 65'536);
|
||||
if (block_count > 1'000'000 / width) throw std::invalid_argument("sweep data exceeds 1,000,000 samples");
|
||||
const auto [minimum, maximum] = generator_range(input, "power_min", "power_max");
|
||||
std::uniform_real_distribution<double> distribution(minimum, maximum);
|
||||
std::vector<std::vector<Plot_Value>> blocks(block_count, std::vector<Plot_Value>(width));
|
||||
for (auto& block : blocks) std::ranges::generate(block, [&] { return distribution(engine); });
|
||||
std::vector<Plot_Value> complete(block_count * width);
|
||||
generate_spectral_row(complete, 0, generator_count(input, "signal_count", 256), minimum, maximum, generator_number(input, "noise_stddev"), engine);
|
||||
for (std::size_t block = 0; block < block_count; ++block)
|
||||
std::ranges::copy_n(complete.begin() + block * width, width, blocks[block].begin());
|
||||
object.template set<&Sweep_Spectrum::Prop::bins_per_block>(width);
|
||||
object.template set<&Sweep_Spectrum::Prop::block_count>(block_count);
|
||||
object.template set<&Sweep_Spectrum::Prop::blocks>(std::move(blocks));
|
||||
@@ -281,9 +313,11 @@ nlohmann::json generate_2d_data(Object& object, const Json& input) {
|
||||
const auto width = generator_count(input, "samples_per_spectrum", 65'536);
|
||||
if (row_count > 1'000'000 / width) throw std::invalid_argument("afterglow data exceeds 1,000,000 samples");
|
||||
const auto [minimum, maximum] = generator_range(input, "power_min", "power_max");
|
||||
std::uniform_real_distribution<double> distribution(minimum, maximum);
|
||||
std::vector<std::vector<Plot_Value>> spectra(row_count, std::vector<Plot_Value>(width));
|
||||
for (auto& spectrum : spectra) std::ranges::generate(spectrum, [&] { return distribution(engine); });
|
||||
const auto signal_count = generator_count(input, "signal_count", 256);
|
||||
const auto noise_stddev = generator_number(input, "noise_stddev");
|
||||
for (std::size_t row = 0; row < row_count; ++row)
|
||||
generate_spectral_row(spectra[row], row, signal_count, minimum, maximum, noise_stddev, engine);
|
||||
object.template set<&Afterglow::Prop::spectra>(std::move(spectra));
|
||||
generated_count = row_count * width;
|
||||
} else if constexpr (std::same_as<Definition, Waterfall>) {
|
||||
@@ -291,12 +325,13 @@ nlohmann::json generate_2d_data(Object& object, const Json& input) {
|
||||
const auto width = generator_count(input, "bins_per_row", 65'536);
|
||||
if (row_count > 1'000'000 / width) throw std::invalid_argument("waterfall data exceeds 1,000,000 samples");
|
||||
const auto [minimum, maximum] = generator_range(input, "power_min", "power_max");
|
||||
std::uniform_real_distribution<double> distribution(minimum, maximum);
|
||||
std::vector<Waterfall_Row> rows;
|
||||
rows.reserve(row_count);
|
||||
const auto signal_count = generator_count(input, "signal_count", 256);
|
||||
const auto noise_stddev = generator_number(input, "noise_stddev");
|
||||
for (std::size_t row = 0; row < row_count; ++row) {
|
||||
std::vector<Plot_Value> row_values(width);
|
||||
std::ranges::generate(row_values, [&] { return distribution(engine); });
|
||||
generate_spectral_row(row_values, row, signal_count, minimum, maximum, noise_stddev, engine);
|
||||
rows.push_back({static_cast<Plot_Time_Tick>(row), std::move(row_values)});
|
||||
}
|
||||
object.template set<&Waterfall::Prop::frequency_bin_count>(width);
|
||||
@@ -461,9 +496,38 @@ std::shared_ptr<Plot> make_axes_plot(asio::any_io_executor executor) {
|
||||
components.push_back(make_axis_component("axis-frequency", "频率轴", *frequency));
|
||||
components.push_back(make_axis_component("axis-value", "数值轴", *numeric));
|
||||
components.push_back(make_axis_component("axis-time", "时间轴", *time));
|
||||
Json generator_fields = Json::array({
|
||||
generator_integer_field("time_sample_count", "时间样本数", "写入时间轴保留窗口的连续时间样本数量。", 16'384),
|
||||
generator_integer_field("time_step_ms", "时间步长 (ms)", "相邻时间样本之间的毫秒间隔。", 10),
|
||||
generator_number_field("frequency_min", "频率下界", "频率轴可见坐标下界。", 0.0, -1'000'000'000.0, 1'000'000'000.0),
|
||||
generator_number_field("frequency_max", "频率上界", "频率轴可见坐标上界,必须大于下界。", 100.0, -1'000'000'000.0, 1'000'000'000.0),
|
||||
generator_number_field("value_min", "数值下界", "数值轴可见坐标下界。", -100.0, -1'000'000'000.0, 1'000'000'000.0),
|
||||
generator_number_field("value_max", "数值上界", "数值轴可见坐标上界,必须大于下界。", 0.0, -1'000'000'000.0, 1'000'000'000.0)
|
||||
});
|
||||
auto generate = [frequency = frequency.get(), numeric = numeric.get(), time = time.get()](const Json& input) {
|
||||
try {
|
||||
const auto sample_count = generator_count(input, "time_sample_count");
|
||||
const auto time_step = generator_count(input, "time_step_ms");
|
||||
const auto [frequency_minimum, frequency_maximum] = generator_range(input, "frequency_min", "frequency_max");
|
||||
const auto [value_minimum, value_maximum] = generator_range(input, "value_min", "value_max");
|
||||
frequency->template set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{frequency_minimum, frequency_maximum});
|
||||
numeric->template set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{value_minimum, value_maximum});
|
||||
time->template set<&Time_Axis::Prop::visible_count>(static_cast<Axis_Visible_Count>(sample_count));
|
||||
constexpr std::uint64_t day_milliseconds = 86'400'000;
|
||||
for (std::size_t index = 0; index < sample_count; ++index)
|
||||
time->append_time(Time_Of_Day{static_cast<std::int64_t>((index * time_step) % day_milliseconds)});
|
||||
return Json{{"success", true}, {"generated_count", sample_count}};
|
||||
} catch (const std::exception& error) {
|
||||
return Json{{"success", false}, {"error", error.what()}};
|
||||
}
|
||||
};
|
||||
auto view = std::make_unique<Scene_View_Model<decltype(frequency), decltype(numeric), decltype(time)>>(
|
||||
std::move(components), std::move(update),
|
||||
Scene_View_Model<decltype(frequency), decltype(numeric), decltype(time)>::Data_Generator{},
|
||||
Scene_View_Model<decltype(frequency), decltype(numeric), decltype(time)>::Data_Generator{
|
||||
Json{{"label", "生成坐标轴压力数据"},
|
||||
{"description", "按时间样本规模和三个业务坐标范围生成可重复的坐标轴压力负载。"},
|
||||
{"fields", std::move(generator_fields)}},
|
||||
std::move(generate)},
|
||||
std::move(frequency), std::move(numeric), std::move(time));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
@@ -44,9 +44,11 @@ Json generator_schema() {
|
||||
fields.push_back(integer_field("depth", "体数据深度", "体素网格 Z 方向尺寸;总量为宽×高×深。", 64, 1, 256));
|
||||
fields.push_back(number_field("value_min", "体素值下界", "每个体素随机标量值的下界。", 0.0, -1'000'000.0, 1'000'000.0, 0.01));
|
||||
fields.push_back(number_field("value_max", "体素值上界", "每个体素随机标量值的上界,必须大于下界。", 1.0, -1'000'000.0, 1'000'000.0, 0.01));
|
||||
fields.push_back(integer_field("seed", "随机种子", "固定种子可复现体素压力数据,便于跨策略对比。", 42, 1, 4'294'967'295ULL));
|
||||
return {{"label", "生成体素标量场"}, {"description", "按三维网格尺寸生成连续体数据,不使用随机位置。"}, {"fields", std::move(fields)}};
|
||||
}
|
||||
fields.push_back(integer_field("count", "图元数量", "本次替换到 Visual 的图元数量。", 10'000, 1, 1'000'000));
|
||||
fields.push_back(integer_field("count", "图元数量", "本次替换到 Visual 的图元数量;用于逐级提升 CPU Prepare、GPU 上传与绘制压力。", 10'000, 1, 5'000'000));
|
||||
fields.push_back(integer_field("seed", "随机种子", "固定种子可复现相同空间分布,确保多图与传输模式的性能结果可比较。", 42, 1, 4'294'967'295ULL));
|
||||
fields.push_back(number_field("x_min", "Scene X 下界", "随机位置在 Scene X 轴上的下界。", -1.0, -1'000'000.0, 1'000'000.0, 0.01));
|
||||
fields.push_back(number_field("x_max", "Scene X 上界", "随机位置在 Scene X 轴上的上界,必须大于下界。", 1.0, -1'000'000.0, 1'000'000.0, 0.01));
|
||||
fields.push_back(number_field("y_min", "Scene Y 下界", "随机位置在 Scene Y 轴上的下界。", -1.0, -1'000'000.0, 1'000'000.0, 0.01));
|
||||
@@ -303,7 +305,7 @@ public:
|
||||
using Prop = typename Definition::Prop;
|
||||
using Items = std::remove_cvref_t<decltype(std::declval<Prop>().items)>;
|
||||
try {
|
||||
std::mt19937_64 engine{std::random_device{}()};
|
||||
std::mt19937_64 engine{input_count(input, "seed", 4'294'967'295ULL)};
|
||||
Items generated;
|
||||
std::size_t count{};
|
||||
if constexpr (std::same_as<Definition, Volume_Visual>) {
|
||||
@@ -324,7 +326,7 @@ public:
|
||||
prop.field_depth = static_cast<std::uint32_t>(depth);
|
||||
});
|
||||
} else {
|
||||
count = input_count(input, "count");
|
||||
count = input_count(input, "count", 5'000'000);
|
||||
const auto& current = visual_->template read_prop<typename Definition::Base_Tag>().items;
|
||||
if (current.empty()) throw std::invalid_argument("visual has no item template");
|
||||
const auto prototype = current.front();
|
||||
@@ -436,6 +438,9 @@ struct Spectrogram_Parameters {
|
||||
double maximum_frequency_hz{20'000.0};
|
||||
double minimum_level_db{18.0};
|
||||
double maximum_level_db{78.0};
|
||||
double animation_speed{1.0};
|
||||
std::size_t update_every_n_frames{1};
|
||||
bool animation_enabled{true};
|
||||
};
|
||||
|
||||
Color spectrogram_color(float value) {
|
||||
@@ -568,14 +573,17 @@ struct Spectrogram_Data_Generator {
|
||||
|
||||
[[nodiscard]] Json schema() const {
|
||||
Json fields = Json::array();
|
||||
fields.push_back(integer_field("time_sample_count", "时间采样数", "时间方向的网格采样数量;增大后表面沿时间方向更细密。", 80, 16, 256));
|
||||
fields.push_back(integer_field("frequency_bin_count", "频率分箱数", "对数频率方向的网格分箱数量。", 96, 16, 256));
|
||||
fields.push_back(integer_field("time_sample_count", "时间采样数", "时间方向网格采样数;GPU 顶点数约为 6×(时间采样数-1)×(频率分箱数-1)。", parameters.time_sample_count, 16, 1024));
|
||||
fields.push_back(integer_field("frequency_bin_count", "频率分箱数", "对数频率方向分箱数;与时间采样数共同决定三角形和每次上传的数据量。", parameters.frequency_bin_count, 16, 1024));
|
||||
fields.push_back(integer_field("ridge_count", "谱峰轨迹数", "生成随时间漂移的窄带谱峰数量。", 5, 1, 12));
|
||||
fields.push_back(number_field("time_span_seconds", "时间跨度", "X 轴覆盖的时间长度,单位秒。", 4.0, 0.1, 120.0, 0.1));
|
||||
fields.push_back(number_field("minimum_frequency_hz", "最低频率", "对数频率轴的下界,必须大于零。", 10.0, 1.0, 1.0e9, 1.0));
|
||||
fields.push_back(number_field("maximum_frequency_hz", "最高频率", "对数频率轴的上界,必须大于最低频率。", 20'000.0, 2.0, 1.0e9, 10.0));
|
||||
fields.push_back(number_field("minimum_level_db", "最低声压级", "Z 轴色阶和高度的下界,单位 dB。", 18.0, -300.0, 300.0, 1.0));
|
||||
fields.push_back(number_field("maximum_level_db", "最高声压级", "Z 轴色阶和高度的上界,必须大于下界。", 78.0, -300.0, 300.0, 1.0));
|
||||
fields.push_back(number_field("time_span_seconds", "时间跨度", "X 轴时间范围,单位秒。", parameters.time_span_seconds, 0.1, 3600.0, 0.1));
|
||||
fields.push_back(number_field("minimum_frequency_hz", "最低频率", "对数频率轴下界,必须大于零。", parameters.minimum_frequency_hz, 0.001, 1.0e12, 1.0));
|
||||
fields.push_back(number_field("maximum_frequency_hz", "最高频率", "对数频率轴上界,必须大于最低频率。", parameters.maximum_frequency_hz, 0.002, 1.0e12, 10.0));
|
||||
fields.push_back(number_field("minimum_level_db", "最低声压级", "Z 轴色阶与高度下界,单位 dB。", parameters.minimum_level_db, -1000.0, 1000.0, 1.0));
|
||||
fields.push_back(number_field("maximum_level_db", "最高声压级", "Z 轴色阶与高度上界,必须大于下界。", parameters.maximum_level_db, -1000.0, 1000.0, 1.0));
|
||||
fields.push_back(number_field("animation_speed", "动态速度倍率", "谱峰随时间运动的倍率;0 表示保持当前相位。", parameters.animation_speed, 0.0, 100.0, 0.1));
|
||||
fields.push_back(integer_field("update_every_n_frames", "数据更新帧间隔", "每 N 个渲染请求重建并上传一次 Mesh;可分离固定几何绘制与持续数据上传压力。", parameters.update_every_n_frames, 1, 10'000));
|
||||
fields.push_back({{"key", "animation_enabled"}, {"label", "持续生成动态数据"}, {"description", "关闭后保留生成的数据集,仅测试固定 Mesh 的重复绘制;开启后按更新间隔持续重建。"}, {"editor", "boolean"}, {"editable", true}, {"value", parameters.animation_enabled}});
|
||||
return {{"label", "生成三维频谱瀑布"},
|
||||
{"description", "按时间采样、对数频率分箱和声压级范围生成连续 GPU Mesh 表面。"},
|
||||
{"fields", std::move(fields)}};
|
||||
@@ -585,19 +593,27 @@ struct Spectrogram_Data_Generator {
|
||||
const Json& input) {
|
||||
try {
|
||||
Spectrogram_Parameters next;
|
||||
next.time_sample_count = input_count(input, "time_sample_count", 256);
|
||||
next.frequency_bin_count = input_count(input, "frequency_bin_count", 256);
|
||||
next.time_sample_count = input_count(input, "time_sample_count", 1024);
|
||||
next.frequency_bin_count = input_count(input, "frequency_bin_count", 1024);
|
||||
next.ridge_count = input_count(input, "ridge_count", 12);
|
||||
next.time_span_seconds = input_number(input, "time_span_seconds");
|
||||
next.minimum_frequency_hz = input_number(input, "minimum_frequency_hz");
|
||||
next.maximum_frequency_hz = input_number(input, "maximum_frequency_hz");
|
||||
next.minimum_level_db = input_number(input, "minimum_level_db");
|
||||
next.maximum_level_db = input_number(input, "maximum_level_db");
|
||||
next.animation_speed = input_number(input, "animation_speed");
|
||||
next.update_every_n_frames = input_count(input, "update_every_n_frames", 10'000);
|
||||
const auto animation = input.at("animation_enabled");
|
||||
if (!animation.is_boolean()) throw std::invalid_argument("animation_enabled must be boolean");
|
||||
next.animation_enabled = animation.get<bool>();
|
||||
if (!(next.time_span_seconds > 0.0) ||
|
||||
!(next.minimum_frequency_hz > 0.0) ||
|
||||
!(next.maximum_frequency_hz > next.minimum_frequency_hz) ||
|
||||
!(next.maximum_level_db > next.minimum_level_db))
|
||||
throw std::invalid_argument("spectrogram ranges are invalid");
|
||||
const auto cells = (next.time_sample_count - 1) * (next.frequency_bin_count - 1);
|
||||
if (cells > 1'400'000)
|
||||
throw std::invalid_argument("spectrogram exceeds the 8,400,000 vertex stress-test limit");
|
||||
parameters = next;
|
||||
auto mesh = spectrogram_mesh(parameters);
|
||||
const auto vertex_count = mesh.size();
|
||||
@@ -623,8 +639,10 @@ struct Spectrogram_Data_Generator {
|
||||
|
||||
void update(Impl<Mesh_Visual>& visual, Impl<Axes_3D>&,
|
||||
Impl<Marker_Visual>* markers,
|
||||
const Plot_Frame_Request& request) const {
|
||||
const double animation_seconds = request.time_milliseconds / 1000.0;
|
||||
const Plot_Frame_Request& request) {
|
||||
if (!parameters.animation_enabled ||
|
||||
request.correlation_id % parameters.update_every_n_frames != 0) return;
|
||||
const double animation_seconds = request.time_milliseconds / 1000.0 * parameters.animation_speed;
|
||||
auto mesh = spectrogram_mesh(parameters, animation_seconds);
|
||||
if (visual.update_items(std::move(mesh)) != Mesh_Visual::Update_Items_Result::updated)
|
||||
throw std::logic_error("animated spectrogram mesh was rejected");
|
||||
@@ -695,9 +713,9 @@ std::shared_ptr<Plot> make_datoviz_spectrogram_plot(asio::any_io_executor execut
|
||||
auto marker_result = Impl<Marker_Visual>::Builder{}
|
||||
.set(&Marker_Visual::Prop::items, std::vector<Marker>{
|
||||
{{-0.35F, -0.18F, 0.72F}, color(255, 244, 170), 23.0F, 0.0F,
|
||||
Marker_Shape::diamond},
|
||||
Marker_Shape::diamond, true},
|
||||
{{0.28F, 0.34F, 0.86F}, color(88, 236, 211), 21.0F, 0.0F,
|
||||
Marker_Shape::cross}})
|
||||
Marker_Shape::cross, true}})
|
||||
.set(&Marker_Visual::Prop::depth_test, false)
|
||||
.build();
|
||||
if (!marker_result)
|
||||
|
||||
Reference in New Issue
Block a user