import { SpectrumRenderer, SpectrumRenderOptions, buildSpectrumPalette, hslToHex } from './SpectrumRenderer' /** * 柱状频谱渲染器 * 经典频率柱状条跳动,带峰值下落指示器 */ export class BarSpectrumRenderer implements SpectrumRenderer { private peaks: number[] = [] private peakVelocities: number[] = [] render(ctx: CanvasRenderingContext2D, bands: number[], energy: number, width: number, height: number, brandColor: string, _options?: SpectrumRenderOptions): void { const bandCount: number = bands.length if (bandCount === 0) return // 初始化峰值数组 if (this.peaks.length !== bandCount) { this.peaks = new Array(bandCount) this.peakVelocities = new Array(bandCount) for (let i = 0; i < bandCount; i++) { this.peaks[i] = 0 this.peakVelocities[i] = 0 } } // 轻微拖影,接近参考图中的荧光残影(使用透明背景) ctx.clearRect(0, 0, width, height) const gap: number = 1.0 // 减小间隙,增加柱子列数 const barWidth: number = 5 // 固定柱子宽度 const totalSpectrumWidth: number = bandCount * barWidth + (bandCount - 1) * gap const startX: number = (width - totalSpectrumWidth) / 2 // 居中起始位置 const usableHeight: number = height * 0.60 // 减小最大高度 const segmentHeight: number = Math.max(2, height * 0.012) // 减小单段高度 const segmentGap: number = 0 // 去掉间隙,让方格紧密相连 const segmentCount: number = Math.max(10, Math.floor(usableHeight / (segmentHeight + segmentGap))) const palette = buildSpectrumPalette(brandColor) const hueSpan: number = (palette.complementaryHue + 360 - palette.primaryHue) % 360 // 地平线已移除,保持透明背景 for (let i = 0; i < bandCount; i++) { const level: number = Math.max(0, Math.min(1, bands[i])) const activeSegments: number = Math.floor(level * segmentCount) const x: number = startX + i * (barWidth + gap) const hue: number = (palette.primaryHue + (i / bandCount) * hueSpan) % 360 for (let seg = 0; seg < segmentCount; seg++) { const segY: number = height - (seg + 1) * (segmentHeight + segmentGap) const lit: boolean = seg < activeSegments // 只绘制点亮的方格,未点亮的不绘制(透明) if (!lit) { continue } const t: number = seg / segmentCount const lightness: number = 0.34 + t * 0.34 const alpha: number = 0.55 + level * 0.45 ctx.globalAlpha = alpha ctx.fillStyle = hslToHex(hue, 0.86, lightness) ctx.fillRect(x, segY, barWidth, segmentHeight) } ctx.globalAlpha = 1.0 // 峰值下落指示器(亮点) if (level > this.peaks[i]) { this.peaks[i] = level this.peakVelocities[i] = 0 } else { this.peakVelocities[i] += 0.0018 this.peaks[i] -= this.peakVelocities[i] if (this.peaks[i] < 0) this.peaks[i] = 0 } const peakSeg: number = Math.floor(this.peaks[i] * segmentCount) const peakY: number = height - (peakSeg + 1) * (segmentHeight + segmentGap) // 使用当前柱子的颜色 const peakT: number = peakSeg / segmentCount const peakLightness: number = 0.34 + peakT * 0.34 ctx.fillStyle = hslToHex(hue, 0.86, peakLightness) ctx.globalAlpha = 0.95 ctx.fillRect(x, peakY, barWidth, segmentHeight + 1) ctx.globalAlpha = 1.0 } } reset(): void { this.peaks = [] this.peakVelocities = [] } }