Skip to content

Commit

Permalink
Feat/audio buffer copy methods (#178)
Browse files Browse the repository at this point in the history
* feat: implemented copyFromChannel and copyToChannel

* refactor: removed setChannelData

* docs: readme update

---------

Co-authored-by: Maciej Makowski <[email protected]>
  • Loading branch information
maciejmakowski2003 and Maciej Makowski authored Oct 25, 2024
1 parent 894daea commit c916700
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 59 deletions.
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

0 comments on commit c916700

Please sign in to comment.