Skip to content

Commit 1e45338

Browse files
committed
feat: convolver impl
1 parent b7431a1 commit 1e45338

27 files changed

+3331
-19
lines changed

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

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import React, { useRef, useState, useEffect, FC } from 'react';
2-
import { StyleSheet, Text, View, Pressable } from 'react-native';
2+
import { StyleSheet, Text, View, Pressable, Image } from 'react-native';
33
import {
44
AudioContext,
55
GainNode,
66
OscillatorNode,
77
StereoPannerNode,
88
} from 'react-native-audio-api';
9-
import type { OscillatorType } from 'react-native-audio-api';
9+
import type { OscillatorType, AudioBuffer, ConvolverNode } from 'react-native-audio-api';
1010

1111
import { Container, Slider, Spacer, Button } from '../../components';
1212
import { layout, colors } from '../../styles';
@@ -31,6 +31,35 @@ const Oscillator: FC = () => {
3131
const oscillatorRef = useRef<OscillatorNode | null>(null);
3232
const gainRef = useRef<GainNode | null>(null);
3333
const panRef = useRef<StereoPannerNode | null>(null);
34+
const convolverRef = useRef<ConvolverNode | null>(null);
35+
36+
useEffect(() => {
37+
const fetchImpulseResponse = async () => {
38+
if (!audioContextRef.current) {
39+
audioContextRef.current = new AudioContext();
40+
}
41+
42+
const length = audioContextRef.current.sampleRate * 2; // 2 seconds
43+
const impulse = audioContextRef.current.createBuffer(2, length, audioContextRef.current.sampleRate);
44+
45+
for (let channel = 0; channel < impulse.numberOfChannels; channel++) {
46+
const channelData = impulse.getChannelData(channel);
47+
for (let i = 0; i < length; i++) {
48+
// Exponentially decay the impulse
49+
channelData[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, 2);
50+
}
51+
}
52+
53+
convolverRef.current = audioContextRef.current?.createConvolver();
54+
convolverRef.current.buffer = impulse;
55+
}
56+
57+
fetchImpulseResponse();
58+
59+
return () => {
60+
audioContextRef.current?.close();
61+
};
62+
}, []);
3463

3564
const setup = () => {
3665
if (!audioContextRef.current) {
@@ -50,7 +79,12 @@ const Oscillator: FC = () => {
5079

5180
oscillatorRef.current.connect(gainRef.current);
5281
gainRef.current.connect(panRef.current);
53-
panRef.current.connect(audioContextRef.current.destination);
82+
if (convolverRef.current) {
83+
panRef.current.connect(convolverRef.current);
84+
convolverRef.current.connect(audioContextRef.current.destination);
85+
} else {
86+
panRef.current.connect(audioContextRef.current.destination);
87+
}
5488
};
5589

5690
const handleGainChange = (newValue: number) => {
@@ -91,6 +125,7 @@ const Oscillator: FC = () => {
91125
} else {
92126
setup();
93127
oscillatorRef.current?.start(0);
128+
oscillatorRef.current?.stop(audioContextRef.current?.currentTime!! + 2);
94129
}
95130

96131
setIsPlaying((prev) => !prev);
@@ -103,16 +138,6 @@ const Oscillator: FC = () => {
103138
}
104139
};
105140

106-
useEffect(() => {
107-
if (!audioContextRef.current) {
108-
audioContextRef.current = new AudioContext();
109-
}
110-
111-
return () => {
112-
audioContextRef.current?.close();
113-
};
114-
}, []);
115-
116141
return (
117142
<Container centered>
118143
<Button onPress={handlePlayPause} title={isPlaying ? 'Pause' : 'Play'} />

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <audioapi/HostObjects/PeriodicWaveHostObject.h>
1515
#include <audioapi/HostObjects/StereoPannerNodeHostObject.h>
1616
#include <audioapi/HostObjects/AnalyserNodeHostObject.h>
17+
#include <audioapi/HostObjects/ConvolverNodeHostObject.h>
1718

1819
#include <jsi/jsi.h>
1920
#include <memory>
@@ -50,6 +51,7 @@ class BaseAudioContextHostObject : public JsiHostObject {
5051
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createBuffer),
5152
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createPeriodicWave),
5253
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createAnalyser),
54+
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createConvolver),
5355
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, decodeAudioData),
5456
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, decodeAudioDataSource),
5557
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, decodePCMAudioDataInBase64));
@@ -167,6 +169,13 @@ JSI_HOST_FUNCTION(createBufferQueueSource) {
167169
return jsi::Object::createFromHostObject(runtime, analyserHostObject);
168170
}
169171

