sourav-das/stem-separator
3
1export const DEFAULT_HEIGHT = 128;2export const MAX_CANVAS_WIDTH = 8000;3export const MAX_NODES = 10;4export function clampToUnit(value) {5 if (value < 0)6 return 0;7 if (value > 1)8 return 1;9 return value;10}11export function calculateBarRenderConfig({ width, height, length, options, pixelRatio, }) {12 const halfHeight = height / 2;13 const barWidth = options.barWidth ? options.barWidth * pixelRatio : 1;14 const barGap = options.barGap ? options.barGap * pixelRatio : options.barWidth ? barWidth / 2 : 0;15 const barRadius = options.barRadius || 0;16 const barMinHeight = options.barMinHeight ? options.barMinHeight * pixelRatio : 0;17 const spacing = barWidth + barGap || 1;18 const barIndexScale = length > 0 ? width / spacing / length : 0;19 return {20 halfHeight,21 barWidth,22 barGap,23 barRadius,24 barMinHeight,25 barIndexScale,26 barSpacing: spacing,27 };28}29export function calculateBarHeights({ maxTop, maxBottom, halfHeight, vScale, barMinHeight = 0, barAlign, }) {30 let topHeight = Math.round(maxTop * halfHeight * vScale);31 const bottomHeight = Math.round(maxBottom * halfHeight * vScale);32 let totalHeight = topHeight + bottomHeight || 1;33 if (totalHeight < barMinHeight) {34 totalHeight = barMinHeight;35 if (!barAlign) {36 topHeight = totalHeight / 2;37 }38 }39 return { topHeight, totalHeight };40}41export function resolveBarYPosition({ barAlign, halfHeight, topHeight, totalHeight, canvasHeight, }) {42 if (barAlign === 'top')43 return 0;44 if (barAlign === 'bottom')45 return canvasHeight - totalHeight;46 return halfHeight - topHeight;47}48export function calculateBarSegments({ channelData, barIndexScale, barSpacing, barWidth, halfHeight, vScale, canvasHeight, barAlign, barMinHeight, }) {49 const topChannel = channelData[0] || [];50 const bottomChannel = channelData[1] || topChannel;51 const length = topChannel.length;52 const segments = [];53 let prevX = 0;54 let maxTop = 0;55 let maxBottom = 0;56 for (let i = 0; i <= length; i++) {57 const x = Math.round(i * barIndexScale);58 if (x > prevX) {59 const { topHeight, totalHeight } = calculateBarHeights({60 maxTop,61 maxBottom,62 halfHeight,63 vScale,64 barMinHeight,65 barAlign,66 });67 const y = resolveBarYPosition({68 barAlign,69 halfHeight,70 topHeight,71 totalHeight,72 canvasHeight,73 });74 segments.push({75 x: prevX * barSpacing,76 y,77 width: barWidth,78 height: totalHeight,79 });80 prevX = x;81 maxTop = 0;82 maxBottom = 0;83 }84 const magnitudeTop = Math.abs(topChannel[i] || 0);85 const magnitudeBottom = Math.abs(bottomChannel[i] || 0);86 if (magnitudeTop > maxTop)87 maxTop = magnitudeTop;88 if (magnitudeBottom > maxBottom)89 maxBottom = magnitudeBottom;90 }91 return segments;92}93export function getRelativePointerPosition(rect, clientX, clientY) {94 const x = clientX - rect.left;95 const y = clientY - rect.top;96 const relativeX = x / rect.width;97 const relativeY = y / rect.height;98 return [relativeX, relativeY];99}100export function resolveChannelHeight({ optionsHeight, optionsSplitChannels, parentHeight, numberOfChannels, defaultHeight = DEFAULT_HEIGHT, }) {101 if (optionsHeight == null)102 return defaultHeight;103 const numericHeight = Number(optionsHeight);104 if (!isNaN(numericHeight))105 return numericHeight;106 if (optionsHeight === 'auto') {107 const height = parentHeight || defaultHeight;108 if (optionsSplitChannels === null || optionsSplitChannels === void 0 ? void 0 : optionsSplitChannels.every((channel) => !channel.overlay)) {109 return height / numberOfChannels;110 }111 return height;112 }113 return defaultHeight;114}115export function getPixelRatio(devicePixelRatio) {116 return Math.max(1, devicePixelRatio || 1);117}118export function shouldRenderBars(options) {119 return Boolean(options.barWidth || options.barGap || options.barAlign);120}121export function resolveColorValue(color, devicePixelRatio, canvasHeight) {122 if (!Array.isArray(color))123 return color || '';124 if (color.length === 0)125 return '#999';126 if (color.length < 2)127 return color[0] || '';128 const canvasElement = document.createElement('canvas');129 const ctx = canvasElement.getContext('2d');130 const gradientHeight = canvasHeight !== null && canvasHeight !== void 0 ? canvasHeight : canvasElement.height * devicePixelRatio;131 const gradient = ctx.createLinearGradient(0, 0, 0, gradientHeight || devicePixelRatio);132 const colorStopPercentage = 1 / (color.length - 1);133 color.forEach((value, index) => {134 gradient.addColorStop(index * colorStopPercentage, value);135 });136 return gradient;137}138export function calculateWaveformLayout({ duration, minPxPerSec = 0, parentWidth, fillParent, pixelRatio, }) {139 const scrollWidth = Math.ceil(duration * minPxPerSec);140 const isScrollable = scrollWidth > parentWidth;141 const useParentWidth = Boolean(fillParent && !isScrollable);142 const width = (useParentWidth ? parentWidth : scrollWidth) * pixelRatio;143 return {144 scrollWidth,145 isScrollable,146 useParentWidth,147 width,148 };149}150export function clampWidthToBarGrid(width, options) {151 if (!shouldRenderBars(options))152 return width;153 const barWidth = options.barWidth || 0.5;154 const barGap = options.barGap || barWidth / 2;155 const totalBarWidth = barWidth + barGap;156 if (totalBarWidth === 0)157 return width;158 return Math.floor(width / totalBarWidth) * totalBarWidth;159}160export function calculateSingleCanvasWidth({ clientWidth, totalWidth, options, }) {161 const baseWidth = Math.min(MAX_CANVAS_WIDTH, clientWidth, totalWidth);162 return clampWidthToBarGrid(baseWidth, options);163}164export function sliceChannelData({ channelData, offset, clampedWidth, totalWidth, }) {165 return channelData.map((channel) => {166 const start = Math.floor((offset / totalWidth) * channel.length);167 const end = Math.floor(((offset + clampedWidth) / totalWidth) * channel.length);168 return channel.slice(start, end);169 });170}171export function shouldClearCanvases(currentNodeCount) {172 return currentNodeCount > MAX_NODES;173}174export function getLazyRenderRange({ scrollLeft, totalWidth, numCanvases, }) {175 if (totalWidth === 0)176 return [0];177 const viewPosition = scrollLeft / totalWidth;178 const startCanvas = Math.floor(viewPosition * numCanvases);179 return [startCanvas - 1, startCanvas, startCanvas + 1];180}181export function calculateVerticalScale({ channelData, barHeight, normalize, maxPeak, }) {182 var _a;183 const baseScale = barHeight || 1;184 if (!normalize)185 return baseScale;186 const firstChannel = channelData[0];187 if (!firstChannel || firstChannel.length === 0)188 return baseScale;189 // Use fixed max peak if provided, otherwise calculate from data190 let max = maxPeak !== null && maxPeak !== void 0 ? maxPeak : 0;191 if (!maxPeak) {192 for (let i = 0; i < firstChannel.length; i++) {193 const value = (_a = firstChannel[i]) !== null && _a !== void 0 ? _a : 0;194 const magnitude = Math.abs(value);195 if (magnitude > max)196 max = magnitude;197 }198 }199 if (!max)200 return baseScale;201 return baseScale / max;202}203export function calculateLinePaths({ channelData, width, height, vScale, }) {204 const halfHeight = height / 2;205 const primaryChannel = channelData[0] || [];206 const secondaryChannel = channelData[1] || primaryChannel;207 const channels = [primaryChannel, secondaryChannel];208 return channels.map((channel, index) => {209 const length = channel.length;210 const hScale = length ? width / length : 0;211 const baseY = halfHeight;212 const direction = index === 0 ? -1 : 1;213 const path = [{ x: 0, y: baseY }];214 let prevX = 0;215 let max = 0;216 for (let i = 0; i <= length; i++) {217 const x = Math.round(i * hScale);218 if (x > prevX) {219 const heightDelta = Math.round(max * halfHeight * vScale) || 1;220 const y = baseY + heightDelta * direction;221 path.push({ x: prevX, y });222 prevX = x;223 max = 0;224 }225 const value = Math.abs(channel[i] || 0);226 if (value > max)227 max = value;228 }229 path.push({ x: prevX, y: baseY });230 return path;231 });232}233/**234 * @deprecated Use calculateScrollPercentages from './reactive/scroll-stream.js' instead.235 * This function is maintained for backward compatibility but will be removed in a future version.236 */237export function calculateScrollPercentages({ scrollLeft, clientWidth, scrollWidth, }) {238 if (scrollWidth === 0) {239 return { startX: 0, endX: 1 };240 }241 const startX = scrollLeft / scrollWidth;242 const endX = (scrollLeft + clientWidth) / scrollWidth;243 return {244 startX: Math.max(0, Math.min(1, startX)),245 endX: Math.max(0, Math.min(1, endX)),246 };247}248export function roundToHalfAwayFromZero(value) {249 const scaled = value * 2;250 const rounded = scaled < 0 ? Math.floor(scaled) : Math.ceil(scaled);251 return rounded / 2;252}253 