Skip to content

Commit

Permalink
Merge pull request #40 from software-mansion-labs/feat/android/panner…
Browse files Browse the repository at this point in the history
…-node

Feat/android/panner node
  • Loading branch information
maciejmakowski2003 authored Jul 25, 2024
2 parents 5f2694f + f643258 commit 240841d
Show file tree
Hide file tree
Showing 26 changed files with 304 additions and 38 deletions.
6 changes: 6 additions & 0 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ include_directories(
../cpp/OscillatorNode
../cpp/AudioNode
../cpp/GainNode
../cpp/StereoPannerNode
src/main/cpp
../node_modules/react-native/ReactCommon/jsi
../node_modules/react-native/ReactAndroid/src/main/jni/react/jni
Expand All @@ -31,6 +32,7 @@ add_library(react-native-audio-context SHARED
src/main/cpp/AudioDestinationNode.h
src/main/cpp/AudioNode
src/main/cpp/GainNode
src/main/cpp/StereoPannerNode

../cpp/AudioContext/AudioContextHostObject
../cpp/AudioContext/AudioContextWrapper.h
Expand All @@ -51,6 +53,10 @@ add_library(react-native-audio-context SHARED
../cpp/GainNode/GainNodeHostObject
../cpp/GainNode/GainNodeWrapper.h
../cpp/GainNode/android/GainNodeWrapper.cpp

../cpp/StereoPannerNode/StereoPannerNodeHostObject
../cpp/StereoPannerNode/StereoPannerNodeWrapper.h
../cpp/StereoPannerNode/android/StereoPannerNodeWrapper.cpp
)

find_package(ReactAndroid REQUIRED CONFIG)
Expand Down
36 changes: 22 additions & 14 deletions android/src/main/cpp/AudioContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@ namespace audiocontext
AudioContextHostObject::createAndInstallFromWrapper(audioContextWrapper, jsContext);
}

std::shared_ptr<OscillatorNode> AudioContext::createOscillator()
{
static const auto method = javaClassLocal()->getMethod<OscillatorNode()>("createOscillator");
auto oscillator = method(javaObject_.get());
auto oscillatorCppInstance = oscillator->cthis();
std::shared_ptr<OscillatorNode> AudioContext::createOscillator()
{
static const auto method = javaClassLocal()->getMethod<OscillatorNode()>("createOscillator");
auto oscillator = method(javaObject_.get());
auto oscillatorCppInstance = oscillator->cthis();

return std::shared_ptr<OscillatorNode>(oscillatorCppInstance);
}
return std::shared_ptr<OscillatorNode>(oscillatorCppInstance);
}

std::shared_ptr<AudioDestinationNode> AudioContext::getDestination()
{
static const auto method = javaClassLocal()->getMethod<AudioDestinationNode()>("getDestination");
auto destination = method(javaObject_.get());
auto destinationCppInstance = destination->cthis();
std::shared_ptr<AudioDestinationNode> AudioContext::getDestination()
{
static const auto method = javaClassLocal()->getMethod<AudioDestinationNode()>("getDestination");
auto destination = method(javaObject_.get());
auto destinationCppInstance = destination->cthis();

return std::shared_ptr<AudioDestinationNode>(destinationCppInstance);
}
return std::shared_ptr<AudioDestinationNode>(destinationCppInstance);
}

std::shared_ptr<GainNode> AudioContext::createGain()
{
Expand All @@ -40,4 +40,12 @@ namespace audiocontext
return std::shared_ptr<GainNode>(gainCppInstance);
}

std::shared_ptr<StereoPannerNode> AudioContext::createStereoPanner()
{
static const auto method = javaClassLocal()->getMethod<StereoPannerNode()>("createStereoPanner");
auto stereoPanner = method(javaObject_.get());
auto stereoPannerCppInstance = stereoPanner->cthis();

return std::shared_ptr<StereoPannerNode>(stereoPannerCppInstance);
}
} // namespace audiocontext
5 changes: 4 additions & 1 deletion android/src/main/cpp/AudioContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "OscillatorNode.h"
#include "AudioDestinationNode.h"
#include "GainNode.h"
#include "StereoPannerNode.h"

