Skip to content

Commit b6a3c40

Browse files
maciejmakowski2003Maciej Makowski
andauthored
Feat/audio buffer source node/override start method (#297)
* feat: implemented overridden buffer source start method * feat: added pause feature to example app * chore: update API coverage --------- Co-authored-by: Maciej Makowski <maciej.makowski2608@gmail.com>
1 parent 017024f commit b6a3c40

File tree

15 files changed

+127
-64
lines changed

15 files changed

+127
-64
lines changed

apps/common-app/src/examples/AudioFile/AudioFile.tsx

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,14 @@ import { ActivityIndicator } from 'react-native';
1212
const AudioFile: FC = () => {
1313
const [isPlaying, setIsPlaying] = useState(false);
1414
const [isLoading, setIsLoading] = useState(false);
15-
const [audioBuffer, setAudioBuffer] = useState<AudioBuffer | null>(null);
16-
17-
const audioContextRef = useRef<AudioContext | null>(null);
18-
const audioBufferSourceNodeRef = useRef<AudioBufferSourceNode | null>(null);
1915

20-
const setup = () => {
21-
if (!audioContextRef.current) {
22-
audioContextRef.current = new AudioContext();
23-
}
16+
const [audioBuffer, setAudioBuffer] = useState<AudioBuffer | null>(null);
2417

25-
audioBufferSourceNodeRef.current =
26-
audioContextRef.current.createBufferSource();
18+
const [startTime, setStartTime] = useState(0);
19+
const [offset, setOffset] = useState(0);
2720

28-
audioBufferSourceNodeRef.current.connect(
29-
audioContextRef.current.destination
30-
);
31-
};
21+
const audioContextRef = useRef<AudioContext | null>(null);
22+
const bufferSourceRef = useRef<AudioBufferSourceNode | null>(null);
3223

3324
const handleSetAudioSourceFromFile = async () => {
3425
try {
@@ -38,7 +29,7 @@ const AudioFile: FC = () => {
3829
});
3930

4031
if (result.canceled === false) {
41-
audioBufferSourceNodeRef.current?.stop();
32+
bufferSourceRef.current?.stop();
4233
setIsPlaying(false);
4334

4435
setIsLoading(true);
@@ -62,19 +53,24 @@ const AudioFile: FC = () => {
6253
}, []);
6354

6455
const handlePress = () => {
65-
if (!audioBuffer) {
56+
if (!audioBuffer || !audioContextRef.current) {
6657
return;
6758
}
6859

6960
if (isPlaying) {
70-
audioBufferSourceNodeRef.current?.stop();
61+
const stopTime = audioContextRef.current.currentTime;
62+
bufferSourceRef.current?.stop(stopTime);
63+
setOffset((prev) => prev + stopTime - startTime);
7164
} else {
72-
setup();
73-
audioBufferSourceNodeRef.current!.buffer = audioBuffer;
74-
audioBufferSourceNodeRef.current?.start();
65+
bufferSourceRef.current = audioContextRef.current.createBufferSource();
66+
bufferSourceRef.current.buffer = audioBuffer;
67+
bufferSourceRef.current.connect(audioContextRef.current.destination);
68+
69+
setStartTime(audioContextRef.current.currentTime);
70+
bufferSourceRef.current.start(startTime, offset);
7571
}
7672

77-
setIsPlaying(!isPlaying);
73+
setIsPlaying((prev) => !prev);
7874
};
7975

8076
useEffect(() => {

apps/common-app/src/examples/AudioVisualizer/AudioVisualizer.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,19 @@ const AudioVisualizer: React.FC = () => {
2424
const [times, setTimes] = useState<number[]>(new Array(FFT_SIZE).fill(127));
2525
const [freqs, setFreqs] = useState<number[]>(new Array(FFT_SIZE / 2).fill(0));
2626

27+
const [startTime, setStartTime] = useState(0);
28+
const [offset, setOffset] = useState(0);
29+
2730
const audioContextRef = useRef<AudioContext | null>(null);
2831
const analyserRef = useRef<AnalyserNode | null>(null);
2932
const bufferSourceRef = useRef<AudioBufferSourceNode | null>(null);
3033
const audioBufferRef = useRef<AudioBuffer | null>(null);
3134

3235
const handlePlayPause = () => {
3336
if (isPlaying) {
34-
bufferSourceRef.current?.stop();
37+
const stopTime = audioContextRef.current!.currentTime;
38+
bufferSourceRef.current?.stop(stopTime);
39+
setOffset((prev) => prev + stopTime - startTime);
3540
} else {
3641
if (!audioContextRef.current || !analyserRef.current) {
3742
return;
@@ -41,7 +46,8 @@ const AudioVisualizer: React.FC = () => {
4146
bufferSourceRef.current.buffer = audioBufferRef.current;
4247
bufferSourceRef.current.connect(analyserRef.current);
4348

44-
bufferSourceRef.current.start();
49+
setStartTime(audioContextRef.current.currentTime);
50+
bufferSourceRef.current.start(startTime, offset);
4551

4652
requestAnimationFrame(draw);
4753
}

docs/web-audio-coverage.md

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@ Some of the noticeable implementation details that are still in progress or not
77
- Support of different number of channels (current approach in most of the audio-graph nodes assumes working with two channel audio)
88
- Multi-input for each node and input mixing (Although specification suggests that most of the nodes can cave only one input or output, common use-cases proves otherwise). Only node that mixes multiple inputs is `DestinationNode`.
99

10-
## ✅ Completed (**11** out of 32)
10+
## ✅ Completed (**12** out of 32)
1111

1212
<details>
1313
<summary><b>AnalyserNode</b></summary>
1414
</details>
1515
<details>
1616
<summary><b>AudioBuffer</b></summary>
1717
</details>
18+
<details>
19+
<summary><b>AudioBufferSourceNode</b></summary>
20+
</details>
1821
<details>
1922
<summary><b>AudioDestinationNode</b></summary>
2023
</details>
@@ -43,7 +46,7 @@ Some of the noticeable implementation details that are still in progress or not
4346
<summary><b>StereoPannerNode</b></summary>
4447
</details>
4548

46-
## 🚧 In Progress (**3** out of 32)
49+
## 🚧 In Progress (**2** out of 32)
4750

4851
<details>
4952
<summary><b>AudioContext</b></summary>
@@ -67,25 +70,6 @@ Some of the noticeable implementation details that are still in progress or not
6770

6871
</details>
6972

70-
<details>
71-
<summary><b>AudioBufferSourceNode</b></summary>
72-
73-
<div style="padding: 16px; padding-left: 42px;">
74-
75-
| Property 🔹/ Method 🔘 | state |
76-
| ---------------------- | ----- |
77-
| 🔹 buffer ||
78-
| 🔹 detune ||
79-
| 🔹 loop ||
80-
| 🔹 loopStart ||
81-
| 🔹 loopEnd ||
82-
| 🔹 playBackRate ||
83-
| 🔘 start(overridden) ||
84-
85-
</div>
86-
87-
</details>
88-
8973
<details>
9074
<summary><b>BaseAudioContext</b></summary>
9175

packages/react-native-audio-api/common/cpp/HostObjects/AudioBufferSourceNodeHostObject.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ class AudioBufferSourceNodeHostObject
2828
addSetters(
2929
JSI_EXPORT_PROPERTY_SETTER(AudioBufferSourceNodeHostObject, loop),
3030
JSI_EXPORT_PROPERTY_SETTER(AudioBufferSourceNodeHostObject, buffer));
31+
32+
// start method is overridden in this class
33+
functions_->erase("start");
34+
35+
addFunctions(
36+
JSI_EXPORT_FUNCTION(AudioBufferSourceNodeHostObject, start));
3137
}
3238

3339
JSI_PROPERTY_GETTER(loop) {
@@ -111,6 +117,18 @@ class AudioBufferSourceNodeHostObject
111117
std::static_pointer_cast<AudioBufferSourceNode>(node_);
112118
audioBufferSourceNode->setLoopEnd(value.getNumber());
113119
}
120+
121+
JSI_HOST_FUNCTION(start) {
122+
auto when = args[0].getNumber();
123+
auto offset = args[1].getNumber();
124+
auto duration = args[2].getNumber();
125+
126+
auto audioBufferSourceNode =
127+
std::static_pointer_cast<AudioBufferSourceNode>(node_);
128+
audioBufferSourceNode->start(when, offset, duration);
129+
130+
return jsi::Value::undefined();
131+
}
114132
};
115133

116134
} // namespace audioapi

packages/react-native-audio-api/common/cpp/HostObjects/AudioScheduledSourceNodeHostObject.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ class AudioScheduledSourceNodeHostObject : public AudioNodeHostObject {
2020
}
2121

2222
JSI_HOST_FUNCTION(start) {
23-
auto time = args[0].getNumber();
23+
auto when = args[0].getNumber();
2424
auto audioScheduleSourceNode =
2525
std::static_pointer_cast<AudioScheduledSourceNode>(node_);
26-
audioScheduleSourceNode->start(time);
26+
audioScheduleSourceNode->start(when);
2727
return jsi::Value::undefined();
2828
}
2929

packages/react-native-audio-api/common/cpp/core/AudioBuffer.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ float AudioBuffer::getSampleRate() const {
2727
return bus_->getSampleRate();
2828
}
2929

30-
float AudioBuffer::getDuration() const {
31-
return static_cast<float>(getLength()) / getSampleRate();
30+
double AudioBuffer::getDuration() const {
31+
return static_cast<double>(getLength()) / getSampleRate();
3232
}
3333

3434
float *AudioBuffer::getChannelData(int channel) const {

packages/react-native-audio-api/common/cpp/core/AudioBuffer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class AudioBuffer : public std::enable_shared_from_this<AudioBuffer> {
1717

1818
[[nodiscard]] size_t getLength() const;
1919
[[nodiscard]] float getSampleRate() const;
20-
[[nodiscard]] float getDuration() const;
20+
[[nodiscard]] double getDuration() const;
2121

2222
[[nodiscard]] int getNumberOfChannels() const;
2323
[[nodiscard]] float *getChannelData(int channel) const;

packages/react-native-audio-api/common/cpp/core/AudioBufferSourceNode.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,19 @@ void AudioBufferSourceNode::setBuffer(
8080
alignedBus_->sum(buffer_->bus_.get());
8181
}
8282

83+
void AudioBufferSourceNode::start(double when, double offset, double duration) {
84+
AudioScheduledSourceNode::start(when);
85+
86+
offset = std::min(offset, buffer_->getDuration());
87+
if (loop_) {
88+
offset = std::min(offset, loopEnd_);
89+
}
90+
91+
vReadIndex_ = static_cast<double>(buffer_->getSampleRate() * offset);
92+
93+
AudioScheduledSourceNode::stop(when + duration);
94+
}
95+
8396
void AudioBufferSourceNode::processNode(
8497
AudioBus *processingBus,
8598
int framesToProcess) {

packages/react-native-audio-api/common/cpp/core/AudioBufferSourceNode.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,17 @@ class AudioBufferSourceNode : public AudioScheduledSourceNode {
1818
[[nodiscard]] bool getLoop() const;
1919
[[nodiscard]] double getLoopStart() const;
2020
[[nodiscard]] double getLoopEnd() const;
21-
2221
[[nodiscard]] std::shared_ptr<AudioParam> getDetuneParam() const;
2322
[[nodiscard]] std::shared_ptr<AudioParam> getPlaybackRateParam() const;
24-
2523
[[nodiscard]] std::shared_ptr<AudioBuffer> getBuffer() const;
2624

2725
void setLoop(bool loop);
2826
void setLoopStart(double loopStart);
2927
void setLoopEnd(double loopEnd);
30-
3128
void setBuffer(const std::shared_ptr<AudioBuffer> &buffer);
3229

30+
void start(double when, double offset, double duration);
31+
3332
protected:
3433
void processNode(AudioBus *processingBus, int framesToProcess) override;
3534

packages/react-native-audio-api/common/cpp/core/AudioScheduledSourceNode.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ AudioScheduledSourceNode::AudioScheduledSourceNode(BaseAudioContext *context)
1515
numberOfInputs_ = 0;
1616
}
1717

18-
void AudioScheduledSourceNode::start(double time) {
18+
void AudioScheduledSourceNode::start(double when) {
1919
playbackState_ = PlaybackState::SCHEDULED;
20-
startTime_ = time;
20+
startTime_ = when;
2121
}
2222

23-
void AudioScheduledSourceNode::stop(double time) {
24-
stopTime_ = time;
23+
void AudioScheduledSourceNode::stop(double when) {
24+
stopTime_ = when;
2525
}
2626

2727
bool AudioScheduledSourceNode::isUnscheduled() {

0 commit comments

Comments
 (0)