UNPKG

@nafr/echo-ui

Version:

A UI library born for WAA

123 lines (122 loc) 4.34 kB
import * as Tone from 'tone'; import { useCallback, useEffect, useRef, useState } from 'react'; import { logger } from '../lib/log'; /** * useVuMeter is a custom React hook that integrates with Tone.js to create a VU (Volume Unit) meter. * It can be used to monitor audio signal levels, supporting both mono and stereo inputs. * * @param {UseVuMeterProps} props - The configuration properties for the VU meter. * @param {number | number[]} props.value - The initial value(s) for the meter. Pass a single number for mono or an array of numbers for stereo. * @param {Function} props.onReady - Callback executed when the meter is ready. * @param {Function} props.onError - Callback executed in case of an error. * * @returns {object} An object containing various properties and methods for the VU meter: * - meter: The Tone.js Meter or Split instance, depending on the input type (mono or stereo). * - value: The current value(s) of the meter. It's a number for mono or an array of numbers for stereo. * - init: Method to initialize the VU meter. * - getValue: Method to retrieve the current value(s) from the meter. * - observe: Method to start observing and updating the meter's value(s). * - cancelObserve: Method to stop observing the meter's value(s). * - error: Boolean indicating if an error has occurred. * - errorMessage: The error message in case of an error. * * This hook can be particularly useful in audio applications where monitoring signal levels is necessary, * such as in mixing consoles, audio workstations, or other digital audio processing tools. */ export const useVuMeter = (props) => { const { value: _value, onReady, onError } = props; const isStereo = Array.isArray(_value); const meter = useRef(null); const meterL = useRef(null); const meterR = useRef(null); const split = useRef(null); const observerId = useRef(0); const [value, setValue] = useState(_value); const [error, setError] = useState(false); const [errorMessage, setErrorMessage] = useState(''); useEffect(() => { return () => { if (!meter.current) return; meter.current.dispose(); cancelObserve(); }; }, []); useEffect(() => { if (!error) return; logger.error(errorMessage); onError && onError(); }, [error]); const init = useCallback(() => { try { if (!isStereo) { meter.current = new Tone.Meter(); } else { meterL.current = new Tone.Meter(); meterR.current = new Tone.Meter(); split.current = new Tone.Split(); split.current.connect(meterL.current, 0); split.current.connect(meterR.current, 1); } onReady && onReady(); } catch (err) { setError(true); setErrorMessage(err); } }, []); const getValue = useCallback(() => { if (error) return; if (!isStereo && !meter.current) return; if (isStereo && (!meterL.current || !meterR.current)) return; try { let newValue; if (isStereo) newValue = [meterL.current.getValue(), meterR.current.getValue()]; else newValue = meter.current.getValue(); setValue(newValue); } catch (err) { setError(true); setErrorMessage(err); } }, []); const observe = useCallback(() => { try { getValue(); observerId.current = requestAnimationFrame(observe); } catch (err) { setError(true); setErrorMessage(err); } }, []); const cancelObserve = useCallback(() => { if (!observerId.current || error) return; try { setValue(_value); cancelAnimationFrame(observerId.current); } catch (err) { setError(true); setErrorMessage(err); } }, [_value]); return { meter: isStereo ? split : meter, value, init, getValue, observe, cancelObserve, error, errorMessage, }; };