From 74f09dc4b7d69ea3bc42b3bf64fdb3a9cf3c29d1 Mon Sep 17 00:00:00 2001 From: Maciej Makowski Date: Tue, 23 Jul 2024 11:25:11 +0200 Subject: [PATCH 1/7] feat: implemented gain node --- android/CMakeLists.txt | 6 +++ android/src/main/cpp/AudioContext.cpp | 11 +++- android/src/main/cpp/AudioContext.h | 4 +- android/src/main/cpp/GainNode.cpp | 16 ++++++ android/src/main/cpp/GainNode.h | 23 +++++++++ .../com/audiocontext/context/AudioContext.kt | 5 ++ .../audiocontext/context/BaseAudioContext.kt | 2 + .../java/com/audiocontext/nodes/GainNode.kt | 22 ++++++++ cpp/AudioContext/AudioContextHostObject.cpp | 8 +++ cpp/AudioContext/AudioContextHostObject.h | 1 + cpp/AudioContext/AudioContextWrapper.h | 2 + .../android/AudioContextWrapper.cpp | 5 ++ cpp/GainNode/GainNodeHostObject.cpp | 51 +++++++++++++++++++ cpp/GainNode/GainNodeHostObject.h | 27 ++++++++++ cpp/GainNode/GainNodeWrapper.h | 33 ++++++++++++ cpp/GainNode/android/GainNodeWrapper.cpp | 12 +++++ cpp/GainNode/ios/.keep | 0 example/src/App.tsx | 42 ++++++++------- src/index.ts | 7 ++- src/types.ts | 5 ++ 20 files changed, 260 insertions(+), 22 deletions(-) create mode 100644 android/src/main/cpp/GainNode.cpp create mode 100644 android/src/main/cpp/GainNode.h create mode 100644 android/src/main/java/com/audiocontext/nodes/GainNode.kt create mode 100644 cpp/GainNode/GainNodeHostObject.cpp create mode 100644 cpp/GainNode/GainNodeHostObject.h create mode 100644 cpp/GainNode/GainNodeWrapper.h create mode 100644 cpp/GainNode/android/GainNodeWrapper.cpp create mode 100644 cpp/GainNode/ios/.keep diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index 329e3836..2ba14671 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -17,6 +17,7 @@ include_directories( ../cpp/AudioDestinationNode ../cpp/OscillatorNode ../cpp/AudioNode + ../cpp/GainNode src/main/cpp ../node_modules/react-native/ReactCommon/jsi ../node_modules/react-native/ReactAndroid/src/main/jni/react/jni @@ -29,6 +30,7 @@ add_library(react-native-audio-context SHARED src/main/cpp/OscillatorNode src/main/cpp/AudioDestinationNode src/main/cpp/AudioNode + src/main/cpp/GainNode ../cpp/AudioContext/AudioContextHostObject ../cpp/AudioContext/AudioContextWrapper.h @@ -45,6 +47,10 @@ add_library(react-native-audio-context SHARED ../cpp/AudioNode/AudioNodeHostObject.h ../cpp/AudioNode/AudioNodeWrapper.h ../cpp/AudioNode/android/AudioNodeWrapper.cpp + + ../cpp/GainNode/GainNodeHostObject + ../cpp/GainNode/GainNodeWrapper.h + ../cpp/GainNode/android/GainNodeWrapper.cpp ) find_package(ReactAndroid REQUIRED CONFIG) diff --git a/android/src/main/cpp/AudioContext.cpp b/android/src/main/cpp/AudioContext.cpp index ebe88894..a925a2d6 100644 --- a/android/src/main/cpp/AudioContext.cpp +++ b/android/src/main/cpp/AudioContext.cpp @@ -22,7 +22,7 @@ namespace audiocontext return std::shared_ptr(oscillatorCppInstance); } - std::shared_ptr AudioContext::getDestination() + std::shared_ptr AudioContext::getDestination() { static const auto method = javaClassLocal()->getMethod("getDestination"); auto destination = method(javaObject_.get()); @@ -31,4 +31,13 @@ namespace audiocontext return std::shared_ptr(destinationCppInstance); } + std::shared_ptr AudioContext::createGain() + { + static const auto method = javaClassLocal()->getMethod("createGain"); + auto gain = method(javaObject_.get()); + auto gainCppInstance = gain->cthis(); + + return std::shared_ptr(gainCppInstance); + } + } // namespace audiocontext diff --git a/android/src/main/cpp/AudioContext.h b/android/src/main/cpp/AudioContext.h index 00982fd8..358fdb69 100644 --- a/android/src/main/cpp/AudioContext.h +++ b/android/src/main/cpp/AudioContext.h @@ -6,9 +6,10 @@ #include #include #include "AudioContextHostObject.h" +#include "AudioContextWrapper.h" #include "OscillatorNode.h" #include "AudioDestinationNode.h" -#include "AudioContextWrapper.h" +#include "GainNode.h" namespace audiocontext { @@ -39,6 +40,7 @@ namespace audiocontext std::shared_ptr createOscillator(); std::shared_ptr getDestination(); + std::shared_ptr createGain(); void install(jlong jsContext); diff --git a/android/src/main/cpp/GainNode.cpp b/android/src/main/cpp/GainNode.cpp new file mode 100644 index 00000000..2534ee6e --- /dev/null +++ b/android/src/main/cpp/GainNode.cpp @@ -0,0 +1,16 @@ +#include "GainNode.h" + +namespace audiocontext{ + + using namespace facebook::jni; + + double GainNode::getGain(){ + static const auto method = javaClassLocal()->getMethod("getGain"); + return method(javaObject_.get()); + } + + void GainNode::setGain(double gain){ + static const auto method = javaClassLocal()->getMethod("setGain"); + method(javaObject_.get(), gain); + } +} diff --git a/android/src/main/cpp/GainNode.h b/android/src/main/cpp/GainNode.h new file mode 100644 index 00000000..cae8705d --- /dev/null +++ b/android/src/main/cpp/GainNode.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include +#include +#include "AudioNode.h" + +namespace audiocontext { + + using namespace facebook; + using namespace facebook::jni; + + class GainNode : public jni::HybridClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/audiocontext/nodes/GainNode;"; + + double getGain(); + + void setGain(double gain); + }; + +} // namespace audiocontext diff --git a/android/src/main/java/com/audiocontext/context/AudioContext.kt b/android/src/main/java/com/audiocontext/context/AudioContext.kt index e966e21f..8f289e51 100644 --- a/android/src/main/java/com/audiocontext/context/AudioContext.kt +++ b/android/src/main/java/com/audiocontext/context/AudioContext.kt @@ -1,6 +1,7 @@ package com.audiocontext.context import com.audiocontext.nodes.AudioDestinationNode +import com.audiocontext.nodes.GainNode import com.audiocontext.nodes.oscillator.OscillatorNode import com.facebook.jni.HybridData @@ -27,4 +28,8 @@ class AudioContext() : BaseAudioContext { override fun createOscillator(): OscillatorNode { return OscillatorNode(this) } + + override fun createGain(): GainNode { + return GainNode(this) + } } diff --git a/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt b/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt index eb5d124b..88d27e5d 100644 --- a/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt +++ b/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt @@ -1,6 +1,7 @@ package com.audiocontext.context import com.audiocontext.nodes.AudioDestinationNode +import com.audiocontext.nodes.GainNode import com.audiocontext.nodes.oscillator.OscillatorNode interface BaseAudioContext { @@ -8,4 +9,5 @@ interface BaseAudioContext { val destination: AudioDestinationNode abstract fun createOscillator(): OscillatorNode + abstract fun createGain(): GainNode } diff --git a/android/src/main/java/com/audiocontext/nodes/GainNode.kt b/android/src/main/java/com/audiocontext/nodes/GainNode.kt new file mode 100644 index 00000000..c8312d32 --- /dev/null +++ b/android/src/main/java/com/audiocontext/nodes/GainNode.kt @@ -0,0 +1,22 @@ +package com.audiocontext.nodes + +import android.media.AudioTrack +import com.audiocontext.context.BaseAudioContext +import com.facebook.jni.HybridData + +class GainNode(context: BaseAudioContext): AudioNode(context) { + override val numberOfInputs: Int = 1 + override val numberOfOutputs: Int = 1 + private var gain: Double = 1.0 + get() = field + set(value) { + field = value + } + + private val mHybridData: HybridData? = initHybrid(); + + override fun process(buffer: ShortArray, audioTrack: AudioTrack) { + audioTrack.setVolume(gain.toFloat()) + super.process(buffer, audioTrack) + } +} diff --git a/cpp/AudioContext/AudioContextHostObject.cpp b/cpp/AudioContext/AudioContextHostObject.cpp index 8902b76a..d6f8550a 100644 --- a/cpp/AudioContext/AudioContextHostObject.cpp +++ b/cpp/AudioContext/AudioContextHostObject.cpp @@ -27,6 +27,14 @@ namespace audiocontext { return jsi::Object::createFromHostObject(runtime, destinationHostObject); } + if(propName == "createGain") { + 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 gain = wrapper_->createGain(); + auto gainHostObject = GainNodeHostObject::createFromWrapper(gain); + return jsi::Object::createFromHostObject(runtime, gainHostObject); + }); + } + throw std::runtime_error("Not yet implemented!"); } diff --git a/cpp/AudioContext/AudioContextHostObject.h b/cpp/AudioContext/AudioContextHostObject.h index bada6f7f..1876153a 100644 --- a/cpp/AudioContext/AudioContextHostObject.h +++ b/cpp/AudioContext/AudioContextHostObject.h @@ -6,6 +6,7 @@ #include "AudioContextWrapper.h" #include "OscillatorNodeHostObject.h" #include "AudioDestinationNodeHostObject.h" +#include "GainNodeHostObject.h" namespace audiocontext { diff --git a/cpp/AudioContext/AudioContextWrapper.h b/cpp/AudioContext/AudioContextWrapper.h index 09b9342e..e45edc59 100644 --- a/cpp/AudioContext/AudioContextWrapper.h +++ b/cpp/AudioContext/AudioContextWrapper.h @@ -4,6 +4,7 @@ #include #include "OscillatorNodeWrapper.h" #include "AudioDestinationNodeWrapper.h" +#include "GainNodeWrapper.h" #ifdef ANDROID #include "AudioContext.h" @@ -34,5 +35,6 @@ namespace audiocontext { #endif std::shared_ptr createOscillator(); std::shared_ptr getDestination(); + std::shared_ptr createGain(); }; } // namespace audiocontext diff --git a/cpp/AudioContext/android/AudioContextWrapper.cpp b/cpp/AudioContext/android/AudioContextWrapper.cpp index 9deb1c6c..1cb43962 100644 --- a/cpp/AudioContext/android/AudioContextWrapper.cpp +++ b/cpp/AudioContext/android/AudioContextWrapper.cpp @@ -11,4 +11,9 @@ namespace audiocontext { auto destination = audiocontext_->getDestination(); return std::make_shared(destination); } + + std::shared_ptr AudioContextWrapper::createGain() { + auto gain = audiocontext_->createGain(); + return std::make_shared(gain); + } } // namespace audiocontext diff --git a/cpp/GainNode/GainNodeHostObject.cpp b/cpp/GainNode/GainNodeHostObject.cpp new file mode 100644 index 00000000..2b0b6945 --- /dev/null +++ b/cpp/GainNode/GainNodeHostObject.cpp @@ -0,0 +1,51 @@ +#include "GainNodeHostObject.h" + +namespace audiocontext +{ + using namespace facebook; + + std::vector GainNodeHostObject::getPropertyNames(jsi::Runtime &runtime) + { + std::vector propertyNames; + propertyNames.push_back(jsi::PropNameID::forAscii(runtime, "gain")); + propertyNames.push_back(jsi::PropNameID::forAscii(runtime, "connect")); + return propertyNames; + } + + jsi::Value GainNodeHostObject::get(jsi::Runtime &runtime, const jsi::PropNameID &propNameId) + { + auto propName = propNameId.utf8(runtime); + + if (propName == "gain") + { + auto gain = wrapper_->getGain(); + return jsi::Value(gain); + } + + if (propName == "connect") + { + return jsi::Function::createFromHostFunction(runtime, propNameId, 1, [this](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value + { + auto node = args[0].getObject(rt).getHostObject(rt); + wrapper_->connect(std::shared_ptr(node)->wrapper_); + return jsi::Value::undefined(); + }); + } + + throw std::runtime_error("Prop not yet implemented!"); + } + + void GainNodeHostObject::set(jsi::Runtime &runtime, const jsi::PropNameID &propNameId, const jsi::Value &value) + { + auto propName = propNameId.utf8(runtime); + + if (propName == "gain") + { + double gain = value.getNumber(); + wrapper_->setGain(gain); + return; + } + + throw std::runtime_error("Not yet implemented!"); + } +} diff --git a/cpp/GainNode/GainNodeHostObject.h b/cpp/GainNode/GainNodeHostObject.h new file mode 100644 index 00000000..5463398b --- /dev/null +++ b/cpp/GainNode/GainNodeHostObject.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include "GainNodeWrapper.h" +#include "AudioNodeHostObject.h" + +namespace audiocontext { + using namespace facebook; + + class GainNodeWrapper; + + class GainNodeHostObject : public AudioNodeHostObject { + protected: + std::shared_ptr wrapper_; + + public: + explicit GainNodeHostObject(std::shared_ptr wrapper) : AudioNodeHostObject(wrapper), wrapper_(wrapper) {} + + jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override; + void set(jsi::Runtime& runtime, const jsi::PropNameID& name, const jsi::Value& value) override; + std::vector getPropertyNames(jsi::Runtime& rt) override; + + static std::shared_ptr createFromWrapper(std::shared_ptr wrapper) { + return std::make_shared(wrapper); + } + }; +} // namespace audiocontext diff --git a/cpp/GainNode/GainNodeWrapper.h b/cpp/GainNode/GainNodeWrapper.h new file mode 100644 index 00000000..96918e33 --- /dev/null +++ b/cpp/GainNode/GainNodeWrapper.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include "AudioNodeWrapper.h" + +#ifdef ANDROID +#include "GainNode.h" +#endif + +namespace audiocontext { + using namespace facebook; + +#ifdef ANDROID + class GainNode; +#endif + + class GainNodeWrapper: public AudioNodeWrapper { +#ifdef ANDROID + private: + std::shared_ptr gain_; +#endif + + public: +#ifdef ANDROID + explicit GainNodeWrapper( std::shared_ptr gain) : AudioNodeWrapper( + gain), gain_(gain) {} +#else + explicit GainNodeWrapper() {} +#endif + double getGain(); + void setGain(double gain); + }; +} // namespace audiocontext diff --git a/cpp/GainNode/android/GainNodeWrapper.cpp b/cpp/GainNode/android/GainNodeWrapper.cpp new file mode 100644 index 00000000..7e99b5e6 --- /dev/null +++ b/cpp/GainNode/android/GainNodeWrapper.cpp @@ -0,0 +1,12 @@ +#include "GainNodeWrapper.h" + +namespace audiocontext { + + double GainNodeWrapper::getGain() { + return gain_->getGain(); + } + + void GainNodeWrapper::setGain(double gain) { + gain_->setGain(gain); + } +} diff --git a/cpp/GainNode/ios/.keep b/cpp/GainNode/ios/.keep new file mode 100644 index 00000000..e69de29b diff --git a/example/src/App.tsx b/example/src/App.tsx index 082ff811..2b39185f 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,26 +1,33 @@ /* eslint-disable react/react-in-jsx-scope */ import { Button, StyleSheet, Text, View } from 'react-native'; -import { useRef, useEffect } from 'react'; +import { useRef, useState, useEffect } from 'react'; -import { AudioContext, type Oscillator } from 'react-native-audio-context'; +import { + AudioContext, + type Oscillator, + type Gain, +} from 'react-native-audio-context'; const App = () => { + const [isPlaying, setIsPlaying] = useState(false); + const audioContextRef = useRef(null); const oscillatorRef = useRef(null); - const secondaryOscillatorRef = useRef(null); + const gainRef = useRef(null); const setUp = () => { audioContextRef.current = new AudioContext(); + oscillatorRef.current = audioContextRef.current.createOscillator(); oscillatorRef.current.frequency = 800; + oscillatorRef.current.type = 'sine'; - secondaryOscillatorRef.current = audioContextRef.current.createOscillator(); - secondaryOscillatorRef.current.frequency = 300; - secondaryOscillatorRef.current.type = 'square'; + gainRef.current = audioContextRef.current.createGain(); + gainRef.current.gain = 1.0; const destination = audioContextRef.current.destination; - oscillatorRef.current.connect(destination); - secondaryOscillatorRef.current.connect(destination); + oscillatorRef.current.connect(gainRef.current); + gainRef.current.connect(destination); }; useEffect(() => { @@ -29,27 +36,24 @@ const App = () => { }; }, []); - const startOscillator = () => { + const handlePlayPause = () => { if (!audioContextRef.current) { setUp(); } - oscillatorRef.current?.start(1); - secondaryOscillatorRef.current?.start(0); - }; - const stopOscillator = () => { - if (!audioContextRef.current) { - setUp(); + if (isPlaying) { + oscillatorRef.current?.stop(0); + } else { + oscillatorRef.current?.start(0); } - oscillatorRef.current?.stop(0); - secondaryOscillatorRef.current?.stop(0); + + setIsPlaying(!isPlaying); }; return ( React Native Oscillator -