From e602011370927e1f1fd3faccfef719820f8bd8c5 Mon Sep 17 00:00:00 2001 From: Maciej Makowski Date: Thu, 11 Jul 2024 12:50:24 +0200 Subject: [PATCH 1/5] feat: implemented osicillator kotlin class with hybridclass and host object --- android/CMakeLists.txt | 33 +++++--- android/build.gradle | 5 ++ android/cpp-adapter.cpp | 34 ++++---- android/src/main/cpp/OnLoad.cpp | 12 +++ android/src/main/cpp/Oscillator.cpp | 27 +++++++ android/src/main/cpp/Oscillator.h | 40 +++++++++ ...amplePackage.kt => AudioContextPackage.kt} | 6 +- .../main/java/com/audiocontext/Oscillator.kt | 76 +++++++++++++++++ .../com/audiocontext/context/AudioContext.kt | 43 ++++++++++ .../audiocontext/context/AudioContextState.kt | 7 ++ .../audiocontext/context/BaseAudioContext.kt | 21 +++++ .../com/audiocontext/jsi/JSIExampleModule.kt | 36 --------- .../nativemodules/AudioContextModule.kt | 17 ++++ .../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/JSIExampleHostObject.cpp | 45 ----------- cpp/JSIExampleHostObject.h | 25 ------ cpp/OscillatorHostObject.cpp | 38 +++++++++ cpp/OscillatorHostObject.h | 24 ++++++ example/src/App.tsx | 50 +++++------- src/AudioContext.tsx | 76 +++++++++++++++++ src/JSIExample/JSIExample.ts | 81 ------------------- src/JSIExample/types.ts | 9 --- src/index.ts | 2 - src/types.ts | 4 + 29 files changed, 598 insertions(+), 259 deletions(-) create mode 100644 android/src/main/cpp/OnLoad.cpp create mode 100644 android/src/main/cpp/Oscillator.cpp create mode 100644 android/src/main/cpp/Oscillator.h rename android/src/main/java/com/audiocontext/{JSIExamplePackage.kt => AudioContextPackage.kt} (72%) create mode 100644 android/src/main/java/com/audiocontext/Oscillator.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 delete mode 100644 android/src/main/java/com/audiocontext/jsi/JSIExampleModule.kt 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 delete mode 100644 cpp/JSIExampleHostObject.cpp delete mode 100644 cpp/JSIExampleHostObject.h create mode 100644 cpp/OscillatorHostObject.cpp create mode 100644 cpp/OscillatorHostObject.h create mode 100644 src/AudioContext.tsx delete mode 100644 src/JSIExample/JSIExample.ts delete mode 100644 src/JSIExample/types.ts delete mode 100644 src/index.ts create mode 100644 src/types.ts diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index fcefba66..5bd4fd5f 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -4,22 +4,31 @@ project(react-native-audio-context) set (CMAKE_VERBOSE_MAKEFILE ON) set (CMAKE_CXX_STANDARD 14) -add_library(react-native-audio-context - SHARED - ../cpp/JSIExampleHostObject.cpp - cpp-adapter.cpp -) +include(../node_modules/react-native/ReactAndroid/cmake-utils/folly-flags.cmake) +add_compile_options(${folly_FLAGS}) -# Specifies a path to native header files. include_directories( - ../cpp - ../node_modules/react-native/ReactCommon/jsi + ../cpp + src/main/cpp + ../node_modules/react-native/ReactCommon/jsi + ../node_modules/react-native/ReactAndroid/src/main/jni/react/jni + ../node_modules/react-native/ReactAndroid/src/main/jni/third-party/folly +) + +add_library(react-native-audio-context SHARED + src/main/cpp/Oscillator + ../cpp/OscillatorHostObject + cpp-adapter.cpp ) find_package(ReactAndroid REQUIRED CONFIG) +find_package(fbjni REQUIRED CONFIG) -target_link_libraries( - react-native-audio-context - ReactAndroid::jsi - android +target_link_libraries(react-native-audio-context + ReactAndroid::jsi + ReactAndroid::reactnativejni + fbjni::fbjni + ReactAndroid::folly_runtime + android + log ) diff --git a/android/build.gradle b/android/build.gradle index 431dd98a..65874cc2 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -72,6 +72,10 @@ android { } } + packagingOptions { + excludes = ["**/libc++_shared.so", "**/libfbjni.so", "**/libjsi.so", "**/libreactnativejni.so", "**/libfolly_json.so", "**/libreanimated.so", "**/libjscexecutor.so", "**/libhermes.so"] + } + externalNativeBuild { cmake { path "CMakeLists.txt" @@ -125,6 +129,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..9446b1a3 100644 --- a/android/cpp-adapter.cpp +++ b/android/cpp-adapter.cpp @@ -1,20 +1,20 @@ -#include -#include -#include "JSIExampleHostObject.h" +// #include +// #include +// #include "JSIExampleHostObject.h" -using namespace facebook; +// using namespace facebook; -void install(jsi::Runtime& runtime) { - auto hostObject = std::make_shared(); - auto object = jsi::Object::createFromHostObject(runtime, hostObject); - runtime.global().setProperty(runtime, "__JSIExampleProxy", std::move(object)); -} +// void install(jsi::Runtime& runtime) { +// 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) { - auto runtime = reinterpret_cast(jsiPtr); - if (runtime) { - install(*runtime); - } -} +// extern "C" +// JNIEXPORT void JNICALL +// Java_com_audiocontext_jsi_JSIExampleModule_00024Companion_nativeInstall(JNIEnv *env, jobject clazz, jlong jsiPtr) { +// auto runtime = reinterpret_cast(jsiPtr); +// if (runtime) { +// install(*runtime); +// } +// } diff --git a/android/src/main/cpp/OnLoad.cpp b/android/src/main/cpp/OnLoad.cpp new file mode 100644 index 00000000..4ac29070 --- /dev/null +++ b/android/src/main/cpp/OnLoad.cpp @@ -0,0 +1,12 @@ +#include +#include "Oscillator.h" +#include "OscillatorHostObject.h" + +using namespace audiocontext; + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) +{ + return facebook::jni::initialize(vm, [] { + Oscillator::registerNatives(); + }); +} diff --git a/android/src/main/cpp/Oscillator.cpp b/android/src/main/cpp/Oscillator.cpp new file mode 100644 index 00000000..187829b7 --- /dev/null +++ b/android/src/main/cpp/Oscillator.cpp @@ -0,0 +1,27 @@ +#include "Oscillator.h" +#include +#include + +namespace audiocontext { + + using namespace facebook::jni; + + Oscillator::Oscillator(const jni::alias_ref &jThis, + jlong jsContext): javaObject_(make_global(jThis)) { + auto runtime = reinterpret_cast(jsContext); + auto hostObject = std::make_shared(this); + auto object = jsi::Object::createFromHostObject(*runtime, hostObject); + runtime->global().setProperty(*runtime, "__OscillatorProxy", std::move(object)); + } + + void Oscillator::start() { + static const auto method = javaClassStatic()->getMethod("start"); + method(javaObject_.get()); + } + + void Oscillator::stop() { + static const auto method = javaClassStatic()->getMethod("stop"); + method(javaObject_.get()); + } + +} // namespace audiocontext diff --git a/android/src/main/cpp/Oscillator.h b/android/src/main/cpp/Oscillator.h new file mode 100644 index 00000000..6eb7ee62 --- /dev/null +++ b/android/src/main/cpp/Oscillator.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include +#include "OscillatorHostObject.h" + +namespace audiocontext { + + using namespace facebook; + using namespace facebook::jni; + + class Oscillator : public jni::HybridClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/audiocontext/Oscillator;"; + + static jni::local_ref initHybrid(jni::alias_ref jThis, jlong jsContext) + { + return makeCxxInstance(jThis, jsContext); + } + + static void registerNatives() { + javaClassStatic()->registerNatives({ + makeNativeMethod("initHybrid", Oscillator::initHybrid), + }); + } + + void start(); + void stop(); + + private: + friend HybridBase; + + global_ref javaObject_; + + explicit Oscillator(const jni::alias_ref& jThis, jlong jsContext); + }; + +} // namespace audiocontext diff --git a/android/src/main/java/com/audiocontext/JSIExamplePackage.kt b/android/src/main/java/com/audiocontext/AudioContextPackage.kt similarity index 72% rename from android/src/main/java/com/audiocontext/JSIExamplePackage.kt rename to android/src/main/java/com/audiocontext/AudioContextPackage.kt index 81750b80..e4cb0d94 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.nativemodules.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/Oscillator.kt b/android/src/main/java/com/audiocontext/Oscillator.kt new file mode 100644 index 00000000..0a06fd2a --- /dev/null +++ b/android/src/main/java/com/audiocontext/Oscillator.kt @@ -0,0 +1,76 @@ +package com.audiocontext + +import android.media.AudioFormat +import android.media.AudioManager +import android.media.AudioTrack +import com.audiocontext.nodes.oscillator.WaveType +import com.facebook.jni.HybridData +import com.facebook.react.bridge.ReactApplicationContext +import kotlin.math.abs +import kotlin.math.floor +import kotlin.math.sin + +class Oscillator(reactContext: ReactApplicationContext) { + val numberOfInputs: Int = 0 + 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) + + private val mHybridData: HybridData?; + + init { + mHybridData = initHybrid(reactContext.javaScriptContextHolder!!.get()) + + val bufferSize = AudioTrack.getMinBufferSize( + 44100, + AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT) + this.audioTrack = AudioTrack( + AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_OUT_MONO, + AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM + ) + } + + external fun initHybrid(l: Long): HybridData? + + fun start() { + if(isPlaying) return + isPlaying = true + audioTrack.play() + playbackThread = Thread { generateSound() }.apply{ start()} + } + + 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) / 44100 + + 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 + } + + audioTrack.write(buffer, 0, buffer.size) + } + audioTrack.flush() + } +} 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/JSIExampleModule.kt deleted file mode 100644 index 371c2886..00000000 --- a/android/src/main/java/com/audiocontext/jsi/JSIExampleModule.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.audiocontext.jsi - -import android.util.Log -import com.facebook.react.bridge.ReactApplicationContext -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?) : - ReactContextBaseJavaModule(reactContext) { - override fun getName(): String { - return NAME - } - - @ReactMethod(isBlockingSynchronousMethod = true) - fun install(): Boolean { - try { - System.loadLibrary("react-native-audio-context") - - val jsContext = reactApplicationContext.javaScriptContextHolder - - nativeInstall(jsContext!!.get()) - return true - } catch (exception: Exception) { - Log.e(NAME, "Failed to install JSI Bindings for react-native-audio-context", exception) - return false - } - } - - companion object { - const val NAME: String = "JSIExample" - - 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..ab084076 --- /dev/null +++ b/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt @@ -0,0 +1,17 @@ +package com.audiocontext.nativemodules + +import com.audiocontext.Oscillator +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod + +class AudioContextModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { + override fun getName(): String { + return "AudioContextModule" + } + + @ReactMethod(isBlockingSynchronousMethod = true) + fun createOscillatorNode() { + Oscillator(reactContext) + } +} 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/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/OscillatorHostObject.cpp b/cpp/OscillatorHostObject.cpp new file mode 100644 index 00000000..80c14f5c --- /dev/null +++ b/cpp/OscillatorHostObject.cpp @@ -0,0 +1,38 @@ +#include "OscillatorHostObject.h" + +namespace audiocontext { + using namespace facebook; + + std::vector OscillatorHostObject::getPropertyNames(jsi::Runtime& runtime) { + std::vector propertyNames; + propertyNames.push_back(jsi::PropNameID::forAscii(runtime, "start")); + propertyNames.push_back(jsi::PropNameID::forAscii(runtime, "stop")); + return propertyNames; + } + + jsi::Value OscillatorHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& propNameId) { + auto propName = propNameId.utf8(runtime); + + if (propName == "start") { + return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& rt, const jsi::Value& thisValue, const jsi::Value* args, size_t count) -> jsi::Value { + oscillator_->start(); + return jsi::Value::undefined(); + }); + } + + if (propName == "stop") { + return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& rt, const jsi::Value& thisValue, const jsi::Value* args, size_t count) -> jsi::Value { + oscillator_->stop(); + return jsi::Value::undefined(); + }); + } + + throw std::runtime_error("Prop not yet implemented!"); + } + + void OscillatorHostObject::set(jsi::Runtime& runtime, const jsi::PropNameID& propNameId, const jsi::Value& value) { + auto propName = propNameId.utf8(runtime); + + throw std::runtime_error("Not yet implemented!"); + } +} diff --git a/cpp/OscillatorHostObject.h b/cpp/OscillatorHostObject.h new file mode 100644 index 00000000..9a8d512e --- /dev/null +++ b/cpp/OscillatorHostObject.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include "Oscillator.h" + +namespace audiocontext { + using namespace facebook; + + class Oscillator; + + class OscillatorHostObject : public jsi::HostObject { + private: + Oscillator* oscillator_; + + public: + explicit OscillatorHostObject(Oscillator* oscillator) : oscillator_(oscillator) {} + + 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; + }; +} // namespace audiocontext diff --git a/example/src/App.tsx b/example/src/App.tsx index 479543b5..636988d8 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,45 +1,37 @@ -import { StyleSheet, View, Text } from 'react-native'; -import JSIExample from '../../src/JSIExample/JSIExample'; +/* eslint-disable react/react-in-jsx-scope */ +import { Button, StyleSheet, Text, View } from 'react-native'; +//import { Oscillator } from '../../src/jsiOscillator'; -export default function App() { - const sayHello = () => { - //JSIExample.helloWorld = 'Hello World'; - return JSIExample.helloWorld(); +const App = () => { + const startOscillator = () => { + //Oscillator.start(); + }; + + const stopOscillator = () => { + //Oscillator.stop(); }; return ( - {sayHello()} + React Native Oscillator +