「弱峰调整」升级:三组识别规则 + 滑动条 ±400% 梯度驱动

一、三组识别规则(以波峰 k 为例;L/l = 左相邻波峰/波谷,r/R = 右相邻波谷/波峰;
   leftMean = (L+l)/2,rightMean = (r+R)/2)
- 第一类:v < leftMean 且 v < rightMean(两侧的"相邻峰+相邻谷"均值都要低于)
- 第二类:v < leftMean 或 v < rightMean(放宽为"或")
- 第三类:v < L 且 v < R(只与左右同类极值比较,最宽松)
波谷把"低于"换成"高于"、min/max 互换。三组不是包含关系(第三类不蕴含第二类),
因此确实存在"只满足第三类"的点。

二、梯度驱动(滑动条 |a| ∈ [0,400%];每组自身仍是 ±200%)
  0–100%   :只有第一类在动(0–100%)
  100–200% :第一类 100–200%,第二类开始 0–100%
  200–300% :第一类到上限,第二类 100–200%,第三类开始 0–100%
  300–400% :前两类到上限,第三类 100–200%
负值完全对称。实现:first = sign*min(|a|,2)、second = sign*clamp(|a|-1,0,2)、
third = sign*clamp(|a|-2,0,2);某点同时满足多组时取幅度绝对值最大的一组(= 最严格的那组)。

三、每组自身的目标值映射(±200%)
  0 – +100%  :当前值 → 左右同类极值中较温和的一侧(波峰取较低者、波谷取较高者)
  +100 – +200%:该侧 → 左右同类极值的均值(不再是"较大的那个")
  0 – -100%  :当前值 → 相邻异类极值中较近的一个(波峰取较高的波谷、波谷取较低的波峰)
  -100 – -200%:该侧 → 相邻异类极值的均值(不再是"最小的那个")
负幅度的"不得反向增强"夹取保持不变;相邻极值之间的 smoothstep 平滑偏移逐字未改。

四、界面
滑动条量程 -400% – +400%(0 在正中间,kWeakPeakSliderScale = 4.0);数值列按 "+400%" 取宽;
右侧数值始终带符号。

五、验证(全部离屏,不占用鼠标/键盘/屏幕)
- uitest 463/463:第一类点(弱峰 520、相邻峰 620/640)0/50/100/150/200% → 520/570/620/625/630;
  弱谷 580 → 580/540/500/500/500,负值 590/600/605/610;
  只满足第二类的峰 130:100% 不动、150/200/250/300/400% → 140/150/162.5/175/175;
  负值 -100% 不动、-150/-200/-250/-300% → 115/100/97.5/95;
  只满足第三类的峰 195:200% 不动、250/300/400% → 197.5/200/200;
  第一类点 200% 已达上限(400% 不再变化);±400% 夹取;默认 0%;滑块中间 = 0%、
  两端 = ±400%、轨道 3/4 处 ≈ +200%。
- [Y] 端到端:合成 ripple 弱峰区间 20 → 70(50% 幅度,健康 120)。
- panperf PASS;layercheck clobbers=0/24、pan frames wrong=0/4;cursorcheck 30/30;
  dragcheck 0 绘制错误;make clean && make all 零警告。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-12 19:38:46 +08:00
1 parent 14b60f5d9e
commit 59a84ba126
6 files changed
+217 -111

No files matched your search

