254 lines
5.8 KiB
JavaScript
254 lines
5.8 KiB
JavaScript
import React, { useState, useEffect } from 'react'
|
|
|
|
const SCALE_MIN = -60
|
|
const SCALE_MAX = 0
|
|
|
|
function toPx (val) {
|
|
return value2px(val, SCALE_MIN, SCALE_MAX, 0, 100)
|
|
}
|
|
|
|
function toY (val) {
|
|
return 100 - value2px(val)
|
|
}
|
|
|
|
function MeterDefs (props) {
|
|
const { width } = props
|
|
let green = [0, toPx(-8)]
|
|
let yellow = [toPx(-8), toPx(-5)]
|
|
let red = [toPx(-5), toPx(0)]
|
|
const common = { x: 0, width }
|
|
|
|
return (
|
|
<defs>
|
|
<g id='peakbg'>
|
|
<rect
|
|
y={100 - red[1]}
|
|
height={100 - red[0]}
|
|
{...common}
|
|
{...style('#ff2244')}
|
|
/>
|
|
<rect
|
|
y={100 - yellow[1]}
|
|
height={100 - yellow[0]}
|
|
{...common}
|
|
{...style('#eeee00')}
|
|
/>
|
|
<rect
|
|
y={100 - green[1]}
|
|
height={100 - green[0]}
|
|
{...common}
|
|
{...style('#88ff00')}
|
|
/>
|
|
</g>
|
|
|
|
<linearGradient id='fadeGrad' y2='0' x2='1'>
|
|
<stop offset='0' stopColor='white' stopOpacity='0.2' />
|
|
<stop offset='0.5' stopColor='white' stopOpacity='0.5' />
|
|
<stop offset='1' stopColor='white' stopOpacity='0.2' />
|
|
</linearGradient>
|
|
|
|
<mask id='bgfade' maskContentUnits='objectBoundingBox'>
|
|
<rect width='1' height='1' fill='url(#fadeGrad)' />
|
|
</mask>
|
|
</defs>
|
|
)
|
|
}
|
|
|
|
let max = 0
|
|
let last = 0
|
|
function getMax (time, val) {
|
|
// let newmax = Math.max(val, max)
|
|
if (val > max) {
|
|
max = val
|
|
last = time
|
|
} else if (time - last > 3) {
|
|
max = val
|
|
} else if (time - last > 1) {
|
|
max = max - 1
|
|
// max = val
|
|
}
|
|
return max
|
|
}
|
|
|
|
export function EbuMeter (props) {
|
|
const { value } = props
|
|
const val = value
|
|
// let [max, setMax] = useState(0)
|
|
|
|
const width = 40
|
|
const gutter = 1
|
|
|
|
let min = SCALE_MIN
|
|
|
|
// let min = -30 // mic
|
|
// let peakMax = -2
|
|
// let ebuMax = -5
|
|
let peakMax = SCALE_MAX
|
|
let ebuMax = SCALE_MAX
|
|
|
|
// Momentary peak
|
|
// let peakM = value2px(val.M, min, ebuMax, 0, 100)
|
|
// Short-term peak
|
|
let peakS = value2px(val.S, min, ebuMax, 0, 100)
|
|
let peakL = 0
|
|
let peakR = 0
|
|
|
|
if (val.FTPK) {
|
|
peakL = value2px(val.FTPK[0], min, peakMax, 0, 100)
|
|
peakR = value2px(val.FTPK[1], min, peakMax, 0, 100)
|
|
}
|
|
|
|
if (peakL > 100) peakL = 0
|
|
if (peakR > 100) peakR = 0
|
|
if (peakS > 100) peakS = 0
|
|
|
|
let max = getMax(val.t, Math.max(peakL, peakR))
|
|
|
|
const meterWidth = width / 2 - gutter / 2
|
|
const peakMeterL = {
|
|
id: 'peakL',
|
|
peak: peakL,
|
|
x: 0,
|
|
width: meterWidth
|
|
}
|
|
const peakMeterR = {
|
|
id: 'peakR',
|
|
peak: peakR,
|
|
x: meterWidth + gutter,
|
|
width: meterWidth
|
|
}
|
|
return (
|
|
<svg
|
|
xmlns='http://www.w3.org/2000/svg'
|
|
width='100%'
|
|
heigh='100%'
|
|
viewBox={`0 0 ${width} 100`}
|
|
preserveAspectRatio='xMaxYMax meet'
|
|
>
|
|
<MeterDefs width={width} />
|
|
|
|
<PeakMeter {...peakMeterL} />
|
|
<PeakMeter {...peakMeterR} />
|
|
|
|
{/* <rect y={100 - max} x='0' width={width} height='0.8' fill='#a9f' /> */}
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function PeakMeter (props = {}) {
|
|
const { peak, width, x, id } = props
|
|
const mask = {
|
|
y: 100 - peak,
|
|
height: peak,
|
|
x,
|
|
width
|
|
}
|
|
|
|
// const range = -60
|
|
// const gutter = 0.5
|
|
// const steps = 24
|
|
// const dbPerStep = range / steps
|
|
// const scale = new Array(steps).fill(null).map((_, i) => {
|
|
// const bottom = toPx(dbPerStep * (i)) - gutter
|
|
// const top = toPx(dbPerStep * (i + 1))
|
|
// let fill = '#fff'
|
|
// // if (bottom > peak) fill = '#444'
|
|
// return {
|
|
// fill,
|
|
// x,
|
|
// width,
|
|
// y: 100 - top,
|
|
// height: top - bottom
|
|
// }
|
|
// })
|
|
|
|
const height = 100
|
|
const gutter = 0.8
|
|
const steps = 24
|
|
const heightPerStep = height / steps
|
|
// const filledSteps = Math.ceil(peak / steps)
|
|
const scale = new Array(steps).fill(null).map((_, i) => {
|
|
const bottom = heightPerStep * i
|
|
const top = bottom + heightPerStep - gutter
|
|
let fill = 'white'
|
|
if (((top + bottom) / 2) > peak) fill = '#444'
|
|
// if ((steps * i) > peak) return
|
|
// const y = 100 - heightPerStep * i
|
|
return {
|
|
fill,
|
|
x,
|
|
width,
|
|
y: 100 - top,
|
|
height: top - bottom
|
|
}
|
|
}).filter(x => x)
|
|
return (
|
|
<>
|
|
<defs>
|
|
<mask id={id}>
|
|
<rect fill='#000' {...mask} />
|
|
{scale.map((rect, i) => <rect key={i} {...rect} />)}
|
|
</mask>
|
|
</defs>
|
|
<g
|
|
mask={`url(#${id})`}
|
|
style={{ opacity: 0.9 }}
|
|
>
|
|
<use xlinkHref='#peakbg' />
|
|
</g>
|
|
</>
|
|
)
|
|
}
|
|
|
|
function sigLog (n) {
|
|
return Math.log(Math.abs(n) + 1) / Math.log(10) * sig(n)
|
|
}
|
|
|
|
function sig (n) {
|
|
return n === 0 ? 0 : Math.abs(n) / n
|
|
}
|
|
|
|
function sigExp (n) {
|
|
return (Math.pow(10, Math.abs(n)) - 1) * sig(n)
|
|
}
|
|
|
|
function value2px (value, valueMin, valueMax, pxMin, pxMax) {
|
|
if (value < valueMin) value = valueMin
|
|
if (value > valueMax) value = valueMax
|
|
if (isNaN(value) || value === null) value = valueMin
|
|
|
|
const valueWidth = valueMax - valueMin
|
|
const pixelWidth = pxMax - pxMin
|
|
const ratio = pixelWidth / valueWidth
|
|
return ratio * (value - valueMin) + pxMin
|
|
|
|
// const ratio = value / (valueMax - valueMin)
|
|
// const scaled = ratio * (pxMax - pxMin)
|
|
// return pxMin + scaled
|
|
}
|
|
|
|
// This is the original function that display the meter in a log scale.
|
|
function value2pxLog (value, valueMin, valueMax, pxMin, pxMax) {
|
|
var valueWidth = sigLog(valueMax) - sigLog(valueMin)
|
|
var pixelWidth = pxMax - pxMin
|
|
var ratio = pixelWidth / valueWidth
|
|
|
|
return ratio * (sigLog(value) - sigLog(valueMin)) + pxMin
|
|
}
|
|
|
|
function px2value (px, valueMin, valueMax, pxMin, pxMax) {
|
|
var valueWidth = sigLog(valueMax) - sigLog(valueMin)
|
|
var pixelWidth = pxMax - pxMin
|
|
var ratio = pixelWidth / valueWidth
|
|
|
|
return sigExp((px - pxMin) / ratio + sigLog(valueMin))
|
|
}
|
|
|
|
function prettify (n) {
|
|
var exp = Math.round(Math.pow(10, Math.log(Math.abs(n)) / Math.log(10)))
|
|
return exp === 0 ? 0 : Math.round(n / exp) * exp
|
|
}
|
|
|
|
function style (color) {
|
|
return { style: { fill: color } }
|
|
}
|