Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 68 additions & 38 deletions apps/common-app/src/examples/Worklets/Worklets.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useEffect, useRef, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { AudioContext } from 'react-native-audio-api';
import {
AudioContext,
OscillatorNode,
} from 'react-native-audio-api';
import { WorkletNode } from 'react-native-audio-worklets';
WorkletNode,
WorkletProcessingNode,
WorkletSourceNode,
} from 'react-native-audio-worklets';
import Animated, {
Extrapolation,
interpolate,
Expand Down Expand Up @@ -68,9 +69,9 @@ function VisualizerBar({

function Worklets() {
const audioContextRef = useRef<AudioContext | null>(null);
const workletSourceRef = useRef<WorkletSourceNode | null>(null);
const workletProcessingRef = useRef<WorkletProcessingNode | null>(null);
const workletNodeRef = useRef<WorkletNode | null>(null);
const oscillatorRef = useRef<OscillatorNode | null>(null);
const lfoRef = useRef<OscillatorNode | null>(null);
const heavyWorkAccRef = useRef(0);
const jsWorkloadTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);

Expand Down Expand Up @@ -120,21 +121,64 @@ function Worklets() {
return;
}

const sampleRate = ctx.sampleRate;

workletSourceRef.current = new WorkletSourceNode(
ctx,
(audioData, outputChannelCount, framesToProcess, currentTime, startOffset) => {
'worklet';

const frequency = 440;

for (let channel = 0; channel < outputChannelCount; channel++) {
for (let i = 0; i < framesToProcess; i++) {
const sampleTime = currentTime + (startOffset + i) / sampleRate;
const phase = 2 * Math.PI * frequency * sampleTime;
audioData[channel]![i] = Math.sin(phase) * 0.25;
}
}
}
);

workletProcessingRef.current = new WorkletProcessingNode(
ctx,
(
inputData,
outputData,
inputChannelCount,
outputChannelCount,
framesToProcess,
currentTime
) => {
'worklet';

const tremolo =
0.75 + 0.25 * Math.sin(2 * Math.PI * 2 * currentTime);

for (let ch = 0; ch < outputChannelCount; ch++) {
const input = inputData[Math.min(ch, inputChannelCount - 1)]!;
const output = outputData[ch]!;

for (let i = 0; i < framesToProcess; i++) {
output[i] = Math.tanh(input[i]! * 1.2) * tremolo;
}
}
}
);

workletNodeRef.current = new WorkletNode(
ctx,
(audioBuffers, numberOfChannels) => {
(audioData, numberOfChannels) => {
'worklet';
if (numberOfChannels < 1) {
return;
}
const buffer = audioBuffers[0];
if (buffer == null) {
return;
}
const channel = new Float32Array(buffer);

const channel = audioData[0]!;
let sum = 0;

for (let i = 0; i < channel.length; i++) {
sum += channel[i] * channel[i];
sum += channel[i]! * channel[i]!;
}
const rms = Math.sqrt(sum / channel.length);
const scaledAmplitude = Math.min(rms * 4, 1);
Expand All @@ -147,30 +191,15 @@ function Worklets() {
damping: 18,
stiffness: 120,
});
}
},
1024
);

const oscillator = ctx.createOscillator();
oscillator.frequency.value = 440;

const lfo = ctx.createOscillator();
lfo.frequency.value = 2;
const lfoGain = ctx.createGain();
lfoGain.gain.value = 0.18;
const amp = ctx.createGain();
amp.gain.value = 0.25;

lfo.connect(lfoGain);
lfoGain.connect(amp.gain);
oscillator.connect(amp);
amp.connect(workletNodeRef.current);
workletSourceRef.current.connect(workletProcessingRef.current);
workletProcessingRef.current.connect(workletNodeRef.current);
workletNodeRef.current.connect(ctx.destination);

oscillator.start();
lfo.start();

oscillatorRef.current = oscillator;
lfoRef.current = lfo;
workletSourceRef.current.start();

if (ctx.state === 'suspended') {
ctx.resume();
Expand All @@ -180,12 +209,13 @@ function Worklets() {
};

const stop = () => {
oscillatorRef.current?.stop();
lfoRef.current?.stop();
workletSourceRef.current?.stop();
workletSourceRef.current?.disconnect();
workletProcessingRef.current?.disconnect();
workletNodeRef.current?.disconnect();

oscillatorRef.current = null;
lfoRef.current = null;
workletSourceRef.current = null;
workletProcessingRef.current = null;
workletNodeRef.current = null;

bar0.value = withSpring(0, { damping: 20, stiffness: 100 });
Expand All @@ -205,7 +235,7 @@ function Worklets() {
Audio Worklets Visualizer
</Text>
<Text style={{ ...styles.subtitle, color: colors.white }}>
OscillatorWorkletNode (RMS on UI runtime) → destination
WorkletSourceWorkletProcessing → WorkletNode → destination
</Text>

<View style={styles.toggleRow}>
Expand Down
55 changes: 0 additions & 55 deletions packages/audiodocs/docs/core/base-audio-context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -180,61 +180,6 @@ Creates [`WaveShaperNode`](/docs/effects/wave-shaper-node).

#### Returns `WaveShaperNode`.

### `createWorkletNode` <MobileOnly />

Creates [`WorkletNode`](/docs/worklets/worklet-node).

| Parameter | Type | Description |
| :---: | :---: | :---- |
| `worklet` | `(Array<Float32Array>, number) => void` | The worklet to be executed. |
| `bufferLength` | `number` | The size of the buffer that will be passed to the worklet on each call. |
| `inputChannelCount` | `number` | The number of channels that the node expects as input (it will get min(expected, provided)). |
| `workletRuntime` | `AudioWorkletRuntime` | The kind of runtime to use for the worklet. See [worklet runtimes](/docs/worklets/worklets-introduction#what-kind-of-worklets-are-used-in-react-native-audio-api) for details. |

#### Errors

| Error type | Description |
| :---: | :---- |
| `Error` | `react-native-worklet` is not found as dependency. |
| `NotSupportedError` | `bufferLength` < `1`. |
| `NotSupportedError` | `inputChannelCount` is not in range [`1`, `32`]. |

#### Returns `WorkletNode`.

### `createWorkletSourceNode` <MobileOnly />

Creates [`WorkletSourceNode`](/docs/worklets/worklet-source-node).

| Parameter | Type | Description |
| :---: | :---: | :---- |
| `worklet` | `(Array<Float32Array>, number, number, number) => void` | The worklet to be executed. |
| `workletRuntime` | `AudioWorkletRuntime` | The kind of runtime to use for the worklet. See [worklet runtimes](/docs/worklets/worklets-introduction#what-kind-of-worklets-are-used-in-react-native-audio-api) for details. |

#### Errors

| Error type | Description |
| :---: | :---- |
| `Error` | `react-native-worklet` is not found as dependency. |

#### Returns `WorkletSourceNode`.

### `createWorkletProcessingNode` <MobileOnly />

Creates [`WorkletProcessingNode`](/docs/worklets/worklet-processing-node).

| Parameter | Type | Description |
| :---: | :---: | :---- |
| `worklet` | `(Array<Float32Array>, Array<Float32Array>, number, number) => void` | The worklet to be executed. |
| `workletRuntime` | `AudioWorkletRuntime` | The kind of runtime to use for the worklet. See [worklet runtimes](/docs/worklets/worklets-introduction#what-kind-of-worklets-are-used-in-react-native-audio-api) for details. |

#### Errors

| Error type | Description |
| :---: | :---- |
| `Error` | `react-native-worklet` is not found as dependency. |

#### Returns `WorkletProcessingNode`.

### `decodeAudioData`

<DecodeAudioDataDocs variant="context" />
Expand Down
5 changes: 2 additions & 3 deletions packages/audiodocs/docs/fundamentals/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,10 @@ bash -c 'echo Hello World!'

### Possible additional dependencies

If you plan to use any of [`WorkletNode`](/docs/worklets/worklet-node), [`WorkletSourceNode`](/docs/worklets/worklet-source-node), [`WorkletProcessingNode`](/docs/worklets/worklet-processing-node), it is required to have
`react-native-worklets` library set up with version <u>0.6.0</u> or higher. See [worklets getting-started page](https://docs.swmansion.com/react-native-worklets/docs/) for info how to do it.
To use [`WorkletNode`](/docs/worklets/worklet-node), install [`react-native-audio-worklets`](https://www.npmjs.com/package/react-native-audio-worklets) and [`react-native-worklets`](https://www.npmjs.com/package/react-native-worklets) **>= 0.10.0**. See the [Worklets introduction](/docs/worklets/introduction) for the full setup guide.

:::info
If you are not planning to use any of mentioned nodes, `react-native-worklets` dependency is **OPTIONAL** and your app will build successfully without them.
If you are not planning to use worklet nodes, `react-native-worklets` and `react-native-audio-worklets` are **optional** — the main library builds successfully without them.
:::

### Usage with expo
Expand Down
28 changes: 16 additions & 12 deletions packages/audiodocs/docs/other/testing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -142,27 +142,31 @@ describe('Offline Processing Tests', () => {
### Custom worklet testing

```typescript
import { AudioContext, WorkletProcessingNode } from 'react-native-audio-api/mock';
import { AudioContext } from 'react-native-audio-api';
import { WorkletProcessingNode } from 'react-native-audio-worklets';

describe('Worklet Tests', () => {
it('should create custom audio processing', () => {
const context = new AudioContext();

const processingCallback = jest.fn((inputData, outputData, framesToProcess) => {
// Mock audio processing logic
for (let channel = 0; channel < outputData.length; channel++) {
for (let i = 0; i < framesToProcess; i++) {
outputData[channel][i] = inputData[channel][i] * 0.5; // Simple gain
const processingCallback = jest.fn(
(
inputData,
outputData,
inputChannelCount,
outputChannelCount,
framesToProcess
) => {
for (let channel = 0; channel < outputChannelCount; channel++) {
for (let i = 0; i < framesToProcess; i++) {
outputData[channel][i] = inputData[channel][i] * 0.5;
}
}
}
});

const workletNode = new WorkletProcessingNode(
context,
'AudioRuntime',
processingCallback
);

const workletNode = new WorkletProcessingNode(context, processingCallback);

expect(workletNode.context).toBe(context);
});
});
Expand Down
Loading
Loading