namespace audiocontext
{
Expand Down Expand Up @@ -38,9 +39,11 @@ namespace audiocontext
});
}

std::shared_ptr<OscillatorNode> createOscillator();
std::shared_ptr<AudioDestinationNode> getDestination();
std::shared_ptr<OscillatorNode> createOscillator();
std::shared_ptr<GainNode> createGain();
std::shared_ptr<StereoPannerNode> createStereoPanner();


void install(jlong jsContext);

Expand Down
16 changes: 16 additions & 0 deletions android/src/main/cpp/StereoPannerNode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "StereoPannerNode.h"

namespace audiocontext{

using namespace facebook::jni;

double StereoPannerNode::getPan(){
static const auto method = javaClassLocal()->getMethod<jdouble()>("getPan");
return method(javaObject_.get());
}

void StereoPannerNode::setPan(double pan){
static const auto method = javaClassLocal()->getMethod<void(jdouble)>("setPan");
method(javaObject_.get(), pan);
}
}
23 changes: 23 additions & 0 deletions android/src/main/cpp/StereoPannerNode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

#include <fbjni/fbjni.h>
#include <react/jni/CxxModuleWrapper.h>
#include <react/jni/JMessageQueueThread.h>
#include <memory>
#include "AudioNode.h"

namespace audiocontext {

using namespace facebook;
using namespace facebook::jni;

class StereoPannerNode : public jni::HybridClass<StereoPannerNode, AudioNode> {
public:
static auto constexpr kJavaDescriptor = "Lcom/audiocontext/nodes/StereoPannerNode;";

double getPan();

void setPan(double pan);
};

} // namespace audiocontext
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.audiocontext.context

import com.audiocontext.nodes.AudioDestinationNode
import com.audiocontext.nodes.GainNode
import com.audiocontext.nodes.StereoPannerNode
import com.audiocontext.nodes.oscillator.OscillatorNode
import com.facebook.jni.HybridData