172+
JSI_HOST_FUNCTION(createConvolver) {
173+
auto convolver = context_->createConvolver();
174+
auto convolverHostObject =
175+
std::make_shared<ConvolverNodeHostObject>(convolver);
176+
return jsi::Object::createFromHostObject(runtime, convolverHostObject);
177+
}
178+
170179
JSI_HOST_FUNCTION(decodeAudioDataSource) {
171180
auto sourcePath = args[0].getString(runtime).utf8(runtime);
172181

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#pragma once
2+
3+
#include <audioapi/HostObjects/AudioNodeHostObject.h>
4+
#include <audioapi/core/effects/ConvolverNode.h>
5+
#include <audioapi/HostObjects/AudioBufferHostObject.h>
6+
#include <iostream>
7+
8+
#include <memory>
9+
#include <vector>
10+
11+
namespace audioapi {
12+
using namespace facebook;
13+
14+
class ConvolverNodeHostObject : public AudioNodeHostObject {
15+
public:
16+
explicit ConvolverNodeHostObject(const std::shared_ptr<ConvolverNode> &node)
17+
: AudioNodeHostObject(node) {
18+
addGetters(JSI_EXPORT_PROPERTY_GETTER(ConvolverNodeHostObject, normalize),
19+
JSI_EXPORT_PROPERTY_GETTER(ConvolverNodeHostObject, buffer));
20+
addSetters(
21+
JSI_EXPORT_PROPERTY_SETTER(ConvolverNodeHostObject, normalize),
22+
JSI_EXPORT_PROPERTY_SETTER(ConvolverNodeHostObject, buffer));
23+
}
24+
25+
JSI_PROPERTY_GETTER(normalize) {
26+
auto convolverNode = std::static_pointer_cast<ConvolverNode>(node_);
27+
return {convolverNode->getNormalize_()};
28+
}
29+
30+
JSI_PROPERTY_GETTER(buffer) {
31+
auto convolverNode = std::static_pointer_cast<ConvolverNode>(node_);
32+
auto buffer = convolverNode->getBuffer();
33+
auto bufferHostObject =
34+
std::make_shared<AudioBufferHostObject>(buffer);
35+
return jsi::Object::createFromHostObject(runtime, bufferHostObject);
36+
}
37+
38+
JSI_PROPERTY_SETTER(normalize) {
39+
auto convolverNode = std::static_pointer_cast<ConvolverNode>(node_);
40+
convolverNode->setNormalize(value.getBool());
41+
}
42+
43+
JSI_PROPERTY_SETTER(buffer) {
44+
auto convolverNode =
45+
std::static_pointer_cast<ConvolverNode>(node_);
46+
if (value.isNull()) {
47+
convolverNode->setBuffer(std::shared_ptr<AudioBuffer>(nullptr));
48+
return;
49+
}
50+
51+
auto bufferHostObject =
52+
value.getObject(runtime).asHostObject<AudioBufferHostObject>(runtime);
53+
convolverNode->setBuffer(bufferHostObject->audioBuffer_);
54+
}
55+
};
56+
} // namespace audioapi

packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <audioapi/core/analysis/AnalyserNode.h>
33
#include <audioapi/core/destinations/AudioDestinationNode.h>
44
#include <audioapi/core/effects/BiquadFilterNode.h>
5+
#include <audioapi/core/effects/ConvolverNode.h>
56
#include <audioapi/core/effects/CustomProcessorNode.h>
67
#include <audioapi/core/effects/GainNode.h>
78
#include <audioapi/core/effects/StereoPannerNode.h>
@@ -15,6 +16,7 @@
1516
#include <audioapi/utils/AudioArray.h>
1617
#include <audioapi/utils/AudioBus.h>
1718
#include <audioapi/utils/CircularAudioArray.h>
19+
#include <iostream>
1820

1921
namespace audioapi {
2022

@@ -117,6 +119,12 @@ std::shared_ptr<AnalyserNode> BaseAudioContext::createAnalyser() {
117119
return analyser;
118120
}
119121

122+
std::shared_ptr<ConvolverNode> BaseAudioContext::createConvolver() {
123+
auto convolver = std::make_shared<ConvolverNode>(this);
124+
nodeManager_->addProcessingNode(convolver);
125+
return convolver;
126+
}
127+
120128
std::shared_ptr<AudioBuffer> BaseAudioContext::decodeAudioDataSource(
121129
const std::string &path) {
122130
auto audioBus = audioDecoder_->decodeWithFilePath(path);

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class AudioBufferQueueSourceNode;
2929
class AudioDecoder;
3030
class AnalyserNode;
3131
class AudioEventHandlerRegistry;
32+
class ConvolverNode;
3233

3334
class BaseAudioContext {
3435
public:
@@ -55,6 +56,7 @@ class BaseAudioContext {
5556
bool disableNormalization,
5657
int length);
5758
std::shared_ptr<AnalyserNode> createAnalyser();
59+
std::shared_ptr<ConvolverNode> createConvolver();
5860

5961
std::shared_ptr<AudioBuffer> decodeAudioDataSource(const std::string &path);
6062
std::shared_ptr<AudioBuffer> decodeAudioData(const void *data, size_t size);
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#include <audioapi/core/BaseAudioContext.h>
2+
#include <audioapi/core/effects/ConvolverNode.h>
3+
#include <audioapi/core/sources/AudioBuffer.h>
4+
#include <audioapi/dsp/AudioUtils.h>
5+
#include <audioapi/dsp/FFT.h>
6+
#include <audioapi/utils/AudioArray.h>
7+
#include <iostream>
8+
9+
namespace audioapi {
10+
ConvolverNode::ConvolverNode(BaseAudioContext *context) : AudioNode(context) {
11+
normalize_ = true;
12+
buffer_ = std::shared_ptr<AudioBuffer>(nullptr);
13+
convolver_ = std::shared_ptr<Convolver>(nullptr);
14+
isInitialized_ = true;
15+
}
16+
17+
bool ConvolverNode::getNormalize_() const {
18+
return normalize_;
19+
}
20+
21+
const std::shared_ptr<AudioBuffer> &ConvolverNode::getBuffer() const {
22+
return buffer_;
23+
}
24+
25+
void ConvolverNode::setNormalize(bool normalize) {
26+
if (normalize_ != normalize) {
27+
normalize_ = normalize;
28+
if (normalize_ && buffer_)
29+
calculateNormalizationScale();
30+
}
31+
if (!normalize_) {
32+
scaleFactor_ = 1.0f;
33+
}
34+
}
35+
36+
void ConvolverNode::setBuffer(const std::shared_ptr<AudioBuffer> &buffer) {
37+
if (buffer_ != buffer) {
38+
buffer_ = buffer;
39+
if (normalize_)
40+
calculateNormalizationScale();
41+
convolver_ = std::make_shared<Convolver>();
42+
auto audioArray = AudioArray(buffer->getLength());
43+
memcpy(
44+
audioArray.getData(), buffer->getChannelData(0), buffer->getLength());
45+
convolver_->init(128, audioArray, audioArray.getSize());
46+
}
47+
}
48+
49+
void ConvolverNode::processNode(
50+
const std::shared_ptr<AudioBus> &processingBus,
51+
int framesToProcess) {
52+
// printf("scale factor: %f\n", scaleFactor_);
53+
convolver_->process(
54+
*processingBus->getChannel(0),
55+
*processingBus->getChannel(0),
56+
framesToProcess);
57+
for (int i = 0; i < framesToProcess; i++) {
58+
processingBus->getChannel(0)->getData()[i] *= scaleFactor_;
59+
}
60+
if (processingBus->getNumberOfChannels() > 1) {
61+
for (int channel = 1; channel < processingBus->getNumberOfChannels();
62+
++channel) {
63+
processingBus->getChannel(channel)->copy(processingBus->getChannel(0));
64+
}
65+
}
66+
}
67+
68+
void ConvolverNode::calculateNormalizationScale() {
69+
int numberOfChannels = buffer_->getNumberOfChannels();
70+
int length = buffer_->getLength();
71+
72+
float power = 0;
73+
74+
for (int channel = 0; channel < numberOfChannels; ++channel) {
75+
float channelPower = 0;
76+
auto channelData = buffer_->getChannelData(channel);
77+
for (int i = 0; i < length; ++i) {
78+
float sample = channelData[i];
79+
channelPower += sample * sample;
80+
}
81+
power += channelPower;
82+
}
83+
84+
power = std::sqrtf(power / (numberOfChannels * length));
85+
if (power < MinPower) {
86+
power = MinPower;
87+
}
88+
scaleFactor_ = 1 / power;
89+
scaleFactor_ *= GainCalibration;
90+
scaleFactor_ *= GainCalibrationSampleRate / buffer_->getSampleRate();
91+
92+
if (numberOfChannels == 4)
93+
scaleFactor_ *= 0.5;
94+
}
95+
} // namespace audioapi
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#pragma once
2+
3+
#include <audioapi/core/AudioNode.h>
4+
#include <audioapi/core/AudioParam.h>
5+
#include <audioapi/dsp/Convolver.h>
6+
7+
#include <memory>
8+
#include <vector>
9+
10+
namespace audioapi {
11+
12+
class AudioBus;
13+
class AudioBuffer;
14+
15+
class ConvolverNode : public AudioNode {
16+
public:
17+
explicit ConvolverNode(BaseAudioContext *context);
18+
19+
[[nodiscard]] bool getNormalize_() const;
20+
[[nodiscard]] const std::shared_ptr<AudioBuffer> &getBuffer() const;
21+
void setNormalize(bool normalize);
22+
void setBuffer(const std::shared_ptr<AudioBuffer> &buffer);
23+
24+
protected:
25+
void processNode(const std::shared_ptr<AudioBus>& processingBus, int framesToProcess) override;
26+
27+
private:
28+
bool normalize_ = true;
29+
std::shared_ptr<AudioBuffer> buffer_;
30+
void calculateNormalizationScale();
31+
float scaleFactor_ = 1.0f;
32+
float GainCalibration = 0.00125;
33+
float GainCalibrationSampleRate = 44100.0f;
34+
float MinPower = 0.000125;
35+
std::shared_ptr<Convolver> convolver_;
36+
};
37+
38+
} // namespace audioapi

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ float linearToDecibels(float value) {
3030
float decibelsToLinear(float value) {
3131
return powf(10, value / 20);
3232
}
33-
} // namespace audioapi::dsp
33+
} // namespace audioapi::dsp

0 commit comments

Comments
 (0)