「弱峰调整」升级:三组识别规则 + 滑动条 ±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

+29 -25
View File
@@ -377,7 +377,7 @@ namespace jdi
* @brief 「弱峰调整」中某个信号自己的参数条目。
*
* 与「均值滤波」完全一致:**每个被勾选的信号各自持有参数**
*(增强等级 0–200% + 迭代次数 N),因此同一网格内不同信号可以使用不同强度。
*(调整幅度 -400% – +400% + 迭代次数 N),因此同一网格内不同信号可以使用不同强度。
*
* ⚠️ `iterations` 的**上限由用户自己设定**(`maxIterations`):面板上「迭代次数 N」
* 滑块的量程就是 `1 … maxIterations`,点击滑块右侧的数值可以把该上限改成其它正整数。
@@ -385,7 +385,7 @@ namespace jdi
struct WeakPeakItem
{
SignalRef ref; /**< 目标信号。 */
int amountPercent = 0; /**< 调整幅度(-200–200,默认 0 = 不改动)。 */
int amountPercent = 0; /**< 调整幅度(-400–400,默认 0 = 不改动)。 */
int iterations = 1; /**< 迭代次数 N(1 … `maxIterations`)。 */
int maxIterations = 10; /**< 「迭代次数 N」滑块的上限(用户可在面板上修改,恒 ≥ 1)。 */
};
@@ -521,7 +521,7 @@ namespace jdi
/**
* @brief 参与弱峰调整预览的信号及其**各自的参数**(只包含被勾选的信号)。
*
* 参数(等级 0–200% + 迭代次数 N)按信号保存,与「均值滤波」的实现方式一致:
* 参数(调整幅度 -400% – +400% + 迭代次数 N)按信号保存,与「均值滤波」的实现方式一致:
* 同一网格内不同信号可以使用不同的增强强度,也可以分别「构建为新信号」。
*/
std::vector<WeakPeakItem> weakPeakItems;
@@ -598,36 +598,40 @@ namespace jdi
* 本算法**不做任何前置滤波**:需要平滑时用户可以先用「均值滤波」构建一条
* 滤波后的信号,再对它做弱峰调整。极值点直接在输入采样上识别。
*
* 「调整幅度」滑动条的量程是 **-400% – +400%**,它按"梯度"同时驱动**三组识别规则**:
* 越宽松的规则参与得越晚,同一滑动条位置上的幅度越小:
* | 滑动条 | 第一类(严格) | 第二类(或) | 第三类(宽松) |
* | --- | --- | --- | --- |
* | 0 – 100% | 0 – 100% | — | — |
* | 100 – 200% | 100 – 200% | 0 – 100% | — |
* | 200 – 300% | 200%(上限) | 100 – 200% | 0 – 100% |
* | 300 – 400% | 200%(上限) | 200%(上限) | 100 – 200% |
* (负值完全对称;某个极值若同时满足多组规则,取**幅度绝对值最大**的那一组。)
*
* 单阶处理(全 O(n)):
* 1. **识别趋势与极值**:扫描单调段,方向发生翻转处的上一个采样点即为
* 波峰(先升后降)或波谷(先降后升);波峰与波谷交替出现。
* 2. **判定“弱”**(用户明确定义,正/负幅度一致;与旧实现不同):
* * 波峰 k 必须**同时**低于「左侧相邻的第一个波谷(k-1) 与第一个波峰(k-2) 的均值」
* 与「右侧相邻的第一个波谷(k+1) 与第一个波峰(k+2) 的均值」;
* * 波谷 k 必须**同时**高于上述两个均值。
* 即与“两侧各自相邻的一个波峰 + 一个波谷”的平均水平比较。
* 3. **目标值**(`amount` = 调整幅度,-2 – +2,对应面板上的 -200% – +200%):
* * **正幅度(增强)**:以**同类**极值(k±2)为参照。
* 波峰:较小者记为 `lower`、较大者记为 `upper`;
* 0 ≤ a ≤ 1 → 目标 = 当前值 + a × (`lower` − 当前值);
* 1 < a ≤ 2 → 目标 = `lower` + (a − 1) × (`upper` − `lower`)。
* 波谷:较大者记为 `upper`、较小者记为 `lower`;
* 0 ≤ a ≤ 1 → 目标 = 当前值 + a × (`upper` − 当前值);
* 1 < a ≤ 2 → 目标 = `upper` − (a − 1) × (`upper` − `lower`)。
* * **负幅度(削弱)**:以相邻的**异类**极值(k±1)为参照。
* 令 `near` = 较靠近当前值的那个(波峰 → 较高的波谷;波谷 → 较低的波峰)、
* `far` = 另一个:
* -1 ≤ a < 0 → 目标 = 当前值 + |a| × (`near` − 当前值);
* -2 ≤ a < -1 → 目标 = `near` + (|a| − 1) × (`far` − `near`)。
* 即 **-100% 把弱峰拉到相邻异类极值中较近的那个**、**-200% 拉到较远的那个**
* (波形被压平甚至抹除)。削弱**不会反向增强**(目标被夹在"不越过当前值"的一侧)。
* 2. **三组识别规则**(以波峰 k 为例;`L` = 左侧相邻第一个波峰、`l` = 左侧相邻第一个波谷,
* `r` = 右侧相邻第一个波谷、`R` = 右侧相邻第一个波峰):
* * 第一类:`v < (L+l)/2` **且** `v < (r+R)/2`(两侧均值都要满足);
* * 第二类:`v < (L+l)/2` **或** `v < (r+R)/2`(放宽为"或");
* * 第三类:`v < L` **且** `v < R`(只与左右同类极值比较)。
* 波谷把"低于"换成"高于"、把"且/或"的上下界互换即可。
* 3. **目标值**(每一组的幅度 `a` 都在 -2 – +2,对应其自身的 -200% – +200%):
* * `0 – +100%`:当前值 → 左右**同类**极值中**较温和**的一侧
* (波峰取较低的波峰、波谷取较高的波谷);
* * `+100 – +200%`:该侧 → 左右同类极值的**均值**;
* * `0 – -100%`:当前值 → 相邻**异类**极值中**较近**的一个
* (波峰取较高的波谷、波谷取较低的波峰);
* * `-100 – -200%`:该侧 → 相邻异类极值的**均值**。
* 削弱**不会反向增强**(目标被夹在"不越过当前值"的一侧)。
* 4. **平滑偏移**:极值点本身取其目标值,两侧的数据点按相邻两个极值点的偏移量
* 平滑过渡(平滑步进,两端导数为 0),使波形整体趋势不变、只在弱峰附近被推回。
*
* 正常周期的极值不满足条件、偏移量恒为 0,因此输出与输入逐位相同。
* 不属于任何一类规则点的极值偏移量恒为 0,因此输出与输入逐位相同。
*
* @param [IN] values 原始采样值。
* @param [IN] amount 调整幅度(-2 – +2,对应 -200% – +200%,超出范围会被夹取;0 = 完全不动)。
* @param [IN] amount 调整幅度(-4 – +4,对应 -400% – +400%,超出范围会被夹取;0 = 完全不动)。
* @param [IN] iterations 迭代次数 N(重复应用次数,至少为 1)。
* @param [OUT] out 输出数组(长度与 values 相同;原值为 NaN 处保持 NaN)。
*/