Expand Down Expand Up @@ -32,4 +33,8 @@ class AudioContext() : BaseAudioContext {
override fun createGain(): GainNode {
return GainNode(this)
}

override fun createStereoPanner(): StereoPannerNode {
return StereoPannerNode(this)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.audiocontext.context

import com.audiocontext.nodes.AudioDestinationNode
import com.audiocontext.nodes.GainNode
import com.audiocontext.nodes.StereoPannerNode
import com.audiocontext.nodes.oscillator.OscillatorNode

interface BaseAudioContext {
Expand All @@ -10,4 +11,5 @@ interface BaseAudioContext {

abstract fun createOscillator(): OscillatorNode
abstract fun createGain(): GainNode
abstract fun createStereoPanner(): StereoPannerNode
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.audiocontext.nodes

import android.media.AudioTrack
import com.audiocontext.context.BaseAudioContext
import com.facebook.jni.HybridData

Expand All @@ -17,7 +16,14 @@ class AudioDestinationNode(context: BaseAudioContext): AudioNode(context) {
}
}

override fun process(buffer: ShortArray, audioTrack: AudioTrack) {
audioTrack.write(buffer, 0, buffer.size)
private fun setVolumeAndPanning(playbackParameters: PlaybackParameters) {
val leftPan = playbackParameters.gain * playbackParameters.leftPan
val rightPan = playbackParameters.gain * playbackParameters.rightPan
playbackParameters.audioTrack.setStereoVolume(leftPan.toFloat(), rightPan.toFloat())
}

override fun process(playbackParameters: PlaybackParameters) {
setVolumeAndPanning(playbackParameters)
playbackParameters.audioTrack.write(playbackParameters.buffer, 0, playbackParameters.buffer.size)
}
}
6 changes: 2 additions & 4 deletions android/src/main/java/com/audiocontext/nodes/AudioNode.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.audiocontext.nodes

import android.media.AudioTrack
import android.util.Log
import com.audiocontext.context.BaseAudioContext
import com.facebook.jni.HybridData

Expand Down Expand Up @@ -35,7 +33,7 @@ abstract class AudioNode(val context: BaseAudioContext) {
connectedNodes.clear()
}

open fun process(buffer: ShortArray, audioTrack: AudioTrack) {
connectedNodes.forEach { it.process(buffer, audioTrack) }
open fun process(playbackParameters: PlaybackParameters) {
connectedNodes.forEach { it.process(playbackParameters) }
}
}
7 changes: 3 additions & 4 deletions android/src/main/java/com/audiocontext/nodes/GainNode.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.audiocontext.nodes

import android.media.AudioTrack
import com.audiocontext.context.BaseAudioContext
import com.facebook.jni.HybridData

Expand All @@ -15,8 +14,8 @@ class GainNode(context: BaseAudioContext): AudioNode(context) {

private val mHybridData: HybridData? = initHybrid();

override fun process(buffer: ShortArray, audioTrack: AudioTrack) {
audioTrack.setVolume(gain.toFloat())
super.process(buffer, audioTrack)
override fun process(playbackParameters: PlaybackParameters) {
playbackParameters.gain = gain
super.process(playbackParameters)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.audiocontext.nodes

import android.media.AudioTrack

class PlaybackParameters(val audioTrack: AudioTrack, var buffer: ShortArray) {
var leftPan = 1.0
var rightPan = 1.0
var gain = 1.0
}
23 changes: 23 additions & 0 deletions android/src/main/java/com/audiocontext/nodes/StereoPannerNode.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.audiocontext.nodes

import com.audiocontext.context.BaseAudioContext
import com.facebook.jni.HybridData
import kotlin.math.min

class StereoPannerNode(context: BaseAudioContext): AudioNode(context) {
override val numberOfInputs: Int = 1
override val numberOfOutputs: Int = 1
private var pan: Double = 0.0
get() = field
set(value) {
field = value
}

private val mHybridData: HybridData? = initHybrid();

override fun process(playbackParameters: PlaybackParameters) {
playbackParameters.leftPan = min(1.0 - pan, 1.0)
playbackParameters.rightPan = min(1.0 + pan, 1.0)
super.process(playbackParameters)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.media.AudioTrack
import android.util.Log
import com.audiocontext.context.BaseAudioContext
import com.audiocontext.nodes.AudioScheduledSourceNode
import com.audiocontext.nodes.PlaybackParameters
import com.facebook.jni.HybridData
import com.facebook.react.bridge.ReactApplicationContext
import kotlin.math.abs
Expand All @@ -28,11 +29,10 @@ class OscillatorNode(context: BaseAudioContext) : AudioScheduledSourceNode(conte
}
private var waveType: WaveType = WaveType.SINE

private val audioTrack: AudioTrack
private var playbackParameters: PlaybackParameters
@Volatile private var isPlaying: Boolean = false
private var playbackThread: Thread? = null
private var stopThread: Thread? = null
private var buffer: ShortArray = ShortArray(1024)

private val mHybridData: HybridData? = initHybrid();

Expand All @@ -58,7 +58,10 @@ class OscillatorNode(context: BaseAudioContext) : AudioScheduledSourceNode(conte
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build()

this.audioTrack = AudioTrack(audioAttributes, audioFormat, bufferSize, AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE)
val audioTrack = AudioTrack(audioAttributes, audioFormat, bufferSize, AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE)
val buffer = ShortArray(bufferSize)

this.playbackParameters = PlaybackParameters(audioTrack, buffer)
}

fun getWaveType(): String {
Expand All @@ -82,7 +85,7 @@ class OscillatorNode(context: BaseAudioContext) : AudioScheduledSourceNode(conte
}

isPlaying = true
audioTrack.play()
playbackParameters.audioTrack.play()
generateSound()
}.apply { start() }
}
Expand All @@ -100,7 +103,7 @@ class OscillatorNode(context: BaseAudioContext) : AudioScheduledSourceNode(conte
}

isPlaying = false
audioTrack.stop()
playbackParameters.audioTrack.stop()
playbackThread?.join()
}.apply { start() }

Expand All @@ -114,12 +117,12 @@ class OscillatorNode(context: BaseAudioContext) : AudioScheduledSourceNode(conte
while(isPlaying) {
phaseChange = 2 * Math.PI * (frequency + detune) / context.sampleRate

for(i in buffer.indices) {
buffer[i] = WaveType.getWaveBufferElement(wavePhase, waveType)
for(i in playbackParameters.buffer.indices) {
playbackParameters.buffer[i] = WaveType.getWaveBufferElement(wavePhase, waveType)
wavePhase += phaseChange
}
process(buffer, audioTrack)
process(playbackParameters)
}
audioTrack.flush()
playbackParameters.audioTrack.flush()
}
}
12 changes: 11 additions & 1 deletion cpp/AudioContext/AudioContextHostObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ namespace audiocontext {

std::vector<jsi::PropNameID> AudioContextHostObject::getPropertyNames(jsi::Runtime& runtime) {
std::vector<jsi::PropNameID> propertyNames;
propertyNames.push_back(jsi::PropNameID::forUtf8(runtime, "createOscillator"));
propertyNames.push_back(jsi::PropNameID::forUtf8(runtime, "destination"));
propertyNames.push_back(jsi::PropNameID::forUtf8(runtime, "createOscillator"));
propertyNames.push_back(jsi::PropNameID::forUtf8(runtime, "createGain"));
propertyNames.push_back(jsi::PropNameID::forUtf8(runtime, "createStereoPanner"));
return propertyNames;
}

Expand Down Expand Up @@ -35,6 +37,14 @@ namespace audiocontext {
});
}

if(propName == "createStereoPanner") {
return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value {
auto stereoPanner = wrapper_->createStereoPanner();
auto stereoPannerHostObject = StereoPannerNodeHostObject::createFromWrapper(stereoPanner);
return jsi::Object::createFromHostObject(runtime, stereoPannerHostObject);
});
}

throw std::runtime_error("Not yet implemented!");
}

Expand Down
1 change: 1 addition & 0 deletions cpp/AudioContext/AudioContextHostObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "OscillatorNodeHostObject.h"
#include "AudioDestinationNodeHostObject.h"
#include "GainNodeHostObject.h"
#include "StereoPannerNodeHostObject.h"

namespace audiocontext
{
Expand Down
2 changes: 2 additions & 0 deletions cpp/AudioContext/AudioContextWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "OscillatorNodeWrapper.h"
#include "AudioDestinationNodeWrapper.h"
#include "GainNodeWrapper.h"
#include "StereoPannerNodeWrapper.h"

#ifdef ANDROID
#include "AudioContext.h"
Expand Down Expand Up @@ -38,5 +39,6 @@ namespace audiocontext {
std::shared_ptr<OscillatorNodeWrapper> createOscillator();
std::shared_ptr<AudioDestinationNodeWrapper> getDestination();
std::shared_ptr<GainNodeWrapper> createGain();
std::shared_ptr<StereoPannerNodeWrapper> createStereoPanner();
};
} // namespace audiocontext
5 changes: 5 additions & 0 deletions cpp/AudioContext/android/AudioContextWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,10 @@ namespace audiocontext {
auto gain = audiocontext_->createGain();
return std::make_shared<GainNodeWrapper>(gain);
}

std::shared_ptr<StereoPannerNodeWrapper> AudioContextWrapper::createStereoPanner() {
auto panner = audiocontext_->createStereoPanner();
return std::make_shared<StereoPannerNodeWrapper>(panner);
}
} // namespace audiocontext
#endif
Loading

0 comments on commit 240841d

Please sign in to comment.