From 44c3808719c0ee94e034e3e73ea3bfc019438726 Mon Sep 17 00:00:00 2001 From: Maciej Makowski Date: Wed, 10 Jul 2024 10:35:59 +0200 Subject: [PATCH 1/3] feat: added some audio context files trying to implement host objects and hybrid classes for those audio context classes --- android/CMakeLists.txt | 2 + android/build.gradle | 1 + android/cpp-adapter.cpp | 6 +- android/src/main/cpp/AudioContext.h | 26 + ...amplePackage.kt => AudioContextPackage.kt} | 6 +- .../audiocontext/AudioContextPackageeee.kt | 20 + .../com/audiocontext/context/AudioContext.kt | 43 + .../audiocontext/context/AudioContextState.kt | 7 + .../audiocontext/context/BaseAudioContext.kt | 21 + ...ExampleModule.kt => AudioContextModule.kt} | 6 +- .../nativemodules/AudioContextModule.kt | 44 + .../nodes/AudioDestinationNode.kt | 14 + .../java/com/audiocontext/nodes/AudioNode.kt | 25 + .../nodes/AudioScheduledSourceNode.kt | 8 + .../com/audiocontext/nodes/gain/GainNode.kt | 22 + .../nodes/oscillator/OscillatorNode.kt | 69 + .../audiocontext/nodes/oscillator/WaveType.kt | 8 + cpp/AudioContextHostObject.cpp | 52 + cpp/AudioContextHostObject.h | 30 + cpp/JSIExampleHostObject.cpp | 45 - cpp/JSIExampleHostObject.h | 25 - cpp/OscillatorNodeHostObject.cpp | 0 cpp/OscillatorNodeHostObject.h | 26 + example/package.json | 1 + example/src/App.tsx | 82 +- package-lock.json | 15790 ++++++++++++++++ package.json | 5 +- src/{JSIExample.ts => AudioContext.ts} | 28 +- src/index.tsx | 77 +- yarn.lock | 1023 +- 30 files changed, 16799 insertions(+), 713 deletions(-) create mode 100644 android/src/main/cpp/AudioContext.h rename android/src/main/java/com/audiocontext/{JSIExamplePackage.kt => AudioContextPackage.kt} (74%) create mode 100644 android/src/main/java/com/audiocontext/AudioContextPackageeee.kt create mode 100644 android/src/main/java/com/audiocontext/context/AudioContext.kt create mode 100644 android/src/main/java/com/audiocontext/context/AudioContextState.kt create mode 100644 android/src/main/java/com/audiocontext/context/BaseAudioContext.kt rename android/src/main/java/com/audiocontext/jsi/{JSIExampleModule.kt => AudioContextModule.kt} (85%) create mode 100644 android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt create mode 100644 android/src/main/java/com/audiocontext/nodes/AudioDestinationNode.kt create mode 100644 android/src/main/java/com/audiocontext/nodes/AudioNode.kt create mode 100644 android/src/main/java/com/audiocontext/nodes/AudioScheduledSourceNode.kt create mode 100644 android/src/main/java/com/audiocontext/nodes/gain/GainNode.kt create mode 100644 android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt create mode 100644 android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt create mode 100644 cpp/AudioContextHostObject.cpp create mode 100644 cpp/AudioContextHostObject.h delete mode 100644 cpp/JSIExampleHostObject.cpp delete mode 100644 cpp/JSIExampleHostObject.h create mode 100644 cpp/OscillatorNodeHostObject.cpp create mode 100644 cpp/OscillatorNodeHostObject.h create mode 100644 package-lock.json rename src/{JSIExample.ts => AudioContext.ts} (60%) diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index fcefba66..08288de8 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -17,9 +17,11 @@ include_directories( ) find_package(ReactAndroid REQUIRED CONFIG) +find_package(fbjni REQUIRED CONFIG) target_link_libraries( react-native-audio-context ReactAndroid::jsi + fbjni::fbjni android ) diff --git a/android/build.gradle b/android/build.gradle index 431dd98a..500d9f0e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -125,6 +125,7 @@ dependencies { //noinspection GradleDynamicVersion implementation "com.facebook.react:react-native:+" implementation 'androidx.core:core-ktx:1.13.1' + implementation 'com.facebook.fbjni:fbjni:0.6.0' } if (isNewArchitectureEnabled()) { diff --git a/android/cpp-adapter.cpp b/android/cpp-adapter.cpp index a7b0247d..5e1e6002 100644 --- a/android/cpp-adapter.cpp +++ b/android/cpp-adapter.cpp @@ -1,18 +1,18 @@ #include #include -#include "JSIExampleHostObject.h" +#include "AudioContextHostObject.h" using namespace facebook; void install(jsi::Runtime& runtime) { - auto hostObject = std::make_shared(); + auto hostObject = std::make_shared(); auto object = jsi::Object::createFromHostObject(runtime, hostObject); runtime.global().setProperty(runtime, "__JSIExampleProxy", std::move(object)); } extern "C" JNIEXPORT void JNICALL -Java_com_audiocontext_jsi_JSIExampleModule_00024Companion_nativeInstall(JNIEnv *env, jobject clazz, jlong jsiPtr) { +Java_com_audiocontext_jsi_AudioContextModule_00024Companion_nativeInstall(JNIEnv *env, jobject clazz, jlong jsiPtr) { auto runtime = reinterpret_cast(jsiPtr); if (runtime) { install(*runtime); diff --git a/android/src/main/cpp/AudioContext.h b/android/src/main/cpp/AudioContext.h new file mode 100644 index 00000000..4d7e76db --- /dev/null +++ b/android/src/main/cpp/AudioContext.h @@ -0,0 +1,26 @@ +#include +#include + +namespace audiocontext { + using namespace facebook; + using namespace facebook::jni; + + class AudioContext: public HybridClass { + public: + static auto constexpr kJavaDescriptor = + "Lcom/audiocontext/context/AudioContext;"; + + static void registerNatives() { + javaClassStatic()->registerNatives({ + makeNativeMethod("initHybrid", AudioContext::initHybrid), + }); + } + + static HybridBase* initHybrid(alias_ref jThis) { + return new AudioContext(jThis); + } + + private: + global_ref javaObject_; + }; +} diff --git a/android/src/main/java/com/audiocontext/JSIExamplePackage.kt b/android/src/main/java/com/audiocontext/AudioContextPackage.kt similarity index 74% rename from android/src/main/java/com/audiocontext/JSIExamplePackage.kt rename to android/src/main/java/com/audiocontext/AudioContextPackage.kt index 81750b80..8fd5bfcd 100644 --- a/android/src/main/java/com/audiocontext/JSIExamplePackage.kt +++ b/android/src/main/java/com/audiocontext/AudioContextPackage.kt @@ -1,14 +1,14 @@ package com.audiocontext -import com.audiocontext.jsi.JSIExampleModule +import com.audiocontext.jsi.AudioContextModule import com.facebook.react.ReactPackage import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.uimanager.ViewManager -class JSIExamplePackage : ReactPackage { +class AudioContextPackage : ReactPackage { override fun createNativeModules(reactContext: ReactApplicationContext): List { - return listOf(JSIExampleModule(reactContext)) + return listOf(AudioContextModule(reactContext)) } override fun createViewManagers(reactContext: ReactApplicationContext): List> { diff --git a/android/src/main/java/com/audiocontext/AudioContextPackageeee.kt b/android/src/main/java/com/audiocontext/AudioContextPackageeee.kt new file mode 100644 index 00000000..898bd5b6 --- /dev/null +++ b/android/src/main/java/com/audiocontext/AudioContextPackageeee.kt @@ -0,0 +1,20 @@ +package com.audiocontext + +import android.view.View +import com.audiocontext.nativemodules.AudioContextModule +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ReactShadowNode +import com.facebook.react.uimanager.ViewManager + +class AudioContextPackageeee : ReactPackage { + + override fun createViewManagers( + reactContext: ReactApplicationContext + ): MutableList>> = mutableListOf() + + override fun createNativeModules( + reactContext: ReactApplicationContext + ): MutableList = listOf(AudioContextModule(reactContext)).toMutableList() +} diff --git a/android/src/main/java/com/audiocontext/context/AudioContext.kt b/android/src/main/java/com/audiocontext/context/AudioContext.kt new file mode 100644 index 00000000..f17c2273 --- /dev/null +++ b/android/src/main/java/com/audiocontext/context/AudioContext.kt @@ -0,0 +1,43 @@ +package com.audiocontext.context + +import android.media.AudioTrack +import com.audiocontext.nodes.AudioDestinationNode +import com.audiocontext.nodes.AudioNode +import com.audiocontext.nodes.gain.GainNode +import com.audiocontext.nodes.oscillator.OscillatorNode +import java.util.concurrent.CopyOnWriteArrayList + +class AudioContext : BaseAudioContext { + override var sampleRate: Int = 44100 + override val destination: AudioDestinationNode = AudioDestinationNode(this) + override val sources = CopyOnWriteArrayList() + + override fun addNode(node: AudioNode) { + sources.add(node) + } + + override fun removeNode(node: AudioNode) { + sources.remove(node) + } + + override fun createOscillatorNode(): OscillatorNode { + val oscillatorNode = OscillatorNode(this) + addNode(oscillatorNode) + return oscillatorNode + } + + override fun createGainNode(): GainNode { + val gainNode = GainNode(this) + return gainNode + } + + override fun dispatchAudio(buffer: ShortArray, audioTrack: AudioTrack) { + val currentBuffer = buffer.clone() + + synchronized(sources) { + sources.forEach { source -> + source.process(currentBuffer, audioTrack) + } + } + } +} diff --git a/android/src/main/java/com/audiocontext/context/AudioContextState.kt b/android/src/main/java/com/audiocontext/context/AudioContextState.kt new file mode 100644 index 00000000..d10683a0 --- /dev/null +++ b/android/src/main/java/com/audiocontext/context/AudioContextState.kt @@ -0,0 +1,7 @@ +package com.audiocontext.context + +enum class AudioContextState { + SUSPENDED, + RUNNING, + CLOSED +} diff --git a/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt b/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt new file mode 100644 index 00000000..5ab91e1a --- /dev/null +++ b/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt @@ -0,0 +1,21 @@ +package com.audiocontext.context + +import android.media.AudioTrack +import android.provider.MediaStore.Audio +import com.audiocontext.nodes.AudioDestinationNode +import com.audiocontext.nodes.AudioNode +import com.audiocontext.nodes.gain.GainNode +import com.audiocontext.nodes.oscillator.OscillatorNode +import java.util.concurrent.CopyOnWriteArrayList + +interface BaseAudioContext { + val sampleRate: Int + val destination: AudioDestinationNode + val sources: List + + fun createOscillatorNode(): OscillatorNode + fun createGainNode(): GainNode + fun dispatchAudio(buffer: ShortArray, audioTrack: AudioTrack) + fun addNode(node: AudioNode) + fun removeNode(node: AudioNode) +} diff --git a/android/src/main/java/com/audiocontext/jsi/JSIExampleModule.kt b/android/src/main/java/com/audiocontext/jsi/AudioContextModule.kt similarity index 85% rename from android/src/main/java/com/audiocontext/jsi/JSIExampleModule.kt rename to android/src/main/java/com/audiocontext/jsi/AudioContextModule.kt index 371c2886..fda805f0 100644 --- a/android/src/main/java/com/audiocontext/jsi/JSIExampleModule.kt +++ b/android/src/main/java/com/audiocontext/jsi/AudioContextModule.kt @@ -6,8 +6,8 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod import com.facebook.react.module.annotations.ReactModule -@ReactModule(name = JSIExampleModule.NAME) -class JSIExampleModule(reactContext: ReactApplicationContext?) : +@ReactModule(name = AudioContextModule.NAME) +class AudioContextModule(reactContext: ReactApplicationContext?) : ReactContextBaseJavaModule(reactContext) { override fun getName(): String { return NAME @@ -29,7 +29,7 @@ class JSIExampleModule(reactContext: ReactApplicationContext?) : } companion object { - const val NAME: String = "JSIExample" + const val NAME: String = "AudioContext" private external fun nativeInstall(jsiPtr: Long) } diff --git a/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt b/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt new file mode 100644 index 00000000..3e9cd2ea --- /dev/null +++ b/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt @@ -0,0 +1,44 @@ +package com.audiocontext.nativemodules + +import com.audiocontext.context.AudioContext +import com.audiocontext.nodes.AudioNode +import com.audiocontext.nodes.AudioScheduledSourceNode +import com.audiocontext.nodes.gain.GainNode +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod + +class AudioContextModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { + private val audioContext = AudioContext() + private var source: AudioScheduledSourceNode? = null + private var destination: AudioNode = audioContext.destination + + override fun getName(): String { + return "AudioContextModule" + } + + @ReactMethod(isBlockingSynchronousMethod = true) + fun createOscillatorNode() { + source = audioContext.createOscillatorNode() + } + + @ReactMethod + fun start() { + source?.start() + } + + @ReactMethod + fun stop() { + source?.stop() + } + + @ReactMethod + fun connect() { + source?.connect(destination) + } + + @ReactMethod + fun disconnect() { + source?.disconnect() + } +} diff --git a/android/src/main/java/com/audiocontext/nodes/AudioDestinationNode.kt b/android/src/main/java/com/audiocontext/nodes/AudioDestinationNode.kt new file mode 100644 index 00000000..96c121a0 --- /dev/null +++ b/android/src/main/java/com/audiocontext/nodes/AudioDestinationNode.kt @@ -0,0 +1,14 @@ +package com.audiocontext.nodes + +import android.media.AudioTrack +import com.audiocontext.context.BaseAudioContext + + +class AudioDestinationNode(context: BaseAudioContext): AudioNode(context) { + override val numberOfInputs = 1 + override val numberOfOutputs = 0 + + override fun process(buffer: ShortArray, audioTrack: AudioTrack) { + audioTrack.write(buffer, 0, buffer.size) + } +} diff --git a/android/src/main/java/com/audiocontext/nodes/AudioNode.kt b/android/src/main/java/com/audiocontext/nodes/AudioNode.kt new file mode 100644 index 00000000..9c68beaa --- /dev/null +++ b/android/src/main/java/com/audiocontext/nodes/AudioNode.kt @@ -0,0 +1,25 @@ +package com.audiocontext.nodes + +import android.media.AudioTrack +import com.audiocontext.context.BaseAudioContext + + +abstract class AudioNode(val context: BaseAudioContext) { + abstract val numberOfInputs: Int; + abstract val numberOfOutputs: Int; + private val connectedNodes = mutableListOf() + + fun connect(destination: AudioNode) { + if(this.numberOfOutputs > 0) { + connectedNodes.add(destination) + } + } + + fun disconnect() { + connectedNodes.clear() + } + + open fun process(buffer: ShortArray, audioTrack: AudioTrack) { + connectedNodes.forEach { it.process(buffer, audioTrack) } + } +} diff --git a/android/src/main/java/com/audiocontext/nodes/AudioScheduledSourceNode.kt b/android/src/main/java/com/audiocontext/nodes/AudioScheduledSourceNode.kt new file mode 100644 index 00000000..b2fcc5d6 --- /dev/null +++ b/android/src/main/java/com/audiocontext/nodes/AudioScheduledSourceNode.kt @@ -0,0 +1,8 @@ +package com.audiocontext.nodes + +import com.audiocontext.context.BaseAudioContext + +abstract class AudioScheduledSourceNode(context: BaseAudioContext) : AudioNode(context) { + abstract fun start() + abstract fun stop() +} diff --git a/android/src/main/java/com/audiocontext/nodes/gain/GainNode.kt b/android/src/main/java/com/audiocontext/nodes/gain/GainNode.kt new file mode 100644 index 00000000..c76ce424 --- /dev/null +++ b/android/src/main/java/com/audiocontext/nodes/gain/GainNode.kt @@ -0,0 +1,22 @@ +package com.audiocontext.nodes.gain + +import android.media.AudioTrack +import com.audiocontext.context.BaseAudioContext +import com.audiocontext.nodes.AudioNode + +class GainNode(context: BaseAudioContext): AudioNode(context) { + override val numberOfInputs = 1 + override val numberOfOutputs = 1 + var gain: Double = 1.0 + get() = field + set(value) { + field = value + if (field < 0) field = 0.0 + if (field > 1) field = 1.0 + } + + override fun process(buffer: ShortArray, audioTrack: AudioTrack) { + audioTrack.setVolume(gain.toFloat()) + super.process(buffer, audioTrack) + } +} diff --git a/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt b/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt new file mode 100644 index 00000000..aea07ee6 --- /dev/null +++ b/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt @@ -0,0 +1,69 @@ +package com.audiocontext.nodes.oscillator + +import android.media.AudioFormat +import android.media.AudioManager +import android.media.AudioTrack +import com.audiocontext.context.BaseAudioContext +import com.audiocontext.nodes.AudioScheduledSourceNode +import kotlin.math.abs +import kotlin.math.floor +import kotlin.math.sin + +class OscillatorNode(context: BaseAudioContext) : AudioScheduledSourceNode(context) { + override val numberOfInputs: Int = 0 + override val numberOfOutputs: Int = 1 + private var frequency: Double = 440.0 + private var detune: Double = 0.0 + private var waveType: WaveType = WaveType.SINE + + private val audioTrack: AudioTrack + @Volatile private var isPlaying: Boolean = false + private var playbackThread: Thread? = null + private var buffer: ShortArray = ShortArray(1024) + + init { + val bufferSize = AudioTrack.getMinBufferSize( + context.sampleRate, + AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT) + this.audioTrack = AudioTrack( + AudioManager.STREAM_MUSIC, context.sampleRate, AudioFormat.CHANNEL_OUT_MONO, + AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM + ) + } + + override fun start() { + if(isPlaying) return + isPlaying = true + audioTrack.play() + playbackThread = Thread { generateSound() }.apply{ start()} + } + + override fun stop() { + if(!isPlaying) return + isPlaying = false + audioTrack.stop() + playbackThread?.join() + } + + private fun generateSound() { + var wavePhase = 0.0 + var phaseChange: Double + + while(isPlaying) { + phaseChange = 2 * Math.PI * (frequency + detune) / context.sampleRate + + for(i in buffer.indices) { + buffer[i] = when(waveType) { + WaveType.SINE -> (sin(wavePhase) * Short.MAX_VALUE).toInt().toShort() + WaveType.SQUARE -> ((if (sin(wavePhase) >= 0) 1 else -1) * Short.MAX_VALUE).toShort() + WaveType.SAWTOOTH -> ((2 * (wavePhase / (2 * Math.PI) - floor(wavePhase / (2 * Math.PI) + 0.5))) * Short.MAX_VALUE).toInt().toShort() + WaveType.TRIANGLE -> ((2 * abs(2 * (wavePhase / (2 * Math.PI) - floor(wavePhase / (2 * Math.PI) + 0.5))) - 1) * Short.MAX_VALUE).toInt().toShort() + } + wavePhase += phaseChange + } + + context.dispatchAudio(buffer, audioTrack) + } + audioTrack.flush() + } +} diff --git a/android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt b/android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt new file mode 100644 index 00000000..ffd63ff2 --- /dev/null +++ b/android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt @@ -0,0 +1,8 @@ +package com.audiocontext.nodes.oscillator + +enum class WaveType { + SINE, + SQUARE, + SAWTOOTH, + TRIANGLE +} diff --git a/cpp/AudioContextHostObject.cpp b/cpp/AudioContextHostObject.cpp new file mode 100644 index 00000000..9cd6f8e2 --- /dev/null +++ b/cpp/AudioContextHostObject.cpp @@ -0,0 +1,52 @@ +#include "AudioContextHostObject.h" +#include +#include + +namespace audiocontext { + using namespace facebook; + + + AudioContextHostObject::AudioContextHostObject() + { + } + + AudioContextHostObject::~AudioContextHostObject() + { + + } + + std::vector AudioContextHostObject::getPropertyNames(jsi::Runtime & runtime) + { + std::vector propertyNames; + //propertyNames.push_back(jsi::PropNameID::forAscii(runtime, "createOscillator")); + return propertyNames; + } + + jsi::Value AudioContextHostObject::get(jsi::Runtime & runtime, const jsi::PropNameID &propNameId) + { + auto propName = propNameId.utf8(runtime); + + // if (propName == "createOscillator") + // { + // return jsi::Function::createFromHostFunction(runtime, propNameId, 0, + // [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) + // { + // return this->createOscillator(rt); + // }); + // } + + throw std::runtime_error("Not yet implemented!"); + } + + void AudioContextHostObject::set(jsi::Runtime & runtime, const jsi::PropNameID &propNameId, const jsi::Value &value) + { + auto propName = propNameId.utf8(runtime); + + throw std::runtime_error("Not yet implemented!"); + } + + jsi::Value AudioContextHostObject::createOscillator(jsi::Runtime & runtime) + { + return jsi::Value::undefined(); + } +} diff --git a/cpp/AudioContextHostObject.h b/cpp/AudioContextHostObject.h new file mode 100644 index 00000000..8499b760 --- /dev/null +++ b/cpp/AudioContextHostObject.h @@ -0,0 +1,30 @@ +#ifndef AUDIOCONTEXTHOSTOBJECT_H +#define AUDIOCONTEXTHOSTOBJECT_H + +#include +#include + +namespace audiocontext +{ + + using namespace facebook; + + class JSI_EXPORT AudioContextHostObject : public jsi::HostObject + { + private: + jobject audioContext; + + public: + explicit AudioContextHostObject(); + ~AudioContextHostObject(); + + public: + jsi::Value get(jsi::Runtime &, const jsi::PropNameID &name) override; + void set(jsi::Runtime &, const jsi::PropNameID &name, const jsi::Value &value) override; + std::vector getPropertyNames(jsi::Runtime &rt) override; + jsi::Value createOscillator(jsi::Runtime &rt); + }; + +} // namespace audiocontext + +#endif /* AUDIOCONTEXTHOSTOBJECT_H */ diff --git a/cpp/JSIExampleHostObject.cpp b/cpp/JSIExampleHostObject.cpp deleted file mode 100644 index 1f28c9e6..00000000 --- a/cpp/JSIExampleHostObject.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "JSIExampleHostObject.h" -#include - -namespace example { - using namespace facebook; - - std::vector JSIExampleHostObject::getPropertyNames(jsi::Runtime &runtime) - { - std::vector propertyNames; - propertyNames.push_back(jsi::PropNameID::forAscii(runtime, "helloWorld")); - return propertyNames; - } - - jsi::Value JSIExampleHostObject::get(jsi::Runtime &runtime, const jsi::PropNameID &propNameId) - { - auto propName = propNameId.utf8(runtime); - - if (propName == "helloWorld") - { - return jsi::Function::createFromHostFunction(runtime, propNameId, 0, - [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) - { - return this->helloWorld(rt); - }); - } - - throw std::runtime_error("Not yet implemented!"); - } - - void JSIExampleHostObject::set(jsi::Runtime &runtime, const jsi::PropNameID &propNameId, const jsi::Value &value) - { - auto propName = propNameId.utf8(runtime); - if (propName == "helloWorld") - { - // Do nothing - return; - } - throw std::runtime_error("Not yet implemented!"); - } - - jsi::Value JSIExampleHostObject::helloWorld(jsi::Runtime &runtime) - { - return jsi::String::createFromUtf8(runtime, "Hello World using jsi::HostObject!"); - } -} diff --git a/cpp/JSIExampleHostObject.h b/cpp/JSIExampleHostObject.h deleted file mode 100644 index 3ec12423..00000000 --- a/cpp/JSIExampleHostObject.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef JSIEXAMPLEHOSTOBJECT_H -#define JSIEXAMPLEHOSTOBJECT_H - -#include - -namespace example -{ - - using namespace facebook; - - class JSI_EXPORT JSIExampleHostObject : public jsi::HostObject - { - public: - explicit JSIExampleHostObject() = default; - - public: - jsi::Value get(jsi::Runtime &, const jsi::PropNameID &name) override; - void set(jsi::Runtime &, const jsi::PropNameID &name, const jsi::Value &value) override; - std::vector getPropertyNames(jsi::Runtime &rt) override; - static jsi::Value helloWorld(jsi::Runtime &); - }; - -} // namespace margelo - -#endif /* JSIEXAMPLEHOSTOBJECT_H */ diff --git a/cpp/OscillatorNodeHostObject.cpp b/cpp/OscillatorNodeHostObject.cpp new file mode 100644 index 00000000..e69de29b diff --git a/cpp/OscillatorNodeHostObject.h b/cpp/OscillatorNodeHostObject.h new file mode 100644 index 00000000..d6edf7f9 --- /dev/null +++ b/cpp/OscillatorNodeHostObject.h @@ -0,0 +1,26 @@ +#ifndef OSCILLATORNODEHOSTOBJECT_H +#define OSCILLATORNODEHOSTOBJECT_H + +#include +#include +#include "AudioContextHostObject.h" + +namespace oscillatornode +{ + + using namespace facebook; + + class JSI_EXPORT OscillatorNodeHostObject : public jsi::HostObject + { + public: + explicit OscillatorNodeHostObject(AudioContextHostObject context); + + public: + jsi::Value get(jsi::Runtime &, const jsi::PropNameID &name) override; + void set(jsi::Runtime &, const jsi::PropNameID &name, const jsi::Value &value) override; + std::vector getPropertyNames(jsi::Runtime &rt) override; + }; + +} // namespace oscillatornode + +#endif /* OSCILLATORNODEHOSTOBJECT_H */ diff --git a/example/package.json b/example/package.json index 5d775135..233d0f2f 100644 --- a/example/package.json +++ b/example/package.json @@ -10,6 +10,7 @@ "build:ios": "react-native build-ios --scheme AudioContextExample --mode Debug --extra-params \"-sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO\"" }, "dependencies": { + "@react-native-community/slider": "^4.5.2", "react": "18.2.0", "react-native": "0.74.3" }, diff --git a/example/src/App.tsx b/example/src/App.tsx index 4234e373..9d0aa807 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,15 +1,62 @@ +/* eslint-disable react/react-in-jsx-scope */ +import { useState, useRef } from 'react'; import { StyleSheet, View, Text } from 'react-native'; -import { JSIExample } from '../../src/JSIExample'; +import { Button } from 'react-native'; +import Slider from '@react-native-community/slider'; + +import { AudioContext, Oscillator, GainNode } from 'react-native-audio-context'; export default function App() { - const sayHello = () => { - //JSIExample.helloWorld = 'Hello World'; - return JSIExample.helloWorld(); + const [isPlaying, setIsPlaying] = useState(false); + const [gain, setGain] = useState(1.0); + const audioContextRef = useRef(null); + const oscillatorRef = useRef(null); + const gainRef = useRef(null); + + const createAudioContext = () => { + if (!audioContextRef.current) { + audioContextRef.current = new AudioContext(); + } + + gainRef.current = audioContextRef.current.createGain(); + oscillatorRef.current = audioContextRef.current.createOscillator(); + oscillatorRef.current.connect(); + }; + + const handlePress = () => { + if (!audioContextRef.current) { + createAudioContext(); + } + + if (!isPlaying) { + oscillatorRef.current?.start(); + } else { + oscillatorRef.current?.stop(); + } + + setIsPlaying(!isPlaying); + }; + + const handleGainChange = (value: number) => { + setGain(value); + gainRef.current?.setGain(value); }; return ( - {sayHello()} +