Realtime Audio Spectrum Analyzer Chart Example

Demonstrates how to create a JavaScript Frequency / Audio Analyzer with Fourier Transform (Frequency spectra) and a real-time frequency history using heatmaps. Note: this example requires microphone permissions to run.

Fullscreen

Edit

 Edit

Docs

drawExample.ts

index.tsx

theme.ts

AudioDataProvider.ts

Radix2FFT.ts

Copy to clipboard
Minimise
Fullscreen
1import { AudioDataProvider } from "./AudioDataProvider";
2import { Radix2FFT } from "./Radix2FFT";
3import { appTheme } from "../../../theme";
4import {
5    XyDataSeries,
6    UniformHeatmapDataSeries,
7    TextAnnotation,
8    ECoordinateMode,
9    EHorizontalAnchorPoint,
10    EVerticalAnchorPoint,
11    SciChartSurface,
12    NumericAxis,
13    EAutoRange,
14    NumberRange,
15    FastLineRenderableSeries,
16    LogarithmicAxis,
17    ENumericFormat,
18    EAxisAlignment,
19    FastMountainRenderableSeries,
20    EllipsePointMarker,
21    PaletteFactory,
22    GradientParams,
23    Point,
24    UniformHeatmapRenderableSeries,
25    HeatmapColorMap,
26} from "scichart";
27
28const AUDIO_STREAM_BUFFER_SIZE = 2048;
29
30export const getChartsInitializationApi = () => {
31    const dataProvider = new AudioDataProvider();
32
33    const bufferSize = dataProvider.bufferSize;
34    const sampleRate = dataProvider.sampleRate;
35
36    const fft = new Radix2FFT(bufferSize);
37
38    const hzPerDataPoint = sampleRate / bufferSize;
39    const fftSize = fft.fftSize;
40    const fftCount = 200;
41
42    let fftXValues: number[];
43    let spectrogramValues: number[][];
44
45    let audioDS: XyDataSeries;
46    let historyDS: XyDataSeries;
47    let fftDS: XyDataSeries;
48    let spectrogramDS: UniformHeatmapDataSeries;
49
50    let hasAudio: boolean;
51
52    const helpText = new TextAnnotation({
53        x1: 0,
54        y1: 0,
55        xAxisId: "history",
56        xCoordinateMode: ECoordinateMode.Relative,
57        yCoordinateMode: ECoordinateMode.Relative,
58        horizontalAnchorPoint: EHorizontalAnchorPoint.Left,
59        verticalAnchorPoint: EVerticalAnchorPoint.Top,
60        text: "This example requires microphone permissions.  Please click Allow in the popup.",
61        textColor: "#FFFFFF88",
62    });
63
64    function updateAnalysers(frame: number): void {
65        // Make sure Audio is initialized
66        if (dataProvider.initialized === false) {
67            return;
68        }
69
70        // Get audio data
71        const audioData = dataProvider.next();
72
73        // Update Audio Chart. When fifoCapacity is set, data automatically scrolls
74        audioDS.appendRange(audioData.xData, audioData.yData);
75
76        // Update History. When fifoCapacity is set, data automatically scrolls
77        historyDS.appendRange(audioData.xData, audioData.yData);
78
79        // Perform FFT
80        const fftData = fft.run(audioData.yData);
81
82        // Update FFT Chart. Clear() and appendRange() is a fast replace for data (if same size)
83        fftDS.clear();
84        fftDS.appendRange(fftXValues, fftData);
85
86        // Update Spectrogram Chart
87        spectrogramValues.shift();
88        spectrogramValues.push(fftData);
89        spectrogramDS.setZValues(spectrogramValues);
90    }
91
92    // AUDIO CHART
93    const initAudioChart = async (rootElement: string | HTMLDivElement) => {
94        // Create a chart for the audio
95        const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
96            theme: appTheme.SciChartJsTheme,
97        });
98
99        // Create an XAxis for the live audio
100        const xAxis = new NumericAxis(wasmContext, {
101            id: "audio",
102            autoRange: EAutoRange.Always,
103            drawLabels: false,
104            drawMinorTickLines: false,
105            drawMajorTickLines: false,
106            drawMajorBands: false,
107            drawMinorGridLines: false,
108            drawMajorGridLines: false,
109        });
110        sciChartSurface.xAxes.add(xAxis);
111
112        // Create an XAxis for the history of the audio on the same chart
113        const xhistAxis = new NumericAxis(wasmContext, {
114            id: "history",
115            autoRange: EAutoRange.Always,
116            drawLabels: false,
117            drawMinorGridLines: false,
118            drawMajorTickLines: false,
119        });
120        sciChartSurface.xAxes.add(xhistAxis);
121
122        // Create a YAxis for the audio data
123        const yAxis = new NumericAxis(wasmContext, {
124            autoRange: EAutoRange.Never,
125            visibleRange: new NumberRange(-32768 * 0.8, 32767 * 0.8), // [short.MIN. short.MAX]
126            drawLabels: false,
127            drawMinorTickLines: false,
128            drawMajorTickLines: false,
129            drawMajorBands: false,
130            drawMinorGridLines: false,
131            drawMajorGridLines: false,
132        });
133        sciChartSurface.yAxes.add(yAxis);
134
135        // Initializing a series with fifoCapacity enables scrolling behaviour and auto discarding old data
136        audioDS = new XyDataSeries(wasmContext, { fifoCapacity: AUDIO_STREAM_BUFFER_SIZE });
137
138        // Fill the data series with zero values
139        for (let i = 0; i < AUDIO_STREAM_BUFFER_SIZE; i++) {
140            audioDS.append(0, 0);
141        }
142
143        // Add a line series for the live audio data
144        // using XAxisId=audio for the live audio trace scaling
145        const rs = new FastLineRenderableSeries(wasmContext, {
146            xAxisId: "audio",
147            stroke: "#4FBEE6",
148            strokeThickness: 2,
149            dataSeries: audioDS,
150        });
151
152        sciChartSurface.renderableSeries.add(rs);
153
154        // Initializing a series with fifoCapacity enables scrolling behaviour and auto discarding old data.
155        historyDS = new XyDataSeries(wasmContext, { fifoCapacity: AUDIO_STREAM_BUFFER_SIZE * fftCount });
156        for (let i = 0; i < AUDIO_STREAM_BUFFER_SIZE * fftCount; i++) {
157            historyDS.append(0, 0);
158        }
159
160        // Add a line series for the historical audio data
161        // using the XAxisId=history for separate scaling for this trace
162        const histrs = new FastLineRenderableSeries(wasmContext, {
163            stroke: "#208EAD33",
164            strokeThickness: 1,
165            opacity: 0.5,
166            xAxisId: "history",
167            dataSeries: historyDS,
168        });
169        sciChartSurface.renderableSeries.add(histrs);
170
171        // Add instructions
172        sciChartSurface.annotations.add(helpText);
173
174        hasAudio = await dataProvider.initAudio();
175
176        return { sciChartSurface };
177    };
178
179    // FFT CHART
180    const initFftChart = async (rootElement: string | HTMLDivElement) => {
181        const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
182            theme: appTheme.SciChartJsTheme,
183        });
184        const xAxis = new LogarithmicAxis(wasmContext, {
185            logBase: 10,
186            labelFormat: ENumericFormat.SignificantFigures,
187            maxAutoTicks: 5,
188            axisTitleStyle: { fontSize: 10 },
189            drawMinorGridLines: false,
190            drawMinorTickLines: false,
191            drawMajorTickLines: false,
192        });
193        sciChartSurface.xAxes.add(xAxis);
194
195        const yAxis = new NumericAxis(wasmContext, {
196            axisAlignment: EAxisAlignment.Left,
197            visibleRange: new NumberRange(0, 80),
198            growBy: new NumberRange(0.1, 0.1),
199            drawMinorGridLines: false,
200            drawMinorTickLines: false,
201            drawMajorTickLines: false,
202            labelPrecision: 0,
203            axisTitleStyle: { fontSize: 10 },
204        });
205        sciChartSurface.yAxes.add(yAxis);
206
207        fftDS = new XyDataSeries(wasmContext);
208        fftXValues = new Array<number>(fftSize);
209        for (let i = 0; i < fftSize; i++) {
210            fftXValues[i] = (i + 1) * hzPerDataPoint;
211        }
212
213        // Make a column chart with a gradient palette on the stroke only
214        const rs = new FastMountainRenderableSeries(wasmContext, {
215            dataSeries: fftDS,
216            pointMarker: new EllipsePointMarker(wasmContext, { width: 9, height: 9 }),
217            strokeThickness: 3,
218            paletteProvider: PaletteFactory.createGradient(
219                wasmContext,
220                new GradientParams(new Point(0, 0), new Point(1, 1), [
221                    { offset: 0, color: "#36B8E6" },
222                    { offset: 0.001, color: "#5D8CC2" },
223                    { offset: 0.01, color: "#8166A2" },
224                    { offset: 0.1, color: "#AE418C" },
225                    { offset: 1.0, color: "#CA5B79" },
226                ]),
227                {
228                    enableStroke: true,
229                    enableFill: true,
230                    enablePointMarkers: true,
231                    fillOpacity: 0.17,
232                    pointMarkerOpacity: 0.37,
233                }
234            ),
235        });
236        sciChartSurface.renderableSeries.add(rs);
237
238        return { sciChartSurface };
239    };
240
241    // SPECTROGRAM CHART
242    const initSpectogramChart = async (rootElement: string | HTMLDivElement) => {
243        spectrogramValues = new Array<number[]>(fftCount);
244        for (let i = 0; i < fftCount; i++) {
245            spectrogramValues[i] = new Array<number>(fftSize);
246            for (let j = 0; j < fftSize; j++) {
247                spectrogramValues[i][j] = 0;
248            }
249        }
250
251        const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
252            theme: appTheme.SciChartJsTheme,
253        });
254
255        const xAxis = new NumericAxis(wasmContext, {
256            autoRange: EAutoRange.Always,
257            drawLabels: false,
258            drawMinorTickLines: false,
259            drawMajorTickLines: false,
260        });
261        sciChartSurface.xAxes.add(xAxis);
262
263        const yAxis = new NumericAxis(wasmContext, {
264            autoRange: EAutoRange.Always,
265            drawLabels: false,
266            drawMinorTickLines: false,
267            drawMajorTickLines: false,
268        });
269        sciChartSurface.yAxes.add(yAxis);
270
271        spectrogramDS = new UniformHeatmapDataSeries(wasmContext, {
272            xStart: 0,
273            xStep: 1,
274            yStart: 0,
275            yStep: 1,
276            zValues: spectrogramValues,
277        });
278
279        const rs = new UniformHeatmapRenderableSeries(wasmContext, {
280            dataSeries: spectrogramDS,
281            colorMap: new HeatmapColorMap({
282                minimum: 0,
283                maximum: 70,
284                gradientStops: [
285                    { offset: 0, color: "#000000" },
286                    { offset: 0.25, color: "#800080" },
287                    { offset: 0.5, color: "#FF0000" },
288                    { offset: 0.75, color: "#FFFF00" },
289                    { offset: 1, color: "#FFFFFF" },
290                ],
291            }),
292        });
293        sciChartSurface.renderableSeries.add(rs);
294
295        return { sciChartSurface };
296    };
297
298    const onAllChartsInit = () => {
299        if (!hasAudio) {
300            console.log("dataProvider", dataProvider);
301            if (dataProvider.permissionError) {
302                helpText.text =
303                    "We were not able to access your microphone.  This may be because you did not accept the permissions.  Open your browser security settings and remove the block on microphone permissions from this site, then reload the page.";
304            } else if (!window.isSecureContext) {
305                helpText.text = "Cannot get microphone access if the site is not localhost or on https";
306            } else {
307                helpText.text = "There was an error trying to get microphone access.  Check the console";
308            }
309
310            return { startUpdate: () => {}, stopUpdate: () => {}, cleanup: () => {} };
311        } else {
312            helpText.text = "This example uses your microphone to generate waveforms. Say something!";
313
314            // START ANIMATION
315
316            let frameCounter = 0;
317            const updateChart = () => {
318                if (!dataProvider.isDeleted) {
319                    updateAnalysers(frameCounter++);
320                }
321            };
322
323            let timerId: NodeJS.Timeout;
324
325            const startUpdate = () => {
326                timerId = setInterval(updateChart, 20);
327            };
328
329            const stopUpdate = () => {
330                clearInterval(timerId);
331            };
332
333            const cleanup = () => {
334                dataProvider.closeAudio();
335            };
336
337            return { startUpdate, stopUpdate, cleanup };
338        }
339    };
340
341    return { initAudioChart, initFftChart, initSpectogramChart, onAllChartsInit };
342};
343