+23 -16
View File
@@ -101,12 +101,18 @@ namespace jdi
const int kWeakPeakIterationsMaxLimit = 1000;
/** 迭代次数滑块上限的默认值(与 `Lane::WeakPeakItem::maxIterations` 一致)。 */
const int kWeakPeakIterationsDefaultMax = 10;
/** 弱峰调整的幅度上限/下限(面板上显示为 +200% / -200%)。 */
const double kWeakPeakAmountScale = 2.0;
/** 弱峰调整幅度百分比上限(用于构建新信号 / 曲线缓存键)。 */
const int kWeakPeakAmountMax = 200;
/** 弱峰调整幅度百分比下限(负值 = 削弱)。 */
const int kWeakPeakAmountMin = -200;
/**
* @brief 「弱峰调整」滑动条的量程(±400%)。
*
* 滑动条同时驱动三组识别规则(见 `ComputeWeakPeakAdjust`):0–100% 只有第一类规则在动,
* 之后每 100% 加入下一类、而前一类继续走完自己的 0–200%。因此量程是算法内部
* 「单组 ±200%」的两倍。
*/
const double kWeakPeakSliderScale = 4.0;
/** 弱峰调整幅度百分比上限(滑动条右端,用于构建新信号 / 曲线缓存键)。 */
const int kWeakPeakAmountMax = 400;
/** 弱峰调整幅度百分比下限(滑动条左端,负值 = 削弱)。 */
const int kWeakPeakAmountMin = -400;
/**
* @brief 派生曲线「区间窗口」缓存的保护带上限(采样点数)。
*
@@ -162,13 +168,13 @@ namespace jdi
/**
* @brief 「弱峰调整」滑块行右侧**数值列**的宽度(布局与面板宽度共用同一算式)。
*
* 取「+200%」与「10000」两者较宽者:前者是幅度滑块的最大显示文本
*(**正负号始终显示**,因此要按带符号的最宽形式留位),
* 取「+400%」与「10000」两者较宽者:前者是幅度滑块的最大显示文本
*(**正负号始终显示**,且量程已扩到 ±400%),
* 后者为迭代次数上限留出足够位宽(上限可被用户改大)。
*/
float WeakPeakValueColumnWidth(HFONT monoSmall)
{
return std::max(UiMeasureTextWidth(monoSmall, L"+200%"),
return std::max(UiMeasureTextWidth(monoSmall, L"+400%"),
UiMeasureTextWidth(monoSmall, L"10000"));
}
@@ -353,7 +359,7 @@ namespace jdi
* @param [IN] kind `DerivedCurveKind` 的整型值。
* @param [IN] points 均值点数 X。
* @param [IN] iterations 迭代次数 N。
* @param [IN] amountPercent 弱峰调整等级(0–200)。
* @param [IN] amountPercent 弱峰调整幅度(-400–400)。
* @return 曲线数值(长度 = 有效窗口长度)。
*/
std::vector<float> ComputeDerivedWindow(const SignalData& signal, std::size_t windowBegin,
@@ -4276,7 +4282,7 @@ namespace jdi
}
case PlotDragMode::PEAK_LEVEL:
{
// 「弱峰调整」某信号的等级滑块:按滑轨横向位置换算 0–200%。
// 「弱峰调整」某信号的等级滑块:按滑轨横向位置换算 -400% – +400%。
if (m_tab == nullptr || m_peakDragLane < 0 ||
m_peakDragLane >= static_cast<int>(m_tab->Lanes().size()))
{
@@ -4298,10 +4304,11 @@ namespace jdi
const double ratio =
UiSliderRatioFromX(layout.rowLevelTracks[static_cast<std::size_t>(rowIndex)], fx);
// 滑块量程 -200% – +200%:0 在正中间。
// 滑块量程 -400% – +400%(三组规则的梯度区间):0 在正中间。
item.amountPercent = std::max(
kWeakPeakAmountMin,
std::min(kWeakPeakAmountMax,
static_cast<int>(std::lround((ratio * 2.0 - 1.0) * 100.0 * kWeakPeakAmountScale))));
static_cast<int>(std::lround((ratio * 2.0 - 1.0) * 100.0 * kWeakPeakSliderScale))));
m_weakPeakCurveCache.clear();
m_dragMoved = true;
return true;
@@ -4731,10 +4738,10 @@ namespace jdi
m_dragMode = PlotDragMode::PEAK_LEVEL;
m_peakDragLane = index;
m_peakDragRef = ref;
// 单击轨道:直接写入该处对应的幅度(-200% … +200%,0 在正中间)。
// 单击轨道:直接写入该处对应的幅度(-400% … +400%,0 在正中间)。
item.amountPercent = static_cast<int>(std::lround(
(UiSliderRatioFromX(layout.rowLevelTracks[rowIndex], fx) * 2.0 - 1.0) *
100.0 * kWeakPeakAmountScale));
100.0 * kWeakPeakSliderScale));
item.amountPercent = std::max(
kWeakPeakAmountMin,
std::min(kWeakPeakAmountMax, item.amountPercent));
@@ -9529,7 +9536,7 @@ namespace jdi
: lane.EdgeCenterEnabled(ref));
if (weakPeak)
{
// 每个被勾选的信号各有两行滑块:等级(0–200%)与迭代次数(1 … 上限)。
// 每个被勾选的信号各有两行滑块:调整幅度(-400% – +400%)与迭代次数(1 … 上限)。
if (checked)
{
const float levelRowTop = rowY + meanRowHeight;
@@ -10647,7 +10654,7 @@ namespace jdi
(static_cast<double>(std::max(kWeakPeakAmountMin,
std::min(kWeakPeakAmountMax,
peakItem->amountPercent))) /
(100.0 * kWeakPeakAmountScale) +
(100.0 * kWeakPeakSliderScale) +
1.0) *
0.5;
paintSliderBody(levelTrack, levelRatio,