Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/audio buffer copy methods #178

Merged
merged 3 commits into from
Oct 25, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Clap implements SoundEngine {
output[i] = Math.random() * 2 - 1;
}

buffer.setChannelData(0, output);
buffer.copyToChannel(output, 0, 0);

return buffer;
}
Expand Down
36 changes: 9 additions & 27 deletions docs/web-audio-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,28 @@ Some of the noticeable implementation details that are still in progress or not
- Support of different number of channels (current approach in most of the audio-graph nodes assumes working with two channel audio)
- 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`.

## ✅ Completed (**5** out of 33)
## ✅ Completed (**6** out of 33)

<details>
<summary><b>AudioScheduledSourceNode</b></summary>
<summary><b>AudioBuffer</b></summary>
</details>
<details>
<summary><b>AudioDestinationNode</b></summary>
</details>
<details>
<summary><b>GainNode</b></summary>
<summary><b>AudioNode</b></summary>
</details>
<details>
<summary><b>StereoPannerNode</b></summary>
<summary><b>AudioScheduledSourceNode</b></summary>
</details>
<details>
<summary><b>AudioNode</b></summary>
<summary><b>GainNode</b></summary>
</details>
<details>
<summary><b>StereoPannerNode</b></summary>
</details>

## 🚧 In Progress (**7** out of 33)
## 🚧 In Progress (**6** out of 33)

<details>
<summary><b>AudioContext</b></summary>
Expand All @@ -49,27 +52,6 @@ Some of the noticeable implementation details that are still in progress or not

</details>

<details>
<summary><b>AudioBuffer</b></summary>

<div style="padding: 16px; padding-left: 42px;">

| Property 🔹/ Method 🔘 | state |
| ---------------------- | ----- |
| 🔹 sampleRate | ✅ |
| 🔹 length | ✅ |
| 🔹 duration | ✅ |
| 🔹 numberOfChannels | ✅ |
| 🔘 getChannelData | ✅ |
| 🔘 getChannelData | ✅ |
| 🔘 setChannelData | ✅ |
| 🔘 copyFromChannel | ❌ |
| 🔘 copyToChannel | ❌ |

</div>

</details>

<details>
<summary><b>AudioBufferSourceNode</b></summary>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ std::vector<jsi::PropNameID> AudioBufferHostObject::getPropertyNames(
propertyNames.push_back(
jsi::PropNameID::forAscii(runtime, "numberOfChannels"));
propertyNames.push_back(jsi::PropNameID::forAscii(runtime, "getChannelData"));
propertyNames.push_back(jsi::PropNameID::forAscii(runtime, "setChannelData"));
propertyNames.push_back(
jsi::PropNameID::forAscii(runtime, "copyFromChannel"));
propertyNames.push_back(jsi::PropNameID::forAscii(runtime, "copyToChannel"));
return propertyNames;
}

Expand Down Expand Up @@ -64,26 +66,63 @@ jsi::Value AudioBufferHostObject::get(
});
}

if (propName == "setChannelData") {
if (propName == "copyFromChannel") {
return jsi::Function::createFromHostFunction(
runtime,
propNameId,
2,
3,
[this](
jsi::Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) -> jsi::Value {
int channel = static_cast<int>(args[0].getNumber());
auto array = args[1].getObject(rt).asArray(rt);
auto *channelData = new float[wrapper_->getLength()];
auto destination = args[0].getObject(rt).asArray(rt);
auto destinationLength = static_cast<int>(
destination.getProperty(rt, "length").asNumber());
auto channelNumber = static_cast<int>(args[1].getNumber());
auto startInChannel = static_cast<int>(args[2].getNumber());

auto *destinationData = new float[destinationLength];

wrapper_->copyFromChannel(
destinationData,
destinationLength,
channelNumber,
startInChannel);

for (int i = 0; i < destinationLength; i++) {
destination.setValueAtIndex(rt, i, jsi::Value(destinationData[i]));
}

for (int i = 0; i < wrapper_->getLength(); i++) {
channelData[i] =
static_cast<float>(array.getValueAtIndex(rt, i).getNumber());
return jsi::Value::undefined();
});
}

if (propName == "copyToChannel") {
return jsi::Function::createFromHostFunction(
runtime,
propNameId,
3,
[this](
jsi::Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) -> jsi::Value {
auto source = args[0].getObject(rt).asArray(rt);
auto sourceLength =
static_cast<int>(source.getProperty(rt, "length").asNumber());
auto channelNumber = static_cast<int>(args[1].getNumber());
auto startInChannel = static_cast<int>(args[2].getNumber());

auto *sourceData = new float[sourceLength];

for (int i = 0; i < sourceLength; i++) {
sourceData[i] =
static_cast<float>(source.getValueAtIndex(rt, i).getNumber());
}

wrapper_->setChannelData(channel, channelData, wrapper_->getLength());
wrapper_->copyToChannel(
sourceData, sourceLength, channelNumber, startInChannel);

return jsi::Value::undefined();
});
Expand Down
55 changes: 41 additions & 14 deletions packages/react-native-audio-api/common/cpp/core/AudioBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,6 @@ float *AudioBuffer::getChannelData(int channel) const {
return channels_[channel];
}

void AudioBuffer::setChannelData(int channel, const float *data, int length) {
if (channel < 0 || channel >= numberOfChannels_) {
throw std::invalid_argument("Invalid channel number");
}

if (length != length_) {
throw std::invalid_argument("Invalid data length");
}

std::copy(data, data + length, channels_[channel]);
}

std::shared_ptr<AudioBuffer> AudioBuffer::mix(int outputNumberOfChannels) {
if (outputNumberOfChannels != 1 && outputNumberOfChannels != 2) {
throw std::invalid_argument("Invalid number of channels");
Expand All @@ -72,8 +60,8 @@ std::shared_ptr<AudioBuffer> AudioBuffer::mix(int outputNumberOfChannels) {

switch (this->numberOfChannels_) {
case 1:
mixedBuffer->setChannelData(0, this->channels_[0], length_);
mixedBuffer->setChannelData(1, this->channels_[0], length_);
mixedBuffer->copyToChannel(this->channels_[0], length_, 0, 0);
mixedBuffer->copyToChannel(this->channels_[0], length_, 1, 0);
break;
case 2:
for (int i = 0; i < length_; i++) {
Expand All @@ -85,4 +73,43 @@ std::shared_ptr<AudioBuffer> AudioBuffer::mix(int outputNumberOfChannels) {

return mixedBuffer;
}

void AudioBuffer::copyFromChannel(
float *destination,
int destinationLength,
int channelNumber,
int startInChannel) const {
if (channelNumber < 0 || channelNumber >= numberOfChannels_) {
throw std::invalid_argument("Invalid channel number");
}

if (startInChannel < 0 || startInChannel >= length_) {
throw std::invalid_argument("Invalid start in channel");
}

std::copy(
channels_[channelNumber] + startInChannel,
channels_[channelNumber] + startInChannel +
std::min(destinationLength, length_ - startInChannel),
destination);
}

void AudioBuffer::copyToChannel(
const float *source,
int sourceLength,
int channelNumber,
int startInChannel) {
if (channelNumber < 0 || channelNumber >= numberOfChannels_) {
throw std::invalid_argument("Invalid channel number");
}

if (startInChannel < 0 || startInChannel >= length_) {
throw std::invalid_argument("Invalid start in channel");
}

std::copy(
source,
source + std::min(sourceLength, length_ - startInChannel),
channels_[channelNumber] + startInChannel);
}
} // namespace audioapi
11 changes: 10 additions & 1 deletion packages/react-native-audio-api/common/cpp/core/AudioBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,16 @@ class AudioBuffer : public std::enable_shared_from_this<AudioBuffer> {
int getSampleRate() const;
double getDuration() const;
float *getChannelData(int channel) const;
void setChannelData(int channel, const float *data, int length);
void copyFromChannel(
float *destination,
int destinationLength,
int channelNumber,
int startInChannel) const;
void copyToChannel(
const float *source,
int sourceLength,
int channelNumber,
int startInChannel);

private:
friend class AudioBufferSourceNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,21 @@ float *AudioBufferWrapper::getChannelData(int channel) const {
return audioBuffer_->getChannelData(channel);
}

void AudioBufferWrapper::setChannelData(int channel, float *data, int length)
const {
audioBuffer_->setChannelData(channel, data, length);
void AudioBufferWrapper::copyFromChannel(
float *destination,
int destinationLength,
int channelNumber,
int startInChannel) const {
audioBuffer_->copyFromChannel(
destination, destinationLength, channelNumber, startInChannel);
}

void AudioBufferWrapper::copyToChannel(
float *source,
int sourceLength,
int channelNumber,
int startInChannel) const {
audioBuffer_->copyToChannel(
source, sourceLength, channelNumber, startInChannel);
}
} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ class AudioBufferWrapper {
double getDuration() const;
int getSampleRate() const;
float *getChannelData(int channel) const;
void setChannelData(int channel, float *data, int length) const;
void copyFromChannel(
float *destination,
int destinationLength,
int channelNumber,
int startInChannel = 0) const;
void copyToChannel(
float *source,
int sourceLength,
int channelNumber,
int startInChannel = 0) const;
};
} // namespace audioapi
13 changes: 11 additions & 2 deletions packages/react-native-audio-api/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,24 @@ export interface BiquadFilterNode extends AudioNode {
type: FilterType;
}

export type ContextState = 'running' | 'closed';
export type ContextState = 'running' | 'closed' | 'suspended';

export interface AudioBuffer {
readonly length: number;
readonly duration: number;
readonly sampleRate: number;
readonly numberOfChannels: number;
getChannelData(channel: number): number[];
setChannelData(channel: number, data: number[]): void;
copyFromChannel(
destination: number[],
channelNumber: number,
startInChannel: number
): void;
copyToChannel(
source: number[],
channelNumber: number,
startInChannel: number
): void;
}

export interface AudioBufferSourceNode extends AudioScheduledSourceNode {
Expand Down
Loading