React Audio Spectral Analyzer

Overview

This example demonstrates a real-time audio analyzer built with React and SciChart.js. It leverages the Web Audio API to capture microphone input, processes the audio data with a Fast Fourier Transform using a custom Radix2FFT implementation, and visualizes the results in three different SciChart.js charts: an audio waveform chart, an FFT spectrum chart, and a spectrogram heatmap. This application requires microphone permissions and shows how to create advanced, real-time data visualizations in a React environment.

Technical Implementation

The example integrates SciChart.js into React by using the <SciChartReact/> component along with SciChartGroup to manage multiple charts in a React application. The chart initialization is handled asynchronously via hooks such as useState and useRef. Once the charts are initialized, an interval is set up to periodically fetch audio data, update the associated data series, and perform an FFT calculation on the incoming data. The use of fifoCapacity in the XyDataSeries enables efficient real-time streaming and auto-discarding of old data. Developers interested in understanding how SciChart.js integrates with React can refer to the React Charts with SciChart.js: Introducing “SciChart React” article and the Creating a SciChart React Component from the Ground Up tutorial for additional context.

Features and Capabilities

Real-Time Updates: The application continuously updates all three charts in real time by capturing microphone input and processing it through a Fourier Transform algorithm. The FFT and spectrogram charts offer dynamic visual feedback on frequency components, while the audio chart displays the raw waveform. For more details on real-time data handling, developers can review Adding Realtime Updates | JavaScript Chart Documentation - SciChart.

