对数-线性值转换:归一化值转Hz值的函数错误排查
对数频率网格的归一化值与实际Hz值互转问题
当前通过DrawGrid()绘制的对数网格竖线显示正常,SetPositionHzValue()能正确将Hz值转为0-1的归一化位置(与网格匹配),但反向转换(把0-1归一化值转回实际Hz值)的GetPositionsHzValue()函数失效,需要解决这个反向转换的逻辑问题。已知显示频率范围是20Hz到44100Hz。
相关代码如下:
procedure TAudioBezierCurves.DrawGrid(Bitmap32: TBitmap32); var GridPosition: Integer; GridPositionF: Double; i: Integer; Base: Double; LogOffsetValue: Double; LogMaxValue: Double; begin GridPosition := 0; Base := 1; if GridFrequencyMin = 0 then begin LogOffsetValue := 0; end else begin LogOffsetValue := Log10(GridFrequencyMin); end; LogMaxValue := Log10(GridFrequencyMax) - LogOffsetValue; repeat for i := 2 to 10 do begin if Base * i < GridFrequencyMin then begin Continue; end; //* This gives the % value relative to the total scale GridPositionF := (Log10(Base * i) - LogOffsetValue) / LogMaxValue; GridPositionF := GridPositionF * Bitmap32.Width; GridPosition := Trunc(GridPositionF); Bitmap32.VertLineS(GridPosition, 0, Bitmap32.Height - 1, GridColor); end; Base := Base * 10; until GridPosition > Bitmap32.Width; end; procedure TAudioBezierCurve.SetPositionHzValue(AValue: Double); var LogOffsetValue: Double; LogMaxValue: Double; begin if AValue = 0 then begin Self.Position := 0; end else begin if Parent.GridFrequencyMin = 0 then begin LogOffsetValue := 0; end else begin LogOffsetValue := Log10(Parent.GridFrequencyMin); end; LogMaxValue := Log10(Parent.GridFrequencyMax) - LogOffsetValue; //* This gives the % value relative to the total scale AValue := (Log10(AValue) - LogOffsetValue) / LogMaxValue; Self.Position := AValue; end; end; function TAudioBezierCurve.GetPositionsHzValue: Double; var AValue: Double; begin AValue := Self.Position; AValue := Power(AValue, 2); Result := AValue * (Parent.GridFrequencyMax); Result := Result - (AValue * Parent.GridFrequencyMin) + Parent.GridFrequencyMin; end;
编辑补充:已尝试将GetPositionsHzValue()中的AValue := Power(AValue, 2);改为AValue := Power(AValue, 10);,但问题仍存在:
- 当显示范围设为0到44100Hz时,设置44100Hz返回正常,但
GetPositionsHzValue()返回41100Hz;设置20Hz时返回0。 - 从高值递减时,44085Hz之前返回正常,44084Hz会被返回为44085Hz,且差值随数值减小而增大。
- 从低值递增时,39Hz以下返回0,40Hz返回1。
问题根源
SetPositionHzValue()采用的是对数映射逻辑,反向转换必须用对应的指数映射还原,但当前GetPositionsHzValue()的逻辑完全偏离了对数映射的逆运算,错误使用幂运算+线性缩放,导致结果偏差。
正确的GetPositionsHzValue()实现
function TAudioBezierCurve.GetPositionsHzValue: Double; var LogOffsetValue: Double; LogMaxValue: Double; begin // 边界情况处理:Position为0直接返回最小频率 if Self.Position <= 0 then begin Result := Parent.GridFrequencyMin; Exit; end; // 边界情况处理:Position为1直接返回最大频率 if Self.Position >= 1 then begin Result := Parent.GridFrequencyMax; Exit; end; // 复用正向转换的参数计算逻辑,保证一致性 if Parent.GridFrequencyMin = 0 then LogOffsetValue := 0 else LogOffsetValue := Log10(Parent.GridFrequencyMin); LogMaxValue := Log10(Parent.GridFrequencyMax) - LogOffsetValue; // 逆推对数映射:先还原到对数空间,再转换为实际Hz值 Result := Power(10, Self.Position * LogMaxValue + LogOffsetValue); end;
关键说明
- 逆运算逻辑:
正向转换公式为Position = (log10(Hz) - LogOffset) / LogMax,逆推后得到:log10(Hz) = Position * LogMax + LogOffset
再通过10的幂运算得到实际Hz值:Hz = 10^(Position * LogMax + LogOffset) - 边界处理:
对Position为0或1的情况直接返回对应边界频率,避免计算误差;若业务允许最小频率为0,需单独处理返回0的逻辑。 - 一致性保障:
复用SetPositionHzValue中的参数计算逻辑,确保正向与反向转换使用完全相同的基准,避免参数不一致导致的偏差。
内容的提问来源于stack exchange,提问作者3delite
相关产品推荐
相关产品推荐

