Skip to main content

Oscilloscope

audio-oscilloscope.tsx
import {Audio} from '@remotion/media'; import { createSmoothSvgPath, getWaveformPortion, useWindowedAudioData, } from '@remotion/media-utils'; import React from 'react'; import { Interactive, useCurrentFrame, useVideoConfig, type InteractiveTransformProps, type InteractivitySchema, } from 'remotion'; type AudioOscilloscopeProps = InteractiveTransformProps & { readonly amplitude?: number; readonly audioSrc?: string; readonly lineColor?: string; readonly lineWidth?: number; readonly windowInSeconds?: number; }; const audioOscilloscopeSchema = { audioSrc: { type: 'asset', assetType: 'audio', default: 'https://remotion.media/elements/remotion-made-this-picture-move.mp3', description: 'Audio source', }, lineColor: { type: 'color', default: '#2563eb', description: 'Waveform color', }, lineWidth: { type: 'number', min: 1, max: 16, step: 1, default: 6, description: 'Line width', hiddenFromList: false, }, amplitude: { type: 'number', min: 0.25, max: 4, step: 0.05, default: 2, description: 'Amplitude', hiddenFromList: false, }, windowInSeconds: { type: 'number', min: 0.05, max: 1, step: 0.05, default: 0.35, description: 'Time window in seconds', hiddenFromList: false, }, } as const satisfies InteractivitySchema; const AudioOscilloscopeInner: React.FC<AudioOscilloscopeProps> = ({ amplitude = 2, audioSrc = 'https://remotion.media/elements/remotion-made-this-picture-move.mp3', lineColor = '#2563eb', lineWidth = 6, style, windowInSeconds = 0.35, }) => { const frame = useCurrentFrame(); const {fps} = useVideoConfig(); const {audioData, dataOffsetInSeconds} = useWindowedAudioData({ fps, frame, src: audioSrc, windowInSeconds: 10, }); const waveform = audioData ? getWaveformPortion({ audioData, channel: 0, dataOffsetInSeconds, durationInSeconds: windowInSeconds, normalize: false, numberOfSamples: 64, outputRange: 'minus-one-to-one', startTimeInSeconds: frame / fps - windowInSeconds / 2, }).map((sample) => sample.amplitude) : []; const path = createSmoothSvgPath({ points: waveform.map((value, index) => ({ x: lineWidth * 2 + (index / (waveform.length - 1)) * (900 - lineWidth * 4), y: 150 + value * 150 * amplitude, })), }); return ( <div style={{ boxSizing: 'border-box', height: 300, overflow: 'hidden', width: 900, ...style, }} > <Audio showInTimeline={false} src={audioSrc} /> <svg height={300} viewBox="0 0 900 300" width={900}> <line x1={0} x2={900} y1={150} y2={150} stroke={lineColor} strokeOpacity={0.18} strokeWidth={1} /> {path ? ( <path d={path} fill="none" stroke={lineColor} strokeLinecap="round" strokeLinejoin="round" strokeWidth={lineWidth} /> ) : null} </svg> </div> ); }; export const AudioOscilloscope = Interactive.withSchema({ Component: AudioOscilloscopeInner, componentName: '<AudioOscilloscope>', schema: audioOscilloscopeSchema, wrapInSequence: true, });