Skip to content

Commit 7a61289

Browse files
authored
fix: removed convolver trimming and unnecesary rerender (#1019)
1 parent 0c810bb commit 7a61289

8 files changed

Lines changed: 220 additions & 20 deletions

File tree

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
2+
import { StyleSheet, Text } from 'react-native';
3+
import {
4+
AudioContext,
5+
AudioManager,
6+
ConvolverNode,
7+
GainNode,
8+
AudioBufferSourceNode,
9+
AudioBuffer,
10+
} from 'react-native-audio-api';
11+
12+
import { Button, Container, Slider, Spacer } from '../../components';
13+
import { colors, layout } from '../../styles';
14+
15+
const LABEL_W = 88;
16+
const IR = require('./ir.wav');
17+
const TRACK = require('./track.mp3');
18+
19+
const ConvolverIR: FC = () => {
20+
const [isPlaying, setIsPlaying] = useState(false);
21+
const [isReady, setIsReady] = useState(false);
22+
const [gain, setGain] = useState(0.28);
23+
24+
const audioContextRef = useRef<AudioContext | null>(null);
25+
const impulseRef = useRef<AudioBuffer | null>(null);
26+
const bufferRef = useRef<AudioBuffer | null>(null);
27+
const bufferSourceRef = useRef<AudioBufferSourceNode | null>(null);
28+
const gainRef = useRef<GainNode | null>(null);
29+
const convolverRef = useRef<ConvolverNode | null>(null);
30+
31+
const teardownGraph = useCallback(() => {
32+
try {
33+
bufferSourceRef.current?.stop(0);
34+
} catch {
35+
/* not started */
36+
}
37+
bufferSourceRef.current?.disconnect();
38+
gainRef.current?.disconnect();
39+
convolverRef.current?.disconnect();
40+
bufferSourceRef.current = null;
41+
gainRef.current = null;
42+
convolverRef.current = null;
43+
}, []);
44+
45+
useEffect(() => {
46+
const ctx = new AudioContext();
47+
audioContextRef.current = ctx;
48+
49+
const downloadIR = async () => {
50+
const buffer = await ctx.decodeAudioData(IR);
51+
const track = await ctx.decodeAudioData(TRACK);
52+
impulseRef.current = buffer;
53+
bufferRef.current = track;
54+
setIsReady(true);
55+
};
56+
57+
downloadIR().catch(() => {});
58+
59+
return () => {
60+
teardownGraph();
61+
impulseRef.current = null;
62+
bufferRef.current = null;
63+
ctx.close().catch(() => {});
64+
audioContextRef.current = null;
65+
AudioManager.setAudioSessionActivity(false).catch(() => {});
66+
};
67+
}, []);
68+
69+
const stopPlayback = useCallback(async () => {
70+
const ctx = audioContextRef.current;
71+
if (!ctx) {
72+
return;
73+
}
74+
bufferSourceRef.current?.stop(0);
75+
await AudioManager.setAudioSessionActivity(false);
76+
setIsPlaying(false);
77+
}, []);
78+
79+
const startPlayback = useCallback(
80+
async (useConvolver: boolean) => {
81+
const ctx = audioContextRef.current;
82+
const ir = impulseRef.current;
83+
const trackBuf = bufferRef.current;
84+
if (!ctx || !trackBuf || isPlaying) {
85+
return;
86+
}
87+
if (useConvolver && !ir) {
88+
return;
89+
}
90+
91+
AudioManager.setAudioSessionOptions({
92+
iosCategory: 'playback',
93+
iosMode: 'default',
94+
iosOptions: [],
95+
});
96+
await AudioManager.setAudioSessionActivity(true);
97+
98+
if (ctx.state === 'suspended') {
99+
await ctx.resume();
100+
}
101+
102+
const source = ctx.createBufferSource();
103+
source.buffer = trackBuf;
104+
105+
const g = ctx.createGain();
106+
g.gain.value = gain;
107+
108+
if (useConvolver && ir) {
109+
const convolver = ctx.createConvolver();
110+
convolver.buffer = ir;
111+
source.connect(g).connect(convolver).connect(ctx.destination);
112+
convolverRef.current = convolver;
113+
} else {
114+
source.connect(g).connect(ctx.destination);
115+
convolverRef.current = null;
116+
}
117+
118+
bufferSourceRef.current = source;
119+
gainRef.current = g;
120+
121+
source.start(ctx.currentTime + 0.1, 10);
122+
setIsPlaying(true);
123+
},
124+
[gain, isPlaying],
125+
);
126+
127+
const onPlayWithConvolver = useCallback(() => {
128+
startPlayback(true).catch(() => {});
129+
}, [startPlayback]);
130+
131+
const onPlayDry = useCallback(() => {
132+
startPlayback(false).catch(() => {});
133+
}, [startPlayback]);
134+
135+
const onStop = useCallback(() => {
136+
stopPlayback().catch(() => {});
137+
}, [stopPlayback]);
138+
139+
const onGainChange = useCallback((v: number) => {
140+
setGain(v);
141+
if (gainRef.current) {
142+
gainRef.current.gain.value = v;
143+
}
144+
}, []);
145+
146+
return (
147+
<Container centered>
148+
<Text style={styles.title}>Reverb</Text>
149+
<Spacer.Vertical size={10} />
150+
<Spacer.Vertical size={24} />
151+
<Button
152+
title="Play (convolver)"
153+
onPress={onPlayWithConvolver}
154+
width={200}
155+
disabled={!isReady || isPlaying}
156+
/>
157+
<Spacer.Vertical size={12} />
158+
<Button
159+
title="Play (dry)"
160+
onPress={onPlayDry}
161+
width={200}
162+
disabled={!isReady || isPlaying}
163+
/>
164+
<Spacer.Vertical size={12} />
165+
<Button title="Stop" onPress={onStop} width={200} disabled={!isPlaying} />
166+
<Spacer.Vertical size={28} />
167+
<Slider
168+
label="Gain"
169+
value={gain}
170+
onValueChange={onGainChange}
171+
min={0.02}
172+
max={0.6}
173+
step={0.01}
174+
minLabelWidth={LABEL_W}
175+
/>
176+
</Container>
177+
);
178+
};
179+
180+
export default ConvolverIR;
181+
182+
const styles = StyleSheet.create({
183+
title: {
184+
color: colors.white,
185+
fontSize: 18,
186+
fontWeight: '600',
187+
textAlign: 'center',
188+
},
189+
copy: {
190+
color: colors.white,
191+
fontSize: 14,
192+
lineHeight: 20,
193+
opacity: 0.85,
194+
textAlign: 'center',
195+
paddingHorizontal: layout.spacing * 2,
196+
},
197+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './ConvolverIR';
375 KB
Binary file not shown.
4.06 MB
Binary file not shown.

apps/common-app/src/examples/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Record from './Record/Record';
1313
import Streaming from './Streaming/Streaming';
1414
import Worklets from './Worklets/Worklets';
1515
import AudioStream from './AudioTag/AudioTag';
16+
import ConvolverIR from './ConvolverIR';
1617

1718
type NavigationParamList = {
1819
Oscillator: undefined;
@@ -28,6 +29,7 @@ type NavigationParamList = {
2829
Worklets: undefined;
2930
Streamer: undefined;
3031
AudioTag: undefined;
32+
ConvolverIR: undefined;
3133
};
3234

3335
export type ExampleKey = keyof NavigationParamList;
@@ -117,5 +119,11 @@ export const Examples: Example[] = [
117119
title: 'Audio Tag',
118120
Icon: icons.Tag,
119121
screen: AudioStream,
120-
}
122+
},
123+
{
124+
key: 'ConvolverIR',
125+
title: 'Reverb Effect',
126+
Icon: icons.AudioWaveform,
127+
screen: ConvolverIR,
128+
},
121129
] as const;

apps/fabric-example/metro.config.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
2-
32
const path = require('path');
43

54
const monorepoRoot = path.resolve(__dirname, '../..');
65
const appsRoot = path.resolve(monorepoRoot, 'apps');
76

7+
const defaultConfig = getDefaultConfig(__dirname);
88
/**
99
* Metro configuration https://reactnative.dev/docs/metro
1010
*
@@ -13,6 +13,9 @@ const appsRoot = path.resolve(monorepoRoot, 'apps');
1313
const config = {
1414
projectRoot: __dirname,
1515
watchFolders: [monorepoRoot, appsRoot],
16+
resolver: {
17+
assetExts: [...defaultConfig.resolver.assetExts, 'ogg', 'flac', 'opus'],
18+
},
1619
/* we are rewriting requests because due to monorepo structure, the assets are found with '../../../' prefix
1720
and we redirect them to the correct path without relative prefixes */
1821
server: {
@@ -37,4 +40,4 @@ const config = {
3740
},
3841
};
3942

40-
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
43+
module.exports = mergeConfig(defaultConfig, config);

packages/react-native-audio-api/common/cpp/audioapi/dsp/Convolver.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,9 @@ bool Convolver::init(size_t blockSize, const AudioArray &ir, size_t irLen) {
4848
return false;
4949
}
5050

51-
// Ignore zeros at the end of the impulse response because they only waste
52-
// computation time
5351
_blockSize = blockSize;
5452
_trueSegmentCount =
5553
static_cast<size_t>((std::ceil(static_cast<float>(irLen) / static_cast<float>(_blockSize))));
56-
while (irLen > 0 && ::fabs(ir[irLen - 1]) < 10e-3) {
57-
--irLen;
58-
}
5954

6055
if (irLen == 0) {
6156
return true;

packages/react-native-audio-api/src/development/react/Audio/Audio.tsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ const Audio = React.forwardRef<AudioTagHandle, AudioProps>((props, ref) => {
8787
return effectiveMutedState ? 0 : (volumeState ?? volume);
8888
}, [effectiveMutedState, volumeState, volume]);
8989

90+
const effectiveVolumeRef = useRef(effectiveVolumeState);
91+
effectiveVolumeRef.current = effectiveVolumeState;
92+
9093
useEffect(() => {
9194
fileSourceRef.current?.setVolume(effectiveVolumeState);
9295
}, [effectiveVolumeState]);
@@ -127,10 +130,12 @@ const Audio = React.forwardRef<AudioTagHandle, AudioProps>((props, ref) => {
127130
setDuration(0);
128131
setPlaybackState('idle');
129132

133+
const initialVolume = effectiveVolumeRef.current;
134+
130135
const node = context.context.createFileSource({
131136
source: nextSource,
132137
loop,
133-
volume: effectiveVolumeState,
138+
volume: initialVolume,
134139
});
135140
if (!node) {
136141
onError(new NotSupportedError('This file format requires FFmpeg build'));
@@ -148,7 +153,7 @@ const Audio = React.forwardRef<AudioTagHandle, AudioProps>((props, ref) => {
148153
},
149154
});
150155

151-
fileSource.setVolume(effectiveVolumeState);
156+
fileSource.setVolume(initialVolume);
152157
fileSourceRef.current = fileSource;
153158
setDuration(nextDuration);
154159
onLoad();
@@ -158,16 +163,7 @@ const Audio = React.forwardRef<AudioTagHandle, AudioProps>((props, ref) => {
158163
setPlaybackState('playing');
159164
onPlay();
160165
}
161-
}, [
162-
context,
163-
loop,
164-
onError,
165-
onEndedCallback,
166-
onLoad,
167-
onPlay,
168-
autoPlay,
169-
effectiveVolumeState,
170-
]);
166+
}, [context, loop, onError, onEndedCallback, onLoad, onPlay, autoPlay]);
171167

172168
useEffect(() => {
173169
if (!path) {

0 commit comments

Comments
 (0)