Skip to content

Commit 636b934

Browse files
authored
Docs: noise guide (#267)
* feat: docs - components for guide about noise generation * feat: noise description
1 parent 879c058 commit 636b934

File tree

11 files changed

+789
-4
lines changed

11 files changed

+789
-4
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
---
2+
sidebar_position: 4
3+
---
4+
5+
import InteractiveExample from '@site/src/components/InteractiveExample';
6+
7+
# Noise generation
8+
9+
Noise is one of the most basic and common tools in digital audio processing, in this guide, we will go through most common noise types and how to implement them using web audio api.
10+
11+
## White noise
12+
13+
The most used type of noise. White is a random signal having equal intensity at different frequencies, giving it a constant [power spectral density. (Wikipedia)](https://en.wikipedia.org/wiki/Spectral_density#Power_spectral_density).
14+
15+
To produce white noise, we simply create an `AudioBuffer` containing random samples in range of `[-1; 1]` (in which audio api operates), which can be used by `AudioBufferSourceNode` for playback, further filtering or modification
16+
17+
```tsx
18+
function createWhiteNoise() {
19+
const aCtx = = new AudioContext();
20+
const bufferSize = aCtx.sampleRate * 2;
21+
const output = new Array<number>(bufferSize);
22+
23+
for (let i = 0; i < bufferSize; i += 1) {
24+
output[i] = Math.random() * 2 - 1;
25+
}
26+
27+
const noiseBuffer = aCtx.createBuffer(1, bufferSize, aCtx.sampleRate);
28+
noiseBuffer.copyToChannel(output, 0, 0);
29+
30+
return noiseBuffer;
31+
}
32+
```
33+
34+
Usually we want the noise to be able to be played constantly. To achieve this we are generating 2 seconds of the noise sound, which we will later loop using the `AudioBufferSourceNode` properties. In audio processing `sampleRate` means number of samples that will be played during one second, thus we simply multiply this value by `2` to achieve desired length of the buffer.
35+
36+
import WhiteNoise from '@site/src/examples/NoiseGeneration/WhiteNoiseComponent';
37+
import WhiteNoiseSrc from '!!raw-loader!@site/src/examples/NoiseGeneration/WhiteNoiseSource';
38+
39+
<InteractiveExample component={WhiteNoise} src={WhiteNoiseSrc} />
40+
41+
## Pink noise
42+
43+
Pink noise, also known as 1/f noise (where "f" stands for frequency), is a type of signal or sound that has equal energy per octave. This means that the power spectral density (PSD) decreases inversely with frequency. In simpler terms, pink noise has more energy at lower frequencies and less energy at higher frequencies, which makes it sound softer and more balanced to the human ear than white noise.
44+
45+
To generate pink noise, we will use the effects of a $\dfrac{-3dB}{octave}$ filter using the [Paul Kellet's refined method](https://www.musicdsp.org/en/latest/Filters/76-pink-noise-filter.html)
46+
47+
```tsx
48+
const createPinkNoise = () => {
49+
const aCtx = new AudioContext();
50+
51+
const bufferSize = 2 * aCtx.sampleRate;
52+
const output = new Array<number>(bufferSize);
53+
54+
let b0, b1, b2, b3, b4, b5, b6;
55+
b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0.0;
56+
57+
for (let i = 0; i < bufferSize; i += 1) {
58+
const white = Math.random() * 2 - 1;
59+
60+
b0 = 0.99886 * b0 + white * 0.0555179;
61+
b1 = 0.99332 * b1 + white * 0.0750759;
62+
b2 = 0.969 * b2 + white * 0.153852;
63+
b3 = 0.8665 * b3 + white * 0.3104856;
64+
b4 = 0.55 * b4 + white * 0.5329522;
65+
b5 = -0.7616 * b5 - white * 0.016898;
66+
67+
output[i] = 0.11 * (b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362);
68+
b6 = white * 0.115926;
69+
}
70+
71+
const noiseBuffer = aCtx.createBuffer(1, bufferSize, aCtx.sampleRate);
72+
noiseBuffer.copyToChannel(output, 0, 0);
73+
74+
return noiseBuffer;
75+
}
76+
```
77+
78+
You can find more information about pink noise generation here: [https://www.firstpr.com.au/dsp/pink-noise/](https://www.firstpr.com.au/dsp/pink-noise/)
79+
80+
import PinkNoise from '@site/src/examples/NoiseGeneration/PinkNoiseComponent';
81+
import PinkNoiseSrc from '!!raw-loader!@site/src/examples/NoiseGeneration/PinkNoiseSource';
82+
83+
<InteractiveExample component={PinkNoise} src={PinkNoiseSrc} />
84+
85+
## Brownian noise
86+
87+
The last noise type I would like to describe is brownian noise (also known as Brown or red noise). Brownian noise is named after the Brownian motion phenomenon, where particles inside a fluid move randomly due to collisions with other particles. It relates to its sonic counterpart in that Brownian noise is characterized by a significant presence of low frequencies, with energy decreasing as the frequency increases. Brownian noise is believed to sound like waterfall.
88+
89+
Brownian noise, similarly to pink one, decreases in power by $\dfrac{12dB}{octave}$ and sounds similar to waterfall. The implementation is taken from article by Zach Denton, [How to Generate Noise with the Web Audio API](https://noisehack.com/generate-noise-web-audio-api/):
90+
91+
```tsx
92+
const createBrownianNoise = () => {
93+
const aCtx = new AudioContext();
94+
95+
const bufferSize = 2 * aCtx.sampleRate;
96+
const output = new Array<number>(bufferSize);
97+
let lastOut = 0.0;
98+
99+
for (let i = 0; i < bufferSize; i += 1) {
100+
const white = Math.random() * 2 - 1;
101+
output[i] = (lastOut + 0.02 * white) / 1.02;
102+
lastOut = output[i];
103+
output[i] *= 3.5;
104+
}
105+
106+
const noiseBuffer = aCtx.createBuffer(1, bufferSize, aCtx.sampleRate);
107+
noiseBuffer.copyToChannel(output, 0, 0);
108+
109+
return noiseBuffer;
110+
}
111+
```
112+
113+
import BrownianNoise from '@site/src/examples/NoiseGeneration/BrownianNoiseComponent';
114+
import BrownianNoiseSrc from '!!raw-loader!@site/src/examples/NoiseGeneration/BrownianNoiseSource';
115+
116+
<InteractiveExample component={BrownianNoise} src={BrownianNoiseSrc} />
117+
118+
## What's next?
119+
120+
[fill after merge :)]

packages/audiodocs/docusaurus.config.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
const lightCodeTheme = require('./src/theme/CodeBlock/highlighting-light.js');
55
const darkCodeTheme = require('./src/theme/CodeBlock/highlighting-dark.js');
6+
const math = require('remark-math');
7+
const katex = require('rehype-katex');
68
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
79

810
const webpack = require('webpack');
@@ -42,6 +44,8 @@ const config = {
4244
breadcrumbs: false,
4345
sidebarCollapsible: false,
4446
sidebarPath: require.resolve('./sidebars.js'),
47+
remarkPlugins: [math],
48+
rehypePlugins: [katex],
4549
editUrl:
4650
'https://github.com/software-mansion-labs/react-native-audio-api/edit/main/packages/audiodocs/docs',
4751
},
@@ -57,6 +61,16 @@ const config = {
5761
],
5862
],
5963

64+
stylesheets: [
65+
{
66+
href: 'https://cdn.jsdelivr.net/npm/katex@0.13.24/dist/katex.min.css',
67+
type: 'text/css',
68+
integrity:
69+
'sha384-odtC+0UGzzFL/6PNoE8rX/SPcQDXBJ+uRepguP4QkPCm2LBxH3FA3y+fKSiJ+AmM',
70+
crossorigin: 'anonymous',
71+
},
72+
],
73+
6074
markdown: {
6175
mermaid: true,
6276
},
@@ -65,6 +79,7 @@ const config = {
6579
themeConfig: {
6680
// Replace with your project's social card
6781
// image: 'img/docusaurus-social-card.jpg',
82+
6883
navbar: {
6984
hideOnScroll: true,
7085
title: 'React Native Audio API',

packages/audiodocs/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"babel-polyfill": "^6.26.0",
3535
"babel-preset-expo": "^9.2.2",
3636
"clsx": "^1.2.1",
37+
"hast-util-is-element": "1.1.0",
3738
"raf": "^3.4.1",
3839
"raw-loader": "^4.0.2",
3940
"react": "^17.0.2",
@@ -45,6 +46,8 @@
4546
"react-native-gesture-handler": "^2.16.0",
4647
"react-native-reanimated": "^3.8.1",
4748
"react-native-web": "^0.18.12",
49+
"rehype-katex": "5",
50+
"remark-math": "3",
4851
"source-map": "^0.7.4",
4952
"source-map-loader": "^4.0.1",
5053
"usehooks-ts": "^2.9.1"

packages/audiodocs/src/components/LandingPage/OscillatorSquare.module.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
cursor: pointer;
1111
background: transparent;
1212
overflow: visible;
13+
background-clip: content-box;
1314
}
1415

1516
.oscillatorBorder {
@@ -73,7 +74,7 @@
7374
background-color: rgba(255, 255, 255, 0.3);
7475
}
7576

76-
.oscillatorPointerInner {
77+
.oscillatorPointerInnerOut {
7778
background-color: rgba(255, 255, 255, 0.1);
7879
}
7980
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import React, { FC, useEffect, useRef } from 'react';
2+
import { View, Pressable, StyleSheet } from 'react-native';
3+
import { AudioContext, AudioBuffer, GainNode, AudioBufferSourceNode } from 'react-native-audio-api';
4+
5+
const WhiteNoise: FC = () => {
6+
const aCtxRef = useRef<AudioContext | null>(null);
7+
const bufferRef = useRef<AudioBuffer | null>(null);
8+
const gainRef = useRef<GainNode | null>(null);
9+
const bufferNodeRef = useRef<AudioBufferSourceNode | null>(null);
10+
11+
const createBrownianNoise = () => {
12+
const aCtx = aCtxRef.current;
13+
14+
if (!aCtx) {
15+
return undefined;
16+
}
17+
18+
const bufferSize = 2 * aCtx.sampleRate;
19+
const output = new Array<number>(bufferSize);
20+
let lastOut = 0.0;
21+
22+
for (let i = 0; i < bufferSize; i += 1) {
23+
const white = Math.random() * 2 - 1;
24+
output[i] = (lastOut + 0.02 * white) / 1.02;
25+
lastOut = output[i];
26+
output[i] *= 3.5;
27+
}
28+
29+
30+
const noiseBuffer = aCtx.createBuffer(1, bufferSize, aCtx.sampleRate);
31+
noiseBuffer.copyToChannel(output, 0, 0);
32+
return noiseBuffer;
33+
}
34+
35+
const onPressIn = () => {
36+
if (!bufferNodeRef.current) {
37+
bufferNodeRef.current = aCtxRef.current.createBufferSource();
38+
bufferNodeRef.current.buffer = bufferRef.current;
39+
bufferNodeRef.current.loop = true;
40+
bufferNodeRef.current.connect(gainRef.current);
41+
42+
bufferNodeRef.current.start();
43+
}
44+
}
45+
46+
const onPressOut = () => {
47+
if (bufferNodeRef.current) {
48+
bufferNodeRef.current.stop();
49+
bufferNodeRef.current = null;
50+
}
51+
}
52+
53+
useEffect(() => {
54+
if (!aCtxRef.current) {
55+
aCtxRef.current = new AudioContext();
56+
}
57+
58+
if (!bufferRef.current) {
59+
bufferRef.current = createBrownianNoise();
60+
}
61+
62+
if (!gainRef.current) {
63+
gainRef.current = aCtxRef.current.createGain();
64+
gainRef.current.gain.value = 0.3;
65+
gainRef.current.connect(aCtxRef.current.destination);
66+
}
67+
}, []);
68+
return (
69+
<View style={styles.container}>
70+
<Pressable onPressIn={onPressIn} onPressOut={onPressOut}>
71+
{({ pressed }) => (
72+
<View style={[styles.button, pressed && styles.buttonPressed]} />
73+
)}
74+
</Pressable>
75+
</View>
76+
);
77+
}
78+
79+
export default WhiteNoise;
80+
81+
const styles = StyleSheet.create({
82+
container: {
83+
flex: 1,
84+
justifyContent: 'center',
85+
alignItems: 'center',
86+
},
87+
button: {
88+
width: 160,
89+
height: 160,
90+
backgroundColor: '#b07eff',
91+
justifyContent: 'center',
92+
alignItems: 'center',
93+
},
94+
buttonPressed: {
95+
transform: [{ scale: 1.1 }],
96+
},
97+
});
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import React, { FC, useEffect, useRef } from 'react';
2+
import { View, Pressable, StyleSheet } from 'react-native';
3+
import { AudioContext, AudioBuffer, GainNode, AudioBufferSourceNode } from 'react-native-audio-api';
4+
5+
const WhiteNoise: FC = () => {
6+
const aCtxRef = useRef<AudioContext | null>(null);
7+
const bufferRef = useRef<AudioBuffer | null>(null);
8+
const gainRef = useRef<GainNode | null>(null);
9+
const bufferNodeRef = useRef<AudioBufferSourceNode | null>(null);
10+
11+
const createBrownianNoise = () => {
12+
const aCtx = aCtxRef.current;
13+
14+
if (!aCtx) {
15+
return undefined;
16+
}
17+
18+
const bufferSize = 2 * aCtx.sampleRate;
19+
const output = new Array<number>(bufferSize);
20+
let lastOut = 0.0;
21+
22+
for (let i = 0; i < bufferSize; i += 1) {
23+
const white = Math.random() * 2 - 1;
24+
output[i] = (lastOut + 0.02 * white) / 1.02;
25+
lastOut = output[i];
26+
output[i] *= 3.5;
27+
}
28+
29+
30+
const noiseBuffer = aCtx.createBuffer(1, bufferSize, aCtx.sampleRate);
31+
noiseBuffer.copyToChannel(output, 0, 0);
32+
return noiseBuffer;
33+
}
34+
35+
const onPressIn = () => {
36+
if (!bufferNodeRef.current) {
37+
bufferNodeRef.current = aCtxRef.current.createBufferSource();
38+
bufferNodeRef.current.buffer = bufferRef.current;
39+
bufferNodeRef.current.loop = true;
40+
bufferNodeRef.current.connect(gainRef.current);
41+
42+
bufferNodeRef.current.start();
43+
}
44+
}
45+
46+
const onPressOut = () => {
47+
if (bufferNodeRef.current) {
48+
bufferNodeRef.current.stop();
49+
bufferNodeRef.current = null;
50+
}
51+
}
52+
53+
useEffect(() => {
54+
if (!aCtxRef.current) {
55+
aCtxRef.current = new AudioContext();
56+
}
57+
58+
if (!bufferRef.current) {
59+
bufferRef.current = createBrownianNoise();
60+
}
61+
62+
if (!gainRef.current) {
63+
gainRef.current = aCtxRef.current.createGain();
64+
gainRef.current.gain.value = 0.3;
65+
gainRef.current.connect(aCtxRef.current.destination);
66+
}
67+
}, []);
68+
return (
69+
<View style={styles.container}>
70+
<Pressable onPressIn={onPressIn} onPressOut={onPressOut}>
71+
{({ pressed }) => (
72+
<View style={[styles.button, pressed && styles.buttonPressed]} />
73+
)}
74+
</Pressable>
75+
</View>
76+
);
77+
}
78+
79+
export default WhiteNoise;
80+
81+
const styles = StyleSheet.create({
82+
container: {
83+
flex: 1,
84+
justifyContent: 'center',
85+
alignItems: 'center',
86+
},
87+
button: {
88+
width: 160,
89+
height: 160,
90+
backgroundColor: '#b07eff',
91+
justifyContent: 'center',
92+
alignItems: 'center',
93+
},
94+
buttonPressed: {
95+
transform: [{ scale: 1.1 }],
96+
},
97+
});

0 commit comments

Comments
 (0)