Advanced Customizations: The charts are highly customizable with options for axes configuration, color palettes, and rendering series. The spectrogram chart, for instance, utilizes a UniformHeatmapDataSeries and a custom HeatmapColorMap to translate FFT data into a visual heatmap.

Integration and Best Practices

The React integration is achieved using standard hooks like useState and useRef to manage chart instance references and asynchronous initialization routines. The SciChartGroup component is used for composing multiple charts together in a seamless layout, in line with best practices for component composition in React. Cleanup is properly handled by stopping the data update interval and closing the audio context when the component is unmounted, which can be seen as a practical example of React cleanup patterns. Additionally, the example demonstrates the integration of browser media APIs for microphone access, ensuring that all necessary permissions are acquired before initiating the data stream. For further insights on integrating media APIs within a React component, consider reading How to Access Microphones Through the Browser API | Speechmatics.

Performance Considerations

Efficient performance is achieved by utilizing techniques such as fifoCapacity in data series, reducing unnecessary redraws and memory allocations. Furthermore, the continuous update cycle via setInterval is optimized to handle high-frequency data updates while maintaining smooth rendering. For more information on performance optimization in React with SciChart.js, check out the article Creating a React Drag & Drop Chart Dashboard Performance Demo with 100 Charts.

react Chart Examples & Demos

See Also: Performance Demos & Showcases (12 Demos)

Realtime React Chart Performance Demo | SciChart.js Demo

Realtime React Chart Performance Demo

This demo showcases the incredible realtime performance of our React charts by updating the series with millions of data-points!

Load 500 Series x 500 Points Performance Demo | SciChart

Load 500 Series x 500 Points Performance Demo

This demo showcases the incredible performance of our React Chart by loading 500 series with 500 points (250k points) instantly!

Load 1 Million Points Performance Demo | SciChart.js Demo

Load 1 Million Points Performance Demo

This demo showcases the incredible performance of our JavaScript Chart by loading a million points instantly.

Realtime Ghosted Traces | React Charts | SciChart.js Demo

Realtime Ghosted Traces

This demo showcases the realtime performance of our React Chart by animating several series with thousands of data-points at 60 FPS

Oil & Gas Explorer React Dashboard | SciChart.js Demo

Oil & Gas Explorer React Dashboard

Demonstrates how to create Oil and Gas Dashboard

Client/Server Websocket Data Streaming | SciChart.js Demo

Client/Server Websocket Data Streaming

This demo showcases the incredible realtime performance of our JavaScript charts by updating the series with millions of data-points!

Server Traffic Dashboard | React Charts | SciChart.js Demo

Server Traffic Dashboard

This dashboard demo showcases the incredible realtime performance of our React charts by updating the series with millions of data-points!

Rich Interactions Showcase | React Charts | SciChart.js

Rich Interactions Showcase

This demo showcases the incredible realtime performance of our React charts by updating the series with millions of data-points!

Dynamic Layout Showcase | React Charts | SciChart.js Demo

Dynamic Layout Showcase

Demonstrates a custom modifier which can convert from single chart to grid layout and back.

Dragabble Event Markers | React Charts | SciChart.js Demo

Dragabble Event Markers

Demonstrates how to repurpose a Candlestick Series into dragabble, labled, event markers

React Population Pyramid | React Charts | SciChart.js Demo

React Population Pyramid

Population Pyramid of Europe and Africa

NEW!
High Performance SVG Cursor & Rollover | SciChart.js Demo

High Performance SVG Cursor & Rollover

Demonstrates how to use the SVG render layer in SciChart.js to maintain smooth cursor interaction on heavy charts with millions of points.

SciChart Ltd, 16 Beaufort Court, Admirals Way, Docklands, London, E14 9XL.